# lwIP

[中文]

ESP-IDF uses the open source lwIP lightweight TCP/IP stack. The ESP-IDF version of lwIP (esp-lwip) has some modifications and additions compared to the upstream project.

## Supported APIs

ESP-IDF supports the following lwIP TCP/IP stack functions:

- BSD Sockets API

- Netconn API is enabled but not officially supported for ESP-IDF applications

### Adapted APIs

> Warning

> When using any lwIP API other than the BSD Sockets API, please make sure that the API is thread-safe. To check if a given API call is thread-safe, enable the CONFIG_LWIP_CHECK_THREAD_SAFETY configuration option and run the application. This enables lwIP to assert the correct access of the TCP/IP core functionality. If the API is not accessed or locked properly from the appropriate lwIP FreeRTOS Task, the execution will be aborted. The general recommendation is to use the ESP-NETIF component to interact with lwIP.

Some common lwIP app APIs are supported indirectly by ESP-IDF:

- Dynamic Host Configuration Protocol (DHCP) Server & Client are supported indirectly via the ESP-NETIF functionality.

- Domain Name System (DNS) is supported in lwIP; DNS servers could be assigned automatically when acquiring a DHCP address, or manually configured using the ESP-NETIF API.

Note

DNS server configuration in lwIP is global, not interface-specific. If you are using multiple network interfaces with distinct DNS servers, exercise caution to prevent inadvertent overwrites of one interface's DNS settings when acquiring a DHCP lease from another interface.

- Simple Network Time Protocol (SNTP) is also supported via the ESP-NETIF, or directly via the lwip/include/apps/esp_sntp.h functions, which also provide thread-safe API to lwip/lwip/src/include/lwip/apps/sntp.h functions, see also SNTP Time Synchronization. For implementation details, see protocols/sntp. This example demonstrates how to use the LwIP SNTP module to obtain time from internet servers, configure the synchronization method and interval, and retrieve time using the SNTP-over-DHCP module.

- ICMP Ping is supported using a variation on the lwIP ping API, see ICMP Echo.

- ICMPv6 Ping, supported by lwIP's ICMPv6 Echo API, is used to test IPv6 network connectivity. For more information, see protocols/sockets/icmpv6_ping. This example demonstrates how to use the network interface to discover an IPv6 address, create a raw ICMPv6 socket, send an ICMPv6 Echo Request to a destination IPv6 address, and wait for an Echo Reply from the target.

- NetBIOS lookup is available using the standard lwIP API. protocols/http_server/restful_server has the option to demonstrate using NetBIOS to look up a host on the LAN.

- mDNS uses a different implementation to the lwIP default mDNS, see mDNS Service. But lwIP can look up mDNS hosts using standard APIs such as `gethostbyname()` and the convention `hostname.local`, provided the CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES setting is enabled.

- The PPP implementation in lwIP can be used to create PPPoS (PPP over serial) interface in ESP-IDF. Please refer to the documentation of the ESP-NETIF component to create and configure a PPP network interface, by means of the `ESP_NETIF_DEFAULT_PPP()` macro defined in esp_netif/include/esp_netif_defaults.h. Additional runtime settings are provided via esp_netif/include/esp_netif_ppp.h. PPPoS interfaces are typically used to interact with NBIoT/GSM/LTE modems. More application-level friendly API is supported by the esp_modem library, which uses this PPP lwIP module behind the scenes.

## BSD Sockets API

The BSD Sockets API is a common cross-platform TCP/IP sockets API that originated in the Berkeley Standard Distribution of UNIX but is now standardized in a section of the POSIX specification. BSD Sockets are sometimes called POSIX Sockets or Berkeley Sockets.

As implemented in ESP-IDF, lwIP supports all of the common usages of the BSD Sockets API. However, not all operations are fully thread-safe, and simultaneous reads and writes from multiple threads may require additional synchronization mechanisms, see Limitations for more details.

### References

A wide range of BSD Sockets reference materials are available, including:

- Single UNIX Specification - BSD Sockets page

- Berkeley Sockets - Wikipedia page

### Application Examples

A number of ESP-IDF examples show how to use the BSD Sockets APIs:

- protocols/sockets/non_blocking demonstrates how to configure and run a non-blocking TCP client and server, supporting both IPv4 and IPv6 protocols.

- protocols/sockets/tcp_server demonstrates how to create a TCP server that accepts client connection requests and receives data.

- protocols/sockets/tcp_client demonstrates how to create a TCP client that connects to a server using a predefined IP address and port.

- protocols/sockets/tcp_client_multi_net demonstrates how to use Ethernet and Wi-Fi interfaces together, connect to both simultaneously, create a TCP client for each interface, and send a basic HTTP request and response.

- protocols/sockets/udp_server demonstrates how to create a UDP server that receives client connection requests and data.

- protocols/sockets/udp_client demonstrates how to create a UDP client that connects to a server using a predefined IP address and port.

- protocols/sockets/udp_multicast demonstrates how to use the IPV4 and IPV6 UDP multicast features via the BSD-style sockets interface.

### Supported Functions

The following BSD socket API functions are supported. For full details, see lwip/lwip/src/include/lwip/sockets.h.

- `socket()`

- `bind()`

- `accept()`

- `shutdown()`

- `getpeername()`

- `getsockopt()` & `setsockopt()`: see Socket Options

- `close()`: via Virtual Filesystem Component

- `read()`, `readv()`, `write()`, `writev()`: via Virtual Filesystem Component

- `recv()`, `recvmsg()`, `recvfrom()`

- `send()`, `sendmsg()`, `sendto()`

- `select()`: via Virtual Filesystem Component

- `poll()` : on ESP-IDF, `poll()` is implemented by calling `select()` internally, so using `select()` directly is recommended, if a choice of methods is available

- `fcntl()`: see fcntl()

Non-standard functions:

- `ioctl()`: see ioctl()

Note

Some lwIP application sample code uses prefixed versions of BSD APIs, e.g., `lwip_socket()`, instead of the standard `socket()`. Both forms can be used with ESP-IDF, but using standard names is recommended.

### Socket Error Handling

BSD Socket error handling code is very important for robust socket applications. Normally, socket error handling involves the following aspects:

- Detecting the error

- Getting the error reason code

- Handling the error according to the reason code

In lwIP, we have two different scenarios for handling socket errors:

- Socket API returns an error. For more information, see Socket API Errors.

- `select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout)` has an exception descriptor indicating that the socket has an error. For more information, see select() Errors.

#### Socket API Errors

Error detection

> - We can know that the socket API fails according to its return value.

Get the error reason code

> - When socket API fails, the return value does not contain the failure reason and the application can get the error reason code by accessing `errno`. Different values indicate different meanings. For more information, see Socket Error Reason Code.

Example:

    int err;
    int sockfd;

    if (sockfd = socket(AF_INET,SOCK_STREAM,0) < 0) {
        // the error code is obtained from errno
        err = errno;
        return err;
    }

#### `select()` Errors

Error detection

> - Socket error when `select()` has exception descriptor.

Get the error reason code

> - If the `select()` indicates that the socket fails, we can not get the error reason code by accessing `errno`, instead we should call `getsockopt()` to get the failure reason code. Since `select()` has exception descriptor, the error code is not given to `errno`.

Note

The `getsockopt()` function has the following prototype: `int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen)`. Its purpose is to get the current value of the option of any type, any state socket, and store the result in `optval`. For example, when you get the error code on a socket, you can get it by `getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &optlen)`.

Example:

    int err;

    if (select(sockfd + 1, NULL, NULL, &exfds, &tval) <= 0) {
        err = errno;
        return err;
    } else {
        if (FD_ISSET(sockfd, &exfds)) {
            // select() exception set using getsockopt()
            int optlen = sizeof(int);
            getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &optlen);
            return err;
        }
    }

#### Socket Error Reason Code

Below is a list of common error codes. For a more detailed list of standard POSIX/C error codes, please see newlib errno.h and the platform-specific extensions newlib/platform_include/sys/errno.h.

Error code | Description
--- | ---
ECONNREFUSED | Connection refused
EADDRINUSE | Address already in use
ECONNABORTED | Software caused connection abort
ENETUNREACH | Network is unreachable
ENETDOWN | Network interface is not configured
ETIMEDOUT | Connection timed out
EHOSTDOWN | Host is down
EHOSTUNREACH | Host is unreachable
EINPROGRESS | Connection already in progress
EALREADY | Socket already connected
EDESTADDRREQ | Destination address required
EPROTONOSUPPORT | Unknown protocol

### Socket Options

The `getsockopt()` and `setsockopt()` functions allow getting and setting per-socket options respectively.

Not all standard socket options are supported by lwIP in ESP-IDF. The following socket options are supported:

#### Common Options

Used with level argument `SOL_SOCKET`.

- `SO_REUSEADDR`: available if CONFIG_LWIP_SO_REUSE is set, whose behavior can be customized by setting CONFIG_LWIP_SO_REUSE_RXTOALL

- `SO_KEEPALIVE`

- `SO_BROADCAST`

- `SO_ACCEPTCONN`

- `SO_RCVBUF`: available if CONFIG_LWIP_SO_RCVBUF is set

- `SO_SNDTIMEO` / `SO_RCVTIMEO`

- `SO_ERROR`: only used with `select()`, see Socket Error Handling

- `SO_TYPE`

- `SO_NO_CHECK`: for UDP sockets only

#### IP Options

Used with level argument `IPPROTO_IP`.

- `IP_TOS`

- `IP_TTL`

- `IP_PKTINFO`: available if CONFIG_LWIP_NETBUF_RECVINFO is set

For multicast UDP sockets:

- `IP_MULTICAST_IF`

- `IP_MULTICAST_LOOP`

- `IP_MULTICAST_TTL`

- `IP_ADD_MEMBERSHIP`

- `IP_DROP_MEMBERSHIP`

#### TCP Options

TCP sockets only. Used with level argument `IPPROTO_TCP`.

- `TCP_NODELAY`

Options relating to TCP keepalive probes:

- `TCP_KEEPALIVE`: int value, TCP keepalive period in milliseconds

- `TCP_KEEPIDLE`: same as `TCP_KEEPALIVE`, but the value is in seconds

- `TCP_KEEPINTVL`: int value, the interval between keepalive probes in seconds

- `TCP_KEEPCNT`: int value, number of keepalive probes before timing out

#### IPv6 Options

IPv6 sockets only. Used with level argument `IPPROTO_IPV6`.

- `IPV6_CHECKSUM`

- `IPV6_V6ONLY`

For multicast IPv6 UDP sockets:

- `IPV6_JOIN_GROUP` / `IPV6_ADD_MEMBERSHIP`

- `IPV6_LEAVE_GROUP` / `IPV6_DROP_MEMBERSHIP`

- `IPV6_MULTICAST_IF`

- `IPV6_MULTICAST_HOPS`

- `IPV6_MULTICAST_LOOP`

### `fcntl()`

The `fcntl()` function is a standard API for manipulating options related to a file descriptor. In ESP-IDF, the Virtual Filesystem Component layer is used to implement this function.

When the file descriptor is a socket, only the following `fcntl()` values are supported:

- `O_NONBLOCK` to set or clear non-blocking I/O mode. Also supports `O_NDELAY`, which is identical to `O_NONBLOCK`.

- `O_RDONLY`, `O_WRONLY`, `O_RDWR` flags for different read or write modes. These flags can only be read using `F_GETFL`, and cannot be set using `F_SETFL`. A TCP socket returns a different mode depending on whether the connection has been closed at either end or is still open at both ends. UDP sockets always return `O_RDWR`.

### `ioctl()`

The `ioctl()` function provides a semi-standard way to access some internal features of the TCP/IP stack. In ESP-IDF, the Virtual Filesystem Component layer is used to implement this function.

When the file descriptor is a socket, only the following `ioctl()` values are supported:

- `FIONREAD` returns the number of bytes of the pending data already received in the socket's network buffer.

- `FIONBIO` is an alternative way to set/clear non-blocking I/O status for a socket, equivalent to `fcntl(fd, F_SETFL, O_NONBLOCK, ...)`.

## Netconn API

lwIP supports two lower-level APIs as well as the BSD Sockets API: the Netconn API and the Raw API.

The lwIP Raw API is designed for single-threaded devices and is not supported in ESP-IDF.

The Netconn API is used to implement the BSD Sockets API inside lwIP, and it can also be called directly from ESP-IDF apps. This API has lower resource usage than the BSD Sockets API. In particular, it can send and receive data without firstly copying it into internal lwIP buffers.

Important

Espressif does not test the Netconn API in ESP-IDF. As such, this functionality is enabled but not supported. Some functionality may only work correctly when used from the BSD Sockets API.

For more information about the Netconn API, consult lwip/lwip/src/include/lwip/api.h and part of the **unofficial** lwIP Application Developers Manual.

## lwIP FreeRTOS Task

lwIP creates a dedicated TCP/IP FreeRTOS task to handle socket API requests from other tasks.

A number of configuration items are available to modify the task and the queues (mailboxes) used to send data to/from the TCP/IP task:

- CONFIG_LWIP_TCPIP_RECVMBOX_SIZE

- CONFIG_LWIP_TCPIP_TASK_STACK_SIZE

- CONFIG_LWIP_TCPIP_TASK_AFFINITY

## IPv6 Support

Both IPv4 and IPv6 are supported in a dual-stack configuration and are enabled by default. Both IPv6 and IPv4 may be disabled separately if they are not needed, see Minimum RAM Usage.

IPv6 support is limited to Stateless Autoconfiguration only. Stateful configuration is not supported in ESP-IDF, nor in upstream lwIP.

IPv6 Address configuration is defined by means of these protocols or services:

- SLAAC IPv6 Stateless Address Autoconfiguration (RFC-2462)

- DHCPv6 Dynamic Host Configuration Protocol for IPv6 (RFC-8415)

None of these two types of address configuration is enabled by default, so the device uses only Link Local addresses or statically-defined addresses.

### Stateless Autoconfiguration Process

To enable address autoconfiguration using the Router Advertisement protocol, please enable:

- CONFIG_LWIP_IPV6_AUTOCONFIG

This configuration option enables IPv6 autoconfiguration for all network interfaces, which differs from the upstream lwIP behavior, where the autoconfiguration needs to be explicitly enabled for each `netif` with `netif->ip6_autoconfig_enabled=1`.

### DHCPv6

DHCPv6 in lwIP is very simple and supports only stateless configuration. It could be enabled using:

- CONFIG_LWIP_IPV6_DHCP6

Since the DHCPv6 works only in its stateless configuration, the Stateless Autoconfiguration Process has to be enabled as well via CONFIG_LWIP_IPV6_AUTOCONFIG.

Moreover, the DHCPv6 needs to be explicitly enabled from the application code using:

    dhcp6_enable_stateless(netif);

### DNS Servers in IPv6 Autoconfiguration

In order to autoconfigure DNS server(s), especially in IPv6-only networks, we have these two options:

- Recursive Domain Name System (DNS): this belongs to the Neighbor Discovery Protocol (NDP) and uses Stateless Autoconfiguration Process.

  The number of servers must be set CONFIG_LWIP_IPV6_RDNSS_MAX_DNS_SERVERS, this option is disabled by default, i.e., set to 0.

- DHCPv6 stateless configuration, uses DHCPv6 to configure DNS servers. Note that this configuration assumes IPv6 Router Advertisement Flags (RFC-5175) to be set to

  > - Managed Address Configuration Flag = 0

  > - Other Configuration Flag = 1

## ESP-lwIP Custom Modifications

### Additions

The following code is added, which is not present in the upstream lwIP release:

#### Thread-Safe Sockets

It is possible to `close()` a socket from a different thread than the one that created it. The `close()` call blocks, until any function calls currently using that socket from other tasks have returned.

It is, however, not possible to delete a task while it is actively waiting on `select()` or `poll()` APIs. It is always necessary that these APIs exit before destroying the task, as this might corrupt internal structures and cause subsequent crashes of the lwIP. These APIs allocate globally referenced callback pointers on the stack so that when the task gets destroyed before unrolling the stack, the lwIP could still hold pointers to the deleted stack.

#### On-Demand Timers

lwIP IGMP and MLD6 feature both initialize a timer in order to trigger timeout events at certain times.

The default lwIP implementation is to have these timers enabled all the time, even if no timeout events are active. This increases CPU usage and power consumption when using automatic Light-sleep mode. `ESP-lwIP` default behavior is to set each timer `on demand`, so it is only enabled when an event is pending.

To return to the default lwIP behavior, which is always-on timers, disable CONFIG_LWIP_TIMERS_ONDEMAND.

#### lwIP Timers API

When not using Wi-Fi, the lwIP timer can be turned off via the API to reduce power consumption.

The following API functions are supported. For full details, see lwip/lwip/src/include/lwip/timeouts.h.

- `sys_timeouts_init()`

- `sys_timeouts_deinit()`

#### Additional Socket Options

- Some standard IPV4 and IPV6 multicast socket options are implemented, see Socket Options.

- Possible to set IPV6-only UDP and TCP sockets with `IPV6_V6ONLY` socket option, while normal lwIP is TCP-only.

#### IP Layer Features

- IPV4-source-based routing implementation is different

- IPV4-mapped IPV6 addresses are supported

#### NAPT and Port Forwarding

IPV4 network address port translation (NAPT) and port forwarding are supported. However, the enabling of NAPT is limited to a single interface.

- To use NAPT for forwarding packets between two interfaces, it needs to be enabled on the interface connecting to the target network. For example, to enable internet access for Ethernet traffic through the Wi-Fi interface, NAPT must be enabled on the Ethernet interface.

- Usage of NAPT is demonstrated in network/vlan_support.

#### Default lwIP Hooks

ESP-IDF port layer provides a default hook file, which is included in the lwIP build process. This file is located at lwip/port/include/lwip_default_hooks.h and defines several hooks that implement default ESP-IDF behavior of lwIP stack. These hooks could be further modified by choosing one of these options:

- None: No hook is declared.

- Default: Default IDF implementation is provided (declared as `weak` in most cases and can be overridden).

- Custom: Only the hook declaration is provided, and the application must implement it.

DHCP Extra Option Hook

ESP-IDF allows applications to define a hook for handling extra DHCP options. This can be useful for implementing custom DHCP-based behaviors, such as retrieving specific vendor options. To enable this feature, configure CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION either to Default (`weak` implementation is provided which could be replaced by custom implementation) or Custom (you will have to implement the hook and define its link dependency to lwip)

Example Usage

An application can define the following function to handle a specific DHCP option (e.g., captive portal URI):

    #include "esp_netif.h"
    #include "lwip/dhcp.h"

    void lwip_dhcp_on_extra_option(struct dhcp *dhcp, uint8_t state,
                                   uint8_t option, uint8_t len,
                                   struct pbuf* p, uint16_t offset)
    {
        if (option == ESP_NETIF_CAPTIVEPORTAL_URI) {
            char *uri = (char *)p->payload + offset;
            ESP_LOGI(TAG, "Captive Portal URI: %s", uri);
        }
    }

Other Default Hooks

ESP-IDF provides additional lwIP hooks that can be overridden. These include:

- TCP ISN Hook (CONFIG_LWIP_HOOK_TCP_ISN): Allows custom randomization of TCP Initial Sequence Numbers (ESP-IDF provided implementation is the default option, set to Custom to provide custom implementation or None to use lwIP implementation).

- IPv6 Route Hook (CONFIG_LWIP_HOOK_IP6_ROUTE): Enables custom route selection for IPv6 packets (No hook by default, use Default or Custom to override).

- IPv6 Get Gateway Hook (CONFIG_LWIP_HOOK_ND6_GET_GW): Enables defining a custom gateway selection logic (No hook by default, use Default or Custom to override).

- IPv6 Source Address Selection Hook (CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR): Allows customization of source address selection (No hook by default, use Default or Custom to override).

- Netconn External Resolve Hook (CONFIG_LWIP_HOOK_NETCONN_EXTERNAL_RESOLVE): Allows overriding the DNS resolution logic for network connections (No hook by default, use Default or Custom to override).

- DNS External Resolve Hook (CONFIG_LWIP_HOOK_DNS_EXTERNAL_RESOLVE): Provides a hook for custom DNS resolution logic with callbacks (No hook by default, but could be selected by an external component to prefer the custom option; use Default or Custom to override).

- IPv6 Packet Input Hook (CONFIG_LWIP_HOOK_IP6_INPUT): Provides filtering or modification of incoming IPv6 packets (ESP-IDF provided `weak` implementation is the default option; use Custom or provide a strong definition to override the Default option; choose None to disable IPv6 packet input filtering)

Each of these hooks can be configured in menuconfig, allowing selection of default, custom, or no implementation.

#### Customized lwIP Hooks

The original lwIP supports implementing custom compile-time modifications via `LWIP_HOOK_FILENAME`. This file is already used by the ESP-IDF port layer, but ESP-IDF users could still include and implement any custom additions via a header file defined by the macro `ESP_IDF_LWIP_HOOK_FILENAME`. Here is an example of adding a custom hook file to the build process, and the hook is called `my_hook.h`, located in the project's `main` folder:

    idf_component_get_property(lwip lwip COMPONENT_LIB)
    target_compile_options(${lwip} PRIVATE "-I${PROJECT_DIR}/main")
    target_compile_definitions(${lwip} PRIVATE "-DESP_IDF_LWIP_HOOK_FILENAME=\"my_hook.h\"")

#### Customized lwIP Options From ESP-IDF Build System

The most common lwIP options are configurable through the component configuration menu. However, certain definitions need to be injected from the command line. The CMake function `target_compile_definitions()` can be employed to define macros, as illustrated below:

    idf_component_get_property(lwip lwip COMPONENT_LIB)
    target_compile_definitions(${lwip} PRIVATE "-DETHARP_SUPPORT_VLAN=1")

This approach may not work for function-like macros, as there is no guarantee that the definition will be accepted by all compilers, although it is supported in GCC. To address this limitation, the `add_definitions()` function can be utilized to define the macro for the entire project, for example: `add_definitions("-DFALLBACK_DNS_SERVER_ADDRESS(addr)=\"IP_ADDR4((addr), 8,8,8,8)\"")`.

Alternatively, you can define your function-like macro in a header file which will be pre-included as an lwIP hook file, see Customized lwIP Hooks.

### Limitations

lwIP in ESP-IDF supports thread safety in certain scenarios, but with limitations. It is possible to perform read, write, and close operations from different threads on the same socket simultaneously. However, performing multiple reads or multiple writes from more than one thread on the same socket at the same time is not supported. Applications that require simultaneous reads or writes from multiple threads on the same socket must implement additional synchronization mechanisms, such as locking around socket operations.

ESP-IDF additions to lwIP still suffer from the global DNS limitation, described in Adapted APIs. To address this limitation from application code, the `FALLBACK_DNS_SERVER_ADDRESS()` macro can be utilized to define a global DNS fallback server accessible from all interfaces. Alternatively, you have the option to maintain per-interface DNS servers and reconfigure them whenever the default interface changes.

The number of IP addresses returned by network database APIs such as `getaddrinfo()` and `gethostbyname()` is restricted by the macro `DNS_MAX_HOST_IP`. By default, the value of this macro is set to 1.

In the implementation of `getaddrinfo()`, the canonical name is not available. Therefore, the `ai_canonname` field of the first returned `addrinfo` structure will always refer to the `nodename` argument or a string with the same contents.

The `getaddrinfo()` system call in lwIP within ESP-IDF has a limitation when using `AF_UNSPEC`, as it defaults to returning only an IPv4 address in dual stack mode. This can cause issues in IPv6-only networks. To handle this, a workaround involves making two sequential calls to `getaddrinfo()`: the first with `AF_INET` to query for IPv4 addresses, and the second with `AF_INET6` to retrieve IPv6 addresses. To provide a more robust solution, the custom `esp_getaddrinfo()` function has been added to the lwIP port layer to handle both IPv4 and IPv6 addresses when `AF_UNSPEC` is used. The CONFIG_LWIP_USE_ESP_GETADDRINFO option, available when both IPv4 and IPv6 are enabled, controls whether `esp_getaddrinfo()` or `getaddrinfo()` is used. It is disabled by default.

Calling `send()` or `sendto()` repeatedly on a UDP socket may eventually fail with `errno` equal to `ENOMEM`. This failure occurs due to the limitations of buffer sizes in the lower-layer network interface drivers. If all driver transmit buffers are full, the UDP transmission will fail. For applications that transmit a high volume of UDP datagrams and aim to avoid any dropped datagrams by the sender, it is advisable to implement error code checking and employ a retransmission mechanism with a short delay.

Increasing the number of TX buffers in the Wi-Fi project configuration may also help.

## Performance Optimization

TCP/IP performance is a complex subject, and performance can be optimized toward multiple goals. The default settings of ESP-IDF are tuned for a compromise between throughput, latency, and moderate memory usage.

### Maximum Throughput

Espressif tests ESP-IDF TCP/IP throughput using the iperf test application: https://iperf.fr/, please refer to Improving Network Speed for more details about the actual testing and using the optimized configuration.

Important

Suggest applying changes a few at a time and checking the performance each time with a particular application workload.

- If a lot of tasks are competing for CPU time on the system, consider that the lwIP task has configurable CPU affinity (CONFIG_LWIP_TCPIP_TASK_AFFINITY) and runs at fixed priority (18, `ESP_TASK_TCPIP_PRIO`). To optimize CPU utilization, consider assigning competing tasks to different cores or adjusting their priorities to lower values. For additional details on built-in task priorities, please refer to Built-in Task Priorities.

- If using `select()` function with socket arguments only, disabling CONFIG_VFS_SUPPORT_SELECT will make `select()` calls faster.

- If there is enough free IRAM, select CONFIG_LWIP_IRAM_OPTIMIZATION and CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION to improve TX/RX throughput.

If using a Wi-Fi network interface, please also refer to Wi-Fi Buffer Usage.

### Minimum Latency

Except for increasing buffer sizes, most changes that increase throughput also decrease latency by reducing the amount of CPU time spent in lwIP functions.

- For TCP sockets, lwIP supports setting the standard `TCP_NODELAY` flag to disable Nagle's algorithm.

### Minimum RAM Usage

Most lwIP RAM usage is on-demand, as RAM is allocated from the heap as needed. Therefore, changing lwIP settings to reduce RAM usage may not change RAM usage at idle, but can change it at peak.

- Reducing CONFIG_LWIP_MAX_SOCKETS reduces the maximum number of sockets in the system. This also causes TCP sockets in the `WAIT_CLOSE` state to be closed and recycled more rapidly when needed to open a new socket, further reducing peak RAM usage.

- Reducing CONFIG_LWIP_TCPIP_RECVMBOX_SIZE, CONFIG_LWIP_TCP_RECVMBOX_SIZE and CONFIG_LWIP_UDP_RECVMBOX_SIZE reduce RAM usage at the expense of throughput, depending on usage.

- Reducing CONFIG_LWIP_TCP_ACCEPTMBOX_SIZE reduce RAM usage by limiting concurrent accepted connections.

- Reducing CONFIG_LWIP_TCP_MSL and CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT reduces the maximum segment lifetime in the system. This also causes TCP sockets in the `TIME_WAIT` and `FIN_WAIT_2` states to be closed and recycled more rapidly.

- Disabling CONFIG_LWIP_IPV6 can save about 39 KB for firmware size and 2 KB RAM when the system is powered up and 7 KB RAM when the TCP/IP stack is running. If there is no requirement for supporting IPV6, it can be disabled to save flash and RAM footprint.

- Disabling CONFIG_LWIP_IPV4 can save about 26 KB of firmware size and 600 B RAM on power up and 6 KB RAM when the TCP/IP stack is running. If the local network supports IPv6-only configuration, IPv4 can be disabled to save flash and RAM footprint.

If using Wi-Fi, please also refer to Wi-Fi Buffer Usage.

#### Peak Buffer Usage

The peak heap memory that lwIP consumes is the theoretically-maximum memory that the lwIP driver consumes. Generally, the peak heap memory that lwIP consumes depends on:

> - the memory required to create a UDP connection: `lwip_udp_conn`

> - the memory required to create a TCP connection: `lwip_tcp_conn`

> - the number of UDP connections that the application has: `lwip_udp_con_num`

> - the number of TCP connections that the application has: `lwip_tcp_con_num`

> - the TCP TX window size: `lwip_tcp_tx_win_size`

> - the TCP RX window size: `lwip_tcp_rx_win_size`

So, the peak heap memory that the lwIP consumes can be calculated with the following formula:

  lwip_dynamic_peek_memory = (lwip_udp_con_num * lwip_udp_conn) + (lwip_tcp_con_num * (lwip_tcp_tx_win_size + lwip_tcp_rx_win_size + lwip_tcp_conn))

Some TCP-based applications need only one TCP connection. However, they may choose to close this TCP connection and create a new one when an error occurs (e.g., a sending failure). This may result in multiple TCP connections existing in the system simultaneously, because it may take a long time for a TCP connection to close, according to the TCP state machine, refer to RFC793.

Was this page helpful?

- Thank you! We received your feedback.
- If you have any comments, fill in Espressif Documentation Feedback Form.

- We value your feedback.
- Let us know how we can improve this page by filling in Espressif Documentation Feedback Form.