[PATCH] AP: Mobility Domain element in a nontransmitted BSSID profile

Andrei-Alexandru Bleortu me at andrei-z.com
Sun Oct 4 09:56:38 PDT 2026


A nontransmitted BSSID profile carried RSN, RSNX and Extended
Capabilities of its own but no Mobility Domain element, so a
nontransmitted BSS with FT inherited the transmitted BSS's: with two
mobility domains (one per SSID) an FT client of the nontransmitted BSS
associated with the wrong MDID and was rejected, and one without FT was
shown the transmitted BSS's. IEEE Std 802.11-2024, 11.1.3.8.4, requires
the profile to include every element specific to the nontransmitted
BSS.

Include the nontransmitted BSS's Mobility Domain element when it differs
from the transmitted BSS's, and list the element in the Non-Inheritance
element when only the transmitted BSS has one.

Assisted-by: LLM
Signed-off-by: Andrei-Alexandru Bleortu <me at andrei-z.com>
---

Found running two SSIDs with different mobility domains as a Multiple
BSSID set on 6 GHz (ath11k, QCN9074): FT-SAE to the nontransmitted BSS
failed until the profile carried its own MDE; with it, on air the
profile shows the right MDID and FT-SAE and FT-EAP clients associate
to the nontransmitted and transmitted BSSs. The two new hwsim tests
fail on current main and pass with this; the he_mbssid module passes.

 src/ap/ieee802_11.c           | 37 +++++++++++++++++--
 tests/hwsim/test_he_mbssid.py | 69 ++++++++++++++++++++++++++++++++++-
 2 files changed, 102 insertions(+), 4 deletions(-)

diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 219a94b0f..0d414148d 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -11167,6 +11167,21 @@ static size_t hostapd_mbssid_ext_capa(struct hostapd_data *bss,
 }
 
 
+/* The Mobility Domain element of a nontransmitted BSS, when it differs from
+ * the transmitted BSS's, so that an FT client gets the right MDID. */
+static const u8 * hostapd_mbssid_mde(struct hostapd_data *bss,
+				     struct hostapd_data *tx_bss)
+{
+	const u8 *mde = hostapd_wpa_ie(bss, WLAN_EID_MOBILITY_DOMAIN);
+	const u8 *tx_mde = hostapd_wpa_ie(tx_bss, WLAN_EID_MOBILITY_DOMAIN);
+
+	if (!mde || (tx_mde && mde[1] == tx_mde[1] &&
+		     os_memcmp(mde + 2, tx_mde + 2, mde[1]) == 0))
+		return NULL;
+	return mde;
+}
+
+
 static size_t hostapd_eid_mbssid_elem_len(struct hostapd_data *hapd,
 					  u32 frame_type, size_t *bss_index,
 					  const u8 *known_bss,
@@ -11191,7 +11206,7 @@ static size_t hostapd_eid_mbssid_elem_len(struct hostapd_data *hapd,
 
 	for (i = *bss_index; i < hapd->iface->num_bss; i++) {
 		struct hostapd_data *bss = hapd->iface->bss[i];
-		const u8 *auth, *rsn = NULL, *rsnx = NULL;
+		const u8 *auth, *rsn = NULL, *rsnx = NULL, *mde;
 		size_t nontx_profile_len, auth_len, xrate_len;
 		u8 ie_count = 0;
 
@@ -11223,6 +11238,10 @@ static size_t hostapd_eid_mbssid_elem_len(struct hostapd_data *hapd,
 				nontx_profile_len += 2 + rsnx[1];
 		}
 
+		mde = hostapd_mbssid_mde(bss, tx_bss);
+		if (mde)
+			nontx_profile_len += 2 + mde[1];
+
 		nontx_profile_len += hostapd_mbssid_ext_capa(bss, tx_bss,
 							     ext_capa);
 
@@ -11230,6 +11249,9 @@ static size_t hostapd_eid_mbssid_elem_len(struct hostapd_data *hapd,
 			ie_count++;
 		if (!rsnx && hostapd_wpa_ie(tx_bss, WLAN_EID_RSNX))
 			ie_count++;
+		if (!hostapd_wpa_ie(bss, WLAN_EID_MOBILITY_DOMAIN) &&
+		    hostapd_wpa_ie(tx_bss, WLAN_EID_MOBILITY_DOMAIN))
+			ie_count++;
 
 		xrate_len = hostapd_eid_ext_supp_rates(bss, buf) - buf;
 
@@ -11353,8 +11375,8 @@ static u8 * hostapd_eid_mbssid_elem(struct hostapd_data *hapd, u8 *eid, u8 *end,
 		struct hostapd_bss_config *conf;
 		struct hostapd_bss_config *tx_conf = tx_bss->conf;
 		u8 *eid_len_pos, *nontx_bss_start = eid;
-		const u8 *auth, *rsn = NULL, *rsnx = NULL;
-		u8 ie_count = 0, non_inherit_ie[3];
+		const u8 *auth, *rsn = NULL, *rsnx = NULL, *mde;
+		u8 ie_count = 0, non_inherit_ie[4];
 		size_t auth_len = 0, xrate_len;
 		u16 capab_info;
 		u8 mbssindex = i;
@@ -11412,6 +11434,12 @@ static u8 * hostapd_eid_mbssid_elem(struct hostapd_data *hapd, u8 *eid, u8 *end,
 				eid += 2 + rsn[1];
 			}
 
+			mde = hostapd_mbssid_mde(bss, tx_bss);
+			if (mde) {
+				os_memcpy(eid, mde, 2 + mde[1]);
+				eid += 2 + mde[1];
+			}
+
 			rsnx = get_ie(auth, auth_len, WLAN_EID_RSNX);
 			if (rsnx) {
 				os_memcpy(eid, rsnx, 2 + rsnx[1]);
@@ -11428,6 +11456,9 @@ static u8 * hostapd_eid_mbssid_elem(struct hostapd_data *hapd, u8 *eid, u8 *end,
 			non_inherit_ie[ie_count++] = WLAN_EID_RSN;
 		if (tx_xrate_len && !xrate_len)
 			non_inherit_ie[ie_count++] = WLAN_EID_EXT_SUPP_RATES;
+		if (!hostapd_wpa_ie(bss, WLAN_EID_MOBILITY_DOMAIN) &&
+		    hostapd_wpa_ie(tx_bss, WLAN_EID_MOBILITY_DOMAIN))
+			non_inherit_ie[ie_count++] = WLAN_EID_MOBILITY_DOMAIN;
 		if (!rsnx && hostapd_wpa_ie(tx_bss, WLAN_EID_RSNX))
 			non_inherit_ie[ie_count++] = WLAN_EID_RSNX;
 #ifdef CONFIG_IEEE80211BE
diff --git a/tests/hwsim/test_he_mbssid.py b/tests/hwsim/test_he_mbssid.py
index 386b84bbc..b214ae384 100644
--- a/tests/hwsim/test_he_mbssid.py
+++ b/tests/hwsim/test_he_mbssid.py
@@ -51,7 +51,8 @@ def mbssid_write_bss_params(f, ifname, idx, params=None, single_ssid=False):
     # Add BSS specific characteristics
     fields = ["wpa", "wpa_pairwise", "rsn_pairwise", "wpa_passphrase",
               "wpa_key_mgmt", "ieee80211w", "sae_pwe", "beacon_prot",
-              "sae_password"]
+              "sae_password", "mobility_domain", "nas_identifier",
+              "ft_psk_generate_local"]
 
     if idx == 0:
         f.write("interface=%s\n" % ifname)
@@ -475,3 +476,69 @@ def run_mbssid_beacon_prot(dev, apdev, params, non_tx_first):
     finally:
         subprocess.call(['ip', 'link', 'set', 'dev', apdev[0]['ifname'],
                          'address', apdev[0]['bssid']])
+
+def mbssid_nontx_mde(dev, bssid):
+    bss = dev.get_bss(bssid)
+    if bss is None:
+        raise Exception("Nontransmitted BSS not found")
+    ie = parse_ie(bss['ie'])
+    return ie.get(54)
+
+def run_he_ap_mbssid_ft(dev, apdev, params, nontx_ft):
+    check_sae_capab(dev[0])
+    f, fname, ifname = mbssid_create_cfg_file(apdev, params)
+
+    tx_params = {"wpa": "2", "wpa_passphrase": "12345678",
+                 "wpa_pairwise": "CCMP", "wpa_key_mgmt": "FT-PSK",
+                 "mobility_domain": "a1b2", "nas_identifier": "nas0.w1.fi",
+                 "ft_psk_generate_local": "1"}
+    nontx_params = {"wpa": "2", "wpa_passphrase": "12345678",
+                    "wpa_pairwise": "CCMP", "sae_pwe": "1",
+                    "ieee80211w": "2"}
+    if nontx_ft:
+        nontx_params.update({"wpa_key_mgmt": "FT-SAE",
+                             "mobility_domain": "c3d4",
+                             "nas_identifier": "nas1.w1.fi"})
+    else:
+        nontx_params["wpa_key_mgmt"] = "SAE"
+
+    mbssid_write_bss_params(f, ifname, 0, tx_params)
+    mbssid_write_bss_params(f, ifname, 1, nontx_params)
+    f.close()
+
+    hapd = None
+    try:
+        dev[0].set("sae_pwe", "1")
+        hapd, pid = mbssid_start_ap(dev, apdev, params, fname, ifname, None,
+                                    only_start_ap=True)
+        nontx = "00:03:7f:12:2a:01"
+        dev[0].scan_for_bss(nontx, freq="2412", force_scan=True)
+        mde = mbssid_nontx_mde(dev[0], nontx)
+        if nontx_ft:
+            if mde is None or mde[0:2] != b'\xc3\xd4':
+                raise Exception("Nontransmitted BSS MDE not its own: " +
+                                (mde.hex() if mde else "none"))
+            dev[0].connect("bss-1", key_mgmt="FT-SAE", sae_password="12345678",
+                           ieee80211w="2", scan_freq="2412")
+        else:
+            if mde is not None:
+                raise Exception("MDE inherited by the nontransmitted BSS")
+            dev[0].connect("bss-1", key_mgmt="SAE", sae_password="12345678",
+                           ieee80211w="2", scan_freq="2412")
+        dev[0].request("DISCONNECT")
+        dev[0].wait_disconnected()
+    finally:
+        if hapd:
+            mbssid_stop_ap(hapd, pid)
+        dev[0].set("sae_pwe", "0")
+        subprocess.call(['ip', 'link', 'set', 'dev', apdev[0]['ifname'],
+                         'address', apdev[0]['bssid']])
+
+def test_he_ap_mbssid_ft_own_mde(dev, apdev, params):
+    """HE AP MBSSID with FT in two mobility domains"""
+    run_he_ap_mbssid_ft(dev, apdev, params, True)
+
+def test_he_ap_mbssid_ft_no_mde(dev, apdev, params):
+    """HE AP MBSSID with FT on the transmitted BSS only"""
+    run_he_ap_mbssid_ft(dev, apdev, params, False)
+
-- 
2.47.3





More information about the Hostap mailing list