Программирование ARM ESP-IDF lwIP Thu, May 14 2026  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


ESP-IDF lwIP Печать
Добавил(а) microsin   

ESP-IDF использует open source сетевой стек lwIP, это облегченная реализация стека TCP/IP. ESP-IDF версия lwIP (esp-lwip) содержит некоторые модификации и дополнения по сравнению с оригинальным базовым проектом lwIP.

ESP-IDF поддерживает следующие lwIP функции стека TCP/IP:

BSD Sockets API #bsd-sockets-api
Netconn API #netconn-api разрешен, но не поддерживается официально для приложений ESP-IDF

[Адаптированное API]

Предупреждение: при использовании любого lwIP API, кроме BSD Sockets API, обратите внимение на его безопасность для использования в потоках (API thread-safe). Чтобы проверить определенный вызов API на потокобезопасность, разрешите опцию конфигурации CONFIG_LWIP_CHECK_THREAD_SAFETY и запустите приложение. Это включит в код lwIP проверки assert на корректность доступа к основному функционалу TCP/IP. Если API не доступен или некорректно блокируется из соответствующей задачи стека (lwIP FreeRTOS Task), то выполнение будет оборвано (abort) Общая рекомендация: использовать компонент ESP-NETIF [6] для взаимодействия с lwIP.

Некоторые распространенные API приложений косвенно поддерживаются ESP-IDF:

● Dynamic Host Configuration Protocol (DHCP) Server & Client косвенно поддерживаются через функционал ESP-NETIF.

● Domain Name System (DNS) поддерживается в lwIP; сервера DNS могут назначаться автоматически через DHCP, или могут быть сконфигурированы вручную с использованием ESP-NETIF API.

Примечание: конфигурация сервера DNS глобальна в lwIP и не относится к определенному сетевому интерфейсу. Если вы используете несколько сетевых интерфейсов, не которых используются разные сервера DNS, то будьте осторожны, чтобы не допустить перезаписи настроек DNS одного интерфейса при получении через DHCP адреса и сетевых настроек на другом интерфейсе.

● Simple Network Time Protocol (SNTP) также поддерживается через ESP-NETIF, или напрямую через функции lwip/include/apps/esp_sntp.h, которые также предоставляют thread-safe API к функциям lwip/lwip/src/include/lwip/apps/sntp.h, см. также SNTP Time Synchronization [8]. Подробности реализации см. в protocols/sntp. Этот пример демонстрирует, как использовать модуль LwIP SNTP, чтобы получить время от серверов Интернет, конфигурировать метод и интервал синхронизации времени, и получать время, используя модуль SNTP-over-DHCP.

● ICMP Ping поддерживается через вариацию lwIP ping API, см. ICMP Echo [9].

● ICMPv6 Ping, поддерживаемый lwIP ICMPv6 Echo API, используется для проверки прохождения сетевого трафика IPv6. Для дополнительной информации см. protocols/sockets/icmpv6_ping. Этот пример демонстрирует, как использовать сетевой интерфейс для обнаружения адреса IPv6, создания raw-сокета ICMPv6, отправки ICMPv6 Echo Request на адрес назначения IPv6 и ожидания Echo Reply от сетевой цели.

● NetBIOS lookup (поиск хостов NetBIOS) доступен при использовании стандартных вызовов lwIP API. См. protocols/http_server/restful_server как демонстрационный вариант обнаружения хоста NetBIOS в сети LAN.

● mDNS использует другую реализацию для lwIP default mDNS, см. mDNS Service [10]. Однако lwIP может искать хосты mDNS с использованием стандартных API-функций, таких как gethostbyname() и соглашения hostname.local, предоставляемых разрешенной настройкой CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES.

● PPP реализация в lwIP может использоваться для создания PPPoS (PPP over serial) интерфейса в приложении ESP-IDF. Обратитесь к документации по компоненту ESP-NETIF [6] для создания и конфигурирования сетевого интерфейса PPP с помощью макроса ESP_NETIF_DEFAULT_PPP(), который определен в esp_netif/include/esp_netif_defaults.h. Дополнительные runtime настройки предоставляются через esp_netif/include/esp_netif_ppp.h. Интерфейсы PPPoS обычно используются для взаимодействия с модемами NBIoT/GSM/LTE. Более удобный API на уровне приложений поддерживается библиотекой esp_modem [11], в которой внутри используется этот модуль PPP lwIP.

[Определение конфликта адреса DHCP]

Обычно сервера DHCP проверяют выбранный адрес IPv4, что он уникален в данном сегменте сети перед выдачей этого адрес клиенту. Однако некоторые сервера, влючая DHCP-сервер в ESP-IDF, по умолчанию не выполняют такую проверку с целью упрощения работы и ускорения выдачи адреса.

Если вы хотите избежать любых потенциальных конфликтов IP-адреса и связанных с ними проблем подключения к сети, то ESP-IDF DHCP клиент предоставляет несколько опций для проверки предоставленного адреса IPv4 перед его привязкой к интерфейсу (см. CONFIG_LWIP_DHCP_CHECKS_OFFERED_ADDRESS):

● Simple ARP check, простая проверка ARP - применяемая по умолчанию опция (CONFIG_LWIP_DHCP_DOES_ARP_CHECK): посылаются две пробы ARP и отклоняет предложение только в том случае, если ответ на предложенный IP-адрес поступает с MAC-адреса, отличного от MAC-адреса интерфейса. Это быстрая проерка (занимает 1–2 секунды), она позволяет избежать ложных конфликтов в сетях, где AP эхом посылает MAC-адрес клиента в ответах ARP. Используйте эту опцию, если столкнулись с циклами DHCP DECLINE, где ARP-ответ для предлагаемого IP-адреса объявляет собственный MAC-адрес интерфейса.

● Address Conflict Detection (ACD) (CONFIG_LWIP_DHCP_DOES_ACD_CHECK): использует lwIP ACD в восходящем направлении (upstream) в соответствии с RFC 5227 для проверки/анонса (probe/announce) адресов. Некоторые точки доступа отвечают на ARP probe клиентов собственным MAC-адресом для предоставленного IP; upstream-поведение рассматривает любой соответствующий IP-адрес отправителя во время PROBING как конфликт, что может вызвать повторные DHCP DECLINE на таких сетях. Используйте эту опцию только если вам нужна полная RFC 5227 совместимость, и известно о потенциальных проблемах с определенными точками доступа.

● Отсутствие детектирования конфликта (CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP): привязывает адрес без дополнительных проверок. Используйте эту опцию для максимальной совместимости, когда определение конфликта IP-адресов не требуется.

[BSD Sockets API]

BSD Sockets API это общее кросс-платформенное TCP/IP sockets API, которое пришло из Berkeley Standard Distribution UNIX, однако стандартизировано в секции POSIX-спецификации. BSD Sockets иногда называют как POSIX Sockets или Berkeley Sockets.

Реализация ESP-IDF lwIP поддерживает все общие варианты использования BSD Sockets API. Однако не все операции безопасны для потоков (не thread-safe), и одновременне чтения и записи из нескольких потоков могут потребовать дополнительных механизмов синхронизации, подробнее см. описание этих ограничений.

Существует множество справочной информации по теме BSD, включая следующие материалы:

Single UNIX Specification - BSD Sockets site:pubs.opengroup.org
Berkeley Sockets - Wikipedia site:wikipedia.org

Примеры приложений. Несколько примеров в ESP-IDF показывают, как использовать BSD Sockets API:

protocols/sockets/non_blocking демонстрирует, как сконфигурировать и запустить неблокирующий TCP клиент и сервер, поддерживающих оба протокола IPv4 и IPv6.

protocols/sockets/tcp_server демонстрирует, как создать сервер TCP, который принимает запросы на соединение от клиентов и принимает данные.

protocols/sockets/tcp_client демонстрирует, как создать TCP-клиент, который подключается к серверу, используя предварительно определенные IP-адрес и порт.

protocols/sockets/tcp_client_multi_net демонстрирует, как использовать совместно интерфейсы Ethernet и Wi-Fi, подключать их оба одновременно, создавать TCP-клиента для каждого интерфейса, и посылать базовые пакеты запросов и ответов HTTP.

protocols/sockets/udp_server демонстрирует, как создать UDP-сервер, который принимает запросы на соединение и данне клиента.

protocols/sockets/udp_client демонстрирует, как создать UDP-клиента, который подключается к серверу, используя заранее определенные IP-адрес и порт.

protocols/sockets/udp_multicast демонстрирует, как использовать фичи IPV4 и IPV6 UDP multicast через интерфейс сокетов BSD-стиля.

Поддерживаемые функции. Поддерживаются следующие функции BSD socket API, подробности см. в lwip/lwip/src/include/lwip/sockets.h.

socket()
bind()
accept()
shutdown()
getpeername()
getsockopt() и setsockopt(): см. Socket Options
close(): через Virtual Filesystem Component [12].
read(), readv(), write(), writev(): через Virtual Filesystem Component [12].
recv(), recvmsg(), recvfrom()
send(), sendmsg(), sendto()
select(): через Virtual Filesystem Component [12].
poll(): в ESP-IDF функция poll() реализвана путем внутреннего вызова select(), так что рекомендуется напрямую использовать select(), если доступны методы выбора
fcntl()

Нестандартные функции:

ioctl()

Примечание: некоторые примеры кода приложений lwIP используют имена функций BSD API с префиксом, например lwip_socket(), вместо стандартного имени socket(). В проектах ESP-IDF можно использовать обе формы имен функций, однако рекомендуется использовать стандартные имена.

Socket Error Handling. Код обработки ошибок BSD Socket очень важен для надежных приложений. Обычно обработка ошибок сокета затрагивает следующие аспекты:

● Детектирование ошибки
● Получение кода причины ошибки (error reason code)
● Обработка ошибки на базе кода причины ошибки

В lwIP у нас есть 2 различных сценария для обработки ошибок сокета:

● Socket API возвратил ошибку. Для дополнительной информации см. Socket API Errors.
● select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout) содержит дескриптор исключения, который показывает, что у сокета ошибка. Для дополнительной информации см. select() Errors.

Socket API Errors. Детектирование ошибки: мы можем узнать, что вызов socket API потерпел неудачу, по значению его кода возврата. Получение кода причины ошибки (error reason code): когда socket API был неудачный, его код возврата не содержит информации о причине отказа, и приложение может получить код error reason путем обращения к errno. Различными значениями этого кода обозначаются различные ситуации, подробнее см. Socket Error Reason Code.

Пример:

int err;
int sockfd;

if (sockfd = socket(AF_INET,SOCK_STREAM,0) < 0) {
// Код ошибки берется из errno:
err = errno;
return err; }

select() Errors. Детектирование ошибки: произошла ошибка, когда у select() есть дескриптор исключения (exception descriptor). Получение кода причины ошибки: если select() показывает отказ сокета, то мы не можем получить error reason code путем обращения к errno, вместо этого нужно вызвать getsockopt() для получения failure reason code. Поскольку у select() есть exception descriptor, код ошибки не предоставляется через errno.

Примечание: у функции getsockopt() следующий прототип: int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen). Её назначение - получить текущее значение опции любого типа, любого состояния сокета, и сохранить его в optval. Например, когда вы получили ошибку на socket, можно получить код ошибки вызовом getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &optlen).

Пример:

int err;

if (select(sockfd + 1, NULL, NULL, &exfds, &tval) <= 0) {
err = errno;
return err; } else {
if (FD_ISSET(sockfd, &exfds)) {
// Исключение select(), установленное с помощью getsockopt()
int optlen = sizeof(int);
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &optlen);
return err;
} }

Socket Error Reason Code. Ниже представлен список общих кодов ошибки. Для более подробного списка стандартных POSIX/C error codes см. newlib errno.h и специфичные для платформы расширения esp_libc/platform_include/sys/errno.h.

Код ошибки Описание
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 (неизвестный протокол)

Функции getsockopt() и setsockopt() позволяют получить и установить опции сокета. Не все стандартные опции socket поддерживаются lwIP в ESP-IDF. Поддерживаются следующие опции socket:

Common Options. Используются с аргументом level, равным SOL_SOCKET.

SO_REUSEADDR: доступно если установлена опция конфигурации CONFIG_LWIP_SO_REUSE, поведение можно настроить установкой CONFIG_LWIP_SO_REUSE_RXTOALL
SO_KEEPALIVE
SO_BROADCAST
SO_ACCEPTCONN
SO_RCVBUF: доступно если установлена опция конфигурации CONFIG_LWIP_SO_RCVBUF
SO_SNDTIMEO / SO_RCVTIMEO
SO_ERROR: используется только вместе с select(), см. Socket Error Handling
SO_TYPE
SO_NO_CHECK: только для UDP sockets

IP Options. Используется с аргументом level, равным IPPROTO_IP.

IP_TOS
IP_TTL
IP_PKTINFO: доступно если установлена опция конфигурации CONFIG_LWIP_NETBUF_RECVINFO

Для multicast UDP sockets:

IP_MULTICAST_IF
IP_MULTICAST_LOOP
IP_MULTICAST_TTL
IP_ADD_MEMBERSHIP
IP_DROP_MEMBERSHIP

TCP Options. Только для TCP sockets. Используются с аргументом level, равным IPPROTO_TCP.

TCP_NODELAY

Опции, относящиеся к TCP keepalive probes:

TCP_KEEPALIVE: значение int, период TCP keepalive в миллисекундах
TCP_KEEPIDLE: то же самое, что и TCP_KEEPALIVE, но значение в секундах
TCP_KEEPINTVL: значение int, значение интервала между keepalive probes в секундах
TCP_KEEPCNT: значение int, количество keepalive probes перед наступлением таймаута

IPv6 Options. Только для IPv6 sockets. Используются с аргументом level, равным IPPROTO_IPV6.

IPV6_CHECKSUM
IPV6_V6ONLY

Для 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(). Функция fcntl() это стандартноое API для манипуляции опциями, связанными с дескриптором файла. В ESP-IDF для реализации этой функции используется слой Virtual Filesystem Component.

Когда дескриптор файла это socket, поддерживаются только следующие значения fcntl():

O_NONBLOCK для установки или очистки режима неблокирующего ввода/вывода (non-blocking I/O). Также поддерживается O_NDELAY, что идентично O_NONBLOCK.

O_RDONLY, O_WRONLY, O_RDWR флаги для различных режимов read или write. Эти флаги могут быть только прочитаны с использованием F_GETFL, и не могут быть установлены с использованием F_SETFL. TCP socket возвратит другой режим в зависимости от того, было ли соединение закрыто на любом конце, или было все еще открыто на обоих концах. UDP sockets всегда возвращают O_RDWR.

ioctl(). Функция The ioctl() предоставляет полустандартый способ доступа к некоторым внутренним фичам стека TCP/IP. В ESP-IDF для реализации этой функции используется слой Virtual Filesystem Component.

Когда дескриптор файла это socket, поддерживаются только следующие значения ioctl():

FIONREAD возвратит количество байт ожидающих данных, уже принятых в сетевой буфер сокета.

FIONBIO это альтернативный способ установки/очистки non-blocking I/O status для socket, что эквивалентно fcntl(fd, F_SETFL, O_NONBLOCK, ...).

[Netconn API]

lwIP поддерживает два низкоуровневых API, как и BSD Sockets API: Netconn API и Raw API.

lwIP Raw API разработано для однопоточных устройств, и оно не поддерживается в ESP-IDF.

Netconn API используется для реализции BSD Sockets API внутри lwIP, и его функции можно напрямую вызывать из приложений ESP-IDF. Это API меньше расходует ресурсы, чем BSD Sockets API. В частности, можно посылать и принимать данные без предварительного их копирования во внутренние буферы lwIP.

Важное замечание: Espressif не тестировала Netconn API в ESP-IDF. Таким образом, эта функциональность разрешена, но не поддерживается. Некоторый функционал может корректно работать только когда используется из BSD Sockets API.

Для дополнительной информации по Netconn API см. lwip/lwip/src/include/lwip/api.h и часть неофициальной документации lwIP Application Developers Manual [12].

[lwIP FreeRTOS Task]

lwIP создает выделенную задачу (TCP/IP FreeRTOS task) для обработки socket API requests из других задач. Доступно несколько элементов конфигурации для модификации задачи и очередей queues (mailboxes), используемых для отправки данных в/из TCP/IP task:

CONFIG_LWIP_TCPIP_RECVMBOX_SIZE
CONFIG_LWIP_TCPIP_TASK_STACK_SIZE
CONFIG_LWIP_TCPIP_TASK_AFFINITY

Поддержка IPv6. По умолчанию поддерживаются оба IPv4 и IPv6 в конфигурации двойного стека. Оба варианта протоколов IPv6 и IPv4 могут быть запрещены, если в них нет необходимости, см. Minimum RAM Usage.

Поддержка IPv6 ограничена только автоматической настройкой без сохранения состояния (Stateless Autoconfiguration). Stateful-конфигурация не поддерживается ни в ESP-IDF, ни в базовой (upstream) библиотеке lwIP.

Конфигурация адреса IPv6 определяется с помощью следующих протоколов или служб:

SLAAC IPv6 Stateless Address Autoconfiguration (RFC-2462)
DHCPv6 Dynamic Host Configuration Protocol for IPv6 (RFC-8415)

Ни один из этих двух типов конфигурации не разрешен по умолчанию, так что устройство использует только адреса Link Local или определенные статически адреса.

Stateless Autoconfiguration Process. Чтобы разрешить автоконфигурацию адреса с использованием протокола Router Advertisement, разрешите опцию CONFIG_LWIP_IPV6_AUTOCONFIG.

Эта опция конфигурации разрешает IPv6 autoconfiguration для всех сетевых интерфейсов, что отличается от поведения базовой (upstream) библиотеки lwIP, где автоконфигурация должна быть явно разрешена для каждого сетевого интерфейса (netif) установкой netif->ip6_autoconfig_enabled=1.

DHCPv6. DHCPv6 в lwIP очень простой и поддерживает только stateless-конфигурацию. Это разрешается опцией CONFIG_LWIP_IPV6_DHCP6.

Поскольку DHCPv6 работает только в stateless конфигурации, Stateless Autoconfiguration Process должен быть также разрешен опцией CONFIG_LWIP_IPV6_AUTOCONFIG.

Кроме того, DHCPv6 должен быть явно разрешен из кода приложения вызовом:

dhcp6_enable_stateless(netif);

DNS-сервера в IPv6 Autoconfiguration. Чтобы автоматически настроить сервер (сервера) DNS, особенно в сетях IPv6, у нас есть две опции:

● Recursive Domain Name System (DNS): принадлежит Neighbor Discovery Protocol (NDP) и использует Stateless Autoconfiguration Process. Количество серверов должно быть установлено опцией CONFIG_LWIP_IPV6_RDNSS_MAX_DNS_SERVERS, и эта опция по умолчанию запрещена, т. е. установлена в 0.
● DHCPv6 stateless configuration, используется DHCPv6 для конфигурирования серверов DNS. Обратите внимание, что эта конфигурация подразумевает установку IPv6 Router Advertisement Flags (RFC-5175) в значения:

Managed Address Configuration Flag = 0
Other Configuration Flag = 1

[Модификации ESP-lwIP]

Следующий код был добавлен, который не присутствует в upstream lwIP релизе:

Thread-Safe Sockets. Можно close() сокет из потока, отличающегося от потока, который его создал. Вызов close() блокирует выполнение до тех пор, пока не выполнит возврат любая функция, которая использует этот сокет из других задач.

Однако невозможно удалить задачу, пока она активно ожидает на API-вызовах select() или poll(). Всегда необходимо, чтобы эти API-функции завершили работу перед уничтожением задачи, поскольку инече могут повредиться внутренние структуры, что может привести к последующим сбоям lwIP. Эти API-функции выделяют глобально доступные callback-указатели на стеке, так что когда задача уничтожается до разворачивания стека, lwIP все еще может содержать указатели на удаленный стек.

On-Demand Timers. lwIP IGMP и MLD6 оба инициализируют таймер, чтобы отслеживать события таймаута в определенные моменты.

По умолчанию в реализации lwIP эти таймеры разрешены всегда, даже если нет активных событий таймаута. Это увеличивает использование CPU и энергопотребление, когда используется режим автоматического ухода в сон (automatic Light-sleep mode). ESP-lwIP поведение по умолчанию - установка каждого таймера по запросу, так что это разрешено только когда ожидается событие.

Для возврата в поведение по умолчанию lwIP, когда таймеры всегда разрешены, запретите опцию конфигурации CONFIG_LWIP_TIMERS_ONDEMAND.

lwIP Timers API. Когда не используется Wi-Fi, таймер lwIP может быть выключен через API, чтобы снизить энергопотребление. Поддерживаются следующие API функции, подробности см. в lwip/lwip/src/include/lwip/timeouts.h

sys_timeouts_init()
sys_timeouts_deinit() 

Дополнительные Socket-опции. Реализованы некоторые стандартные опции IPV4 и IPV6 multicast socket, см. Socket Options. Можно установить IPV6-only UDP и TCP сокеты опцией IPV6_V6ONLY socket, в то время как обычный lwIP работает как TCP-only.

Фичи слоя IP. Реализация маршрутизации на основе источника IPV4 отличается. Поддерживаются адреса IPV6, сопоставленные IPV4.

NAPT и Port Forwarding. Поддерживается преобразование сетевых адресов и портов IPV4 (network address port translation, NAPT) и перенаправление портов (port forwarding). Однако разрешение NAPT ограничено одним интерфейсом.

● Чтобы использовать NAPT для перенаправления пактов между двумя интерфейсами, это должно быть разрешено на интерфейсе, подключенном к целевой сети. Например, чтобы разрешить доступ к Интернет для Ethernet-трафика через интерфейс Wi-Fi, NAPT должен быть разрешен на интерфейсе Ethernet.
● Использование NAPT демонстрируется в примере network/vlan_support.

Default lwIP Hooks. Слой порта ESP-IDF предоставляет default hook file, который подклчается в процессе сборки lwIP. Этот файл находится в lwip/port/include/lwip_default_hooks.h и он определяет несколько hook-функций, которые реализуют поведение по умолчанию стека lwIP в ESP-IDF. Эти hook-и можно дополнительно модифицировать, выбрав один из следующих вариантов:

None: ни один hook не объявлен.
Default: предоставлена реализация IDF по умолчанию (декларирована в большинстве случаев как и может быть переопределена).
Custom: предоставлена только декларация hook-а, и приложение должно это реализовать.

DHCP Extra Option Hook. ESP-IDF позволяет приложениям определить hook для обработки дополнительных опций DHCP. Это может быть полезным для реализации пользовательского поведения на основе DHCP, например, получение определенных параметров вендора. Чтобы разрешить эту фичу, сконфигурируйте CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION либо в Default (weak-реализация, которая может быть заменена пользовательской реализацией) или Custom (вы должны будете реализовать hook и определить его link-зависимость для lwip).

Пример использования. Приложение может определить следующую функцию для обработки специальной опции DHCP (например 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);
} }

Другие хуки по умолчанию. ESP-IDF предоставляет дополнительные lwIP hooks, которые можно переопределить. Они включают:

● TCP ISN Hook (CONFIG_LWIP_HOOK_TCP_ISN): позволяет задать пользовательскую рандомизацию чисел начальной последовательности протокола, TCP Initial Sequence Numbers (ESP-IDF предоставляет реализацию опции по умолчанию, установите опцию в Custom для предоставления собственной реализации, или None для использования lwIP-реализации).
● IPv6 Route Hook (CONFIG_LWIP_HOOK_IP6_ROUTE): разрешает пользовательский выбор маршрута для пакетов IPv6 (по умолчанию нет hook-а, используйте Default или Custom для переопределения).
● IPv6 Get Gateway Hook (CONFIG_LWIP_HOOK_ND6_GET_GW): разрешает определения пользовательской логики выбора шлюза (по умолчанию нет hook-а, используйте Default или Custom для переопределения).
● IPv6 Source Address Selection Hook (CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR): позволяет настроить выбор адреса источника (по умолчанию нет hook-а, используйте Default или Custom для переопределения).
● Netconn External Resolve Hook (CONFIG_LWIP_HOOK_NETCONN_EXTERNAL_RESOLVE): позволяет переопределить логику разрешения адресов DNS для сетевых подключений (по умолчанию нет hook-а, используйте Default или Custom для переопределения).
● DNS External Resolve Hook (CONFIG_LWIP_HOOK_DNS_EXTERNAL_RESOLVE): предоставляет hook для пользовательской логики разрешения адресов DNS с функциями обратного вызова, callbacks (по умолчанию нет hook-а, но может быть выбран внешним компонентом, чтобы отдать предпочтение пользовательской опции; используйте Default или Custom для переопределения).
● IPv6 Packet Input Hook (CONFIG_LWIP_HOOK_IP6_INPUT): предоставляет фильтрацию или модификацию приходящих пакетов IPv6 (предоставленная ESP-IDF weak-реализация это опция по умолчанию; используйте Custom или предоставьте строгое определение для переназначения опции Default; выберите None для запрета входной фильтации пакета IPv6).

Каждый из этих хуков может быть сконфигурирован в menuconfig, позволяя выбрать вариант по умолчанию (default), пользовательский (custom), или без реализации.

Пользовательские lwIP Hooks. Оригинальная библиотека lwIP поддерживает реализацию пользовательских модификаций времени компиляции через LWIP_HOOK_FILENAME. Этот файл уже используется на слое портирования ESP-IDF, но пользователи ESP-IDF все еще могут подключить и реализовать любые пользовательские добавления через файл заголовка, определенного макросом ESP_IDF_LWIP_HOOK_FILENAME. Вот пример добавления пользовательского hook-файла и hook-а в файле my_hook.h, который находится в папке main проекта: 

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\"")

Пользовательские опции lwIP из системы сборки ESP-IDF [14]. Большинство общих опций lwIP конфигурируются через меню конфигурации компонента. Однако какие-то определения должны быть вставлены из командной строки. CMake-функция target_compile_definitions() может быть использована для определения макроса, как показано ниже: 

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

Этот способ может не сработать для макросов, реализованых по типу функции, поскольку нет гарантии, что такое определение будет принято всеми компиляторами, хотя это поддерживается компилятором GCC. Для обхода этого ограничения может использоваться функция add_definitions(), чтобы определить макрос для всего проекта, например:

add_definitions("-DFALLBACK_DNS_SERVER_ADDRESS(addr)=\"IP_ADDR4((addr), 8,8,8,8)\"")

Альтернативно вы можете в файле заголовка определить свой марос в стиле функции, который будет предварительно подключаться в lwIP hook-файле, см. "Пользовательские lwIP Hooks".

[Функции обратного вызова сетевого интерфейса (Network Interface Callback)]

● Status Callback (CONFIG_LWIP_NETIF_STATUS_CALLBACK): разрешает netif_set_status_callback() для оповещения, когда интерфейс поднимается/опускается, и когда меняется адрес IPv4/IPv6.
● Link Callback (CONFIG_LWIP_NETIF_LINK_CALLBACK): разрешает netif_set_link_callback() для оповещения, когда физический линк поднимается/опускается. Этот callback срабатывает при вызовах netif_set_link_up() / netif_set_link_down() в драйверах или виртуальных интерфейсах. Может использоваться вместе с LWIP_NETIF_EXT_STATUS_CALLBACK для получения улучшенного оповещения о событиях.

Ограничения. lwIP в ESP-IDF поддерживает безопасную работу в многопоточной среде (thread-safe) при определенных сценариях, но с ограничениями. Можно одновременно выполнять операции чтения (read), записи (write) и закрытия соединения (close) из разных потоков на одном и том же сокете. Однако не поддерживается одновременное выполнение нескольких чтений или записей из более чем одного потока на одном и том же сокете. Приложения, которые нуждаются в одновременных чтениях или записях из нескольких потоков одного и того же сокета, должнф рквлизоывть дополнительные механизмы синхронизации, такие как блокировка вокруг операций с сокетом.

Дополнения ESP-IDF для lwIP все еще страдают от ограничения глобализации DNS на интерфейсах, что описано выше в разделе "Адаптированное API". Чтобы справиться с этим ограничением в коде приложения, может исопользоваться макрос FALLBACK_DNS_SERVER_ADDRESS() для определения глобального резервного сервера разрешения имен (global DNS fallback server) доступного со всех интерфейсов. Альтернативно у вас есть опция поддерживать сервера DNS на каждом интерфейсе и переконфигурировать их каждый раз, когда изменяется интерфейс по умолчанию.

Количество IP-адресов, возвращаемое функциями базы данных адресов сети (network database API), такими как getaddrinfo() и gethostbyname(), ограничено макросом DNS_MAX_HOST_IP. По умолчанию значение этого макроса установлено в 1.

В реализации getaddrinfo() каноническое имя недоступно. Таким образом, поле ai_canonname первой возвращенной структуры addrinfo будет всегда ссылаться на аргумент nodename или строку с таким же содержимым.

Каноническое имя (canonical name) — это официальное, основное имя узла в DNS, то есть то, которое считается «истинным» или «эталонным».

Простыми словами: когда вы делаете запрос по одному имени (например, `www.example.com`), DNS может указать, что на самом деле настоящее имя этого сервера — `example-server-123.example.com`. Это второе имя и есть каноническое имя.

[Как это работает с CNAME-записями]

Чаще всего каноническое имя связано с DNS-записью типа CNAME (Canonical Name). Запись CNAME создаёт псевдоним (alias) для другого имени.

Пример:

- Вы запрашиваете IP-адрес для: `www.my-site.com`
- В DNS есть запись: `www.my-site.com CNAME my-server-01.my-site.com`
- Каноническое имя = `my-server-01.my-site.com`
- Затем DNS смотрит уже A-запись для `my-server-01.my-site.com` и возвращает IP.

Что означает цитата из документации lwIP, которое говорит, что для "getaddrinfo() каноническое имя недоступно": в обычной (правильной) реализации `getaddrinfo()` возвращает список структур `addrinfo`. В первой из них поле `ai_canonname` должно содержать каноническое имя узла (то есть то самое `my-server-01.my-site.com`).

Но в реализации lwIP для ESP-IDF эта возможность отсутствует — `ai_canonname` не будет содержать настоящее каноническое имя. Вместо этого там окажется:

- Либо то же самое имя, которое вы передали в `nodename` (например, `www.my-site.com`),
- Либо строка с тем же содержимым.

[Практическое следствие для программиста]

Не полагайтесь на `ai_canonname` в lwIP. Если ваш код ожидает там настоящее каноническое имя (например, для сравнения, логирования или проверки сертификатов), он будет работать некорректно или получать неверные данные.

Корректная работа с getaddrinfo() на ESP-IDF возможна, но поле `canonname` следует игнорировать.

Системный вызов getaddrinfo() в lwIP реализации ESP-IDF имеет ограничение, когда используется AF_UNSPEC, поскольку его умолчание состоит в возврате только IPv4-адреса в режиме dual stack. Это может вызвать проблему в сетях, где используется только IPv6. Чтобы справиться с этим, обходное решение состоит в двух последовательных вызовах getaddrinfo(): первый с AF_INET для запроса IPv4-адресов, и второй с AF_INET6 для запроса адресов IPv6. Для предоставления более надежного решения используйте пользовательскую функцию esp_getaddrinfo(), которая добавлена в слой портирования lwIP с целью обработки обоих адресов IPv4 и IPv6, когда используется AF_UNSPEC. Опция CONFIG_LWIP_USE_ESP_GETADDRINFO, доступная при разрешенных обоих IPv4 и IPv6, управляет тем, какая из функций esp_getaddrinfo() или getaddrinfo() используется. По умолванию это запрещено.

Повторные вызовы send() или sendto() на UDP socket могут иногда закончиться неудачей с ошибкой errno, равной ENOMEM. Этот отказ случается из-за ограничений на размеры буферов в низкоуровневых драйверах сетевого интерфейса. Если все буферы передачи драйвера заполнены, то передача UDP закончится неудачей. Для приложений, которые передают большой объем датаграмм UDP, и стремятся избежать любых отброшенных дейтаграмм отправителем, целесообразно реализовать проверку кода ошибки и использовать механизм повторной передачи с короткой задержкой.

Также может помочь увеличение количества буферов передачи (TX buffers) в конфигурации Wi-Fi (или Ethernet) проекта.

[Оптимизация производительности]

Производительность TCP/IP сложная тема, и производительность может быть оптимизирована с целью достижения различных параметров. Настройки по умолчанию ESP-IDF нацелены на компромисс между пропускной способностью (throughput), минимизации задержки обработки (latency) и не очень значительным расходом памяти.

Maximum Throughput. Espressif тестировала пропускную способность ESP-IDF TCP/IP с помощью приложения iperf (https://iperf.fr/), см. "Improving Network Speed" документации [15] для более подробной информации по фактическому тестированию и использованию оптимизированной конфигурации.

Важное замечание: рекомендуется применять изменения по нескольку раз и проверять производительность каждый раз при определенной рабочей нагрузке приложения.

● Если многие задачи в приложении конкурируют друг с другом за время CPU, рассмотрите возможность привязки задачи к определенному ядру (CPU affinity, опция конфигурации CONFIG_LWIP_TCPIP_TASK_AFFINITY) и запуск с фиксированным приоритетом (18, ESP_TASK_TCPIP_PRIO). Для оптимизации ресурса времени CPU привяжите конкурирующие задачи к другим ядрам или настройте их приоритеты в пониженные значения. Для подробностей см. "Built-in Task Priorities" документации [15].
● При использовании функции select() только с аргументами сокера запрет CONFIG_VFS_SUPPORT_SELECT сделает бысрее вызовы select().
● Если недостаточно свободной памяти IRAM, то выберите CONFIG_LWIP_IRAM_OPTIMIZATION и CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION для повышения пропускной способности TX/RX.

Если используется сетевой интерфейс Wi-Fi, то также обратитесь к секции "Wi-Fi Buffer Usage" документации [16].

Minimum Latency. Кроме увеличения размеров буферов, большинство изменений, которые повышают пропускную способность (throughput), также уменьшают задержку (latency) путем уменьшения времени обработки CPU, которое тратится в функциях lwIP.

Для сокетов TCP библиотека lwIP поддерживает установку стандартного флага TCP_NODELAY для запрета Nagle-алгоритма.

Алгоритм Нейгла (Nagle's algorithm) — это метод, объединяющий несколько маленьких TCP-пакетов в один большой перед отправкой. Он был разработан для повышения эффективности сети, особенно в медленных (например, dial-up) соединениях.

Зачем он нужен?

В TCP каждый отправленный пакет содержит:
- Полезные данные (например, 1 байт)
- Заголовки TCP/IP (обычно 40 байт)

Если отправлять много маленьких пакетов (например, по 1 байту), эффективность сети падает: вы отправляете 40 байт служебной информации всего на 1 байт данных. Алгоритм Нейгла решает эту проблему.

[Как работает алгоритм Нейгла]

Правила простые (упрощённо):

1. Если в очереди на отправку нет неподтверждённых данных → отправляем пакет немедленно.

2. Если есть хотя бы один неподтверждённый пакет → накапливаем новые маленькие данные в буфере, пока:
   - не накопится полный сегмент MSS (Maximum Segment Size), или
   - не придёт подтверждение (ACK) для предыдущего пакета.

Пример без алгоритма Нейгла: вы хотите отправить 4 байта по 1 байту → уйдёт 4 маленьких пакета (160 байт заголовков + 4 байта данных).

Пример с алгоритмом Нейгла: при быстром потоке маленьких байтов они объединятся в один большой пакет (или отправятся после получения ACK) → гораздо меньше служебных расходов.

[В чём проблема алгоритма Нейгла?]

Для интерактивных приложений (игры, удалённый терминал, чаты) алгоритм может создавать задержки.

Пример:

- Вы нажимаете клавишу (1 байт данных).
- Он не отправляется сразу, так как ждёт либо подтверждения предыдущего пакета, либо накопления данных.
- Задержка может достигать 200 мс (стандартный таймер задержки ACK в TCP).
- Вы чувствуете «лаги» при наборе текста.

[Флаг TCP_NODELAY]

Когда вы устанавливаете флаг TCP_NODELAY, вы отключаете алгоритм Нейгла для этого сокета. Все маленькие пакеты отправляются немедленно.

int flag = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));

Когда включать/выключать флаг TCP_NODELAY?

Сценарий Алгоритм Нейгла (NODELAY = 0) Без алгоритма (NODELAY = 1)
Передача больших файлов ✅ хорошо (эффективно) ❌ плохо (много маленьких пакетов)
Интерактивные приложения (чат, игра, SSH) ❌ плохо (задержки) ✅ хорошо (отклик)
Потоковое видео/аудио ❌ плохо (задержки) ✅ хорошо (низкая задержка)
IoT с редкими маленькими сообщениями может не оказывать влияния обычно лучше

[В контексте lwIP на ESP-IDF]

Документация сообщает, что lwIP поддерживает TCP_NODELAY. Это означает, что вы можете управлять поведением алгоритма Нейгла так же, как и в обычном Linux/BSD стеке.

Рекомендация для ESP-IDF:

- Если ваш ESP-устройство отправляет команды по TCP и требует быстрой реакции на каждое маленькое сообщение — включайте TCP_NODELAY.
- Если оно передаёт поток данных (лог-файлы, телеметрию блоками) — оставляйте алгоритм Нейгла включённым (по умолчанию).

Minimum RAM Usage. Большинство количество используемой библиотекой памяти (lwIP RAM usage) выделяется по запросу (on-demand), поскольку RAM выделяется из кучи по мере необходимости. Таким образом, изменение настроек lwIP для уменьшения использования RAM может не поменять количество занятой памяти в состоянии ожидания сетевого стека, однако может поменяться при пике нагрузки.

● Уменьшение CONFIG_LWIP_MAX_SOCKETS уменьшит максимальное количество сокетов в системе. Это также прведет к тому, что сокеты TCP в состоянии WAIT_CLOSE будут закрыты и будут более часто использоваться повторно при необходимости, когда нужно открыть новый сокет, что дополнительно снизит пиковый расход RAM.
● Уменьшение CONFIG_LWIP_TCPIP_RECVMBOX_SIZE, CONFIG_LWIP_TCP_RECVMBOX_SIZE и CONFIG_LWIP_UDP_RECVMBOX_SIZE уменьшит использование RAM ценой снижения пропускной способности, в зависимости от использования.
● Уменьшение CONFIG_LWIP_TCP_ACCEPTMBOX_SIZE уменьшит использование RAM благодаря ограничению конкурентно принятых соединений.
● Уменьшение CONFIG_LWIP_TCP_MSL и CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT уменьшит максимальное время жизни сегмента в системе. Это также приведет к тому, что сокеты TCP в состояниях TIME_WAIT и FIN_WAIT_2 будут чаще закрываться и повторно использоваться.
● Запрет CONFIG_LWIP_IPV6 может сохранить около 39 KB для кода firmware и 2 KB RAM, когда система включилась, и до 7 KB RAM, когда заработал стек TCP/IP. Если не нужна поддержка IPV6, это можно запретить для экономии расхода памяти программ и оперативной памяти.
● Запрет CONFIG_LWIP_IPV4 может сохранить около 26 KB кода firmware и 0.6 KB RAM при включении, и 6 KB RAM когда заработал стек TCP/IP. Если локальная сеть поддерживает конфигурацию только для IPv6, то IPv4 можно запретить для экономии расхода памяти программ и оперативной памяти.

Если используется Wi-Fi, см. также секцию "Wi-Fi Buffer Usage" документации [16].

Peak Buffer Usage. Пиковая память кучи, которую потребляет lwIP, является теоретически максимальной памятью, которую потребляет драйвер lwIP. Как правило, пиковая память кучи, потребляемая lwIP, зависит от:

● количества памяти, требуемого для создания соединения UDP: lwip_udp_conn
● количества памяти, требуемого для создания соединения TCP: lwip_tcp_conn
● количества соединений UDP, с которыми работает приложение: lwip_udp_con_num
● количества соединений TCP, с которыми работает приложение: lwip_tcp_con_num
● размер окна TCP TX: lwip_tcp_tx_win_size
● размер окна TCP RX: lwip_tcp_rx_win_size

Таким образом, пиковый расход памяти кучи, потребляемый lwIP, можно вычислить по формуле:

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))

Некоторым приложениям TCP требуется только одно соединение TCP. Однако они могут принять решение закрыть это соединение TCP и создать новое, когда произошла ошибка (например, сбой при отправке данных). Это может привести к тому, что одновременно в системе существует несколько соединений TCP, потому что может пройти много времени для закрытия соединения TCP, в соответствии с машиной состояний TCP, см. RFC793.

[Ссылки]

1. ESP-IDF lwIP site:espressif.com.
2. lwIP: вывод отладочных сообщений.
3. LwIP: распространенные ошибки.
4. LwIP Raw/TCP.
5. EE-312: построение сложных LwIP-приложений на основе VDK Blackfin.
6. ESP-NETIF.
7. ESP-IDF: драйвер Wi-Fi.
8. SNTP Time Synchronization site:espressif.com.
9. ICMP Echo site:espressif.com.
10. mDNS Service site:espressif.com.
11. espressif/esp_modem site:espressif.com.
12. Virtual Filesystem Component site:espressif.com.
13. Netconn API site:lwip.fandom.com.
14. ESP-IDF Build System.
15. Speed Optimization site:espressif.com.
16. Wi-Fi Performance and Power Save site:espressif.com.

 

Добавить комментарий


Защитный код
Обновить

Top of Page