[PATCH 2/3] MLD: Scan each missing link separately after the ML probe

Louis Kotze loukot at gmail.com
Sat Aug 22 15:40:42 PDT 2026


wpa_supplicant_connect_ml_missing() sends a multi-link probe request on
the channel of the association link and then associates with whatever
link information the AP MLD returned. Not all AP MLDs return the per-STA
profiles of the requested APs, and the association then proceeds without
those links (observed with a TP-Link Deco BE65 in a 5 GHz + 6 GHz AP MLD
configuration).

Once the multi-link probe response has been processed, check which of
the links are still missing and scan the channel of each of them so that
the information can be updated from the Probe Response frame of the
affiliated AP itself. The channel and the BSSID of each link are known
from the RNR information in the scan entry of the association link. Such
a scan is addressed to the affiliated AP of the link that is being
scanned and does not include a Probe Request Multi-Link element.

The channels cannot be covered by the multi-link probe scan instead.
IEEE Std 802.11be-2024, 35.3.4.2 requires the Address 3 field of a
multi-link probe request to be the BSSID of an AP, only a single BSSID
can be specified per scan request, and using the association link's
BSSID would leave cfg80211 filtering the channel of a co-located 6 GHz
affiliated AP out of the scan. This is why commit 517b0d7cc841 ("MLD:
Scan the channels of missing links for the ML probe") was reverted.

The cost is one additional scan for each link that the AP MLD did not
describe. A link is removed from the set of links to scan when its scan
is started, so a link that cannot be found is not scanned again during
the same connection attempt.

Suggested-by: Michael-cy Lee <Michael-cy.Lee at mediatek.com>
Signed-off-by: Louis Kotze <loukot at gmail.com>
---
 wpa_supplicant/bss.c              |  1 +
 wpa_supplicant/events.c           | 96 +++++++++++++++++++++++++++++++
 wpa_supplicant/notify.c           |  1 +
 wpa_supplicant/wpa_supplicant.c   |  1 +
 wpa_supplicant/wpa_supplicant_i.h |  5 ++
 5 files changed, 104 insertions(+)

diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c
index c594f2523..a70a45cf7 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -267,6 +267,7 @@ void wpa_bss_remove(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
 		wpa_printf(MSG_DEBUG,
 			   "BSS: Clear ml_connect_probe_bss due to bss removal");
 		wpa_s->ml_connect_probe_bss = NULL;
+		wpa_s->ml_probe_pending_links = 0;
 	}
 
 	for (j = 0; j < MAX_NUM_MLD_LINKS; j++) {
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index a434f5ffe..5161db27a 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -2016,11 +2016,105 @@ static void wpa_supplicant_req_new_scan(struct wpa_supplicant *wpa_s,
 }
 
 
+/*
+ * Scan the channel of one of the links that are still missing after the ML
+ * probe. The Probe Request frame is addressed to the affiliated AP of that
+ * link, so a Probe Request Multi-Link element is not used and the information
+ * is updated from the Probe Response frame of that AP. Only a single BSSID can
+ * be specified per scan request, so each link is scanned separately.
+ * Returns: true if a scan was started
+ */
+static bool wpas_ml_scan_missing_link(struct wpa_supplicant *wpa_s)
+{
+	struct wpa_bss *bss = wpa_s->ml_connect_probe_bss;
+	struct wpa_ssid *ssid = wpa_s->ml_connect_probe_ssid;
+	u16 missing_links = 0, pending;
+	int link_id;
+
+	if (!wpa_s->ml_probe_pending_links)
+		return false;
+
+	/*
+	 * The AP MLD may have provided the information for some or all of the
+	 * missing links in the ML probe response.
+	 */
+	wpa_bss_get_usable_links(wpa_s, bss, ssid, &missing_links);
+	pending = wpa_s->ml_probe_pending_links & missing_links;
+	if (!pending) {
+		wpa_s->ml_probe_pending_links = 0;
+		return false;
+	}
+
+	for_each_link(pending, link_id) {
+		const u8 *bssid = bss->mld_links[link_id].bssid;
+		int freq = bss->mld_links[link_id].freq;
+		int *freqs;
+
+		/*
+		 * Do not scan for this link again during this connection
+		 * attempt even if it is not found.
+		 */
+		wpa_s->ml_probe_pending_links &= ~BIT(link_id);
+
+		if (freq <= 0 || is_zero_ether_addr(bssid))
+			continue;
+
+		freqs = os_malloc(sizeof(int) * 2);
+		if (!freqs)
+			return false;
+		freqs[0] = freq;
+		freqs[1] = 0;
+
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"MLD: Scan link %d (" MACSTR " %d MHz) that is still missing after the ML probe",
+			link_id, MAC2STR(bssid), freq);
+
+		wpa_s->manual_scan_passive = 0;
+		wpa_s->manual_scan_use_id = 0;
+		wpa_s->manual_scan_only_new = 0;
+		wpa_s->scan_id_count = 0;
+		os_free(wpa_s->manual_scan_freqs);
+		wpa_s->manual_scan_freqs = freqs;
+
+		os_memcpy(wpa_s->next_scan_bssid, bssid, ETH_ALEN);
+
+		/* An explicit SSID is needed to keep this an active scan. */
+		if (ssid->ssid_len) {
+			os_free(wpa_s->ssids_from_scan_req);
+			wpa_s->num_ssids_from_scan_req = 0;
+
+			wpa_s->ssids_from_scan_req =
+				os_zalloc(sizeof(struct wpa_ssid_value));
+			if (wpa_s->ssids_from_scan_req) {
+				wpa_s->num_ssids_from_scan_req = 1;
+				wpa_s->ssids_from_scan_req[0].ssid_len =
+					ssid->ssid_len;
+				os_memcpy(wpa_s->ssids_from_scan_req[0].ssid,
+					  ssid->ssid, ssid->ssid_len);
+			}
+		}
+
+		wpa_s->normal_scans = 0;
+		wpa_s->scan_req = MANUAL_SCAN_REQ;
+		wpa_s->after_wps = 0;
+		wpa_s->known_wps_freq = 0;
+		wpa_supplicant_req_scan(wpa_s, 0, 0);
+
+		return true;
+	}
+
+	return false;
+}
+
+
 static bool ml_link_probe_scan(struct wpa_supplicant *wpa_s)
 {
 	if (!wpa_s->ml_connect_probe_ssid || !wpa_s->ml_connect_probe_bss)
 		return false;
 
+	if (wpas_ml_scan_missing_link(wpa_s))
+		return true;
+
 	wpa_msg(wpa_s, MSG_DEBUG,
 		"Request association with " MACSTR " after ML probe",
 		MAC2STR(wpa_s->ml_connect_probe_bss->bssid));
@@ -2030,6 +2124,7 @@ static bool ml_link_probe_scan(struct wpa_supplicant *wpa_s)
 
 	wpa_s->ml_connect_probe_ssid = NULL;
 	wpa_s->ml_connect_probe_bss = NULL;
+	wpa_s->ml_probe_pending_links = 0;
 
 	return true;
 }
@@ -2110,6 +2205,7 @@ static int wpa_supplicant_connect_ml_missing(struct wpa_supplicant *wpa_s,
 	}
 
 	wpa_s->ml_probe_links = missing_links;
+	wpa_s->ml_probe_pending_links = missing_links;
 
 	wpa_s->normal_scans = 0;
 	wpa_s->scan_req = MANUAL_SCAN_REQ;
diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c
index e0a6d8262..7c62e39fa 100644
--- a/wpa_supplicant/notify.c
+++ b/wpa_supplicant/notify.c
@@ -417,6 +417,7 @@ void wpas_notify_network_removed(struct wpa_supplicant *wpa_s,
 	if (wpa_s->ml_connect_probe_ssid == ssid) {
 		wpa_s->ml_connect_probe_ssid = NULL;
 		wpa_s->ml_connect_probe_bss = NULL;
+		wpa_s->ml_probe_pending_links = 0;
 	}
 	if (wpa_s->connect_without_scan == ssid)
 		wpa_s->connect_without_scan = NULL;
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index 5818017a8..cc848c418 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -5267,6 +5267,7 @@ static void wpa_supplicant_clear_connection(struct wpa_supplicant *wpa_s,
 
 	wpa_s->ml_connect_probe_ssid = NULL;
 	wpa_s->ml_connect_probe_bss = NULL;
+	wpa_s->ml_probe_pending_links = 0;
 	wpas_connect_work_done(wpa_s);
 	wpa_clear_keys(wpa_s, addr);
 	old_ssid = wpa_s->current_ssid;
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 6a54f70a2..44141b092 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -1711,6 +1711,11 @@ struct wpa_supplicant {
 
 	struct wpa_ssid *ml_connect_probe_ssid;
 	struct wpa_bss *ml_connect_probe_bss;
+	/*
+	 * Links of the AP MLD that have not yet been scanned separately after
+	 * the ML probe
+	 */
+	u16 ml_probe_pending_links;
 
 #ifdef CONFIG_OWE
 	/* An array of frequencies to scan for OWE transition mode BSSs when
-- 
2.55.0




More information about the Hostap mailing list