From added3a0607c005a41ca99c4a9a71d07f9e3c65f Mon Sep 17 00:00:00 2001 From: Sarvesh Bodakhe Date: Tue, 30 Jun 2026 15:30:45 +0530 Subject: [PATCH] feat(nan): pin datapath IPv6 neighbor via esp_netif static entry - Add esp_wifi_netif_set_static_neighbor() that to add/remove IPv6 static entry using netif API to skip Neighbor Discovery Protocol - Move the fe80::/64 + EUI-64 derivation to esp_wifi_netif.c --- components/esp_wifi/include/esp_wifi_netif.h | 36 ++++++++++++++ components/esp_wifi/src/wifi_default.c | 4 ++ components/esp_wifi/src/wifi_netif.c | 43 ++++++++++++++++ .../esp_wifi/wifi_apps/nan_app/src/nan_app.c | 49 ++++++++++++------- 4 files changed, 114 insertions(+), 18 deletions(-) diff --git a/components/esp_wifi/include/esp_wifi_netif.h b/components/esp_wifi/include/esp_wifi_netif.h index 7dfa724b066..ca054e8a6e1 100644 --- a/components/esp_wifi/include/esp_wifi_netif.h +++ b/components/esp_wifi/include/esp_wifi_netif.h @@ -81,6 +81,42 @@ bool esp_wifi_is_if_ready_when_started(wifi_netif_driver_t ifx); */ esp_err_t esp_wifi_register_if_rxcb(wifi_netif_driver_t ifx, esp_netif_receive_t fn, void * arg); +/** + * @brief Derive an IPv6 link-local address from a link-layer (MAC) address + * + * Computes fe80::/64 combined with the EUI-64 form of the given MAC (the 802 + * group bit complemented) into an esp_ip6_addr_t (zone 0). Interface-agnostic. + * + * @param[out] ip6 destination, set to the derived IPv6 link-local address + * @param[in] mac source link-layer (MAC) address (6 bytes) + */ +void esp_wifi_netif_get_ip6_linklocal_from_mac(esp_ip6_addr_t *ip6, const uint8_t mac[6]); + +#if CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES +/** + * @brief Pin (or remove) a static IPv6 link-local neighbor mapping on a wifi netif + * + * Installs a fixed link-local IPv6 -> MAC mapping for a peer reachable on the + * given wifi interface so that traffic to the peer bypasses Neighbor Discovery + * (no NS/NA exchanged), or removes a previously installed one. This layer owns + * both the netif lookup (from the interface type) and the derivation of the + * peer's link-local address from its MAC, so the caller only supplies the + * interface and the peer MAC. Only available when lwIP static ND6 entries are + * enabled. + * + * @param[in] wifi_if wifi interface the peer is reachable on + * @param[in] mac peer's link-layer (MAC) address + * @param[in] add true to add the mapping, false to remove it + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if mac is NULL or wifi_if is out of range + * - ESP_ERR_INVALID_STATE if the interface's netif is not up + * - error code from the underlying esp_netif call otherwise + */ +esp_err_t esp_wifi_netif_set_static_neighbor(wifi_interface_t wifi_if, const uint8_t mac[6], bool add); +#endif /* CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES */ + #ifdef __cplusplus } #endif diff --git a/components/esp_wifi/src/wifi_default.c b/components/esp_wifi/src/wifi_default.c index 66ed536ac8a..3ba9d6197ab 100644 --- a/components/esp_wifi/src/wifi_default.c +++ b/components/esp_wifi/src/wifi_default.c @@ -184,6 +184,10 @@ static void wifi_default_action_nan_started(void *arg, esp_event_base_t base, in if (s_wifi_netifs[WIFI_IF_NAN] != NULL) { wifi_start(s_wifi_netifs[WIFI_IF_NAN], base, event_id, data); esp_nan_action_start(s_wifi_netifs[WIFI_IF_NAN]); + /* Bring the netif up before creating the link-local address; + * esp_netif_create_ip6_linklocal() is a no-op unless netif_is_up(). */ + esp_netif_action_connected(s_wifi_netifs[WIFI_IF_NAN], base, event_id, data); + esp_netif_create_ip6_linklocal(s_wifi_netifs[WIFI_IF_NAN]); } } diff --git a/components/esp_wifi/src/wifi_netif.c b/components/esp_wifi/src/wifi_netif.c index 96d4135dd21..b632d51ff40 100644 --- a/components/esp_wifi/src/wifi_netif.c +++ b/components/esp_wifi/src/wifi_netif.c @@ -3,6 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ +#include #include "esp_wifi.h" #include "esp_netif.h" #include "esp_log.h" @@ -181,3 +182,45 @@ esp_err_t esp_wifi_register_if_rxcb(wifi_netif_driver_t ifx, esp_netif_receive_t } return ESP_OK; } + +void esp_wifi_netif_get_ip6_linklocal_from_mac(esp_ip6_addr_t *ip6, const uint8_t mac[6]) +{ + if (ip6 == NULL || mac == NULL) { + return; + } + + /* fe80::/64 + EUI-64 of the MAC (802 group bit complemented), laid out in + * network byte order straight into esp_ip6_addr_t. Zone stays 0. */ + const uint8_t linklocal[16] = { + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + (uint8_t)(mac[0] ^ 0x02), mac[1], mac[2], 0xff, + 0xfe, mac[3], mac[4], mac[5], + }; + memset(ip6, 0, sizeof(*ip6)); + memcpy(ip6->addr, linklocal, sizeof(linklocal)); +} + +#if CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES +esp_err_t esp_wifi_netif_set_static_neighbor(wifi_interface_t wifi_if, const uint8_t mac[6], bool add) +{ + if (mac == NULL || wifi_if >= MAX_WIFI_IFS) { + return ESP_ERR_INVALID_ARG; + } + + /* The netif handle is owned by this layer (recorded when the interface's RX + * callback is registered), so callers only need to supply the interface and + * the peer MAC. */ + esp_netif_t *esp_netif = s_wifi_netifs[wifi_if]; + if (esp_netif == NULL) { + return ESP_ERR_INVALID_STATE; + } + + /* Derive the peer's link-local address; esp_netif copies only the address + * words for static neighbor entries, so no zone handling is needed here. */ + esp_ip6_addr_t addr6; + esp_wifi_netif_get_ip6_linklocal_from_mac(&addr6, mac); + + return add ? esp_netif_add_static_neighbor(esp_netif, &addr6, mac) + : esp_netif_remove_static_neighbor(esp_netif, &addr6); +} +#endif /* CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES */ diff --git a/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c b/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c index f72882e4274..9a85de3e439 100644 --- a/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c @@ -351,20 +351,12 @@ void esp_wifi_nan_get_ipv6_linklocal_from_mac(ip6_addr_t *ip6, uint8_t *mac_addr if (ip6 == NULL || mac_addr == NULL) { return; } - /* Link-local prefix. */ - ip6->addr[0] = htonl(0xfe800000ul); - ip6->addr[1] = 0; - - /* Assume hwaddr is a 48-bit IEEE 802 MAC. Convert to EUI-64 address. Complement Group bit. */ - ip6->addr[2] = htonl((((uint32_t)(mac_addr[0] ^ 0x02)) << 24) | - ((uint32_t)(mac_addr[1]) << 16) | - ((uint32_t)(mac_addr[2]) << 8) | - (0xff)); - ip6->addr[3] = htonl((uint32_t)(0xfeul << 24) | - ((uint32_t)(mac_addr[3]) << 16) | - ((uint32_t)(mac_addr[4]) << 8) | - (mac_addr[5])); - + /* Reuse the interface-agnostic derivation in the esp_wifi netif layer, then + * copy the address words into the lwIP ip6_addr_t. The two structures share + * the same layout, which is how esp_netif converts between them. */ + esp_ip6_addr_t esp_ip6; + esp_wifi_netif_get_ip6_linklocal_from_mac(&esp_ip6, mac_addr); + memcpy(ip6->addr, esp_ip6.addr, sizeof(ip6->addr)); ip6->zone = IP6_NO_ZONE; } @@ -1063,8 +1055,8 @@ static void nan_app_replied_cb(uint8_t pub_id, struct nan_cb_peer_info *peer_inf } static void nan_app_receive_cb(uint8_t svc_id, struct nan_cb_peer_info *peer_info, - uint8_t *shared_key_attr, uint16_t shared_key_attr_buf_len, - struct nan_cb_npba_t *npba) + uint8_t *shared_key_attr, uint16_t shared_key_attr_buf_len, + struct nan_cb_npba_t *npba) { if (!peer_info) { return; @@ -1525,8 +1517,6 @@ static void nan_app_ndp_confirm_cb(uint8_t status, struct ndp_cb_peer_info *peer ESP_LOG_BUFFER_HEXDUMP(TAG, ssi, ssi_len, ESP_LOG_DEBUG); } - esp_netif_action_connected(s_nan_ctx.nan_netif, WIFI_EVENT, WIFI_EVENT_NDP_CONFIRM, evt); - esp_netif_create_ip6_linklocal(s_nan_ctx.nan_netif); NAN_DATA_UNLOCK(); ip6_addr_t peer_ip6 = {0}; @@ -1537,6 +1527,20 @@ static void nan_app_ndp_confirm_cb(uint8_t status, struct ndp_cb_peer_info *peer ESP_LOGI(TAG, "NDP confirmed with Peer "MACSTR" [NDP ID - %d, Peer IPv6 - %s]", MAC2STR(peer_nmi), ndp_id, inet6_ntoa(peer_ip6)); +#if CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES + /* Pin the peer's link-local -> NDI mapping so traffic to the peer skips + * Neighbor Discovery (no NS/NA) on the NAN link. The esp_wifi netif layer + * owns the netif lookup and derives the peer's link-local from its NDI + * (the address the peer actually sources from). */ + esp_err_t nbr_err = esp_wifi_netif_set_static_neighbor(WIFI_IF_NAN, peer_ndi, true); + if (nbr_err != ESP_OK) { + ESP_LOGW(TAG, "static nbr ADD failed: %s", esp_err_to_name(nbr_err)); + } +#else + esp_netif_action_connected(s_nan_ctx.nan_netif, WIFI_EVENT, WIFI_EVENT_NDP_CONFIRM, evt); + esp_netif_create_ip6_linklocal(s_nan_ctx.nan_netif); +#endif + os_event_group_set_bits(nan_event_group, NDP_ACCEPTED); nan_app_post_event(WIFI_EVENT_NDP_CONFIRM, evt, evt_data_len); os_free(evt); @@ -1559,6 +1563,15 @@ static void nan_app_ndp_terminated_cb(uint8_t reason, uint8_t ndp_id, uint8_t in s_nan_ctx.event &= ~(NDP_INDICATION); NAN_DATA_UNLOCK(); +#if CONFIG_LWIP_ND6_SUPPORT_STATIC_ENTRIES + /* Drop the peer's static neighbor mapping added on NDP confirm. (It is also + * cleared automatically if the NAN netif goes down on the last datapath.) */ + esp_err_t nbr_err = esp_wifi_netif_set_static_neighbor(WIFI_IF_NAN, init_ndi, false); + if (nbr_err != ESP_OK) { + ESP_LOGW(TAG, "static nbr DEL failed: %s", esp_err_to_name(nbr_err)); + } +#endif + wifi_event_ndp_terminated_t *evt = (wifi_event_ndp_terminated_t *)os_zalloc(sizeof(wifi_event_ndp_terminated_t)); if (!evt) { ESP_LOGE(TAG, "Failed to allocate for event");