[PATCH] AP MLD: Keep all links of a reassociating non-AP MLD

Piotr Kubik piotr at kubik.pl
Tue Oct 20 06:37:38 PDT 2026


A non-AP MLD that reassociates while it is still associated gets only
its association link. With PMF, handle_auth() keeps the station entries
of the existing association, so that an unprotected Authentication frame
cannot tear it down. ieee80211_ml_process_link() then finds the link
station of the old association on each partner link and refuses that
link with an unspecified failure.

When the (re)association is accepted on the association link, remove a
pure link station of the same MLD on the partner link before it is
processed again. Leave the entry of the old association link: it owns
the wpa_sm that the stations share. check_assoc_ies() already set the
new ML info in that wpa_sm, so detach it from the stale station first.

add_associated_sta() then removes and adds the MLD station again for the
association link, because the station is neither added_unassoc nor has
an FT or FILS key. hostapd_process_assoc_ml_info() runs before it, so
that removal also drops the partner links from the driver: the AP keeps
only the association link, while the station uses all links. Add the
partner link stations again after the MLD station is added.

Tested with mac80211/mt7996e: a 4-address non-AP MLD with two links
keeps both links after repeated reassociation. The mac80211 side needs
two fixes for 4-address MLD stations that were sent to linux-wireless
separately.

Signed-off-by: Piotr Kubik <piotr at kubik.pl>
---
 src/ap/ieee802_11.c |   60 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 59 insertions(+), 1 deletion(-)

diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -7920,6 +7920,31 @@
 #endif /* CONFIG_IEEE80211BE */
 
 
+#ifdef CONFIG_IEEE80211BE
+/*
+ * With PMF, handle_auth() keeps the entries of an associated MLD, so a
+ * reassociation still finds the link stations of the old association, and
+ * ieee80211_ml_process_link() refuses a link that has one. The entry of the
+ * old association link owns the shared wpa_sm, so remove pure link stations
+ * only. check_assoc_ies() already set the new ML info in that wpa_sm; detach
+ * it, so that ap_free_sta() does not release the link of the new association.
+ */
+static void hostapd_ml_remove_stale_link_sta(struct hostapd_data *bss,
+					     struct sta_info *sta)
+{
+	struct sta_info *lsta = ap_get_sta(bss, sta->addr);
+
+	if (!lsta || lsta == sta || !hostapd_sta_is_link_sta(bss, lsta))
+		return;
+
+	wpa_printf(MSG_DEBUG, "MLD: Remove stale link STA " MACSTR
+		   " on link %u", MAC2STR(sta->addr), bss->mld_link_id);
+	lsta->wpa_sm = NULL;
+	ap_free_sta(bss, lsta);
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 int hostapd_process_assoc_ml_info(struct hostapd_data *hapd,
 				  struct sta_info *sta,
 				  const u8 *ies, size_t ies_len,
@@ -7966,6 +7991,7 @@
 			if (!offload)
 				ieee80211_ml_build_assoc_resp(hapd, link, sta);
 		} else {
+			hostapd_ml_remove_stale_link_sta(bss, sta);
 			if (ieee80211_ml_process_link(
 				    bss, sta, link, ies, ies_len,
 				    reassoc ? LINK_PARSE_REASSOC :
@@ -8002,6 +8028,32 @@
 }
 
 
+static void hostapd_ml_readd_link_sta(struct hostapd_data *hapd,
+				      struct sta_info *sta, int reassoc)
+{
+#ifdef CONFIG_IEEE80211BE
+	struct hostapd_data *bss;
+
+	for_each_mld_link(bss, hapd) {
+		struct sta_info *lsta;
+
+		if (bss == hapd)
+			continue;
+
+		lsta = ap_get_sta(bss, sta->addr);
+		if (!lsta || !hostapd_sta_is_link_sta(bss, lsta))
+			continue;
+
+		if (add_associated_sta(bss, lsta, reassoc))
+			wpa_printf(MSG_INFO,
+				   "MLD: Could not add link %u of " MACSTR
+				   " again", bss->mld_link_id,
+				   MAC2STR(sta->addr));
+	}
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
 static int add_associated_sta(struct hostapd_data *hapd,
 			      struct sta_info *sta, int reassoc)
 {
@@ -8012,7 +8064,7 @@
 	struct ieee80211_uhr_capabilities uhr_cap;
 	int set = 1;
 	const u8 *mld_link_addr = NULL;
-	bool mld_link_sta = false, epp_sta = false;
+	bool mld_link_sta = false, epp_sta = false, removed = false;
 	u16 eml_cap = 0;
 
 #ifdef CONFIG_ENC_ASSOC
@@ -8067,6 +8119,7 @@
 		hostapd_drv_sta_remove(hapd, sta->addr);
 		wpa_auth_sm_event(sta->wpa_sm, WPA_DRV_STA_REMOVED);
 		set = 0;
+		removed = true;
 
 		 /* Do not allow the FT-over-DS exception to be used more than
 		  * once per authentication exchange to guarantee a new TK is
@@ -8133,6 +8186,11 @@
 
 	sta->added_unassoc = 0;
 
+	/* The removal above also dropped the partner links that
+	 * hostapd_process_assoc_ml_info() added to the driver before. */
+	if (removed && ap_sta_is_mld(hapd, sta))
+		hostapd_ml_readd_link_sta(hapd, sta, reassoc);
+
 	return 0;
 }
 



More information about the Hostap mailing list