diff --git a/components/lwip/test_apps/main/lwip_test.c b/components/lwip/test_apps/main/lwip_test.c index 187f5abd91..8feacb5593 100644 --- a/components/lwip/test_apps/main/lwip_test.c +++ b/components/lwip/test_apps/main/lwip_test.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -24,6 +24,11 @@ #include "dhcpserver/dhcpserver.h" #include "dhcpserver/dhcpserver_options.h" #include "esp_sntp.h" +#include "lwip/dhcp.h" +#include "lwip/acd.h" +#include "lwip/prot/dhcp.h" +#include "lwip/prot/acd.h" +#include "lwip/prot/etharp.h" #define ETH_PING_END_BIT BIT(1) #define ETH_PING_DURATION_MS (5000) @@ -278,6 +283,82 @@ static void dhcps_test_dns_options(dns_callback_type_t cb_type, TEST_ASSERT((api.ret_start == ERR_OK) == pass); } +typedef struct { + EventGroupHandle_t event; + int self_mac_cb_calls; + int other_mac_cb_calls; +} acd_test_ctx_t; + +static acd_test_ctx_t g_acd_ctx; + +static void acd_test_cb(struct netif *netif, acd_callback_enum_t state) +{ + (void)netif; + (void)state; + /* We only need to know that a callback was triggered (decline/restart). */ + g_acd_ctx.other_mac_cb_calls++; +} + +static void dhcp_acd_arp_check_api(void *arg) +{ + acd_test_ctx_t *ctx = (acd_test_ctx_t *)arg; + struct netif *netif = NULL; + NETIF_FOREACH(netif) { + if (netif->name[0] == 'l' && netif->name[1] == 'o') { + break; + } + } + TEST_ASSERT_NOT_NULL(netif); + + /* Set our interface MAC to a known value */ + const struct eth_addr self = { .addr = { 0x08, 0x3a, 0x8d, 0x41, 0x13, 0x14 } }; + netif->hwaddr_len = ETH_HWADDR_LEN; + SMEMCPY(netif->hwaddr, self.addr, ETH_HWADDR_LEN); + + /* Attach a DHCP client struct with CHECKING state and PROBING ACD state */ + static struct dhcp dhcp; + memset(&dhcp, 0, sizeof(dhcp)); + netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP, &dhcp); + IP4_ADDR(&dhcp.offered_ip_addr, 192, 168, 88, 4); + dhcp.state = DHCP_STATE_CHECKING; + dhcp.acd.state = ACD_STATE_PROBING; + dhcp.acd.acd_conflict_callback = acd_test_cb; + + /* Case 1: ARP reply from our offered IP but with our own MAC -> NO conflict */ + struct etharp_hdr hdr = {0}; + hdr.opcode = PP_HTONS(ARP_REPLY); + IPADDR_WORDALIGNED_COPY_FROM_IP4_ADDR_T(&hdr.sipaddr, &dhcp.offered_ip_addr); + SMEMCPY(hdr.shwaddr.addr, self.addr, ETH_HWADDR_LEN); + ctx->self_mac_cb_calls = 0; + g_acd_ctx.other_mac_cb_calls = 0; + acd_arp_reply(netif, &hdr); + /* No callback should be invoked for self-MAC */ + TEST_ASSERT_EQUAL_INT(0, g_acd_ctx.other_mac_cb_calls); + + /* Case 2: ARP reply from offered IP with a different MAC -> conflict expected */ + const struct eth_addr other = { .addr = { 0x08, 0x3a, 0x8d, 0x41, 0x13, 0x15 } }; + SMEMCPY(hdr.shwaddr.addr, other.addr, ETH_HWADDR_LEN); + g_acd_ctx.other_mac_cb_calls = 0; + acd_arp_reply(netif, &hdr); + /* Our callback should be called (DECLINE/RESTART) at least once */ + TEST_ASSERT(g_acd_ctx.other_mac_cb_calls > 0); + + xEventGroupSetBits(ctx->event, 1); +} + +TEST(lwip, dhcp_arp_probe_self_mac_is_ok) +{ + test_case_uses_tcpip(); + g_acd_ctx.event = xEventGroupCreate(); + TEST_ASSERT_NOT_NULL(g_acd_ctx.event); + g_acd_ctx.self_mac_cb_calls = 0; + g_acd_ctx.other_mac_cb_calls = 0; + + tcpip_callback(dhcp_acd_arp_check_api, &g_acd_ctx); + xEventGroupWaitBits(g_acd_ctx.event, 1, true, true, pdMS_TO_TICKS(5000)); + vEventGroupDelete(g_acd_ctx.event); +} + TEST(lwip, dhcp_server_dns_options) { test_case_uses_tcpip(); @@ -410,6 +491,7 @@ TEST_GROUP_RUNNER(lwip) RUN_TEST_CASE(lwip, dhcp_server_dns_options) RUN_TEST_CASE(lwip, sntp_client_time_2015) RUN_TEST_CASE(lwip, sntp_client_time_2048) + RUN_TEST_CASE(lwip, dhcp_arp_probe_self_mac_is_ok) } void app_main(void) diff --git a/docs/en/api-guides/lwip.rst b/docs/en/api-guides/lwip.rst index a8f28955fa..35c09470ad 100644 --- a/docs/en/api-guides/lwip.rst +++ b/docs/en/api-guides/lwip.rst @@ -38,6 +38,17 @@ Some common lwIP app APIs are supported indirectly by ESP-IDF: - mDNS uses a different implementation to the lwIP default mDNS, see :doc:`/api-reference/protocols/mdns`. But lwIP can look up mDNS hosts using standard APIs such as ``gethostbyname()`` and the convention ``hostname.local``, provided the :ref:`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 :doc:`/api-reference/network/esp_netif` component to create and configure a PPP network interface, by means of the ``ESP_NETIF_DEFAULT_PPP()`` macro defined in :component_file:`esp_netif/include/esp_netif_defaults.h`. Additional runtime settings are provided via :component_file:`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. +DHCP Address Conflict Detection +------------------------------- + +Usually, DHCP servers verify that a selected IPv4 address is unique on the network before offering it. However, some, including the DHCP server in ESP-IDF, do not perform verification by default to simplify operation and speed up address assignment. + +If you want to avoid any possible IP address conflicts and connectivity issues, the ESP-IDF DHCP client provides several options to validate the offered IPv4 address before binding it (see :ref:`CONFIG_LWIP_DHCP_CHECKS_OFFERED_ADDRESS`): + +- **Simple ARP check, default option** (``CONFIG_LWIP_DHCP_DOES_ARP_CHECK``): Sends two ARP probes and only declines the offer if a reply for the offered IP comes from a different MAC address than the interface MAC. This is fast (about 1–2 seconds) and avoids false conflicts on networks where the AP echoes the client's MAC in ARP replies. **Use this option if you encounter DHCP DECLINE loops where the ARP reply for the offered IP advertises the interface's own MAC.** +- **Address Conflict Detection (ACD)** (``CONFIG_LWIP_DHCP_DOES_ACD_CHECK``): Uses upstream lwIP ACD per RFC 5227 to probe/announce addresses. Some access points respond to ARP probes with the client's own MAC for the offered IP; upstream behavior treats any matching sender IP during PROBING as a conflict, which can cause repeated DHCP DECLINEs on such networks. **Use this option only if you need full RFC 5227 compliance and are aware of the potential issues with certain access points.** +- **No conflict detection** (``CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP``): Binds the address without additional checks. **Use this option for maximum compatibility when conflict detection is not required.** + BSD Sockets API --------------- diff --git a/docs/zh_CN/api-guides/lwip.rst b/docs/zh_CN/api-guides/lwip.rst index 338d548e21..fccced3841 100644 --- a/docs/zh_CN/api-guides/lwip.rst +++ b/docs/zh_CN/api-guides/lwip.rst @@ -38,6 +38,17 @@ ESP-IDF 间接支持以下常见的 lwIP 应用程序 API: - mDNS 与 lwIP 的默认 mDNS 使用不同实现方式,请参阅 :doc:`/api-reference/protocols/mdns`。但启用 :ref:`CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES` 设置项后,lwIP 可以使用 ``gethostbyname()`` 等标准 API 和 ``hostname.local`` 约定查找 mDNS 主机。 - lwIP 中的 PPP 实现可用于在 ESP-IDF 中创建 PPPoS(串行 PPP)接口。请参阅 :doc:`/api-reference/network/esp_netif` 组件文档,使用 :component_file:`esp_netif/include/esp_netif_defaults.h` 中定义的 ``ESP_NETIF_DEFAULT_PPP()`` 宏创建并配置 PPP 网络接口。:component_file:`esp_netif/include/esp_netif_ppp.h` 中提供了其他的运行时设置。PPPoS 接口通常用于与 NBIoT/GSM/LTE 调制解调器交互。`esp_modem `_ 仓库还支持更多应用层友好的 API,该仓库内部使用了上述 PPP lwIP 模块。 +DHCP 地址冲突检测 +------------------------------- + +通常情况下,DHCP 服务器在提供选定的 IPv4 地址前,会验证该地址在网络上的唯一性。然而,有些服务器(包括 ESP-IDF 中的 DHCP 服务器)为了简化操作并加快地址分配速度,默认情况下不会进行这种验证。 + +为避免潜在的 IP 地址冲突与连接问题,ESP-IDF 的 DHCP 客户端提供了多种选项,可在绑定 IPv4 地址前对服务器分配的地址进行验证(参见 :ref:`CONFIG_LWIP_DHCP_CHECKS_OFFERED_ADDRESS`): + +- **简单 ARP 检测,默认选项** (``CONFIG_LWIP_DHCP_DOES_ARP_CHECK``):发送两个 ARP 探测,只有当分配的 IP 的回复来自与接口 MAC 不同的 MAC 地址时才拒绝该分配。这种方式速度快约 1-2 秒,可避免 AP 在 ARP 回复中回显客户端 MAC 的网络中出现误判冲突。**如果遇到 DHCP DECLINE 循环,即分配 IP 的 ARP 回复显示接口自身的 MAC 时,请使用此选项。** +- **地址冲突检测 (ACD)** (``CONFIG_LWIP_DHCP_DOES_ACD_CHECK``):使用上游 lwIP ACD (符合 RFC 5227 标准)来探测或宣告地址。某些接入点会使用客户端自身的 MAC 地址来响应 ARP 探测;上游行为会将探测期间收到的任何匹配发送方 IP 视为冲突,这可能导致在此类网络上反复出现 DHCP DECLINE。**只有在需要完全符合 RFC 5227 标准,并了解某些接入点的潜在问题时,才使用此选项。** +- **无冲突检测** (``CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP``):不进行额外检查直接绑定地址。**当不需要冲突检测时,使用此选项可获得最大兼容性。** + BSD 套接字 API -----------------