int err;
int sockfd;
if (sockfd = socket(AF_INET,SOCK_STREAM,0) < 0) {
// the error code is obtained from errno
err = errno;
return err;
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 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
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.
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
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.
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 or Ethernet project configuration as applicable 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.