[RFC PATCH] initial UHR support

Johannes Berg johannes at sipsolutions.net
Thu Sep 25 15:51:31 PDT 2025


From: Johannes Berg <johannes.berg at intel.com>

Add initial UHR support, based on a very "superficial"
reading of D1.0 (it's incomplete and not well-specified
in quite a few places.)

Signed-off-by: Johannes Berg <johannes.berg at intel.com>
---
 hostapd/Android.mk                        |   6 +
 hostapd/Makefile                          |   6 +
 hostapd/config_file.c                     |   8 ++
 hostapd/hostapd.conf                      |  13 ++
 src/ap/ap_config.h                        |   6 +
 src/ap/ap_drv_ops.c                       |   4 +
 src/ap/ap_drv_ops.h                       |   2 +
 src/ap/beacon.c                           |  17 +++
 src/ap/ctrl_iface_ap.c                    |  17 +++
 src/ap/ieee802_11.c                       |  51 +++++++-
 src/ap/ieee802_11.h                       |  13 ++
 src/ap/ieee802_11_uhr.c                   | 146 ++++++++++++++++++++++
 src/ap/sta_info.c                         |  10 +-
 src/ap/sta_info.h                         |  65 +++++-----
 src/common/ieee802_11_common.c            |  16 +++
 src/common/ieee802_11_common.h            |   4 +
 src/common/ieee802_11_defs.h              |  23 ++++
 src/drivers/driver.h                      |  22 ++++
 src/drivers/driver_common.c               |   7 ++
 src/drivers/driver_nl80211.c              |  14 +++
 src/drivers/driver_nl80211_capa.c         |  19 +++
 src/drivers/driver_nl80211_event.c        |   2 +
 src/utils/common.h                        |   4 +
 tests/hwsim/example-hostapd.config        |   2 +
 tests/hwsim/example-wpa_supplicant.config |   2 +
 tests/hwsim/test_uhr.py                   | 145 +++++++++++++++++++++
 tests/hwsim/vm/inside.sh                  |   2 +-
 wpa_supplicant/Android.mk                 |   3 +
 wpa_supplicant/Makefile                   |   3 +
 wpa_supplicant/config.c                   |   1 +
 wpa_supplicant/config_file.c              |   1 +
 wpa_supplicant/config_ssid.h              |   8 ++
 wpa_supplicant/ctrl_iface.c               |   8 +-
 wpa_supplicant/events.c                   |  14 +++
 wpa_supplicant/sme.c                      |   1 +
 wpa_supplicant/wpa_cli.c                  |   1 +
 wpa_supplicant/wpa_supplicant.c           |  17 ++-
 wpa_supplicant/wpa_supplicant_i.h         |   4 +
 38 files changed, 645 insertions(+), 42 deletions(-)
 create mode 100644 src/ap/ieee802_11_uhr.c
 create mode 100644 tests/hwsim/test_uhr.py

diff --git a/hostapd/Android.mk b/hostapd/Android.mk
index d097971f2dd7..4f4d72539de7 100644
--- a/hostapd/Android.mk
+++ b/hostapd/Android.mk
@@ -299,6 +299,12 @@ ifdef CONFIG_IEEE80211AC
 L_CFLAGS += -DCONFIG_IEEE80211AC
 endif
 
+ifdef CONFIG_IEEE80211BN
+CONFIG_IEEE80211BE=y
+L_CFLAGS += -DCONFIG_IEEE80211BN
+OBJS += src/ap/ieee802_11_uhr.c
+endif
+
 ifdef CONFIG_IEEE80211BE
 CONFIG_IEEE80211AX=y
 L_CFLAGS += -DCONFIG_IEEE80211BE
diff --git a/hostapd/Makefile b/hostapd/Makefile
index 93a11dd86326..e966bd7d7d2c 100644
--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -344,6 +344,12 @@ ifdef CONFIG_IEEE80211AC
 CFLAGS += -DCONFIG_IEEE80211AC
 endif
 
+ifdef CONFIG_IEEE80211BN
+CONFIG_IEEE80211BE=y
+CFLAGS += -DCONFIG_IEEE80211BN
+OBJS += ../src/ap/ieee802_11_uhr.o
+endif
+
 ifdef CONFIG_IEEE80211BE
 CONFIG_IEEE80211AX=y
 CFLAGS += -DCONFIG_IEEE80211BE
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 983dfad0a3f8..57f3355a9522 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -4859,6 +4859,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->disable_11ax = !!atoi(pos);
 	} else if (os_strcmp(buf, "disable_11be") == 0) {
 		bss->disable_11be = !!atoi(pos);
+	} else if (os_strcmp(buf, "disable_11bn") == 0) {
+		bss->disable_11bn = !!atoi(pos);
 #ifdef CONFIG_PASN
 #ifdef CONFIG_TESTING_OPTIONS
 	} else if (os_strcmp(buf, "force_kdk_derivation") == 0) {
@@ -4954,6 +4956,12 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->mld_indicate_disabled = atoi(pos);
 #endif /* CONFIG_TESTING_OPTIONS */
 #endif /* CONFIG_IEEE80211BE */
+#ifdef CONFIG_IEEE80211BN
+	} else if (os_strcmp(buf, "ieee80211bn") == 0) {
+		conf->ieee80211bn = atoi(pos);
+	} else if (os_strcmp(buf, "require_uhr") == 0) {
+		conf->require_uhr = atoi(pos);
+#endif /* CONFIG_IEEE80211BN */
 	} else if (os_strcmp(buf, "i2r_lmr_policy") == 0) {
 		conf->i2r_lmr_policy = atoi(pos);
 	} else {
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 2302e2abe2d8..09e4dbbb0a53 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -1114,6 +1114,19 @@ wmm_ac_vo_acm=0
 # will be used as the AP MLD MAC address.
 #mld_addr=02:03:04:05:06:07
 
+##### IEEE 802.11bn related configuration #####################################
+
+#ieee80211bn: Whether IEEE 802.11bn (UHR) is enabled
+# 0 = disabled (default)
+# 1 = enabled
+#ieee80211bn=1
+
+#disable_11bn: Boolean (0/1) to disable UHR for a specific BSS
+#disable_11bn=0
+
+# Require stations to support UHR PHY (reject association if they do not)
+#require_uhr=0
+
 ##### IEEE 802.1X-2004 related configuration ##################################
 
 # Require IEEE 802.1X authorization
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index db5f75ef00d7..2456e8e875f1 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -563,6 +563,7 @@ struct hostapd_bss_config {
 	bool disable_11ac;
 	bool disable_11ax;
 	bool disable_11be;
+	bool disable_11bn;
 
 	/* IEEE 802.11v */
 	int time_advertisement;
@@ -1224,6 +1225,11 @@ struct hostapd_config {
 	u8 eht_bw320_offset;
 #endif /* CONFIG_IEEE80211BE */
 
+	int ieee80211bn;
+#ifdef CONFIG_IEEE80211BN
+	bool require_uhr;
+#endif
+
 	/* EHT enable/disable config from CHAN_SWITCH */
 #define CH_SWITCH_EHT_ENABLED BIT(0)
 #define CH_SWITCH_EHT_DISABLED BIT(1)
diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index 7a5c5a492ec9..ae87ae7fb9ff 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -470,6 +470,8 @@ int hostapd_sta_add(struct hostapd_data *hapd,
 		    size_t he_capab_len,
 		    const struct ieee80211_eht_capabilities *eht_capab,
 		    size_t eht_capab_len,
+		    const struct ieee80211_uhr_capabilities *uhr_capab,
+		    size_t uhr_capab_len,
 		    const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab,
 		    u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps,
 		    int set, const u8 *link_addr, bool mld_link_sta,
@@ -495,6 +497,8 @@ int hostapd_sta_add(struct hostapd_data *hapd,
 	params.he_capab_len = he_capab_len;
 	params.eht_capab = eht_capab;
 	params.eht_capab_len = eht_capab_len;
+	params.uhr_capab = uhr_capab;
+	params.uhr_capab_len = uhr_capab_len;
 	params.he_6ghz_capab = he_6ghz_capab;
 	params.vht_opmode_enabled = !!(flags & WLAN_STA_VHT_OPMODE_ENABLED);
 	params.vht_opmode = vht_opmode;
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index 517c16e35c08..fa330e6a094a 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -47,6 +47,8 @@ int hostapd_sta_add(struct hostapd_data *hapd,
 		    size_t he_capab_len,
 		    const struct ieee80211_eht_capabilities *eht_capab,
 		    size_t eht_capab_len,
+		    const struct ieee80211_uhr_capabilities *uhr_capab,
+		    size_t uhr_capab_len,
 		    const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab,
 		    u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps,
 		    int set, const u8 *link_addr, bool mld_link_sta,
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index 6cfaf099e5e1..ed9c14d4c74e 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -961,6 +961,13 @@ static u8 * hostapd_probe_resp_fill_elems(struct hostapd_data *hapd,
 						      true, false);
 #endif /* CONFIG_IEEE80211BE */
 
+#ifdef CONFIG_IEEE80211BN
+	if (hapd->iconf->ieee80211bn && !hapd->conf->disable_11bn) {
+		pos = hostapd_eid_uhr_capab(hapd, pos, IEEE80211_MODE_AP);
+		pos = hostapd_eid_uhr_operation(hapd, pos, false);
+	}
+#endif /* CONFIG_IEEE80211BN */
+
 #ifdef CONFIG_IEEE80211AC
 	if (hapd->conf->vendor_vht)
 		pos = hostapd_eid_vendor_vht(hapd, pos);
@@ -2246,6 +2253,11 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
 	}
 #endif /* CONFIG_IEEE80211BE */
 
+#ifdef CONFIG_IEEE80211BN
+	if (hapd->iconf->ieee80211bn & !hapd->conf->disable_11bn)
+		tail_len += 3 + sizeof(struct ieee80211_uhr_operation);
+#endif /* CONFIG_IEEE80211BN */
+
 	if (hapd->iconf->mbssid == ENHANCED_MBSSID_ENABLED &&
 	    hapd == hostapd_mbssid_get_tx_bss(hapd))
 		tail_len += 5; /* Multiple BSSID Configuration element */
@@ -2429,6 +2441,11 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
 	}
 #endif /* CONFIG_IEEE80211BE */
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->iconf->ieee80211bn && !hapd->conf->disable_11bn)
+		tailpos = hostapd_eid_uhr_operation(hapd, tailpos, true);
+#endif /* CONFIG_IEEE80211BE */
+
 #ifdef CONFIG_IEEE80211AC
 	if (hapd->conf->vendor_vht)
 		tailpos = hostapd_eid_vendor_vht(hapd, tailpos);
diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c
index 799aff76597c..9fdc0a47ef8a 100644
--- a/src/ap/ctrl_iface_ap.c
+++ b/src/ap/ctrl_iface_ap.c
@@ -404,6 +404,20 @@ static int hostapd_ctrl_iface_sta_mib(struct hostapd_data *hapd,
 	}
 #endif /* CONFIG_IEEE80211BE */
 
+#ifdef CONFIG_IEEE80211BN
+	if ((sta->flags & WLAN_STA_UHR) && sta->uhr_capab) {
+		res = os_snprintf(buf + len, buflen - len, "uhr_capab=");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
+		len += wpa_snprintf_hex(buf + len, buflen - len,
+					(const u8 *) sta->uhr_capab,
+					sta->uhr_capab_len);
+		res = os_snprintf(buf + len, buflen - len, "\n");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
+	}
+#endif /* CONFIG_IEEE80211BN */
+
 #ifdef CONFIG_IEEE80211AC
 	if ((sta->flags & WLAN_STA_VHT) && sta->vht_capabilities) {
 		res = os_snprintf(buf + len, buflen - len,
@@ -884,6 +898,7 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf,
 			  "ieee80211ac=%d\n"
 			  "ieee80211ax=%d\n"
 			  "ieee80211be=%d\n"
+			  "ieee80211bn=%d\n"
 			  "beacon_int=%u\n"
 			  "dtim_period=%d\n",
 			  iface->conf->channel,
@@ -898,6 +913,8 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf,
 			  !hapd->conf->disable_11ax,
 			  iface->conf->ieee80211be &&
 			  !hapd->conf->disable_11be,
+			  iface->conf->ieee80211bn &&
+			  !hapd->conf->disable_11bn,
 			  iface->conf->beacon_int,
 			  hapd->conf->dtim_period);
 	if (os_snprintf_error(buflen - len, ret))
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index e92faa5276a1..951e8b37ca8f 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -143,6 +143,11 @@ static size_t hostapd_supp_rates(struct hostapd_data *hapd, u8 *buf)
 		*pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_HE_PHY;
 #endif /* CONFIG_IEEE80211AX */
 
+#ifdef CONFIG_IEEE80211BN
+	if (hapd->iconf->ieee80211bn && hapd->iconf->require_uhr)
+		*pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_UHR_PHY;
+#endif /* CONFIG_IEEE80211BN */
+
 #ifdef CONFIG_SAE
 	if ((hapd->conf->sae_pwe == SAE_PWE_HASH_TO_ELEMENT ||
 	     hostapd_sae_pw_id_in_use(hapd->conf) == 2) &&
@@ -4378,6 +4383,24 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		}
 	}
 #endif /* CONFIG_IEEE80211BE */
+#ifdef CONFIG_IEEE80211BN
+	if (hapd->iconf->ieee80211bn && !hapd->conf->disable_11bn) {
+		resp = copy_sta_uhr_capab(hapd, sta, IEEE80211_MODE_AP,
+					  elems->uhr_capabilities,
+					  elems->uhr_capabilities_len);
+		if (resp != WLAN_STATUS_SUCCESS)
+			return resp;
+
+		if (hapd->iconf->require_uhr && !(sta->flags & WLAN_STA_UHR)) {
+			hostapd_logger(hapd, sta->addr,
+				       HOSTAPD_MODULE_IEEE80211,
+				       HOSTAPD_LEVEL_INFO,
+				       "Station does not support mandatory UHR PHY - reject association");
+			/* FIXME - need assignment from spec */
+			return WLAN_STATUS_DENIED_HE_NOT_SUPPORTED;
+		}
+	}
+#endif /* CONFIG_IEEE80211BN */
 
 #ifdef CONFIG_P2P
 	if (elems->p2p && ies && ies_len) {
@@ -4831,6 +4854,10 @@ void ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd,
 			p = hostapd_eid_eht_operation(hapd, p);
 		}
 	}
+	if (hapd->iconf->ieee80211bn && !hapd->conf->disable_11bn) {
+		p = hostapd_eid_uhr_capab(hapd, p, IEEE80211_MODE_AP);
+		p = hostapd_eid_uhr_operation(hapd, p, false);
+	}
 
 	p = hostapd_eid_ext_capab(hapd, p, false);
 	p = hostapd_eid_mbo(hapd, p, buf + buflen - p);
@@ -5100,6 +5127,7 @@ static int add_associated_sta(struct hostapd_data *hapd,
 	struct ieee80211_vht_capabilities vht_cap;
 	struct ieee80211_he_capabilities he_cap;
 	struct ieee80211_eht_capabilities eht_cap;
+	struct ieee80211_uhr_capabilities uhr_cap;
 	int set = 1;
 	const u8 *mld_link_addr = NULL;
 	bool mld_link_sta = false;
@@ -5177,6 +5205,11 @@ static int add_associated_sta(struct hostapd_data *hapd,
 		hostapd_get_eht_capab(hapd, sta->eht_capab, &eht_cap,
 				      sta->eht_capab_len);
 #endif /* CONFIG_IEEE80211BE */
+#ifdef CONFIG_IEEE80211BN
+	if (sta->flags & WLAN_STA_UHR)
+		hostapd_get_uhr_capab(hapd, sta->uhr_capab, &uhr_cap,
+				      sta->uhr_capab_len);
+#endif /* CONFIG_IEEE80211BN */
 
 	/*
 	 * Add the station with forced WLAN_STA_ASSOC flag. The sta->flags
@@ -5192,6 +5225,8 @@ static int add_associated_sta(struct hostapd_data *hapd,
 			    sta->flags & WLAN_STA_HE ? sta->he_capab_len : 0,
 			    sta->flags & WLAN_STA_EHT ? &eht_cap : NULL,
 			    sta->flags & WLAN_STA_EHT ? sta->eht_capab_len : 0,
+			    sta->flags & WLAN_STA_UHR ? &uhr_cap : NULL,
+			    sta->flags & WLAN_STA_UHR ? sta->uhr_capab_len : 0,
 			    sta->he_6ghz_capab,
 			    sta->flags | WLAN_STA_ASSOC, sta->qosinfo,
 			    sta->vht_opmode, sta->p2p_ie ? 1 : 0,
@@ -5250,6 +5285,12 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
 			buflen += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE;
 	}
 #endif /* CONFIG_IEEE80211BE */
+#ifdef CONFIG_IEEE80211BN
+	if (hapd->iconf->ieee80211bn && !hapd->conf->disable_11bn) {
+		buflen += hostapd_eid_uhr_capab_len(hapd, IEEE80211_MODE_AP);
+		buflen += 3 + sizeof(struct ieee80211_uhr_operation);
+	}
+#endif /* CONFIG_IEEE80211BN */
 
 	buf = os_zalloc(buflen);
 	if (!buf) {
@@ -5411,6 +5452,13 @@ rsnxe_done:
 	}
 #endif /* CONFIG_IEEE80211BE */
 
+#ifdef CONFIG_IEEE80211BN
+	if (hapd->iconf->ieee80211bn && !hapd->conf->disable_11bn) {
+		p = hostapd_eid_uhr_capab(hapd, p, IEEE80211_MODE_AP);
+		p = hostapd_eid_uhr_operation(hapd, p, false);
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 #ifdef CONFIG_OWE
 	if (((hapd->conf->wpa_key_mgmt | hapd->conf->rsn_override_key_mgmt |
 	      hapd->conf->rsn_override_key_mgmt_2) & WPA_KEY_MGMT_OWE) &&
@@ -5867,7 +5915,7 @@ static void handle_assoc(struct hostapd_data *hapd,
 			hostapd_logger(hapd, mgmt->sa,
 				       HOSTAPD_MODULE_IEEE80211,
 				       HOSTAPD_LEVEL_INFO,
-				       "Station tried to associate before authentication (aid=%d flags=0x%x)",
+				       "Station tried to associate before authentication (aid=%d flags=0x%llx)",
 				       sta ? sta->aid : -1,
 				       sta ? sta->flags : 0);
 			send_deauth(hapd, mgmt->sa,
@@ -5963,6 +6011,7 @@ static void handle_assoc(struct hostapd_data *hapd,
 			       reassoc ? LINK_PARSE_REASSOC : LINK_PARSE_ASSOC);
 	if (resp != WLAN_STATUS_SUCCESS)
 		goto fail;
+
 #ifdef CONFIG_IEEE80211R_AP
 	if (reassoc && sta->auth_alg == WLAN_AUTH_FT)
 		omit_rsnxe = !get_ie(pos, left, WLAN_EID_RSNX);
diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h
index a2bd583beda3..e660e5deba14 100644
--- a/src/ap/ieee802_11.h
+++ b/src/ap/ieee802_11.h
@@ -322,4 +322,17 @@ void hostapd_link_reconf_resp_tx_status(struct hostapd_data *hapd,
 					const struct ieee80211_mgmt *mgmt,
 					size_t len, int ok);
 
+size_t hostapd_eid_uhr_capab_len(struct hostapd_data *hapd,
+				 enum ieee80211_op_mode opmode);
+u8 * hostapd_eid_uhr_capab(struct hostapd_data *hapd, u8 *eid,
+			   enum ieee80211_op_mode opmode);
+u8 * hostapd_eid_uhr_operation(struct hostapd_data *hapd, u8 *eid, bool beacon);
+u16 copy_sta_uhr_capab(struct hostapd_data *hapd, struct sta_info *sta,
+		       enum ieee80211_op_mode opmode,
+		       const u8 *uhr_capab, size_t uhr_capab_len);
+void hostapd_get_uhr_capab(struct hostapd_data *hapd,
+			   const struct ieee80211_uhr_capabilities *src,
+			   struct ieee80211_uhr_capabilities *dest,
+			   size_t len);
+
 #endif /* IEEE802_11_H */
diff --git a/src/ap/ieee802_11_uhr.c b/src/ap/ieee802_11_uhr.c
new file mode 100644
index 000000000000..6befe208bd28
--- /dev/null
+++ b/src/ap/ieee802_11_uhr.c
@@ -0,0 +1,146 @@
+/*
+ * hostapd / IEEE 802.11bn UHR
+ * Copyright (C) 2025 Intel Corporation
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "common/ocv.h"
+#include "common/wpa_ctrl.h"
+#include "crypto/crypto.h"
+#include "crypto/dh_groups.h"
+#include "hostapd.h"
+#include "sta_info.h"
+#include "ap_drv_ops.h"
+#include "wpa_auth.h"
+#include "ieee802_11.h"
+
+
+size_t hostapd_eid_uhr_capab_len(struct hostapd_data *hapd,
+				 enum ieee80211_op_mode opmode)
+{
+	struct hostapd_hw_modes *mode;
+	struct uhr_capabilities *uhr_cap;
+
+	mode = hapd->iface->current_mode;
+	if (!mode)
+		return 0;
+
+	uhr_cap = &mode->uhr_capab[opmode];
+	if (!uhr_cap->uhr_supported)
+		return 0;
+
+	return 6;
+}
+
+
+u8 * hostapd_eid_uhr_capab(struct hostapd_data *hapd, u8 *eid,
+			   enum ieee80211_op_mode opmode)
+{
+	struct hostapd_hw_modes *mode;
+	struct uhr_capabilities *uhr_cap;
+	struct ieee80211_uhr_capabilities *cap;
+	u8 *pos = eid, *length_pos;
+
+	mode = hapd->iface->current_mode;
+	if (!mode)
+		return eid;
+
+	uhr_cap = &mode->uhr_capab[opmode];
+	if (!uhr_cap->uhr_supported)
+		return eid;
+
+	*pos++ = WLAN_EID_EXTENSION;
+	length_pos = pos++;
+	*pos++ = WLAN_EID_EXT_UHR_CAPABILITIES;
+
+	cap = (struct ieee80211_uhr_capabilities *) pos;
+	os_memcpy(cap->mac, uhr_cap->mac, sizeof(cap->mac));
+	os_memcpy(cap->phy, uhr_cap->phy, sizeof(cap->phy));
+	pos += sizeof(*cap);
+
+	*length_pos = pos - (eid + 2);
+	return pos;
+}
+
+
+u8 * hostapd_eid_uhr_operation(struct hostapd_data *hapd, u8 *eid, bool beacon)
+{
+	struct ieee80211_uhr_operation *oper;
+	u8 *pos = eid;
+
+	if (!hapd->iface->current_mode)
+		return eid;
+
+	*pos++ = WLAN_EID_EXTENSION;
+	*pos++ = 1 + sizeof(*oper);
+	*pos++ = WLAN_EID_EXT_UHR_OPERATION;
+
+	oper = (void *) pos;
+	oper->oper_params = 0;
+
+	/* TODO: Fill in appropriate UHR-MCS max Nss information */
+	oper->basic_uhr_mcs_nss_set[0] = 0x11;
+	oper->basic_uhr_mcs_nss_set[1] = 0x00;
+	oper->basic_uhr_mcs_nss_set[2] = 0x00;
+	oper->basic_uhr_mcs_nss_set[3] = 0x00;
+
+	return pos + sizeof(*oper);
+}
+
+
+static bool ieee80211_invalid_uhr_cap_size(enum hostapd_hw_mode mode,
+					   const u8 *uhr_cap, size_t len)
+{
+	return len < sizeof(struct ieee80211_uhr_capabilities);
+}
+
+
+u16 copy_sta_uhr_capab(struct hostapd_data *hapd, struct sta_info *sta,
+		       enum ieee80211_op_mode opmode,
+		       const u8 *uhr_capab, size_t uhr_capab_len)
+{
+	struct hostapd_hw_modes *c_mode = hapd->iface->current_mode;
+	enum hostapd_hw_mode mode = c_mode ? c_mode->mode : NUM_HOSTAPD_MODES;
+
+	if (!hapd->iconf->ieee80211bn || hapd->conf->disable_11bn ||
+	    !uhr_capab ||
+	    ieee80211_invalid_uhr_cap_size(mode, uhr_capab, uhr_capab_len)) {
+		sta->flags &= ~WLAN_STA_UHR;
+		os_free(sta->uhr_capab);
+		sta->uhr_capab = NULL;
+		return WLAN_STATUS_SUCCESS;
+	}
+
+	os_free(sta->uhr_capab);
+	sta->uhr_capab = os_memdup(uhr_capab, uhr_capab_len);
+	if (!sta->uhr_capab) {
+		sta->uhr_capab_len = 0;
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	sta->flags |= WLAN_STA_UHR;
+	sta->uhr_capab_len = uhr_capab_len;
+
+	return WLAN_STATUS_SUCCESS;
+}
+
+
+void hostapd_get_uhr_capab(struct hostapd_data *hapd,
+			   const struct ieee80211_uhr_capabilities *src,
+			   struct ieee80211_uhr_capabilities *dest,
+			   size_t len)
+{
+	if (!src || !dest)
+		return;
+
+	if (len > sizeof(*dest))
+		len = sizeof(*dest);
+	/* TODO: mask out unsupported features */
+
+	os_memset(dest, 0, sizeof(*dest));
+	os_memcpy(dest, src, len);
+}
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index e594696ae9eb..1abcf43a052c 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -436,6 +436,7 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 	os_free(sta->he_capab);
 	os_free(sta->he_6ghz_capab);
 	os_free(sta->eht_capab);
+	os_free(sta->uhr_capab);
 	hostapd_free_psk_list(sta->psk);
 	os_free(sta->identity);
 	os_free(sta->radius_cui);
@@ -556,7 +557,7 @@ void ap_handle_timer(void *eloop_ctx, void *timeout_ctx)
 	int reason;
 	int max_inactivity = hapd->conf->ap_max_inactivity;
 
-	wpa_printf(MSG_DEBUG, "%s: %s: " MACSTR " flags=0x%x timeout_next=%d",
+	wpa_printf(MSG_DEBUG, "%s: %s: " MACSTR " flags=0x%llx timeout_next=%d",
 		   hapd->conf->iface, __func__, MAC2STR(sta->addr), sta->flags,
 		   sta->timeout_next);
 	if (sta->timeout_next == STA_REMOVE) {
@@ -1828,13 +1829,13 @@ void ap_sta_clear_assoc_timeout(struct hostapd_data *hapd,
 }
 
 
-int ap_sta_flags_txt(u32 flags, char *buf, size_t buflen)
+int ap_sta_flags_txt(unsigned long long flags, char *buf, size_t buflen)
 {
 	int res;
 
 	buf[0] = '\0';
 	res = os_snprintf(buf, buflen,
-			  "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+			  "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
 			  (flags & WLAN_STA_AUTH ? "[AUTH]" : ""),
 			  (flags & WLAN_STA_ASSOC ? "[ASSOC]" : ""),
 			  (flags & WLAN_STA_AUTHORIZED ? "[AUTHORIZED]" : ""),
@@ -1855,6 +1856,7 @@ int ap_sta_flags_txt(u32 flags, char *buf, size_t buflen)
 			  (flags & WLAN_STA_VHT ? "[VHT]" : ""),
 			  (flags & WLAN_STA_HE ? "[HE]" : ""),
 			  (flags & WLAN_STA_EHT ? "[EHT]" : ""),
+			  (flags & WLAN_STA_UHR ? "[UHR]" : ""),
 			  (flags & WLAN_STA_6GHZ ? "[6GHZ]" : ""),
 			  (flags & WLAN_STA_VENDOR_VHT ? "[VENDOR_VHT]" : ""),
 			  (flags & WLAN_STA_SPP_AMSDU ? "[SPP-A-MSDU]" : ""),
@@ -1976,7 +1978,7 @@ int ap_sta_re_add(struct hostapd_data *hapd, struct sta_info *sta)
 	if (hostapd_sta_add(hapd, sta->addr, 0, 0,
 			    sta->supported_rates,
 			    sta->supported_rates_len,
-			    0, NULL, NULL, NULL, 0, NULL, 0, NULL,
+			    0, NULL, NULL, NULL, 0, NULL, 0, NULL, 0, NULL,
 			    sta->flags, 0, 0, 0, 0,
 			    mld_link_addr, mld_link_sta, eml_cap)) {
 		hostapd_logger(hapd, sta->addr,
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index 6ed2930dd24b..8a8375a5cbe0 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -20,35 +20,36 @@
 #include "hostapd.h"
 
 /* STA flags */
-#define WLAN_STA_AUTH BIT(0)
-#define WLAN_STA_ASSOC BIT(1)
-#define WLAN_STA_SPP_AMSDU BIT(2)
-#define WLAN_STA_AUTHORIZED BIT(5)
-#define WLAN_STA_PENDING_POLL BIT(6) /* pending activity poll not ACKed */
-#define WLAN_STA_SHORT_PREAMBLE BIT(7)
-#define WLAN_STA_PREAUTH BIT(8)
-#define WLAN_STA_WMM BIT(9)
-#define WLAN_STA_MFP BIT(10)
-#define WLAN_STA_HT BIT(11)
-#define WLAN_STA_WPS BIT(12)
-#define WLAN_STA_MAYBE_WPS BIT(13)
-#define WLAN_STA_WDS BIT(14)
-#define WLAN_STA_ASSOC_REQ_OK BIT(15)
-#define WLAN_STA_WPS2 BIT(16)
-#define WLAN_STA_GAS BIT(17)
-#define WLAN_STA_VHT BIT(18)
-#define WLAN_STA_WNM_SLEEP_MODE BIT(19)
-#define WLAN_STA_VHT_OPMODE_ENABLED BIT(20)
-#define WLAN_STA_VENDOR_VHT BIT(21)
-#define WLAN_STA_PENDING_FILS_ERP BIT(22)
-#define WLAN_STA_MULTI_AP BIT(23)
-#define WLAN_STA_HE BIT(24)
-#define WLAN_STA_6GHZ BIT(25)
-#define WLAN_STA_PENDING_PASN_FILS_ERP BIT(26)
-#define WLAN_STA_EHT BIT(27)
-#define WLAN_STA_PENDING_DISASSOC_CB BIT(29)
-#define WLAN_STA_PENDING_DEAUTH_CB BIT(30)
-#define WLAN_STA_NONERP BIT(31)
+#define WLAN_STA_AUTH BIT_ULL(0)
+#define WLAN_STA_ASSOC BIT_ULL(1)
+#define WLAN_STA_SPP_AMSDU BIT_ULL(2)
+#define WLAN_STA_AUTHORIZED BIT_ULL(5)
+#define WLAN_STA_PENDING_POLL BIT_ULL(6) /* pending activity poll not ACKed */
+#define WLAN_STA_SHORT_PREAMBLE BIT_ULL(7)
+#define WLAN_STA_PREAUTH BIT_ULL(8)
+#define WLAN_STA_WMM BIT_ULL(9)
+#define WLAN_STA_MFP BIT_ULL(10)
+#define WLAN_STA_HT BIT_ULL(11)
+#define WLAN_STA_WPS BIT_ULL(12)
+#define WLAN_STA_MAYBE_WPS BIT_ULL(13)
+#define WLAN_STA_WDS BIT_ULL(14)
+#define WLAN_STA_ASSOC_REQ_OK BIT_ULL(15)
+#define WLAN_STA_WPS2 BIT_ULL(16)
+#define WLAN_STA_GAS BIT_ULL(17)
+#define WLAN_STA_VHT BIT_ULL(18)
+#define WLAN_STA_WNM_SLEEP_MODE BIT_ULL(19)
+#define WLAN_STA_VHT_OPMODE_ENABLED BIT_ULL(20)
+#define WLAN_STA_VENDOR_VHT BIT_ULL(21)
+#define WLAN_STA_PENDING_FILS_ERP BIT_ULL(22)
+#define WLAN_STA_MULTI_AP BIT_ULL(23)
+#define WLAN_STA_HE BIT_ULL(24)
+#define WLAN_STA_6GHZ BIT_ULL(25)
+#define WLAN_STA_PENDING_PASN_FILS_ERP BIT_ULL(26)
+#define WLAN_STA_EHT BIT_ULL(27)
+#define WLAN_STA_PENDING_DISASSOC_CB BIT_ULL(29)
+#define WLAN_STA_PENDING_DEAUTH_CB BIT_ULL(30)
+#define WLAN_STA_NONERP BIT_ULL(31)
+#define WLAN_STA_UHR BIT_ULL(32)
 
 /* Maximum number of supported rates (from both Supported Rates and Extended
  * Supported Rates IEs). */
@@ -93,7 +94,7 @@ struct sta_info {
 	struct dl_list ip6addr; /* list head for struct ip6addr */
 	u16 aid; /* STA's unique AID (1 .. 2007) or 0 if not yet assigned */
 	u16 disconnect_reason_code; /* RADIUS server override */
-	u32 flags; /* Bitfield of WLAN_STA_* */
+	unsigned long long flags; /* Bitfield of WLAN_STA_* */
 	u16 capability;
 	u16 listen_interval; /* or beacon_int for APs */
 	u8 supported_rates[WLAN_SUPP_RATES_MAX];
@@ -197,6 +198,8 @@ struct sta_info {
 	struct ieee80211_he_6ghz_band_cap *he_6ghz_capab;
 	struct ieee80211_eht_capabilities *eht_capab;
 	size_t eht_capab_len;
+	struct ieee80211_uhr_capabilities *uhr_capab;
+	size_t uhr_capab_len;
 
 	int sa_query_count; /* number of pending SA Query requests;
 			     * 0 = no SA Query in progress */
@@ -405,7 +408,7 @@ void ap_sta_clear_disconnect_timeouts(struct hostapd_data *hapd,
 void ap_sta_clear_assoc_timeout(struct hostapd_data *hapd,
 				struct sta_info *sta);
 
-int ap_sta_flags_txt(u32 flags, char *buf, size_t buflen);
+int ap_sta_flags_txt(unsigned long long flags, char *buf, size_t buflen);
 void ap_sta_delayed_1x_auth_fail_disconnect(struct hostapd_data *hapd,
 					    struct sta_info *sta,
 					    unsigned timeout);
diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index ae7a7647a5bb..b8416234dae9 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -420,6 +420,14 @@ static int ieee802_11_parse_extension(const u8 *pos, size_t elen,
 		elems->pasn_encrypted_data = pos;
 		elems->pasn_encrypted_data_len = elen;
 		break;
+	case WLAN_EID_EXT_UHR_CAPABILITIES:
+		elems->uhr_capabilities = pos;
+		elems->uhr_capabilities_len = elen;
+		break;
+	case WLAN_EID_EXT_UHR_OPERATION:
+		elems->uhr_operation = pos;
+		elems->uhr_operation_len = elen;
+		break;
 	default:
 		if (show_errors) {
 			wpa_printf(MSG_MSGDUMP,
@@ -983,6 +991,14 @@ void ieee802_11_elems_clear_ext_ids(struct ieee802_11_elems *elems,
 			elems->eht_operation = NULL;
 			elems->eht_operation_len = 0;
 			break;
+		case WLAN_EID_EXT_UHR_CAPABILITIES:
+			elems->uhr_capabilities = NULL;
+			elems->uhr_capabilities_len = 0;
+			break;
+		case WLAN_EID_EXT_UHR_OPERATION:
+			elems->uhr_operation = NULL;
+			elems->uhr_operation_len = 0;
+			break;
 		}
 	}
 }
diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h
index 5f52b573cf54..6b236394a86a 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -122,6 +122,8 @@ struct ieee802_11_elems {
 	const u8 *rsnxe_override;
 	const u8 *rsn_selection;
 	const u8 *wfa_capab;
+	const u8 *uhr_capabilities;
+	const u8 *uhr_operation;
 
 	u8 ssid_len;
 	u8 supp_rates_len;
@@ -191,6 +193,8 @@ struct ieee802_11_elems {
 	size_t rsnxe_override_len;
 	size_t rsn_selection_len;
 	u8 wfa_capab_len;
+	u8 uhr_capabilities_len;
+	u8 uhr_operation_len;
 
 	struct mb_ies_info mb_ies;
 
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index 6cf8b146d6e1..0a3f37e73914 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -528,6 +528,8 @@
 #define WLAN_EID_EXT_BANDWIDTH_INDICATION 135
 #define WLAN_EID_EXT_KNOWN_STA_IDENTIFICATION 136
 #define WLAN_EID_EXT_PASN_ENCRYPTED_DATA 140
+#define WLAN_EID_EXT_UHR_OPERATION 151
+#define WLAN_EID_EXT_UHR_CAPABILITIES 152
 
 /* Extended Capabilities field */
 #define WLAN_EXT_CAPAB_20_40_COEX 0
@@ -1383,6 +1385,7 @@ struct ieee80211_ampe_ie {
 #define HT_OPER_PARAM_PCO_PHASE				((u16) BIT(11))
 /* B36..B39 - Reserved */
 
+#define BSS_MEMBERSHIP_SELECTOR_UHR_PHY 120
 #define BSS_MEMBERSHIP_SELECTOR_HE_PHY 122
 #define BSS_MEMBERSHIP_SELECTOR_SAE_H2E_ONLY 123
 #define BSS_MEMBERSHIP_SELECTOR_EPD 124
@@ -3214,6 +3217,26 @@ struct ieee80211_s1g_beacon_compat {
 	le32 tsf_completion;
 } STRUCT_PACKED;
 
+/* UHR Capabilities element format */
+struct ieee80211_uhr_capabilities {
+	/* UHR MAC Capabilities Information */
+	u8 mac[5];
+	/* UHR PHY Capabilities Information */
+	u8 phy[1];
+} STRUCT_PACKED;
+
+#define UHR_OPER_PARAMS_DPS_ENA               0x0001
+#define UHR_OPER_PARAMS_NPCA_ENA              0x0002
+#define UHR_OPER_PARAMS_DBE_ENA               0x0004
+#define UHR_OPER_PARAMS_PEDCA_ENA             0x0008
+
+/* UHR Operation element format */
+struct ieee80211_uhr_operation {
+	le16 oper_params; /* UHR Operation Parameters: UHR_OPER_* bits */
+	u8 basic_uhr_mcs_nss_set[4];
+	/* FIXME: DPS, NPCA, P-EDCA, DBE */
+} STRUCT_PACKED;
+
 #ifdef _MSC_VER
 #pragma pack(pop)
 #endif /* _MSC_VER */
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index d943062feb41..b08db15f75aa 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -216,6 +216,13 @@ struct eht_capabilities {
 	u8 ppet[EHT_PPE_THRESH_CAPAB_LEN];
 };
 
+/* struct uhr_capabilities - IEEE 802.11bn UHR capabilities */
+struct uhr_capabilities {
+	bool uhr_supported;
+	u8 mac[5];
+	u8 phy[1];
+};
+
 #define HOSTAPD_MODE_FLAG_HT_INFO_KNOWN BIT(0)
 #define HOSTAPD_MODE_FLAG_VHT_INFO_KNOWN BIT(1)
 #define HOSTAPD_MODE_FLAG_HE_INFO_KNOWN BIT(2)
@@ -325,6 +332,11 @@ struct hostapd_hw_modes {
 	 * eht_capab - EHT (IEEE 802.11be) capabilities
 	 */
 	struct eht_capabilities eht_capab[IEEE80211_MODE_NUM];
+
+	/**
+	 * uhr_capab - UHR (IEEE 802.11bb) capabilities
+	 */
+	struct uhr_capabilities uhr_capab[IEEE80211_MODE_NUM];
 };
 
 
@@ -1399,6 +1411,11 @@ struct wpa_driver_associate_params {
 	 */
 	int disable_eht;
 
+	/**
+	 * disable_uhr - Disable UHR for this connection
+	 */
+	int disable_uhr;
+
 	/*
 	 * mld_params - MLD association parameters
 	 */
@@ -2653,6 +2670,9 @@ struct hostapd_sta_add_params {
 	s8 mld_link_id;
 	const u8 *mld_link_addr;
 	u16 eml_cap;
+
+	const struct ieee80211_uhr_capabilities *uhr_capab;
+	size_t uhr_capab_len;
 };
 
 struct mac_address {
@@ -7253,6 +7273,8 @@ int ht_supported(const struct hostapd_hw_modes *mode);
 int vht_supported(const struct hostapd_hw_modes *mode);
 bool he_supported(const struct hostapd_hw_modes *hw_mode,
 		  enum ieee80211_op_mode op_mode);
+bool uhr_supported(const struct hostapd_hw_modes *hw_mode,
+		   enum ieee80211_op_mode op_mode);
 
 struct wowlan_triggers *
 wpa_get_wowlan_triggers(const char *wowlan_triggers,
diff --git a/src/drivers/driver_common.c b/src/drivers/driver_common.c
index 045c82967f5a..f381e4c74315 100644
--- a/src/drivers/driver_common.c
+++ b/src/drivers/driver_common.c
@@ -204,6 +204,13 @@ bool he_supported(const struct hostapd_hw_modes *hw_mode,
 }
 
 
+bool uhr_supported(const struct hostapd_hw_modes *hw_mode,
+		   enum ieee80211_op_mode op_mode)
+{
+	return hw_mode->uhr_capab[op_mode].uhr_supported;
+}
+
+
 static int wpa_check_wowlan_trigger(const char *start, const char *trigger,
 				    int capa_trigger, u8 *param_trigger)
 {
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index c5bbe119c9a3..ebd28880fa07 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -5853,6 +5853,14 @@ static int wpa_driver_nl80211_sta_add(void *priv,
 				goto fail;
 		}
 
+		if (params->uhr_capab) {
+			wpa_hexdump(MSG_DEBUG, "  * uhr_capab",
+				    params->uhr_capab, params->uhr_capab_len);
+			if (nla_put(msg, NL80211_ATTR_UHR_CAPABILITY,
+				    params->uhr_capab_len, params->uhr_capab))
+				goto fail;
+		}
+
 		if (params->ext_capab) {
 			wpa_hexdump(MSG_DEBUG, "  * ext_capab",
 				    params->ext_capab, params->ext_capab_len);
@@ -6697,6 +6705,12 @@ static int nl80211_ht_vht_overrides(struct nl_msg *msg,
 			return -1;
 	}
 
+	if (params->disable_uhr) {
+		wpa_printf(MSG_DEBUG, "  * UHR disabled");
+		if (nla_put_flag(msg, NL80211_ATTR_DISABLE_UHR))
+			return -1;
+	}
+
 	return 0;
 }
 
diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
index 88ee9e724a02..8590caa3d4df 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -1941,6 +1941,7 @@ static void phy_info_iftype_copy(struct hostapd_hw_modes *mode,
 	size_t len;
 	struct he_capabilities *he_capab = &mode->he_capab[opmode];
 	struct eht_capabilities *eht_capab = &mode->eht_capab[opmode];
+	struct uhr_capabilities *uhr_capab = &mode->uhr_capab[opmode];
 
 	switch (opmode) {
 	case IEEE80211_MODE_INFRA:
@@ -2048,6 +2049,24 @@ static void phy_info_iftype_copy(struct hostapd_hw_modes *mode,
 			  nla_data(tb[NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PPE]),
 			  len);
 	}
+
+	if (!tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC] ||
+	    !tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_PHY])
+		return;
+
+	uhr_capab->uhr_supported = true;
+
+	if (tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC] &&
+	    nla_len(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]) >= sizeof(uhr_capab->mac))
+		os_memcpy(uhr_capab->mac,
+			  nla_data(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]),
+			  sizeof(uhr_capab->mac));
+
+	if (tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_PHY] &&
+	    nla_len(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_PHY]) >= sizeof(uhr_capab->phy))
+		os_memcpy(uhr_capab->phy,
+			  nla_data(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_PHY]),
+			  sizeof(uhr_capab->phy));
 }
 
 
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index 9e4a3e6e1a9a..bbca337a7b2d 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -189,6 +189,8 @@ static const char * nl80211_command_to_string(enum nl80211_commands cmd)
 	C2S(NL80211_CMD_SET_TID_TO_LINK_MAPPING)
 	C2S(NL80211_CMD_ASSOC_MLO_RECONF)
 	C2S(NL80211_CMD_EPCS_CFG)
+	C2S(NL80211_CMD_NAN_NEXT_DW_NOTIFICATION)
+	C2S(NL80211_CMD_NAN_CLUSTER_JOINED)
 	C2S(__NL80211_CMD_AFTER_LAST)
 	}
 #undef C2S
diff --git a/src/utils/common.h b/src/utils/common.h
index d7b3600f27e4..1d5634948849 100644
--- a/src/utils/common.h
+++ b/src/utils/common.h
@@ -458,6 +458,10 @@ void perror(const char *s);
 #define BIT(x) (1U << (x))
 #endif
 
+#ifndef BIT_ULL
+#define BIT_ULL(x) (1ULL << (x))
+#endif
+
 #ifndef MIN
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
 #endif
diff --git a/tests/hwsim/example-hostapd.config b/tests/hwsim/example-hostapd.config
index 8d94091f35e8..1b4b156dc190 100644
--- a/tests/hwsim/example-hostapd.config
+++ b/tests/hwsim/example-hostapd.config
@@ -128,3 +128,5 @@ CONFIG_NAN_USD=y
 CONFIG_MACSEC=y
 CONFIG_DRIVER_MACSEC_LINUX=y
 CONFIG_SUITEB192=y
+
+CONFIG_IEEE80211BN=y
diff --git a/tests/hwsim/example-wpa_supplicant.config b/tests/hwsim/example-wpa_supplicant.config
index bbbdfd562c12..4389684cb66d 100644
--- a/tests/hwsim/example-wpa_supplicant.config
+++ b/tests/hwsim/example-wpa_supplicant.config
@@ -173,3 +173,5 @@ CONFIG_IEEE80211BE=y
 CONFIG_MACSEC=y
 CONFIG_DRIVER_MACSEC_LINUX=y
 CONFIG_SUITEB192=y
+
+CONFIG_IEEE80211BN=y
diff --git a/tests/hwsim/test_uhr.py b/tests/hwsim/test_uhr.py
new file mode 100644
index 000000000000..a5dfea0aee35
--- /dev/null
+++ b/tests/hwsim/test_uhr.py
@@ -0,0 +1,145 @@
+# UHR tests
+# Copyright (c) 2022-2024, Qualcomm Innovation Center, Inc.
+# Copyright (C) 2025 Intel Corporation
+#
+# This software may be distributed under the terms of the BSD license.
+# See README for more details.
+
+import binascii
+import subprocess
+import tempfile
+import time
+
+import hostapd
+from utils import *
+from hwsim import HWSimRadio
+import hwsim_utils
+from wpasupplicant import WpaSupplicant
+from test_eht import eht_verify_status, traffic_test
+
+def uhr_verify_wifi_version(dev):
+    status = dev.get_status()
+    logger.info("station status: " + str(status))
+
+    if 'wifi_generation' not in status:
+        raise Exception("Missing wifi_generation information")
+    if status['wifi_generation'] != "8":
+        raise Exception("Unexpected wifi_generation value: " + status['wifi_generation'])
+
+def uhr_verify_status(wpas, hapd, is_ht=False, is_vht=False):
+    status = hapd.get_status()
+
+    logger.info("hostapd STATUS: " + str(status))
+    if is_ht and status["ieee80211n"] != "1":
+        raise Exception("Unexpected STATUS ieee80211n value")
+    if is_vht and status["ieee80211ac"] != "1":
+        raise Exception("Unexpected STATUS ieee80211ac value")
+    if status["ieee80211ax"] != "1":
+        raise Exception("Unexpected STATUS ieee80211ax value")
+    if status["ieee80211be"] != "1":
+        raise Exception("Unexpected STATUS ieee80211be value")
+    if status["ieee80211bn"] != "1":
+        raise Exception("Unexpected STATUS ieee80211bn value")
+
+    sta = hapd.get_sta(wpas.own_addr())
+    logger.info("hostapd STA: " + str(sta))
+    if sta['addr'] == 'FAIL':
+        raise Exception("hostapd " + hapd.ifname + " did not have a STA entry for the STA " + wpas.own_addr())
+    if is_ht and "[HT]" not in sta['flags']:
+        raise Exception("Missing STA flag: HT")
+    if is_vht and "[VHT]" not in sta['flags']:
+        raise Exception("Missing STA flag: VHT")
+    if "[HE]" not in sta['flags']:
+        raise Exception("Missing STA flag: HE")
+    if "[EHT]" not in sta['flags']:
+        raise Exception("Missing STA flag: EHT")
+    if "[UHR]" not in sta['flags']:
+        raise Exception("Missing STA flag: UHR")
+
+def test_uhr_open(dev, apdev):
+    """UHR AP with open mode configuration"""
+    params = {
+        "ssid": "uhr",
+        "ieee80211ax": "1",
+        "ieee80211be": "1",
+        "ieee80211bn": "1",
+        "require_uhr": "1",
+    }
+    try:
+        hapd = hostapd.add_ap(apdev[0], params)
+    except Exception as e:
+        if isinstance(e, Exception) and \
+           str(e) == "Failed to set hostapd parameter ieee80211bn":
+            raise HwsimSkip("UHR not supported")
+        raise
+    if hapd.get_status_field("ieee80211bn") != "1":
+        raise Exception("AP STATUS did not indicate ieee80211bn=1")
+    dev[0].connect("uhr", key_mgmt="NONE", scan_freq="2412")
+    time.sleep(1)
+    sta = hapd.get_sta(dev[0].own_addr())
+    uhr_verify_status(dev[0], hapd, is_ht=True)
+    status = dev[0].request("STATUS")
+    if "wifi_generation=8" not in status:
+        raise Exception("STA STATUS did not indicate wifi_generation=7")
+
+def uhr_mld_ap_wpa2_params(ssid, passphrase=None, key_mgmt="WPA-PSK-SHA256",
+                           mfp="2", pwe=None, beacon_prot="1", bridge=False):
+    params = hostapd.wpa2_params(ssid=ssid, passphrase=passphrase,
+                                 wpa_key_mgmt=key_mgmt, ieee80211w=mfp)
+    params['ieee80211n'] = '1'
+    params['ieee80211ax'] = '1'
+    params['ieee80211be'] = '1'
+    params['ieee80211bn'] = '1'
+    params['channel'] = '1'
+    params['hw_mode'] = 'g'
+    params['group_mgmt_cipher'] = "AES-128-CMAC"
+    params['beacon_prot'] = beacon_prot
+    if bridge:
+        params['bridge'] = 'ap-br0'
+
+    if pwe is not None:
+        params['sae_pwe'] = pwe
+
+    return params
+
+def uhr_mld_enable_ap(iface, link_id, params):
+    hapd = hostapd.add_mld_link(iface, link_id, params)
+    hapd.enable()
+
+    ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=1)
+    if ev is None:
+        raise Exception("AP startup timed out")
+    if "AP-ENABLED" not in ev:
+        raise Exception("AP startup failed")
+
+    return hapd
+
+def run_uhr_mld_sae_single_link(dev, apdev, anti_clogging_token=False):
+    with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \
+            HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface):
+        wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
+        wpas.interface_add(wpas_iface)
+        check_sae_capab(wpas)
+
+        passphrase = 'qwertyuiop'
+        ssid = "mld_ap_sae_single_link"
+        params = uhr_mld_ap_wpa2_params(ssid, passphrase, key_mgmt="SAE",
+                                        mfp="2", pwe='2')
+        if anti_clogging_token:
+            params['sae_anti_clogging_threshold'] = '0'
+
+        hapd0 = uhr_mld_enable_ap(hapd_iface, 0, params)
+
+        wpas.set("sae_pwe", "1")
+        wpas.connect(ssid, sae_password=passphrase, scan_freq="2412",
+                     key_mgmt="SAE", ieee80211w="2")
+
+        eht_verify_status(wpas, hapd0, 2412, 20, is_ht=True, mld=True,
+                          valid_links=1, active_links=1)
+        uhr_verify_status(wpas, hapd0, is_ht=True)
+        uhr_verify_wifi_version(wpas)
+        traffic_test(wpas, hapd0)
+
+def test_uhr_mld_sae_single_link(dev, apdev):
+    """UHR MLD AP with MLD client SAE H2E connection using single link"""
+    run_uhr_mld_sae_single_link(dev, apdev)
diff --git a/tests/hwsim/vm/inside.sh b/tests/hwsim/vm/inside.sh
index 53d67472cd50..8a131cb4346a 100755
--- a/tests/hwsim/vm/inside.sh
+++ b/tests/hwsim/vm/inside.sh
@@ -109,7 +109,7 @@ ip link set lo up
 # create logs mountpoint and mount the logshare
 mkdir /tmp/logs
 if grep -q rootfstype=hostfs /proc/cmdline; then
-    mount -t hostfs none /tmp/logs -o $LOGDIR
+    mount -t hostfs none /tmp/logs -o hostfs=$LOGDIR
 else
     mount -t 9p -o trans=virtio,rw logshare /tmp/logs
 fi
diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk
index cb07e7364352..2f1b4f244070 100644
--- a/wpa_supplicant/Android.mk
+++ b/wpa_supplicant/Android.mk
@@ -948,6 +948,9 @@ endif
 ifdef CONFIG_IEEE80211BE
 OBJS += src/ap/ieee802_11_eht.c
 endif
+ifdef CONFIG_IEEE80211BN
+OBJS += src/ap/ieee802_11_uhr.c
+endif
 ifdef CONFIG_WNM_AP
 L_CFLAGS += -DCONFIG_WNM_AP
 OBJS += src/ap/wnm_ap.c
diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile
index fd7d7a0bd004..74ff02719352 100644
--- a/wpa_supplicant/Makefile
+++ b/wpa_supplicant/Makefile
@@ -1022,6 +1022,9 @@ endif
 ifdef CONFIG_IEEE80211BE
 OBJS += ../src/ap/ieee802_11_eht.o
 endif
+ifdef CONFIG_IEEE80211BN
+OBJS += ../src/ap/ieee802_11_uhr.o
+endif
 ifdef CONFIG_WNM_AP
 CFLAGS += -DCONFIG_WNM_AP
 OBJS += ../src/ap/wnm_ap.o
diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c
index a0f71cfc6ecc..36600fb9695f 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -2759,6 +2759,7 @@ static const struct parse_data ssid_fields[] = {
 	{ INT_RANGE(transition_disable, 0, 255) },
 	{ INT_RANGE(sae_pk, 0, 2) },
 	{ INT_RANGE(disable_eht, 0, 1)},
+	{ INT_RANGE(disable_uhr, 0, 1)},
 	{ INT_RANGE(enable_4addr_mode, 0, 1)},
 	{ INT_RANGE(max_idle, 0, 65535)},
 	{ INT_RANGE(ssid_protection, 0, 1)},
diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c
index b1ba03ac5a71..8c95257ba53b 100644
--- a/wpa_supplicant/config_file.c
+++ b/wpa_supplicant/config_file.c
@@ -990,6 +990,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
 	INT(disable_he);
 #endif /* CONFIG_HE_OVERRIDES */
 	INT(disable_eht);
+	INT(disable_uhr);
 	INT(enable_4addr_mode);
 	INT(max_idle);
 	INT(ssid_protection);
diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h
index 530b5e96385b..9df5a4c8e0b9 100644
--- a/wpa_supplicant/config_ssid.h
+++ b/wpa_supplicant/config_ssid.h
@@ -1296,6 +1296,14 @@ struct wpa_ssid {
 	 */
 	int disable_eht;
 
+	/**
+	 * disable_uhr - Disable UHR (IEEE 802.11bn) for this network
+	 *
+	 * By default, use it if it is available, but this can be configured
+	 * to 1 to have it disabled.
+	 */
+	int disable_uhr;
+
 	/**
 	 * enable_4addr_mode - Set 4addr mode after association
 	 * 0 = Do not attempt to set 4addr mode
diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index ebcc5d7ee4d2..5fca153369ba 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -2370,12 +2370,14 @@ static int wpa_supplicant_ctrl_iface_status(struct wpa_supplicant *wpa_s,
 
 		if (wpa_s->connection_set &&
 		    (wpa_s->connection_ht || wpa_s->connection_vht ||
-		     wpa_s->connection_he || wpa_s->connection_eht)) {
+		     wpa_s->connection_he || wpa_s->connection_eht ||
+		     wpa_s->connection_uhr)) {
 			ret = os_snprintf(pos, end - pos,
 					  "wifi_generation=%u\n",
+					  wpa_s->connection_uhr ? 8 :
 					  wpa_s->connection_eht ? 7 :
-					  (wpa_s->connection_he ? 6 :
-					   (wpa_s->connection_vht ? 5 : 4)));
+					  wpa_s->connection_he ? 6 :
+					  wpa_s->connection_vht ? 5 : 4);
 			if (os_snprintf_error(end - pos, ret))
 				return pos - buf;
 			pos += ret;
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 0e48a31a22fb..5bd23ec2288c 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -1039,6 +1039,17 @@ static int rate_match(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
 				continue;
 			}
 
+			if (flagged && ((rate_ie[j] & 0x7f) ==
+					BSS_MEMBERSHIP_SELECTOR_UHR_PHY)) {
+				if (!uhr_supported(mode, IEEE80211_MODE_INFRA)) {
+					if (debug_print)
+						wpa_dbg(wpa_s, MSG_DEBUG,
+							"   hardware does not support UHR PHY");
+					return 0;
+				}
+				continue;
+			}
+
 #ifdef CONFIG_SAE
 			if (flagged && ((rate_ie[j] & 0x7f) ==
 					BSS_MEMBERSHIP_SELECTOR_SAE_H2E_ONLY)) {
@@ -3528,6 +3539,9 @@ static int wpa_supplicant_event_associnfo(struct wpa_supplicant *wpa_s,
 				resp_elems.he_capabilities;
 			wpa_s->connection_eht = req_elems.eht_capabilities &&
 				resp_elems.eht_capabilities;
+			wpa_s->connection_uhr = req_elems.uhr_capabilities &&
+				resp_elems.uhr_capabilities;
+
 			if (req_elems.rrm_enabled)
 				wpa_s->rrm.rrm_used = 1;
 		}
diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index 47c965bfdc70..277f3394ae61 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -2571,6 +2571,7 @@ mscs_fail:
 	wpa_supplicant_apply_he_overrides(wpa_s, ssid, &params);
 #endif /* CONFIG_HE_OVERRIDES */
 	wpa_supplicant_apply_eht_overrides(wpa_s, ssid, &params);
+	wpa_supplicant_apply_uhr_overrides(wpa_s, ssid, &params);
 #ifdef CONFIG_IEEE80211R
 	if (auth_type == WLAN_AUTH_FT && wpa_s->sme.ft_ies &&
 	    get_ie(wpa_s->sme.ft_ies, wpa_s->sme.ft_ies_len,
diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c
index 03180a316ffe..a617020b7cdb 100644
--- a/wpa_supplicant/wpa_cli.c
+++ b/wpa_supplicant/wpa_cli.c
@@ -1497,6 +1497,7 @@ static const char *network_fields[] = {
 	"disable_he",
 #endif /* CONFIG_HE_OVERRIDES */
 	"disable_eht",
+	"disable_uhr",
 	"ap_max_inactivity", "dtim_period", "beacon_int",
 #ifdef CONFIG_MACSEC
 	"macsec_policy",
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index d45002fd912f..5b91589e37f9 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -2243,7 +2243,7 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s,
 		wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_USE_EXT_KEY_ID, 0);
 	}
 
-	/* Mark WMM enabled for any HT/VHT/HE/EHT association to get more
+	/* Mark WMM enabled for any HT/VHT/HE/EHT/UHR association to get more
 	 * appropriate advertisement of the supported number of PTKSA receive
 	 * counters. In theory, this could be based on a driver capability, but
 	 * in practice all cases using WMM support at least eight replay
@@ -2254,7 +2254,8 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s,
 	 * is far more likely for any current device to support WMM. */
 	wmm = wpa_s->connection_set &&
 		(wpa_s->connection_ht || wpa_s->connection_vht ||
-		 wpa_s->connection_he || wpa_s->connection_eht);
+		 wpa_s->connection_he || wpa_s->connection_eht ||
+		 wpa_s->connection_uhr);
 	if (!wmm && bss)
 		wmm = !!wpa_bss_get_vendor_ie(bss, WMM_IE_VENDOR_TYPE);
 	wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_WMM_ENABLED, wmm);
@@ -4857,6 +4858,7 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit)
 	wpa_supplicant_apply_he_overrides(wpa_s, ssid, &params);
 #endif /* CONFIG_HE_OVERRIDES */
 	wpa_supplicant_apply_eht_overrides(wpa_s, ssid, &params);
+	wpa_supplicant_apply_uhr_overrides(wpa_s, ssid, &params);
 
 #ifdef CONFIG_P2P
 	/*
@@ -6810,6 +6812,17 @@ void wpa_supplicant_apply_eht_overrides(
 }
 
 
+void wpa_supplicant_apply_uhr_overrides(
+	struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
+	struct wpa_driver_associate_params *params)
+{
+	if (!ssid)
+		return;
+
+	params->disable_uhr = ssid->disable_uhr;
+}
+
+
 static int pcsc_reader_init(struct wpa_supplicant *wpa_s)
 {
 #ifdef PCSC_FUNCS
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index e7675a4abe0a..b7d0362223f7 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -984,6 +984,7 @@ struct wpa_supplicant {
 	unsigned int connection_vht:1;
 	unsigned int connection_he:1;
 	unsigned int connection_eht:1;
+	unsigned int connection_uhr:1;
 	unsigned int disable_mbo_oce:1;
 
 	struct os_reltime last_mac_addr_change;
@@ -1649,6 +1650,9 @@ void wpa_supplicant_apply_he_overrides(
 void wpa_supplicant_apply_eht_overrides(
 	struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
 	struct wpa_driver_associate_params *params);
+void wpa_supplicant_apply_uhr_overrides(
+	struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
+	struct wpa_driver_associate_params *params);
 
 int wpa_set_wep_keys(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid);
 int wpa_supplicant_set_wpa_none_key(struct wpa_supplicant *wpa_s,
-- 
2.51.0




More information about the Hostap mailing list