lwIP PPP mode is not enabled by default in esp-idf

I recently implemented a cellular-network-connected device driven by an ESP32 as the main microcontroller with an external cellular modem controlled by AT commands for a client. This implementation leveraged several of the esp-idf SDK built-in components within the firmware, including their lwIP port.

lwIP, which stands for “Lightweight IP”, is an embedded-focused TCP/IP stack that is extremely popular and used across many different devices. It’s been around for a long time and has support for many different connection protocols, including PPP (point-to-point). PPPoS and PPPoE are implementations of the point-to-point protocol over serial and ethernet respectively. PPP is a very common way for cellular modems to connect in embedded devices, as the connection is the singular point of a cell tower to the singular device, rather than something like a router serving as a gateway for many connected devices and assigning different local IP addresses while advertising a public IP.

The PPPoS implementation in the esp-idf lwIP port is not used directly by the majority of users. Most opt to use the higher-level functions defined in SDK that wrap the lower-level components and typically stick to simple IP or IPv6 for internet connection over WiFi or Ethernet.

When setting up the lwIP network interface for a PPP connection, a config struct specific to a PPP connection must be passed to the generic esp_netif_new function like below.

esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP();
esp_netif_t       *ppp_netif        = esp_netif_new(&netif_ppp_config);

This will produce an implicit declaration warning compiler error:

/Users/<me>/Developer/<project>/main/<file>.c:505:43: error: implicit declaration of function 'ESP_NETIF_DEFAULT_PPP'; did you mean 'ESP_NETIF_DEFAULT_ETH'? [-Werror=implicit-function-declaration]
  505 |     esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP();
      |                                           ^~~~~~~~~~~~~~~~~~~~~
      |                                           ESP_NETIF_DEFAULT_ETH
/Users/<me>/Developer/<project>/main/<file>.c:505:43: error: invalid initializer

If you also happen to be using the esp-idf esp_modem component, you might also see undefined reference errors generated from within the component code:

/Users/<home>/<espressif linker>: esp-idf/esp_netif/libesp_netif.a(esp_netif_lwip_defaults.c.obj):/Users/<home>/esp/esp-idf/components/esp_netif/lwip/esp_netif_lwip_defaults.c:51: undefined reference to `esp_netif_lwip_ppp_input'
/Users/<home>/<espressif linker>: esp-idf/main/libmain.a(<file>.c.obj):(.literal.<file>_init+0x20): undefined reference to `NETIF_PPP_STATUS'
/Users/<home>/<espressif linker>: esp-idf/main/libmain.a(<file>.c.obj):(.literal.<file>_init+0x48): undefined reference to `esp_netif_ppp_set_auth'
/Users/<home>/<espressif linker>: esp-idf/main/libmain.a(<file>.c.obj): in function `<file>_init':
/Users/<me>/Developer/<project>/main/<file>.c:505: undefined reference to `esp_netif_ppp_set_auth'
/Users/<home>/<espressif linker>: esp-idf/espressif__esp_modem/libespressif__esp_modem.a(esp_modem_netif.cpp.obj):(.literal._ZN9esp_modem5Netif21esp_modem_post_attachEP13esp_netif_objPv+0x10): undefined reference to `esp_netif_ppp_get_params'
/Users/<home>/<espressif linker>: esp-idf/espressif__esp_modem/libespressif__esp_modem.a(esp_modem_netif.cpp.obj):(.literal._ZN9esp_modem5Netif21esp_modem_post_attachEP13esp_netif_objPv+0x14): undefined reference to `esp_netif_ppp_set_params'
/Users/<home>/<espressif linker>: esp-idf/espressif__esp_modem/libespressif__esp_modem.a(esp_modem_netif.cpp.obj): in function `esp_modem::Netif::esp_modem_post_attach(esp_netif_obj*, void*)':
/Users/<me>/Developer/<project>//managed_components/espressif__esp_modem/src/esp_modem_netif.cpp:52: undefined reference to `esp_netif_ppp_get_params'
/Users/<home>/<espressif linker>: /Users/<me>/Developer/<project>//managed_components/espressif__esp_modem/src/esp_modem_netif.cpp:59: undefined reference to `esp_netif_ppp_set_params'

None of these errors are super clear what the actual cause is. If you look up the missing symbols in the idf SDK you’ll find them defined as expected.

The fix is a single menuconfig option that is disabled by default. Comments on a Github issue in the Arduino esp32 repo seem to indicate that it used to be enabled by default and no longer is in an attempt to minimize compiled binary size.

The option to enable can be found in the esp-idf menuconfig (idf.py menuconfig) at Component Config -> LWIP -> Enable PPP Support. This option is highlighted in the screenshot below.

If not already enabled, I would also recommend enabling the Enable PPP debug log output option that appears a few lines below when the main PPP is enabled.

Once enabled, another call to idf.py build should rebuild the project and correctly resolve all references.