[PATCH] AP: Avoid deleting replacement MLD station during stale cleanup

Nicholas Sielicki opensource at nslick.com
Fri Oct 23 23:42:05 PDT 2026


When an MLD station reassociates through another AP link, the previous
association's disassociation callback can still be pending after the new
association is authorized. Both records use the same MLD address and
network interface. The delayed ap_sta_remove() callback uses
DEL_STATION by address, which removes the replacement station from the
driver and disconnects it shortly after the four-way handshake.

Use the existing link-aware station removal path from ap_sta_remove(),
and skip driver removal when another AP link holds an associated
replacement with a different association link ID. Apply the same guard
when the stale hostapd station record is finally freed.

This was observed with a three-link AP MLD and a client moving between
the AP MLD and a standalone BSS sharing its SSID. The AP logged a
successful four-way handshake followed by pruning of old MLD records;
the delayed callback then sent DEL_STATION for the newly associated
MLD address.

Relevant hostapd DEBUG log excerpt (STA MAC redacted):

  00:49:15 mld0: Prune association for <STA-MAC>
  00:49:15 mld0: Prune association for <STA-MAC>
  00:49:15 mld0: AP-STA-CONNECTED <STA-MAC>
  00:49:15 mld0: EAPOL-4WAY-HS-COMPLETED <STA-MAC>
  00:49:17 mld0: Disassociation callback for STA <STA-MAC>
  00:49:17 nl80211: sta_remove -> DEL_STATION mld0
           <STA-MAC> --> 0 (Success)
  00:49:17 nl80211: Drv Event 20 (NL80211_CMD_DEL_STATION)
           received for mld0

Signed-off-by: Nicholas Sielicki <opensource at nslick.com>
---
 src/ap/sta_info.c | 38 +++++++++++++++++++++++++++++++++-----
 1 file changed, 33 insertions(+), 5 deletions(-)

diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index 1e8ef1b4f..be2cb1cee 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -207,16 +207,44 @@ void ap_free_sta_pasn(struct hostapd_data *hapd, struct sta_info *sta)
 #endif /* CONFIG_PASN */
 
 
-static void __ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
+static int __ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 {
 #ifdef CONFIG_IEEE80211BE
+	struct hostapd_data *lhapd;
+
+	/*
+	 * A new association on another link can use the same MLD address while
+	 * the old station's disassociation callback is still pending. Removing
+	 * the old station by address would also remove the new one from the
+	 * driver, since both associations use the same MLD netdev.
+	 */
+	if (hapd->mld && ap_sta_is_mld(hapd, sta)) {
+		for_each_mld_link(lhapd, hapd) {
+			struct sta_info *lsta;
+
+			if (lhapd == hapd)
+				continue;
+			lsta = ap_get_sta(lhapd, sta->addr);
+			if (lsta &&
+			    lsta->mld_assoc_link_id != sta->mld_assoc_link_id &&
+			    lsta->mld_assoc_link_id == lhapd->mld_link_id &&
+			    (lsta->flags & WLAN_STA_ASSOC)) {
+				wpa_printf(MSG_DEBUG,
+					   "%s: Keep replacement MLD STA " MACSTR
+					   " on link %u",
+					   __func__, MAC2STR(sta->addr),
+					   lhapd->mld_link_id);
+				return 0;
+			}
+		}
+	}
+
 	if (hostapd_sta_is_link_sta(hapd, sta)) {
-		hostapd_drv_link_sta_remove(hapd, sta->addr);
-		return;
+		return hostapd_drv_link_sta_remove(hapd, sta->addr);
 	}
 #endif /* CONFIG_IEEE80211BE */
 
-	hostapd_drv_sta_remove(hapd, sta->addr);
+	return hostapd_drv_sta_remove(hapd, sta->addr);
 }
 
 
@@ -1011,7 +1039,7 @@ static int ap_sta_remove(struct hostapd_data *hapd, struct sta_info *sta)
 
 	wpa_printf(MSG_DEBUG, "%s: Removing STA " MACSTR " from kernel driver",
 		   hapd->conf->iface, MAC2STR(sta->addr));
-	if (hostapd_drv_sta_remove(hapd, sta->addr) &&
+	if (__ap_free_sta(hapd, sta) &&
 	    sta->flags & WLAN_STA_ASSOC) {
 		wpa_printf(MSG_DEBUG, "%s: Could not remove station " MACSTR
 			   " from kernel driver",
-- 
2.56.0




More information about the Hostap mailing list