[PATCH 6/9] WNM: Implement ML handling of neighbor report entries
Benjamin Berg
benjamin at sipsolutions.net
Fri Jul 18 04:01:02 PDT 2025
From: Benjamin Berg <benjamin.berg at intel.com>
Parses out the MLD address and also the provided list of link IDs using
the per-STA profile when provided. If given, then the MLD address will
be non-zero, the same is true for the link bitmask which will be
non-zero when links have been specified.
Use this information in wnm_is_bss_excluded to correctly limit the links
for MLD neighbor report entries. Note that this implementation may
permit more links than expected if these links are part of different
neighbor report entries. This is rarely the case though and should it
happen then the AP can simply reject the association on one of the
links.
Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
src/common/ieee802_11_defs.h | 1 +
wpa_supplicant/wnm_sta.c | 126 ++++++++++++++++++++++++++++++++++-
wpa_supplicant/wnm_sta.h | 2 +
3 files changed, 126 insertions(+), 3 deletions(-)
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index 6cf8b146d6..884adc9270 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -2064,6 +2064,7 @@ enum bss_trans_mgmt_reason {
#define WNM_NEIGHBOR_MULTIPLE_BSSID 71
#define WNM_NEIGHBOR_VHT_CAPAB 191
#define WNM_NEIGHBOR_VHT_OPER 192
+#define WNM_NEIGHBOR_MULTI_LINK 201
/* QoS action */
enum qos_action {
diff --git a/wpa_supplicant/wnm_sta.c b/wpa_supplicant/wnm_sta.c
index 1ca46d3e83..ad7e6933dc 100644
--- a/wpa_supplicant/wnm_sta.c
+++ b/wpa_supplicant/wnm_sta.c
@@ -443,6 +443,103 @@ void wnm_btm_reset(struct wpa_supplicant *wpa_s)
#endif /* CONFIG_MBO */
}
+static void wnm_parse_neighbor_report_multi_link(struct neighbor_report *rep,
+ u8 id, u8 elen, const u8 *pos)
+{
+ const struct ieee80211_eht_ml *ml = (void *)pos;
+ bool has_link_id;
+
+ if (elen < 9) {
+ wpa_printf(MSG_DEBUG, "WNM: Too short ML element");
+ return;
+ }
+
+ /* The ML control should be all zeroes except for link ID. */
+ if ((le_to_host16(ml->ml_control) &
+ ~BASIC_MULTI_LINK_CTRL_PRES_LINK_ID) != 0) {
+ wpa_printf(MSG_DEBUG,
+ "WNM: Unsupported ML control: %04x",
+ le_to_host16(ml->ml_control));
+ return;
+ }
+
+ has_link_id = !!(le_to_host16(ml->ml_control) &
+ BASIC_MULTI_LINK_CTRL_PRES_LINK_ID);
+
+ /* Followed by common info length and MLD addr */
+ if (pos[2] < 7) {
+ wpa_printf(MSG_DEBUG,
+ "WNM: Too short ML common info: %u < 7",
+ pos[2]);
+ return;
+ }
+
+ os_memcpy(rep->mld_addr, &pos[3], ETH_ALEN);
+
+ if (!has_link_id)
+ return;
+
+ if (pos[2] < 8 || pos[2] + 2 > elen) {
+ wpa_printf(MSG_DEBUG,
+ "WNM: ML common info too short or does not fit: %u (elen: %u)",
+ pos[2], elen);
+ return;
+ }
+
+ if ((pos[9] & EHT_ML_LINK_ID_MSK) >= MAX_NUM_MLD_LINKS) {
+ wpa_printf(MSG_DEBUG,
+ "WNM: ML common info contains invalid link ID");
+ return;
+ }
+
+ rep->mld_links = BIT(pos[9] & EHT_ML_LINK_ID_MSK);
+
+ elen -= pos[2] + 2;
+ pos += pos[2] + 2;
+
+ /* Parse out per-STA information */
+ while (elen >= 2) {
+ u8 sub_elem_len = *(pos + 1);
+
+ if (2 + sub_elem_len > elen) {
+ wpa_printf(MSG_DEBUG,
+ "WNM: Invalid sub-element length: %u %u",
+ 2 + sub_elem_len, elen);
+ rep->mld_links = 0;
+ break;
+ }
+
+ if (*pos == EHT_ML_SUB_ELEM_PER_STA_PROFILE) {
+ const struct ieee80211_eht_per_sta_profile *sta_prof =
+ (void *)pos + 2;
+ u16 control;
+ u8 link_id;
+
+ if (sub_elem_len < sizeof(*sta_prof)) {
+ wpa_printf(MSG_DEBUG,
+ "WNM: Invalid STA-profile length: %u",
+ sub_elem_len);
+ rep->mld_links = 0;
+ break;
+ }
+
+ control = le_to_host16(sta_prof->sta_control);
+
+ link_id = control & EHT_PER_STA_RECONF_CTRL_LINK_ID_MSK;
+ rep->mld_links |= BIT(link_id);
+ }
+
+ pos += 2 + sub_elem_len;
+ elen -= 2 + sub_elem_len;
+ }
+
+ if (elen != 0) {
+ wpa_printf(MSG_DEBUG,
+ "WNM: Data left at end of multi-link element: %u",
+ elen);
+ rep->mld_links = 0;
+ }
+}
static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep,
u8 id, u8 elen, const u8 *pos)
@@ -532,6 +629,9 @@ static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep,
rep->mul_bssid->subelem_len = elen - 1;
os_memcpy(rep->mul_bssid->subelems, pos + 1, elen - 1);
break;
+ case WNM_NEIGHBOR_MULTI_LINK:
+ wnm_parse_neighbor_report_multi_link(rep, id, elen, pos);
+ break;
default:
wpa_printf(MSG_DEBUG,
"WNM: Unsupported neighbor report subelement id %u",
@@ -1188,15 +1288,22 @@ static void wnm_dump_cand_list(struct wpa_supplicant *wpa_s)
return;
for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) {
struct neighbor_report *nei;
+ char mld_info[42] = "";
nei = &wpa_s->wnm_neighbor_report_elements[i];
+
+ if (!is_zero_ether_addr(nei->mld_addr))
+ os_snprintf(mld_info, sizeof(mld_info) - 1,
+ " mld_addr=" MACSTR " links=0x%02x",
+ MAC2STR(nei->mld_addr), nei->mld_links);
+
wpa_printf(MSG_DEBUG, "%u: " MACSTR
- " info=0x%x op_class=%u chan=%u phy=%u pref=%d freq=%d",
+ " info=0x%x op_class=%u chan=%u phy=%u pref=%d freq=%d%s",
i, MAC2STR(nei->bssid), nei->bssid_info,
nei->regulatory_class,
nei->channel_number, nei->phy_type,
nei->preference_present ? nei->preference : -1,
- nei->freq);
+ nei->freq, mld_info);
}
}
@@ -1971,7 +2078,9 @@ bool wnm_is_bss_excluded(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
struct neighbor_report *nei;
nei = &wpa_s->wnm_neighbor_report_elements[i];
- if (!ether_addr_equal(nei->bssid, bss->bssid))
+ if (!ether_addr_equal(nei->bssid, bss->bssid) &&
+ (is_zero_ether_addr(bss->mld_addr) ||
+ !ether_addr_equal(nei->mld_addr, bss->mld_addr)))
continue;
if (nei->preference_present && nei->preference == 0)
@@ -1982,6 +2091,17 @@ bool wnm_is_bss_excluded(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
return true;
#endif /* CONFIG_MBO */
+ /*
+ * NOTE: We should select one entry and stick with it, but to
+ * do that we need to refactor the BSS selection to be MLD
+ * aware from the beginning.
+ * Instead we just check whether the link is permitted in any
+ * possible configuration. We are not supposed to do that,
+ * however the AP is able to reject some of the links.
+ */
+ if (nei->mld_links && !(nei->mld_links & BIT(bss->mld_link_id)))
+ continue;
+
break;
}
diff --git a/wpa_supplicant/wnm_sta.h b/wpa_supplicant/wnm_sta.h
index 80928f7550..2977e482bf 100644
--- a/wpa_supplicant/wnm_sta.h
+++ b/wpa_supplicant/wnm_sta.h
@@ -23,6 +23,8 @@ struct multiple_bssid {
struct neighbor_report {
u8 bssid[ETH_ALEN];
+ u8 mld_addr[ETH_ALEN];
+ u16 mld_links; /* may be zero if no link IDs were specified */
u32 bssid_info;
u8 regulatory_class;
u8 channel_number;
--
2.50.0
More information about the Hostap
mailing list