[PATCH] AP: Member of an ESS with co-located 2.4/5 GHz APs in 6 GHz

Andrei-Alexandru Bleortu me at andrei-z.com
Sun Oct 4 07:32:51 PDT 2026


A STA that knows every 6 GHz AP of an ESS has a co-located 2.4/5 GHz AP
can skip scanning the 6 GHz band for it: IEEE Std 802.11-2024 carries
this in the Member Of ESS With 2.4/5 GHz Colocated AP subfield, of the
BSS Parameters of a reported 6 GHz AP in the Reduced Neighbor Report
element and of the BSSID Information in a Neighbor Report element.
hostapd defines both bits but never set them: the condition concerns
the whole ESS, which hostapd cannot see, and is known only to whoever
deploys it (dot11MemberOfColocated6GHzESSOptionActivated).

Add member_of_colocated_6ghz_ess for a 6 GHz BSS to declare it. Its own
Neighbor Report and the Reduced Neighbor Report elements of co-located
2.4/5 GHz BSSs reporting it then set the subfield.

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

The test is appended to test_he.py where "AP: Set the RNR 20 MHz PSD
subfield for co-located 6 GHz APs" (in the series sent earlier today)
also appends one, so whichever goes in second needs that trivial
merge. The ESS-wide condition is the deployer's to state, hence the
option rather than deriving it from co-located interfaces: hostapd
cannot see other APs of the ESS.

 hostapd/config_file.c  |  2 ++
 hostapd/hostapd.conf   | 13 +++++++++
 src/ap/ap_config.h     |  3 ++
 src/ap/ieee802_11.c    |  4 +++
 src/ap/neighbor_db.c   |  3 ++
 tests/hwsim/test_he.py | 62 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 87 insertions(+)

diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 59827966e..98aa7a9c0 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -3896,6 +3896,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 			return 1;
 		}
 		bss->unsol_bcast_probe_resp_interval = val;
+	} else if (os_strcmp(buf, "member_of_colocated_6ghz_ess") == 0) {
+		bss->member_of_colocated_6ghz_ess = atoi(pos);
 #ifdef CONFIG_AFC
 	} else if (os_strcmp(buf, "afcd_sock") == 0) {
 		os_free(conf->afc.socket);
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 0e9001221..e19bd3334 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -1038,6 +1038,19 @@ wmm_ac_vo_acm=0
 # Valid range: 0..20 TUs; default is 0 (disabled)
 #unsol_bcast_probe_resp_interval=0
 
+# Member of an ESS whose 6 GHz APs all have a co-located 2.4/5 GHz AP
+# (dot11MemberOfColocated6GHzESSOptionActivated, IEEE Std 802.11-2024)
+# This is for the 6 GHz band only and states a property of the whole ESS that
+# hostapd cannot see: set it only if no AP of the ESS operates in the 6 GHz
+# band without one in the 2.4 or 5 GHz band in the same colocated AP set. The
+# BSS then sets the Member Of ESS With 2.4/5 GHz Colocated AP subfield in its
+# own Neighbor Report and is reported with it in the Reduced Neighbor Report
+# element of the co-located 2.4/5 GHz BSSs, so that STAs that received one can
+# skip scanning the 6 GHz band for this ESS.
+# 0 = disabled (default)
+# 1 = enabled
+#member_of_colocated_6ghz_ess=0
+
 #channel_usage: Whether Channel Usage procedures is supported by AP.
 # 0 = Channel Usage support is disabled (default)
 # 1 = Channel Usage support is enabled
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index c96481df7..8bf72d1d6 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -995,6 +995,9 @@ struct hostapd_bss_config {
 
 	unsigned int unsol_bcast_probe_resp_interval;
 
+	/* dot11MemberOfColocated6GHzESSOptionActivated (6 GHz only) */
+	bool member_of_colocated_6ghz_ess;
+
 	u8 ext_capa_mask[EXT_CAPA_MAX_LEN];
 	u8 ext_capa[EXT_CAPA_MAX_LEN];
 
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 219a94b0f..f374b0cfa 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -10860,6 +10860,10 @@ static bool hostapd_eid_rnr_bss(struct hostapd_data *hapd,
 	    bss->conf->unsol_bcast_probe_resp_interval)
 		bss_param |= RNR_BSS_PARAM_UNSOLIC_PROBE_RESP_ACTIVE;
 
+	if (is_6ghz_op_class(op_class) &&
+	    bss->conf->member_of_colocated_6ghz_ess)
+		bss_param |= RNR_BSS_PARAM_MEMBER_CO_LOCATED_ESS;
+
 	bss_param |= RNR_BSS_PARAM_CO_LOCATED;
 
 	*eid++ = bss_param;
diff --git a/src/ap/neighbor_db.c b/src/ap/neighbor_db.c
index fd4f881df..d20347257 100644
--- a/src/ap/neighbor_db.c
+++ b/src/ap/neighbor_db.c
@@ -290,6 +290,9 @@ void hostapd_neighbor_set_own_report(struct hostapd_data *hapd)
 					  &op_class, &channel) ==
 	    NUM_HOSTAPD_MODES)
 		return;
+	if (is_6ghz_op_class(op_class) &&
+	    hapd->conf->member_of_colocated_6ghz_ess)
+		bssid_info |= NEI_REP_BSSID_INFO_MEMBER_OF_ESS_WITH_COLOC_AP;
 	width = hostapd_get_nr_chan_width(hapd, ht, vht, he, oper_chwidth,
 					  secondary_channel);
 
diff --git a/tests/hwsim/test_he.py b/tests/hwsim/test_he.py
index 1b1ed664d..dcbd1e284 100644
--- a/tests/hwsim/test_he.py
+++ b/tests/hwsim/test_he.py
@@ -2138,3 +2138,65 @@ def test_he_6ghz_incumbt_sig_intf_sec40(dev, apdev):
                                           expectation)
     he_6ghz_incumbt_sig_intf_bw_reduction(dev, apdev, params, '0xC',
                                           expectation)
+
+def run_he_6ghz_colocated_ess(dev, apdev, member):
+    check_sae_capab(dev[0])
+    hapd = None
+    try:
+        params = {"ssid": "coloc", "country_code": "DE", "hw_mode": "g",
+                  "channel": "1", "ieee80211ax": "1", "wpa": "2",
+                  "rsn_pairwise": "CCMP", "wpa_key_mgmt": "SAE",
+                  "sae_pwe": "1", "sae_password": "password",
+                  "ieee80211w": "2", "rnr": "1",
+                  "rrm_neighbor_report": "1"}
+        hapd = hostapd.add_ap(apdev[0], params)
+        params6 = dict(params)
+        del params6["hw_mode"]
+        params6.update({"op_class": "131", "channel": "5",
+                        "member_of_colocated_6ghz_ess": member})
+        hapd6 = hostapd.add_ap(apdev[1], params6, set_channel=False)
+        bssid6 = hapd6.own_addr()
+
+        dev[0].scan_for_bss(hapd.own_addr(), freq="2412", force_scan=True)
+        ie = bytes.fromhex(dev[0].get_bss(hapd.own_addr())['ie'])
+        params_6ghz = None
+        while len(ie) >= 2:
+            eid, data = ie[0], ie[2:2 + ie[1]]
+            ie = ie[2 + ie[1]:]
+            if eid != 201:
+                continue
+            while len(data) >= 4:
+                count = (data[0] >> 4) + 1
+                tlen = data[1]
+                info = data[4:]
+                for k in range(count):
+                    tbtt = info[k * tlen:(k + 1) * tlen]
+                    if len(tbtt) >= 12 and tbtt[1:7].hex(':') == bssid6:
+                        params_6ghz = tbtt[11]
+                data = info[count * tlen:]
+        if params_6ghz is None:
+            raise Exception("6 GHz AP not in the RNR")
+        if bool(params_6ghz & 0x10) != (member == "1"):
+            raise Exception("Unexpected BSS Parameters 0x%02x" % params_6ghz)
+
+        own = [l for l in hapd6.request("SHOW_NEIGHBOR").splitlines()
+               if l.startswith(bssid6)][0]
+        nr = bytes.fromhex(own.split("nr=")[1].split()[0])
+        bssid_info = int.from_bytes(nr[6:10], 'little')
+        if bool(bssid_info & (1 << 18)) != (member == "1"):
+            raise Exception("Unexpected BSSID Information 0x%08x" % bssid_info)
+    except Exception as e:
+        if isinstance(e, Exception) and str(e) == "AP startup failed":
+            if not he_supported():
+                raise HwsimSkip("HE 6 GHz channel not supported in regulatory information")
+        raise
+    finally:
+        clear_regdom(hapd, dev)
+
+def test_he_6ghz_colocated_ess(dev, apdev):
+    """6 GHz AP member of an ESS with co-located 2.4/5 GHz APs"""
+    run_he_6ghz_colocated_ess(dev, apdev, "1")
+
+def test_he_6ghz_colocated_ess_not_member(dev, apdev):
+    """6 GHz AP not declared a member of a co-located ESS"""
+    run_he_6ghz_colocated_ess(dev, apdev, "0")
-- 
2.47.3





More information about the Hostap mailing list