mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
fix(lwip): Clarify and test DHCP conflict detection
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 <https://components.espressif.com/component/espressif/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
|
||||
---------------
|
||||
|
||||
|
||||
@@ -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 <https://components.espressif.com/component/espressif/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
|
||||
-----------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user