diff --git a/components/esp_netif/include/esp_netif.h b/components/esp_netif/include/esp_netif.h index 409eaabc9d8..204bffc41a7 100644 --- a/components/esp_netif/include/esp_netif.h +++ b/components/esp_netif/include/esp_netif.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -682,7 +682,19 @@ esp_err_t esp_netif_dhcps_stop(esp_netif_t *esp_netif); */ esp_err_t esp_netif_dhcps_get_clients_by_mac(esp_netif_t *esp_netif, int num, esp_netif_pair_mac_ip_t *mac_ip_pair); - +/** + * @brief Populate IP addresses of client from the lwIP ARP cache on this interface + * + * Walks the ARP table and fills in ip when the Ethernet address matches and the entry belongs to esp_netif. + * + * @param[in] esp_netif Handle to esp-netif instance + * @param[in,out] mac_ip_pair MAC/IP pair to fill in the IP + * @return + * - ESP_OK on success (including when nothing was found in ARP) + * - ESP_ERR_ESP_NETIF_INVALID_PARAMS on invalid params + * - ESP_ERR_NOT_SUPPORTED if ARP is disabled in lwIP + */ +esp_err_t esp_netif_arp_get_client_by_mac(esp_netif_t *esp_netif, esp_netif_pair_mac_ip_t *mac_ip_pair); /** * @brief Set DNS Server information diff --git a/components/esp_netif/lwip/esp_netif_lwip.c b/components/esp_netif/lwip/esp_netif_lwip.c index f4d5d6cc703..06dd102ea5c 100644 --- a/components/esp_netif/lwip/esp_netif_lwip.c +++ b/components/esp_netif/lwip/esp_netif_lwip.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -1338,7 +1338,7 @@ esp_err_t esp_netif_receive(esp_netif_t *esp_netif, void *buffer, size_t len, vo } #if CONFIG_LWIP_IPV4 -static esp_err_t esp_netif_start_ip_lost_timer(esp_netif_t *esp_netif); +static esp_err_t esp_netif_start_ip_lost_timer(esp_netif_t *esp_netif, bool renew); // // DHCP: @@ -1401,7 +1401,7 @@ static void esp_netif_internal_dhcpc_cb(struct netif *netif) } } else { if (!ip4_addr_cmp(&ip_info->ip, IP4_ADDR_ANY4)) { - esp_netif_start_ip_lost_timer(esp_netif); + esp_netif_start_ip_lost_timer(esp_netif, false); } } } @@ -1441,19 +1441,22 @@ static void esp_netif_ip_lost_timer(void *arg) } } -static esp_err_t esp_netif_start_ip_lost_timer(esp_netif_t *esp_netif) +static esp_err_t esp_netif_start_ip_lost_timer(esp_netif_t *esp_netif, bool renew) { esp_netif_ip_info_t *ip_info_old = esp_netif->ip_info; struct netif *netif = esp_netif->lwip_netif; ESP_LOGD(TAG, "%s esp_netif:%p", __func__, esp_netif); - if (esp_netif->timer_running) { - ESP_LOGD(TAG, "if%p start ip lost tmr: already started", esp_netif); - return ESP_OK; - } - if ( netif && (CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL > 0)) { + if (esp_netif->timer_running) { + if (renew) { + sys_untimeout(esp_netif_ip_lost_timer, (void *)esp_netif); + } else{ + ESP_LOGD(TAG, "if%p start ip lost tmr: already started", esp_netif); + return ESP_OK; + } + } esp_netif->timer_running = true; sys_timeout(CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL * 1000, esp_netif_ip_lost_timer, (void *)esp_netif); ESP_LOGD(TAG, "if%p start ip lost tmr: interval=%d", esp_netif, CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL); @@ -1483,7 +1486,7 @@ static esp_err_t esp_netif_dhcpc_stop_api(esp_netif_api_msg_t *msg) if (p_netif != NULL) { dhcp_stop(p_netif); esp_netif_reset_ip_info(esp_netif); - esp_netif_start_ip_lost_timer(esp_netif); + esp_netif_start_ip_lost_timer(esp_netif, false); } else { ESP_LOGD(TAG, "dhcp client if not ready"); return ESP_ERR_ESP_NETIF_IF_NOT_READY; @@ -1546,7 +1549,7 @@ static esp_err_t esp_netif_dhcpc_start_api(esp_netif_api_msg_t *msg) ip_addr_set_zero(&p_netif->ip_addr); ip_addr_set_zero(&p_netif->netmask); ip_addr_set_zero(&p_netif->gw); - esp_netif_start_ip_lost_timer(esp_netif); + esp_netif_start_ip_lost_timer(esp_netif, true); } else { ESP_LOGD(TAG, "dhcp client re init"); esp_netif->dhcpc_status = ESP_NETIF_DHCP_INIT; @@ -1797,7 +1800,7 @@ static esp_err_t esp_netif_down_api(esp_netif_api_msg_t *msg) if (esp_netif->flags & ESP_NETIF_DHCP_CLIENT) { #if CONFIG_LWIP_IPV4 - esp_netif_start_ip_lost_timer(esp_netif); + esp_netif_start_ip_lost_timer(esp_netif, false); #endif } @@ -1977,6 +1980,52 @@ esp_err_t esp_netif_dhcps_get_clients_by_mac(esp_netif_t *esp_netif, int num, es #endif // CONFIG_LWIP_DHCPS } +#if CONFIG_LWIP_IPV4 && LWIP_ARP +static esp_err_t esp_netif_arp_get_client_by_mac_api(esp_netif_api_msg_t *msg) +{ + esp_netif_t *esp_netif = msg->esp_netif; + esp_netif_pair_mac_ip_t *mac_ip_pair = msg->data; + struct netif *lwip_netif = esp_netif ? esp_netif->lwip_netif : NULL; + + if (lwip_netif == NULL || mac_ip_pair == NULL) { + return ESP_ERR_ESP_NETIF_INVALID_PARAMS; + } + + mac_ip_pair->ip.addr = 0; + + for (size_t i = 0; i < ARP_TABLE_SIZE; i++) { + ip4_addr_t *ip_ptr = NULL; + struct netif *nif = NULL; + struct eth_addr *eth_ptr = NULL; + + if (!etharp_get_entry(i, &ip_ptr, &nif, ð_ptr)) { + continue; + } + if (nif != lwip_netif) { + continue; + } + if (memcmp(eth_ptr->addr, mac_ip_pair->mac, ETH_HWADDR_LEN) != 0) { + continue; + } + mac_ip_pair->ip.addr = ip_ptr->addr; + break; + } + return ESP_OK; +} +#endif /* CONFIG_LWIP_IPV4 && LWIP_ARP */ + +esp_err_t esp_netif_arp_get_client_by_mac(esp_netif_t *esp_netif, esp_netif_pair_mac_ip_t *mac_ip_pair) +{ +#if CONFIG_LWIP_IPV4 && LWIP_ARP + if (esp_netif == NULL || mac_ip_pair == NULL) { + return ESP_ERR_ESP_NETIF_INVALID_PARAMS; + } + return esp_netif_lwip_ipc_call(esp_netif_arp_get_client_by_mac_api, esp_netif, (void *)mac_ip_pair); +#else + return ESP_ERR_NOT_SUPPORTED; +#endif /* CONFIG_LWIP_IPV4 && LWIP_ARP */ +} + static esp_err_t esp_netif_set_dns_info_api(esp_netif_api_msg_t *msg) { esp_netif_t *esp_netif = msg->esp_netif; diff --git a/components/esp_wifi/include/esp_wifi_ap_get_sta_list.h b/components/esp_wifi/include/esp_wifi_ap_get_sta_list.h index fc539184eba..f1e10c53762 100644 --- a/components/esp_wifi/include/esp_wifi_ap_get_sta_list.h +++ b/components/esp_wifi/include/esp_wifi_ap_get_sta_list.h @@ -36,7 +36,8 @@ typedef struct { /** * @brief Get IP information for stations connected to the Wi-Fi AP interface * - * @note If `CONFIG_LWIP_DHCPS` is disabled then `ip` address field will not be populated in sta list + * @note If `CONFIG_LWIP_DHCPS` is disabled then DHCP lookup is skipped; IPv4 may still be filled from the lwIP ARP cache when available. + * After DHCP (when enabled), any station still without an IP is resolved via ARP table on the same interface. * * @warning This API works only for the default Wi-Fi AP interface, i.e. esp-netif with key="WIFI_AP_DEF" * diff --git a/components/esp_wifi/src/wifi_default_ap.c b/components/esp_wifi/src/wifi_default_ap.c index 481f3183465..440673a95d4 100644 --- a/components/esp_wifi/src/wifi_default_ap.c +++ b/components/esp_wifi/src/wifi_default_ap.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -30,7 +30,17 @@ esp_err_t esp_wifi_ap_get_sta_list_with_ip(const wifi_sta_list_t *wifi_sta_list, memcpy(wifi_sta_ip_mac_list->sta[i].mac, wifi_sta_list->sta[i].mac, 6); memset(&wifi_sta_ip_mac_list->sta[i].ip, 0, sizeof(esp_ip4_addr_t)); } - return esp_netif_dhcps_get_clients_by_mac(ap, num, wifi_sta_ip_mac_list->sta); + esp_err_t err = esp_netif_dhcps_get_clients_by_mac(ap, num, wifi_sta_ip_mac_list->sta); + if (err != ESP_OK && err != ESP_ERR_NOT_SUPPORTED) { + return err; + } + for (int i = 0; i < num; i++) { + if (wifi_sta_ip_mac_list->sta[i].ip.addr == 0) { + // Try to get the IP from the ARP table, and not care about the result + esp_netif_arp_get_client_by_mac(ap, &wifi_sta_ip_mac_list->sta[i]); + } + } + return ESP_OK; #else return ESP_ERR_NOT_SUPPORTED; #endif diff --git a/components/lwip/apps/dhcpserver/dhcpserver.c b/components/lwip/apps/dhcpserver/dhcpserver.c index b1fe87e4f3d..58ceae910ee 100644 --- a/components/lwip/apps/dhcpserver/dhcpserver.c +++ b/components/lwip/apps/dhcpserver/dhcpserver.c @@ -5,6 +5,7 @@ */ #include #include +#include #include #include "lwip/dhcp.h" #include "lwip/err.h" @@ -48,6 +49,7 @@ #define DHCPACK 5 #define DHCPNAK 6 #define DHCPRELEASE 7 +#define DHCPINFORM 8 #define DHCP_OPTION_PAD 0 #define DHCP_OPTION_SUBNET_MASK 1 @@ -101,6 +103,7 @@ #define DHCPS_STATE_NAK 4 #define DHCPS_STATE_IDLE 5 #define DHCPS_STATE_RELEASE 6 +#define DHCPS_STATE_INFORM_ACK 7 typedef enum { DHCPS_HANDLE_CREATED = 0, @@ -428,7 +431,7 @@ static u8_t *add_msg_type(u8_t *optptr, u8_t type) * Parameters : optptr -- the addr of DHCP message option * Returns : the addr of DHCP message option *******************************************************************************/ -static u8_t *add_offer_options(dhcps_t *dhcps, u8_t *optptr) +static u8_t *add_offer_options(dhcps_t *dhcps, u8_t *optptr, bool include_lease_time) { u32_t i; ip4_addr_t ipadd; @@ -439,12 +442,14 @@ static u8_t *add_offer_options(dhcps_t *dhcps, u8_t *optptr) *optptr++ = 4; optptr = dhcps_option_ip(optptr, &dhcps->dhcps_mask); - *optptr++ = DHCP_OPTION_LEASE_TIME; - *optptr++ = 4; - *optptr++ = ((dhcps->dhcps_lease_time * DHCPS_LEASE_UNIT) >> 24) & 0xFF; - *optptr++ = ((dhcps->dhcps_lease_time * DHCPS_LEASE_UNIT) >> 16) & 0xFF; - *optptr++ = ((dhcps->dhcps_lease_time * DHCPS_LEASE_UNIT) >> 8) & 0xFF; - *optptr++ = ((dhcps->dhcps_lease_time * DHCPS_LEASE_UNIT) >> 0) & 0xFF; + if (include_lease_time) { + *optptr++ = DHCP_OPTION_LEASE_TIME; + *optptr++ = 4; + *optptr++ = ((dhcps->dhcps_lease_time * DHCPS_LEASE_UNIT) >> 24) & 0xFF; + *optptr++ = ((dhcps->dhcps_lease_time * DHCPS_LEASE_UNIT) >> 16) & 0xFF; + *optptr++ = ((dhcps->dhcps_lease_time * DHCPS_LEASE_UNIT) >> 8) & 0xFF; + *optptr++ = ((dhcps->dhcps_lease_time * DHCPS_LEASE_UNIT) >> 0) & 0xFF; + } *optptr++ = DHCP_OPTION_SERVER_ID; *optptr++ = 4; @@ -535,6 +540,44 @@ static u8_t *add_end(u8_t *optptr) return optptr; } +/** + * Read DHCP message type (option 53) from the variable-length option area + * (starts after the magic cookie at options[4]). + */ +static u8_t dhcps_get_option_msg_type(u8_t *optptr, u16_t len) +{ + u8_t *end; + + if (len <= 0) { + return 0; + } + end = optptr + len; + + while (optptr < end) { + u8_t code = *optptr; + if (code == DHCP_OPTION_END) { + break; + } + if (code == 0) { + /* Pad */ + optptr++; + continue; + } + if (optptr + 1 >= end) { + break; + } + u8_t optlen = optptr[1]; + if ((optptr + 2 + optlen) > end) { + break; + } + if (code == DHCP_OPTION_MSG_TYPE && optlen >= 1) { + return optptr[2]; + } + optptr += (u16_t)optlen + 2; + } + return 0; +} + /****************************************************************************** * FunctionName : create_msg * Description : create response message @@ -592,7 +635,11 @@ static void dhcps_response_ip_set(dhcps_t *dhcps, struct dhcps_msg *m, ip4_addr_ /* If the 'giaddr' field is zero and the 'ciaddr' is nonzero, * the server unicasts DHCPOFFER and DHCPACK message to the address in 'ciaddr'*/ ip4_addr_set(ip4_out, &ip4_ciaddr); - etharp_add_static_entry(&ip4_ciaddr, &chaddr); + /* when 'yiaddr' is zero, means DHCPACK INFO, no need to add static entry */ + if (!ip4_addr_isany_val(ip4_yiaddr)) { + /* add the IP<->MAC as static entry into the arp table. */ + etharp_add_static_entry(&ip4_ciaddr, &chaddr); + } } else if (!BROADCAST_BIT_IS_SET(htons(m->flags))) { /* If the 'giaddr' is zero and 'ciaddr' is zero, and the broadcast bit is not set, * the server unicasts DHCPOFFER and DHCPACK message to the client's hardware address and @@ -643,7 +690,7 @@ static void send_offer(dhcps_t *dhcps, struct dhcps_msg *m, u16_t len) create_msg(dhcps, m); end = add_msg_type(&m->options[4], DHCPOFFER); - end = add_offer_options(dhcps, end); + end = add_offer_options(dhcps, end, true); LWIP_HOOK_DHCPS_POST_APPEND_OPTS(dhcps->dhcps_netif, dhcps, DHCPOFFER, &end) end = add_end(end); @@ -826,7 +873,7 @@ static void send_ack(dhcps_t *dhcps, struct dhcps_msg *m, u16_t len) create_msg(dhcps, m); end = add_msg_type(&m->options[4], DHCPACK); - end = add_offer_options(dhcps, end); + end = add_offer_options(dhcps, end, true); LWIP_HOOK_DHCPS_POST_APPEND_OPTS(dhcps->dhcps_netif, dhcps, DHCPACK, &end) end = add_end(end); @@ -893,6 +940,50 @@ static void send_ack(dhcps_t *dhcps, struct dhcps_msg *m, u16_t len) } } +/****************************************************************************** + * FunctionName : send_ack_inform + * Description : DHCPACK response to DHCPINFORM (RFC 2131: yiaddr = 0, no lease time) + * Parameters : m -- DHCP message info + * Returns : none +*******************************************************************************/ +static void send_ack_inform(dhcps_t *dhcps, struct dhcps_msg *m, u16_t len) +{ + u8_t *end; + struct pbuf *p, *q; + u8_t *data; + u16_t cnt = 0; + u16_t i; + create_msg(dhcps, m); + memset(m->yiaddr, 0, sizeof(m->yiaddr)); + + end = add_msg_type(&m->options[4], DHCPACK); + end = add_offer_options(dhcps, end, false); + LWIP_HOOK_DHCPS_POST_APPEND_OPTS(dhcps->dhcps_netif, dhcps, DHCPACK, &end) + end = add_end(end); + + p = dhcps_pbuf_alloc(len); + if (p == NULL) { + return; + } + + q = p; + while (q != NULL) { + data = (u8_t *)q->payload; + for (i = 0; i < q->len; i++) { + data[i] = ((u8_t *) m)[cnt++]; + } + q = q->next; + } + + ip_addr_t ip_temp = IPADDR4_INIT(0x0); + dhcps_response_ip_set(dhcps, m, ip_2_ip4(&ip_temp)); + udp_sendto(dhcps->dhcps_pcb, p, &ip_temp, DHCPS_CLIENT_PORT); + + if (p->ref != 0) { + pbuf_free(p); + } +} + /****************************************************************************** * FunctionName : parse_options * Description : parse DHCP message options @@ -1025,6 +1116,23 @@ static s16_t parse_msg(dhcps_t *dhcps, struct dhcps_msg *m, u16_t len) #if DHCPS_DEBUG DHCPS_LOG("dhcps: len = %d\n", len); #endif + { + u8_t msg_type = dhcps_get_option_msg_type(&m->options[4], (s16_t)len); + if (msg_type == DHCPINFORM) { + ip4_addr_t ciaddr; + memcpy(&ciaddr.addr, m->ciaddr, sizeof(ciaddr.addr)); + if (ip4_addr_isany(&ciaddr)) { + /* RFC 2131: client MUST set ciaddr to its configured address */ + return 0; + } + if (!ip4_addr_netcmp(&ciaddr, &dhcps->server_address, &dhcps->dhcps_mask)) { + /* No configuration for this client */ + return 0; + } + return DHCPS_STATE_INFORM_ACK; + } + } + ip4_addr_t addr_tmp; struct dhcps_pool *pdhcps_pool = NULL; @@ -1249,6 +1357,13 @@ static void handle_dhcp(void *arg, send_nak(dhcps, pmsg_dhcps, malloc_len); break; + case DHCPS_STATE_INFORM_ACK://7 +#if DHCPS_DEBUG + DHCPS_LOG("dhcps: handle_dhcp-> DHCPD_STATE_INFORM_ACK\n"); +#endif + send_ack_inform(dhcps, pmsg_dhcps, malloc_len); + break; + default : break; } diff --git a/components/lwip/include/apps/dhcpserver/dhcpserver.h b/components/lwip/include/apps/dhcpserver/dhcpserver.h index 975f00c73f5..aee3aef4523 100644 --- a/components/lwip/include/apps/dhcpserver/dhcpserver.h +++ b/components/lwip/include/apps/dhcpserver/dhcpserver.h @@ -61,6 +61,7 @@ typedef enum * - MAX_STATION_NUM: Maximum number of clients, set to Kconfig value CONFIG_LWIP_DHCPS_MAX_STATION_NUM * - LWIP_HOOK_DHCPS_POST_STATE: Used to inject user code after parsing DHCP message, not defined * - could be enabled in lwipopts.h or via CC_FLAGS + * - parse states include DHCPS_STATE_OFFER (1), ACK (3), NAK (4), INFORM_ACK (7), etc. * - basic usage of the hook to print hex representation of the entire option field is below: * #define LWIP_HOOK_DHCPS_POST_STATE(msg, len, state) \ * ({ s16_t ret = state; if (state == DHCPS_STATE_ACK) { ESP_LOG_BUFFER_HEXDUMP("DHCPS",msg->options, 312, ESP_LOG_INFO);} ret; })