From gubertoli at gmail.com Tue May 5 11:05:30 2026 From: gubertoli at gmail.com (Gustavo Bertoli) Date: Tue, 5 May 2026 20:05:30 +0200 Subject: [PATCH] gas_query: use announced comeback_delay for Comeback Response Message-ID: <20260505180530.51387-1-gubertoli@gmail.com> Currently, the GAS client manages the Comeback Response wait timer (gas_query_rx_comeback_timeout) using value based on GAS_QUERY_WAIT_TIME_COMEBACK. This timer fails to take the server-announced comeback_delay into consideration. During DPP Enterprise provisioning, a DPP Configurator may announce longer comeback_delay (dpp_supplicant.c, comeback_delay = 1000). Because the client only waits 160 ms for the response, it prematurely times out and restarts the GAS dialog with a new dialog token. This change fix protocol-level asymmetry by making the client respect the server's provided delay. The Comeback Response wait timer is now derived from the server's announced comeback_delay (plus a 10 ms margin for processing delay). Signed-off-by: Gustavo Bertoli --- wpa_supplicant/gas_query.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/wpa_supplicant/gas_query.c b/wpa_supplicant/gas_query.c index 73cc34e0a..1198eafd2 100644 --- a/wpa_supplicant/gas_query.c +++ b/wpa_supplicant/gas_query.c @@ -42,6 +42,7 @@ struct gas_query_pending { u8 addr[ETH_ALEN]; u8 dialog_token; u8 next_frag_id; + u16 comeback_delay; unsigned int wait_comeback:1; unsigned int offchannel_tx_started:1; unsigned int retry:1; @@ -279,10 +280,20 @@ static void gas_query_tx_status(struct wpa_supplicant *wpa_s, gas_query_timeout, gas, query); } if (query->wait_comeback && !query->retry) { + unsigned int wait_us; + u32 cd_us; + eloop_cancel_timeout(gas_query_rx_comeback_timeout, gas, query); + + cd_us = query->comeback_delay + ? (u32) query->comeback_delay * 1024 + : GAS_QUERY_WAIT_TIME_COMEBACK * 1000; + + wait_us = cd_us + 10 * 1000; /* Add 10ms margin */ + eloop_register_timeout( - 0, (GAS_QUERY_WAIT_TIME_COMEBACK + 10) * 1000, + 0, wait_us, gas_query_rx_comeback_timeout, gas, query); } } @@ -412,6 +423,7 @@ static void gas_query_tx_comeback_req_delay(struct gas_query *gas, query->offchannel_tx_started = 0; } + query->comeback_delay = comeback_delay; secs = (comeback_delay * 1024) / 1000000; usecs = comeback_delay * 1024 - secs * 1000000; wpa_printf(MSG_DEBUG, "GAS: Send comeback request to " MACSTR -- 2.43.0 From gubertoli at gmail.com Tue May 5 11:28:36 2026 From: gubertoli at gmail.com (Gustavo Bertoli) Date: Tue, 5 May 2026 20:28:36 +0200 Subject: [PATCH] gas_server: extend timeout for pending responses Message-ID: <20260505182836.61214-1-gubertoli@gmail.com> Currently, gas_server_response_timeout() unconditionally drops the session and frees the entry after 60 seconds. This causes deferred provisioning operations, such as DPP Enterprise certBag delivery, to silently fail if the CA takes longer than 60 seconds to respond. Fix this by checking if the response is still pending and a comeback delay is active. If so, reschedule the timeout to keep the entry alive. This extends the Configurator's wait window safely, relying on the Enrollee's own watchdog timer (120 seconds) to bound the session. Signed-off-by: Gustavo Bertoli --- src/common/gas_server.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/common/gas_server.c b/src/common/gas_server.c index 52c14df36..8ec3b0df1 100644 --- a/src/common/gas_server.c +++ b/src/common/gas_server.c @@ -61,6 +61,18 @@ static void gas_server_response_timeout(void *eloop_ctx, void *user_ctx) { struct gas_server_response *response = eloop_ctx; + if (!response->resp && response->comeback_delay) { + wpa_printf(MSG_DEBUG, + "GAS: Response @%p for " MACSTR + " (dialog_token=%u) - response pending, extending timeout", + response, MAC2STR(response->dst), response->dialog_token); + + eloop_register_timeout(GAS_QUERY_TIMEOUT, 0, + gas_server_response_timeout, response, user_ctx); + + return; + } + wpa_printf(MSG_DEBUG, "GAS: Response @%p timeout for " MACSTR " (dialog_token=%u freq=%d frag_id=%u sent=%lu/%lu) - drop pending data", response, MAC2STR(response->dst), response->dialog_token, -- 2.43.0 From johannes at sipsolutions.net Wed May 6 02:11:22 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Wed, 6 May 2026 11:11:22 +0200 Subject: [PATCH] tests: ap_pmf: fix an exception string format Message-ID: <20260506111121.f6e1e25c7cf6.I3be0bd4a7c01e6a29474eb462b348b1789acc88e@changeid> From: Johannes Berg Since I ran into this while playing with the SW crypto code, fix the Exception string format to use f-strings, it's broken as is since it's missing parentheses. Signed-off-by: Johannes Berg --- tests/hwsim/test_ap_pmf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hwsim/test_ap_pmf.py b/tests/hwsim/test_ap_pmf.py index 22ace9f9fa41..06e93d4d256d 100644 --- a/tests/hwsim/test_ap_pmf.py +++ b/tests/hwsim/test_ap_pmf.py @@ -1406,7 +1406,7 @@ def check_mac80211_bigtk(dev, hapd): replays = int(sta_key['replays']) icverrors = int(sta_key['icverrors']) if replays > 0 or icverrors > 0: - raise Exception("STA reported errors: replays=%d icverrors=%d" % replays, icverrors) + raise Exception(f"STA reported errors: replays={replays} icverrors={icverrors}") rx_spec = int(sta_key['rx_spec'], base=16) if rx_spec < 3: -- 2.53.0 From andrwe at axis.com Thu May 7 01:57:24 2026 From: andrwe at axis.com (Andreas Westin) Date: Thu, 7 May 2026 10:57:24 +0200 Subject: [PATCH] macsec_linux: Don't send NULL pointer to rtnl_link_delete Message-ID: If rtnl_link_add fails hostapd will still set created_link to true and also if rtnl_link_macsec_alloc fails the link pointer will be NULL and this is passed to rtnl_link_delete which crashes. Signed-off-by: Andreas Westin --- src/drivers/driver_macsec_linux.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/drivers/driver_macsec_linux.c b/src/drivers/driver_macsec_linux.c index fad47a292..57bea3b42 100644 --- a/src/drivers/driver_macsec_linux.c +++ b/src/drivers/driver_macsec_linux.c @@ -1200,6 +1200,7 @@ static int macsec_drv_create_transmit_sc( rtnl_link_put(link); wpa_printf(MSG_ERROR, DRV_PREFIX "couldn't create link: err %d", err); + drv->created_link = false; return err; } @@ -1223,6 +1224,7 @@ static int macsec_drv_create_transmit_sc( drv->link = rtnl_link_macsec_alloc(); if (!drv->link) { wpa_printf(MSG_ERROR, DRV_PREFIX "couldn't allocate link"); + drv->created_link = false; return -1; } @@ -1259,6 +1261,11 @@ static int macsec_drv_delete_transmit_sc(void *priv, struct transmit_sc *sc) return 0; } + if (!drv->link) { + wpa_printf(MSG_ERROR, DRV_PREFIX "no link to delete"); + return -1; + } + err = rtnl_link_delete(drv->sk, drv->link); if (err < 0) wpa_printf(MSG_ERROR, DRV_PREFIX "couldn't delete link"); --- Best regards, -- Andreas Westin From johannes at sipsolutions.net Thu May 7 10:03:20 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 07 May 2026 19:03:20 +0200 Subject: [PATCH v3 1/8] initial UHR support In-Reply-To: <05f54b1b-182e-4c30-b4df-896843d3b971@oss.qualcomm.com> References: <20260424071256.71834-10-johannes@sipsolutions.net> <20260424091256.c415029a0067.I1dd16bb1ffbcf19c38d2d73626776aca4070fbc1@changeid> <516527c0-4d24-4acf-bf6e-597011de4d0c@oss.qualcomm.com> <541e3229703c83a5ecf12642b44441d7f5cd0887.camel@sipsolutions.net> <05f54b1b-182e-4c30-b4df-896843d3b971@oss.qualcomm.com> Message-ID: Hi, > > > > + if (tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC] && > > > > + nla_len(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]) >= (int)sizeof(uhr_capab->mac)) > > > > + os_memcpy(uhr_capab->mac, > > > > + nla_data(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]), > > > > + sizeof(uhr_capab->mac)); > > > > > > Just to consider minimum possible mac cap size for os_memcpy(), > > > Can this be modified like this? > > > > > > if (tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]) { > > > len = nla_len(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]); > > > > > > if (len > sizeof(uhr_capab->mac)) > > > len = sizeof(uhr_capab->mac); > > > os_memcpy(uhr_capab->mac, > > > nla_data(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]), > > > len); > > > } > > > > > > and in the below hunk as well? > > > > I don't follow. Why? > > The suggested style is same as how HE/EHT Phy and Mac bytes copy are > handled in the same function. Nothing new. Would that style be useful > to handle a case when mac80211 sends lesser bytes in Mac/Phy attr than > hostapd expected ? (for ex: mac80211 sends 1 byte info uhr phy cap > and hostapd expects 2 byte phy cap). This is not a problem now. > Such style helpful to handle when mac80211 and hostapd are not in sync ? Sorry, was making adjustments and realised I never replied to this. I'm not sure I really agree, if it's inconsistent between the two then something bad happened, no? Even the >= is a bit questionable to me, if the driver thinks the MAC capabilities should be bigger, then both are working off different drafts of the spec, and things can't really be good anyway ... johannes From johannes at sipsolutions.net Thu May 7 10:21:27 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:27 +0200 Subject: [PATCH v4 00/12] some UHR support Message-ID: <20260507172335.546456-14-johannes@sipsolutions.net> Changes since v3: - fold in the hostapd_set_freq_params() change - add more DBE code - move to always using MLO for UHR - fix EHT BPCC - add UHR critical update field Most of these are because I'm making some changes in mac80211 as well: - will require MLO for UHR because of critical updates in MLE, this in turn requires security - will require enhanced critical update in MLE johannes From johannes at sipsolutions.net Thu May 7 10:21:28 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:28 +0200 Subject: [PATCH v3 01/12] common: put hostapd_set_freq_params() inputs into a struct In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192335.f4aeb39e5c73.I00c6f374083c341bad0850abe9458dc1a4e0defb@changeid> From: Johannes Berg This function prototype has grown unchecked, and really isn't sustainable any longer. Move all the arguments into a struct. type=cleanup ticket=none Signed-off-by: Johannes Berg --- src/ap/ap_drv_ops.c | 59 +++++++++++++++++++++------------ src/ap/beacon.c | 36 ++++++++++++-------- src/ap/dfs.c | 39 ++++++++++++---------- src/ap/hostapd.c | 41 +++++++++++++---------- src/common/hw_features_common.c | 29 ++++++++++------ src/common/hw_features_common.h | 54 ++++++++++++++++++++++++------ wpa_supplicant/mesh.c | 40 +++++++++++----------- wpa_supplicant/wpa_supplicant.c | 29 +++++++++++----- 8 files changed, 206 insertions(+), 121 deletions(-) diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c index 61b823abe138..dc46b599cfb1 100644 --- a/src/ap/ap_drv_ops.c +++ b/src/ap/ap_drv_ops.c @@ -654,18 +654,27 @@ int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode, { struct hostapd_freq_params data; struct hostapd_hw_modes *cmode = hapd->iface->current_mode; + struct hostapd_channel_info info = { + .mode = mode, + .freq = freq, + .channel = channel, + .edmg.enabled = edmg, + .edmg.channel = edmg_channel, + .ht.enabled = ht_enabled, + .vht.enabled = vht_enabled, + .he.enabled = he_enabled, + .eht.enabled = eht_enabled, + .ht.sec_channel_offset = sec_channel_offset, + .oper_chwidth = oper_chwidth, + .center_segment0 = center_segment0, + .center_segment1 = center_segment1, + .vht.caps = cmode ? cmode->vht_capab : 0, + .he.cap = cmode ? &cmode->he_capab[IEEE80211_MODE_AP] : NULL, + .eht.cap = cmode ? &cmode->eht_capab[IEEE80211_MODE_AP] : NULL, + .eht.punct_bitmap = hostapd_get_punct_bitmap(hapd), + }; - if (hostapd_set_freq_params(&data, mode, freq, channel, edmg, - edmg_channel, ht_enabled, - vht_enabled, he_enabled, eht_enabled, - sec_channel_offset, oper_chwidth, - center_segment0, center_segment1, - cmode ? cmode->vht_capab : 0, - cmode ? - &cmode->he_capab[IEEE80211_MODE_AP] : NULL, - cmode ? - &cmode->eht_capab[IEEE80211_MODE_AP] : - NULL, hostapd_get_punct_bitmap(hapd))) + if (hostapd_set_freq_params(&data, &info)) return -1; if (hapd->driver == NULL) @@ -1084,6 +1093,23 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface, struct hostapd_freq_params data; int res; struct hostapd_hw_modes *cmode = iface->current_mode; + struct hostapd_channel_info info = { + .mode = mode, + .freq = freq, + .channel = channel, + .ht.enabled = ht_enabled, + .vht.enabled = vht_enabled, + .he.enabled = he_enabled, + .eht.enabled = eht_enabled, + .ht.sec_channel_offset = sec_channel_offset, + .oper_chwidth = oper_chwidth, + .center_segment0 = center_segment0, + .center_segment1 = center_segment1, + .vht.caps = cmode ? cmode->vht_capab : 0, + .he.cap = cmode ? &cmode->he_capab[IEEE80211_MODE_AP] : NULL, + .eht.cap = cmode ? &cmode->eht_capab[IEEE80211_MODE_AP] : NULL, + .eht.punct_bitmap = hostapd_get_punct_bitmap(hapd), + }; if (!hapd->driver || !hapd->driver->start_dfs_cac || !cmode) return 0; @@ -1094,16 +1120,7 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface, return -1; } - if (hostapd_set_freq_params(&data, mode, freq, channel, 0, 0, - ht_enabled, - vht_enabled, he_enabled, eht_enabled, - sec_channel_offset, - oper_chwidth, center_segment0, - center_segment1, - cmode->vht_capab, - &cmode->he_capab[IEEE80211_MODE_AP], - &cmode->eht_capab[IEEE80211_MODE_AP], - hostapd_get_punct_bitmap(hapd))) { + if (hostapd_set_freq_params(&data, &info)) { wpa_printf(MSG_ERROR, "Can't set freq params"); return -1; } diff --git a/src/ap/beacon.c b/src/ap/beacon.c index 059d22862087..fba6a473396f 100644 --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -2733,6 +2733,7 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd) bool twt_he_responder = false; int res, ret = -1, i; struct hostapd_hw_modes *mode; + struct hostapd_channel_info info; if (!hapd->drv_priv) { wpa_printf(MSG_ERROR, "Interface is disabled"); @@ -2802,20 +2803,27 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd) params.punct_bitmap = iconf->punct_bitmap; #endif /* CONFIG_IEEE80211BE */ - if (cmode && - hostapd_set_freq_params(&freq, iconf->hw_mode, iface->freq, - iconf->channel, iconf->enable_edmg, - iconf->edmg_channel, iconf->ieee80211n, - iconf->ieee80211ac, iconf->ieee80211ax, - iconf->ieee80211be, - iconf->secondary_channel, - hostapd_get_oper_chwidth(iconf), - hostapd_get_oper_centr_freq_seg0_idx(iconf), - hostapd_get_oper_centr_freq_seg1_idx(iconf), - cmode->vht_capab, - &cmode->he_capab[IEEE80211_MODE_AP], - &cmode->eht_capab[IEEE80211_MODE_AP], - hostapd_get_punct_bitmap(hapd)) == 0) { + info = (struct hostapd_channel_info) { + .mode = iconf->hw_mode, + .freq = iface->freq, + .channel = iconf->channel, + .edmg.enabled = iconf->enable_edmg, + .edmg.channel = iconf->edmg_channel, + .ht.enabled = iconf->ieee80211n, + .vht.enabled = iconf->ieee80211ac, + .he.enabled = iconf->ieee80211ax, + .eht.enabled = iconf->ieee80211be, + .ht.sec_channel_offset = iconf->secondary_channel, + .oper_chwidth = hostapd_get_oper_chwidth(iconf), + .center_segment0 = hostapd_get_oper_centr_freq_seg0_idx(iconf), + .center_segment1 = hostapd_get_oper_centr_freq_seg1_idx(iconf), + .vht.caps = cmode ? cmode->vht_capab : 0, + .he.cap = cmode ? &cmode->he_capab[IEEE80211_MODE_AP] : NULL, + .eht.cap = cmode ? &cmode->eht_capab[IEEE80211_MODE_AP] : NULL, + .eht.punct_bitmap = hostapd_get_punct_bitmap(hapd), + }; + + if (cmode && hostapd_set_freq_params(&freq, &info) == 0) { freq.link_id = -1; #ifdef CONFIG_IEEE80211BE if (hapd->conf->mld_ap) diff --git a/src/ap/dfs.c b/src/ap/dfs.c index d72c8ddb4464..8b85a25558b4 100644 --- a/src/ap/dfs.c +++ b/src/ap/dfs.c @@ -983,6 +983,7 @@ static int hostapd_dfs_request_channel_switch(struct hostapd_iface *iface, unsigned int i; unsigned int num_err = 0; u8 op_class, chan; + struct hostapd_channel_info info; wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d", channel); wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NEW_CHANNEL @@ -1012,24 +1013,28 @@ static int hostapd_dfs_request_channel_switch(struct hostapd_iface *iface, if (iface->mconf) ieee80211_mode = IEEE80211_MODE_MESH; #endif /* CONFIG_MESH */ - err = hostapd_set_freq_params(&csa_settings.freq_params, - iface->conf->hw_mode, - freq, channel, - iface->conf->enable_edmg, - iface->conf->edmg_channel, - iface->conf->ieee80211n, - iface->conf->ieee80211ac, - iface->conf->ieee80211ax, - iface->conf->ieee80211be, - secondary_channel, - new_vht_oper_chwidth, - oper_centr_freq_seg0_idx, - oper_centr_freq_seg1_idx, - cmode->vht_capab, - &cmode->he_capab[ieee80211_mode], - &cmode->eht_capab[ieee80211_mode], - hostapd_get_punct_bitmap(iface->bss[0])); + info = (struct hostapd_channel_info) { + .mode = iface->conf->hw_mode, + .freq = freq, + .channel = channel, + .edmg.enabled = iface->conf->enable_edmg, + .edmg.channel = iface->conf->edmg_channel, + .ht.enabled = iface->conf->ieee80211n, + .vht.enabled = iface->conf->ieee80211ac, + .he.enabled = iface->conf->ieee80211ax, + .eht.enabled = iface->conf->ieee80211be, + .ht.sec_channel_offset = secondary_channel, + .oper_chwidth = new_vht_oper_chwidth, + .center_segment0 = oper_centr_freq_seg0_idx, + .center_segment1 = oper_centr_freq_seg1_idx, + .vht.caps = cmode->vht_capab, + .he.cap = &cmode->he_capab[ieee80211_mode], + .eht.cap = &cmode->eht_capab[ieee80211_mode], + .eht.punct_bitmap = hostapd_get_punct_bitmap(iface->bss[0]), + }; + + err = hostapd_set_freq_params(&csa_settings.freq_params, &info); if (err) { wpa_printf(MSG_ERROR, "DFS failed to calculate CSA freq params"); diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 72a0bf503efe..703dadf85e7d 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -4522,23 +4522,30 @@ int hostapd_change_config_freq(struct hostapd_data *hapd, mode = hapd->iface->current_mode; /* if a pointer to old_params is provided we save previous state */ - if (old_params && - hostapd_set_freq_params(old_params, conf->hw_mode, - hostapd_hw_get_freq(hapd, conf->channel), - conf->channel, conf->enable_edmg, - conf->edmg_channel, conf->ieee80211n, - conf->ieee80211ac, conf->ieee80211ax, - conf->ieee80211be, conf->secondary_channel, - hostapd_get_oper_chwidth(conf), - hostapd_get_oper_centr_freq_seg0_idx(conf), - hostapd_get_oper_centr_freq_seg1_idx(conf), - conf->vht_capab, - mode ? &mode->he_capab[IEEE80211_MODE_AP] : - NULL, - mode ? &mode->eht_capab[IEEE80211_MODE_AP] : - NULL, - hostapd_get_punct_bitmap(hapd))) - return -1; + if (old_params) { + struct hostapd_channel_info info = { + .mode = conf->hw_mode, + .freq = hostapd_hw_get_freq(hapd, conf->channel), + .channel = conf->channel, + .edmg.enabled = conf->enable_edmg, + .edmg.channel = conf->edmg_channel, + .ht.enabled = conf->ieee80211n, + .vht.enabled = conf->ieee80211ac, + .he.enabled = conf->ieee80211ax, + .eht.enabled = conf->ieee80211be, + .ht.sec_channel_offset = conf->secondary_channel, + .oper_chwidth = hostapd_get_oper_chwidth(conf), + .center_segment0 = hostapd_get_oper_centr_freq_seg0_idx(conf), + .center_segment1 = hostapd_get_oper_centr_freq_seg1_idx(conf), + .vht.caps = conf->vht_capab, + .he.cap = mode ? &mode->he_capab[IEEE80211_MODE_AP] : NULL, + .eht.cap = mode ? &mode->eht_capab[IEEE80211_MODE_AP] : NULL, + .eht.punct_bitmap = hostapd_get_punct_bitmap(hapd), + }; + + if (hostapd_set_freq_params(old_params, &info)) + return -1; + } switch (params->bandwidth) { case 0: diff --git a/src/common/hw_features_common.c b/src/common/hw_features_common.c index e928738ee568..ea5d24300048 100644 --- a/src/common/hw_features_common.c +++ b/src/common/hw_features_common.c @@ -476,18 +476,25 @@ void punct_update_legacy_bw(u16 bitmap, u8 pri, enum oper_chan_width *width, int hostapd_set_freq_params(struct hostapd_freq_params *data, - enum hostapd_hw_mode mode, - int freq, int channel, int enable_edmg, - u8 edmg_channel, int ht_enabled, - int vht_enabled, int he_enabled, - bool eht_enabled, int sec_channel_offset, - enum oper_chan_width oper_chwidth, - int center_segment0, - int center_segment1, u32 vht_caps, - struct he_capabilities *he_cap, - struct eht_capabilities *eht_cap, - u16 punct_bitmap) + const struct hostapd_channel_info *info) { + enum hostapd_hw_mode mode = info->mode; + int freq = info->freq; + int channel = info->channel; + int enable_edmg = info->edmg.enabled; + u8 edmg_channel = info->edmg.channel; + int ht_enabled = info->ht.enabled; + int vht_enabled = info->vht.enabled; + int he_enabled = info->he.enabled; + bool eht_enabled = info->eht.enabled; + int sec_channel_offset = info->ht.sec_channel_offset; + enum oper_chan_width oper_chwidth = info->oper_chwidth; + int center_segment0 = info->center_segment0; + int center_segment1 = info->center_segment1; + u32 vht_caps = info->vht.caps; + const struct he_capabilities *he_cap = info->he.cap; + const struct eht_capabilities *eht_cap = info->eht.cap; + u16 punct_bitmap = info->eht.punct_bitmap; enum oper_chan_width oper_chwidth_legacy; u8 seg0_legacy, seg1_legacy; diff --git a/src/common/hw_features_common.h b/src/common/hw_features_common.h index 80e33adf2d5c..62c9ac0df018 100644 --- a/src/common/hw_features_common.h +++ b/src/common/hw_features_common.h @@ -37,18 +37,50 @@ int check_40mhz_2g4(struct hostapd_hw_modes *mode, int sec_chan); void punct_update_legacy_bw(u16 bitmap, u8 pri_chan, enum oper_chan_width *width, u8 *seg0, u8 *seg1); + +/* Channel information to derive freq params from */ +struct hostapd_channel_info { + enum hostapd_hw_mode mode; + + int freq; + int channel; + enum oper_chan_width oper_chwidth; + + /* EDMG */ + struct { + int enabled; + u8 channel; + } edmg; + + struct { + int enabled; + int sec_channel_offset; + } ht; + + int center_segment0; + + /* Only valid for 80+80 */ + int center_segment1; + + struct { + int enabled; + u32 caps; + } vht; + + struct { + int enabled; + const struct he_capabilities *cap; + } he; + + struct { + int enabled; + const struct eht_capabilities *cap; + u16 punct_bitmap; + } eht; +}; + int hostapd_set_freq_params(struct hostapd_freq_params *data, - enum hostapd_hw_mode mode, - int freq, int channel, int edmg, u8 edmg_channel, - int ht_enabled, - int vht_enabled, int he_enabled, - bool eht_enabled, int sec_channel_offset, - enum oper_chan_width oper_chwidth, - int center_segment0, - int center_segment1, u32 vht_caps, - struct he_capabilities *he_caps, - struct eht_capabilities *eht_cap, - u16 punct_bitmap); + const struct hostapd_channel_info *info); void set_disable_ht40(struct ieee80211_ht_capabilities *htcaps, int disabled); int ieee80211ac_cap_check(u32 hw, u32 conf); diff --git a/wpa_supplicant/mesh.c b/wpa_supplicant/mesh.c index 8f7143f6ea63..f49ce68db65b 100644 --- a/wpa_supplicant/mesh.c +++ b/wpa_supplicant/mesh.c @@ -190,28 +190,26 @@ static int wpas_mesh_update_freq_params(struct wpa_supplicant *wpa_s) { struct wpa_driver_mesh_join_params *params = wpa_s->mesh_params; struct hostapd_iface *ifmsh = wpa_s->ifmsh; - struct he_capabilities *he_capab = NULL; + struct hostapd_channel_info info = { + .mode = ifmsh->conf->hw_mode, + .freq = ifmsh->freq, + .channel = ifmsh->conf->channel, + .edmg.enabled = ifmsh->conf->enable_edmg, + .edmg.channel = ifmsh->conf->edmg_channel, + .ht.enabled = ifmsh->conf->ieee80211n, + .vht.enabled = ifmsh->conf->ieee80211ac, + .he.enabled = ifmsh->conf->ieee80211ax, + .eht.enabled = ifmsh->conf->ieee80211be, + .ht.sec_channel_offset = ifmsh->conf->secondary_channel, + .oper_chwidth = hostapd_get_oper_chwidth(ifmsh->conf), + .center_segment0 = hostapd_get_oper_centr_freq_seg0_idx(ifmsh->conf), + .center_segment1 = hostapd_get_oper_centr_freq_seg1_idx(ifmsh->conf), + .vht.caps = ifmsh->conf->vht_capab, + .he.cap = ifmsh->current_mode ? + &ifmsh->current_mode->he_capab[IEEE80211_MODE_MESH] : NULL, + }; - if (ifmsh->current_mode) - he_capab = &ifmsh->current_mode->he_capab[IEEE80211_MODE_MESH]; - - if (hostapd_set_freq_params( - ¶ms->freq, - ifmsh->conf->hw_mode, - ifmsh->freq, - ifmsh->conf->channel, - ifmsh->conf->enable_edmg, - ifmsh->conf->edmg_channel, - ifmsh->conf->ieee80211n, - ifmsh->conf->ieee80211ac, - ifmsh->conf->ieee80211ax, - ifmsh->conf->ieee80211be, - ifmsh->conf->secondary_channel, - hostapd_get_oper_chwidth(ifmsh->conf), - hostapd_get_oper_centr_freq_seg0_idx(ifmsh->conf), - hostapd_get_oper_centr_freq_seg1_idx(ifmsh->conf), - ifmsh->conf->vht_capab, - he_capab, NULL, 0)) { + if (hostapd_set_freq_params(¶ms->freq, &info)) { wpa_printf(MSG_ERROR, "Error updating mesh frequency params"); wpa_supplicant_mesh_deinit(wpa_s, true); return -1; diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 5414eab0fba2..c9dfe41bca41 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -3351,6 +3351,7 @@ static bool ibss_mesh_select_80_160mhz(struct wpa_supplicant *wpa_s, }; struct hostapd_freq_params vht_freq; + struct hostapd_channel_info info; int i; unsigned int j, k; int chwidth, seg0, seg1; @@ -3515,15 +3516,25 @@ static bool ibss_mesh_select_80_160mhz(struct wpa_supplicant *wpa_s, } skip_80mhz: - if (hostapd_set_freq_params(&vht_freq, mode->mode, freq->freq, - freq->channel, ssid->enable_edmg, - ssid->edmg_channel, freq->ht_enabled, - freq->vht_enabled, freq->he_enabled, - freq->eht_enabled, - freq->sec_channel_offset, - chwidth, seg0, seg1, vht_caps, - &mode->he_capab[ieee80211_mode], - &mode->eht_capab[ieee80211_mode], 0) != 0) + info = (struct hostapd_channel_info) { + .mode = mode->mode, + .freq = freq->freq, + .channel = freq->channel, + .edmg.enabled = ssid->enable_edmg, + .edmg.channel = ssid->edmg_channel, + .ht.enabled = freq->ht_enabled, + .vht.enabled = freq->vht_enabled, + .he.enabled = freq->he_enabled, + .eht.enabled = freq->eht_enabled, + .ht.sec_channel_offset = freq->sec_channel_offset, + .oper_chwidth = chwidth, + .center_segment0 = seg0, + .center_segment1 = seg1, + .vht.caps = vht_caps, + .he.cap = &mode->he_capab[ieee80211_mode], + .eht.cap = &mode->eht_capab[ieee80211_mode], + }; + if (hostapd_set_freq_params(&vht_freq, &info)) return false; *freq = vht_freq; -- 2.53.0 From johannes at sipsolutions.net Thu May 7 10:21:30 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:30 +0200 Subject: [PATCH v3 03/12] common: add UHR NPCA bit definitions In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192335.120bb6c7c1ae.I30cf5f46efb2836cb2b340248dee3ba429d10738@changeid> From: Johannes Berg Define the bits needed for UHR Operation NPCA parameters, based on Draft P802.11bn_D1.3. Signed-off-by: Johannes Berg --- src/common/ieee802_11_defs.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index 756c36d2b439..fda844fea671 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -3315,13 +3315,22 @@ struct ieee80211_uhr_capabilities { 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 */ + /* FIXME: DPS, P-EDCA, DBE */ } STRUCT_PACKED; /* Max size in Draft P802.11bn D1.3 with DPS, NPCA, P-EDCA, DBE */ #define IEEE80211_UHR_OPER_MAX_SIZE \ (sizeof(struct ieee80211_uhr_operation) + 4 + 6 + 3 + 3) +/* IEEE P802.11bn/D1.3, Figure 9-aa4 */ +#define UHR_OPER_PARAMS_NPCA_PRIM_CHAN_OFFS 0x0000000F +#define UHR_OPER_PARAMS_NPCA_NPCA_MIN_DUR_THRESH 0x000000F0 +#define UHR_OPER_PARAMS_NPCA_NPCA_SWITCH_DELAY 0x00003F00 +#define UHR_OPER_PARAMS_NPCA_NPCA_SWITCH_BACK_DELAY 0x000FC000 +#define UHR_OPER_PARAMS_NPCA_INIT_NPCA_QRSC 0x00300000 +#define UHR_OPER_PARAMS_NPCA_MOPLEN_NPCA 0x00400000 +#define UHR_OPER_PARAMS_NPCA_DIS_SUBCH_BITMAP_PRES 0x00800000 + #ifdef _MSC_VER #pragma pack(pop) #endif /* _MSC_VER */ -- 2.53.0 From johannes at sipsolutions.net Thu May 7 10:21:31 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:31 +0200 Subject: [PATCH v3 04/12] AP: add UHR operation as a separate attribute In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192335.75d838abecf6.I29bb7488b95ae487a6985706dc6cb4fa47bbc795@changeid> From: Johannes Berg Since the beacon data doesn't contain the full UHR operation element, the driver will need to have the information about it separately. Add it to the driver interface. Signed-off-by: Johannes Berg --- src/ap/beacon.c | 20 +++++++++++++++++++- src/drivers/driver.h | 6 ++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/ap/beacon.c b/src/ap/beacon.c index ecf19c7910e3..65cad1ec0274 100644 --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -2501,8 +2501,22 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, #endif /* CONFIG_IEEE80211BE */ #ifdef CONFIG_IEEE80211BN - if (hostapd_is_uhr_enabled(hapd)) + if (hostapd_is_uhr_enabled(hapd)) { + u8 *uhr_oper; + tailpos = hostapd_eid_uhr_operation(hapd, tailpos, true); + params->uhr_oper = os_zalloc(3 + IEEE80211_UHR_OPER_MAX_SIZE); + if (!params->uhr_oper) + goto error; + + uhr_oper = hostapd_eid_uhr_operation(hapd, params->uhr_oper, + false); + /* check that it was filled */ + if (uhr_oper == params->uhr_oper) { + os_free(params->uhr_oper); + params->uhr_oper = NULL; + } + } #endif /* CONFIG_IEEE80211BN */ #ifdef CONFIG_IEEE80211AC @@ -2712,6 +2726,8 @@ error: os_free(head); os_free(tail); os_free(resp); + os_free(params->uhr_oper); + params->uhr_oper = NULL; return -1; #endif /* CONFIG_SAE || NEED_AP_MLME */ } @@ -2743,6 +2759,8 @@ void ieee802_11_free_ap_params(struct wpa_driver_ap_params *params) #endif /* CONFIG_IEEE80211AX */ os_free(params->allowed_freqs); params->allowed_freqs = NULL; + os_free(params->uhr_oper); + params->uhr_oper = NULL; } diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 2f50265657a7..485015ef4918 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -1950,6 +1950,12 @@ struct wpa_driver_ap_params { * sae_password - SAE password for SAE offload */ const char *sae_password; + + /** + * uhr_oper - Full UHR operation (beacon only has abridged data), + * includes the extended element header + */ + u8 *uhr_oper; }; struct wpa_driver_mesh_bss_params { -- 2.53.0 From johannes at sipsolutions.net Thu May 7 10:21:32 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:32 +0200 Subject: [PATCH v3 05/12] driver_nl80211: send full UHR operation In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192335.061bcca809bc.I60c4927d49cb76537aa751c4e624627e3f886d03@changeid> From: Johannes Berg Since the beacon data doesn't contain the full UHR operation element, nl80211 has a new attribute for it. Fill it. Signed-off-by: Johannes Berg --- src/drivers/driver_nl80211.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 167f12069718..8c2721d979fb 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -5952,6 +5952,15 @@ static int wpa_driver_nl80211_set_ap(void *priv, goto fail; #endif /* CONFIG_IEEE80211AX */ +#ifdef CONFIG_IEEE80211BN + if (params->uhr_oper && + nla_put(msg, NL80211_ATTR_UHR_OPERATION, + /* nl80211 wants it without the extended element header */ + params->uhr_oper[1] - 1, + params->uhr_oper + 3)) + goto fail; +#endif /* CONFIG_IEEE80211BN */ + #ifdef CONFIG_SAE if (wpa_key_mgmt_sae(params->key_mgmt_suites) && nl80211_put_sae_pwe(msg, params->sae_pwe) < 0) -- 2.53.0 From johannes at sipsolutions.net Thu May 7 10:21:33 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:33 +0200 Subject: [PATCH v3 06/12] AP: advertise DBE capability if driver has it In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192335.10ffd67a1463.I3d604152d53fdc56d10e750e8c113b2f971fcabd@changeid> From: Johannes Berg If the driver has DBE capability, then the DBE capabilities field must be created. Do this based on the EHT capability the driver is advertising. Signed-off-by: Johannes Berg --- src/ap/ieee802_11_uhr.c | 43 ++++++++++++++++++++++++++++++++++-- src/common/ieee802_11_defs.h | 9 ++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/ap/ieee802_11_uhr.c b/src/ap/ieee802_11_uhr.c index 6538e0e0b9ea..2476b3412b75 100644 --- a/src/ap/ieee802_11_uhr.c +++ b/src/ap/ieee802_11_uhr.c @@ -24,6 +24,7 @@ size_t hostapd_eid_uhr_capab_len(struct hostapd_data *hapd, { struct hostapd_hw_modes *mode; struct uhr_capabilities *uhr_cap; + size_t len = 3 /* ext elem header */ + 6 /* MAC + PHY */; mode = hapd->iface->current_mode; if (!mode) @@ -33,7 +34,10 @@ size_t hostapd_eid_uhr_capab_len(struct hostapd_data *hapd, if (!uhr_cap->uhr_supported) return 0; - return 3 /* ext elem header */ + 6 /* MAC + PHY */; + if (uhr_cap->mac[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) + len++; + + return len; } @@ -41,6 +45,8 @@ u8 * hostapd_eid_uhr_capab(struct hostapd_data *hapd, u8 *eid, enum ieee80211_op_mode opmode) { struct hostapd_hw_modes *mode; + struct he_capabilities *he_cap; + struct eht_capabilities *eht_cap; struct uhr_capabilities *uhr_cap; struct ieee80211_uhr_capabilities *cap; u8 *pos = eid, *length_pos; @@ -49,8 +55,12 @@ u8 * hostapd_eid_uhr_capab(struct hostapd_data *hapd, u8 *eid, if (!mode) return eid; + he_cap = &mode->he_capab[opmode]; + eht_cap = &mode->eht_capab[opmode]; uhr_cap = &mode->uhr_capab[opmode]; - if (!uhr_cap->uhr_supported) + if (!he_cap->he_supported || + !eht_cap->eht_supported || + !uhr_cap->uhr_supported) return eid; *pos++ = WLAN_EID_EXTENSION; @@ -62,6 +72,35 @@ u8 * hostapd_eid_uhr_capab(struct hostapd_data *hapd, u8 *eid, os_memcpy(cap->phy, uhr_cap->phy, sizeof(cap->phy)); pos += sizeof(*cap); + if (opmode == IEEE80211_MODE_AP && + cap->mac[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) { + /* + * Assume that HE implies VHT implies 80 MHz at least, + * DBE doesn't exist on 2.4 GHz and the capability + * shouldn't be set by the driver. + * + * It's also not very relevant if DBE isn't enabled. + */ + u8 dbe_bw = IEEE80211_UHR_DBE_CAP_MAX_BW_80MHZ; + + if (he_cap->phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX] & + HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G) + dbe_bw = IEEE80211_UHR_DBE_CAP_MAX_BW_160MHZ; + + if (is_6ghz_op_class(hapd->iconf->op_class) && + eht_cap->phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] & + EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK) + dbe_bw = IEEE80211_UHR_DBE_CAP_MAX_BW_320MHZ; + + /* + * No MCS maps or other further DBE capabilities + * since we just use EHT capabilities and advertise + * full EHT capabilities even if operating in a + * lower bandwidth. + */ + *pos++ = dbe_bw; + } + *length_pos = pos - (eid + 2); return pos; } diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index fda844fea671..3a0e27a64de0 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -3298,12 +3298,21 @@ struct ieee80211_s1g_beacon_compat { le32 tsf_completion; } STRUCT_PACKED; +#define IEEE80211_UHR_MAC_CAP1_DBE_SUPP 0x04 + +#define IEEE80211_UHR_DBE_CAP_MAX_BW_MASK 0x07 +#define IEEE80211_UHR_DBE_CAP_MAX_BW_40MHZ 1 +#define IEEE80211_UHR_DBE_CAP_MAX_BW_80MHZ 2 +#define IEEE80211_UHR_DBE_CAP_MAX_BW_160MHZ 3 +#define IEEE80211_UHR_DBE_CAP_MAX_BW_320MHZ 4 + /* UHR Capabilities element format */ struct ieee80211_uhr_capabilities { /* UHR MAC Capabilities Information */ u8 mac[5]; /* UHR PHY Capabilities Information */ u8 phy[1]; + /* followed by DBE capabilities on AP */ } STRUCT_PACKED; #define UHR_OPER_PARAMS_DPS_ENA 0x0001 -- 2.53.0 From johannes at sipsolutions.net Thu May 7 10:21:34 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:34 +0200 Subject: [PATCH v3 07/12] tests: uhr: no connection if UHR required but not supported In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192335.7a90f4ee62ca.I02bef5299528386544846847cc3d500cd9afcae2@changeid> From: Johannes Berg Add a test that checks there's no connection if UHR is required by the AP but not supported by (disabled on) the client. Signed-off-by: Johannes Berg --- tests/hwsim/test_uhr.py | 23 +++++++++++++++++++++++ tests/hwsim/wpasupplicant.py | 3 ++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/hwsim/test_uhr.py b/tests/hwsim/test_uhr.py index b2a075aaae91..aaffd5f51f83 100644 --- a/tests/hwsim/test_uhr.py +++ b/tests/hwsim/test_uhr.py @@ -89,6 +89,29 @@ def test_uhr_simple(dev, apdev): if "wifi_generation=8" not in status: raise Exception("STA STATUS did not indicate wifi_generation=8") +def test_uhr_required_not_supported(dev, apdev): + """no connection if UHR is required but not supported by client""" + 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", disable_uhr="1", + wait_connect=False) + ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED"], timeout=10) + assert ev is None, "connected despite disable_uhr/require_uhr" + 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, diff --git a/tests/hwsim/wpasupplicant.py b/tests/hwsim/wpasupplicant.py index ebce5519d719..520eefe1866e 100644 --- a/tests/hwsim/wpasupplicant.py +++ b/tests/hwsim/wpasupplicant.py @@ -1146,7 +1146,8 @@ class WpaSupplicant: "sae_password_id_change", "enable_4addr_mode", "pmksa_privacy", - "eap_over_auth_frame"] + "eap_over_auth_frame", + "disable_uhr"] for field in not_quoted: if field in kwargs and kwargs[field]: self.set_network(id, field, kwargs[field]) -- 2.53.0 From johannes at sipsolutions.net Thu May 7 10:21:35 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:35 +0200 Subject: [PATCH v3 08/12] AP: allow configuring UHR DBE In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192335.c5520f104dbd.I68b7cff43ed6117c948ac7d7e75a5d68e60c7e27@changeid> From: Johannes Berg Allow configuring, and push down to the driver's frequency configuration, UHR DBE (dynamic bandwidth extension). Make up the UHR DBE capabilities on the fly assuming that the EHT capabilities cover the MCS set etc. already. The position of the DBE capabilities is after PHY, as per 802.11bn D1.4. Signed-off-by: Johannes Berg --- hostapd/config_file.c | 7 +++ hostapd/hostapd.conf | 11 ++++ src/ap/ap_config.c | 5 ++ src/ap/ap_config.h | 2 + src/ap/beacon.c | 4 ++ src/ap/ieee802_11_uhr.c | 30 +++++++++-- src/common/hw_features_common.c | 93 ++++++++++++++++++++++++++++++++- src/common/hw_features_common.h | 7 +++ src/common/ieee802_11_defs.h | 10 +++- 9 files changed, 164 insertions(+), 5 deletions(-) diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 3cb200ea358b..cc22001e61c1 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -4997,6 +4997,13 @@ static int hostapd_config_fill(struct hostapd_config *conf, conf->ieee80211bn = atoi(pos); } else if (os_strcmp(buf, "require_uhr") == 0) { conf->require_uhr = atoi(pos); + } else if (os_strcmp(buf, "dbe_bandwidth") == 0) { + conf->dbe_bandwidth = atoi(pos); + if (conf->dbe_bandwidth > 5) + return 1; + } else if (os_strcmp(buf, "dbe_punct_bitmap") == 0) { + if (get_u16(pos, line, &conf->dbe_punct_bitmap)) + return 1; #endif /* CONFIG_IEEE80211BN */ } else if (os_strcmp(buf, "i2r_lmr_policy") == 0) { conf->i2r_lmr_policy = atoi(pos); diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 9eca437ec565..d9c6268dfdb9 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1145,6 +1145,17 @@ wmm_ac_vo_acm=0 # Require stations to support UHR PHY (reject association if they do not) #require_uhr=0 +# UHR DBE (dynamic bandwidth extension) bandwidth +# Indicates the bandwidth (as encoded in the spec, 802.11bn D1.4 Table 9-bb2 +# "Encoding of the DBE Bandwidth field") for UHR DBE, 0 is reserved in the +# spec and here means DBE is not used. +# Must be wider than the EHT bandwidth (from eht_oper_chwidth or operating +# class). +#dbe_bandwidth=0 + +# UHR DBE puncturing bitmap +#dbe_punct_bitmap=0 + ##### IEEE 802.1X-2004 related configuration ################################## # Require IEEE 802.1X authorization diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 36a4dad65626..6dbf30bbb7b2 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -316,6 +316,11 @@ struct hostapd_config * hostapd_config_defaults(void) conf->disable_mcs15_rx = true; +#ifdef CONFIG_IEEE80211BN + conf->dbe_bandwidth = 0; + conf->dbe_punct_bitmap = 0; +#endif + return conf; } diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 57ee0917bbef..7709f3ae82cb 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -1249,6 +1249,8 @@ struct hostapd_config { int ieee80211bn; #ifdef CONFIG_IEEE80211BN bool require_uhr; + u8 dbe_bandwidth; + u16 dbe_punct_bitmap; #endif /* EHT enable/disable config from CHAN_SWITCH */ diff --git a/src/ap/beacon.c b/src/ap/beacon.c index 65cad1ec0274..43da52a98c74 100644 --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -2863,6 +2863,10 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd) .he.cap = cmode ? &cmode->he_capab[IEEE80211_MODE_AP] : NULL, .eht.cap = cmode ? &cmode->eht_capab[IEEE80211_MODE_AP] : NULL, .eht.punct_bitmap = hostapd_get_punct_bitmap(hapd), + .uhr.enabled = iconf->ieee80211bn, + .uhr.dbe_bandwidth = iconf->dbe_bandwidth, + .uhr.cap = cmode ? &cmode->uhr_capab[IEEE80211_MODE_AP] : NULL, + .uhr.dbe_punct_bitmap = iconf->dbe_punct_bitmap, }; if (cmode && hostapd_set_freq_params(&freq, &info) == 0) { diff --git a/src/ap/ieee802_11_uhr.c b/src/ap/ieee802_11_uhr.c index 2476b3412b75..9e3dcdc6da25 100644 --- a/src/ap/ieee802_11_uhr.c +++ b/src/ap/ieee802_11_uhr.c @@ -109,25 +109,49 @@ u8 * hostapd_eid_uhr_capab(struct hostapd_data *hapd, u8 *eid, u8 * hostapd_eid_uhr_operation(struct hostapd_data *hapd, u8 *eid, bool beacon) { struct ieee80211_uhr_operation *oper; - u8 *pos = eid; + u8 *pos = eid, *len, *start = eid; if (!hapd->iface->current_mode) return eid; *pos++ = WLAN_EID_EXTENSION; - *pos++ = 1 + sizeof(*oper); + len = pos++; *pos++ = WLAN_EID_EXT_UHR_OPERATION; oper = (void *) pos; oper->oper_params = 0; + if (hapd->iconf->dbe_bandwidth) { + u8 dbe_bw = hapd->iconf->dbe_bandwidth << + UHR_OPER_PARAMS_DBE_BW_SHIFT; + + oper->oper_params |= host_to_le16(UHR_OPER_PARAMS_DBE_ENA); + oper->oper_params |= host_to_le16(dbe_bw); + } + /* 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); + pos += sizeof(*oper); + + if (!beacon && hapd->iconf->dbe_bandwidth) { + *pos = hapd->iconf->dbe_bandwidth; + + if (hapd->iconf->dbe_punct_bitmap) + *pos |= IEEE80211_UHR_OPER_DBE_DIS_SUBCH_BMAP_PRES; + + pos++; + if (hapd->iconf->dbe_punct_bitmap) { + WPA_PUT_LE16(pos, hapd->iconf->dbe_punct_bitmap); + pos += sizeof(u16); + } + } + + *len = pos - start - 2; + return pos; } diff --git a/src/common/hw_features_common.c b/src/common/hw_features_common.c index ea5d24300048..1a3b8dcd7757 100644 --- a/src/common/hw_features_common.c +++ b/src/common/hw_features_common.c @@ -475,6 +475,79 @@ void punct_update_legacy_bw(u16 bitmap, u8 pri, enum oper_chan_width *width, } +static int hostapd_set_freq_dbe(struct hostapd_freq_params *data, u8 dbe_bw) +{ + int starting_freq, offset, index, bw_mhz, start_new, start_old; + bool is_5ghz = is_5ghz_freq(data->freq); + bool is_6ghz = is_6ghz_freq(data->freq); + unsigned int punct_shift; + + if (is_6ghz) + starting_freq = 5955; + else if (data->freq < 5745) + starting_freq = 5180; + else + starting_freq = 5745; + + switch (dbe_bw) { + case IEEE80211_UHR_OPER_DBE_BW_40_MHZ: + case IEEE80211_UHR_OPER_DBE_BW_80_MHZ: + case IEEE80211_UHR_OPER_DBE_BW_160_MHZ: + if (is_5ghz) + break; + /* fallthrough */ + case IEEE80211_UHR_OPER_DBE_BW_320_2_MHZ: + case IEEE80211_UHR_OPER_DBE_BW_320_1_MHZ: + if (is_6ghz) + break; + /* fallthrough */ + default: + return -1; + } + + switch (dbe_bw) { + case IEEE80211_UHR_OPER_DBE_BW_40_MHZ: + bw_mhz = 40; + break; + case IEEE80211_UHR_OPER_DBE_BW_80_MHZ: + bw_mhz = 80; + break; + case IEEE80211_UHR_OPER_DBE_BW_160_MHZ: + bw_mhz = 160; + break; + case IEEE80211_UHR_OPER_DBE_BW_320_2_MHZ: + starting_freq += 160; + /* fallthrough */ + case IEEE80211_UHR_OPER_DBE_BW_320_1_MHZ: + bw_mhz = 320; + break; + default: + /* already handled above - shut up stupid compilers */ + return -1; + } + + if (data->freq < starting_freq) + return -1; + + start_old = data->center_freq1 - data->bandwidth / 2; + + offset = data->freq - starting_freq; + index = offset / bw_mhz; + start_new = starting_freq - 10 + index * bw_mhz; + data->center_freq1 = start_new + bw_mhz / 2; + data->bandwidth = bw_mhz; + + if (start_new < start_old) + punct_shift = (start_old - start_new) / 20; + else + punct_shift = 0; + + data->punct_bitmap <<= punct_shift; + + return 0; +} + + int hostapd_set_freq_params(struct hostapd_freq_params *data, const struct hostapd_channel_info *info) { @@ -495,6 +568,7 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, const struct he_capabilities *he_cap = info->he.cap; const struct eht_capabilities *eht_cap = info->eht.cap; u16 punct_bitmap = info->eht.punct_bitmap; + int uhr_enabled = info->uhr.enabled; enum oper_chan_width oper_chwidth_legacy; u8 seg0_legacy, seg1_legacy; @@ -502,6 +576,9 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, he_enabled = 0; if (!eht_cap || !eht_cap->eht_supported) eht_enabled = 0; + if (!info->uhr.cap || !info->uhr.cap->uhr_supported) + uhr_enabled = 0; + os_memset(data, 0, sizeof(*data)); data->mode = mode; data->freq = freq; @@ -597,7 +674,7 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, data->ht_enabled = 0; data->vht_enabled = 0; - return 0; + goto handle_uhr; } if (data->eht_enabled) switch (oper_chwidth) { @@ -840,6 +917,20 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, break; } +handle_uhr: + if (uhr_enabled && info->uhr.dbe_bandwidth) { + /* check DBE against UHR capabilities? */ + + if (hostapd_set_freq_dbe(data, info->uhr.dbe_bandwidth)) { + wpa_printf(MSG_ERROR, + "invalid DBE bandwidth %d\n", + info->uhr.dbe_bandwidth); + return -1; + } + + data->punct_bitmap = info->uhr.dbe_punct_bitmap; + } + return 0; } diff --git a/src/common/hw_features_common.h b/src/common/hw_features_common.h index 62c9ac0df018..d3ffe5ecdb1a 100644 --- a/src/common/hw_features_common.h +++ b/src/common/hw_features_common.h @@ -77,6 +77,13 @@ struct hostapd_channel_info { const struct eht_capabilities *cap; u16 punct_bitmap; } eht; + + struct { + int enabled; + const struct uhr_capabilities *cap; + u8 dbe_bandwidth; + u16 dbe_punct_bitmap; + } uhr; }; int hostapd_set_freq_params(struct hostapd_freq_params *data, diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index 3a0e27a64de0..9f654b83f76f 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -3319,14 +3319,22 @@ struct ieee80211_uhr_capabilities { #define UHR_OPER_PARAMS_NPCA_ENA 0x0002 #define UHR_OPER_PARAMS_PEDCA_ENA 0x0004 #define UHR_OPER_PARAMS_DBE_ENA 0x0008 +#define UHR_OPER_PARAMS_DBE_BW_SHIFT 4 /* 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, P-EDCA, DBE */ + /* FIXME: DPS, P-EDCA */ } STRUCT_PACKED; +#define IEEE80211_UHR_OPER_DBE_BW_40_MHZ 1 +#define IEEE80211_UHR_OPER_DBE_BW_80_MHZ 2 +#define IEEE80211_UHR_OPER_DBE_BW_160_MHZ 3 +#define IEEE80211_UHR_OPER_DBE_BW_320_1_MHZ 4 +#define IEEE80211_UHR_OPER_DBE_BW_320_2_MHZ 5 +#define IEEE80211_UHR_OPER_DBE_DIS_SUBCH_BMAP_PRES 0x8 + /* Max size in Draft P802.11bn D1.3 with DPS, NPCA, P-EDCA, DBE */ #define IEEE80211_UHR_OPER_MAX_SIZE \ (sizeof(struct ieee80211_uhr_operation) + 4 + 6 + 3 + 3) -- 2.53.0 From johannes at sipsolutions.net Thu May 7 10:21:36 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:36 +0200 Subject: [PATCH v3 09/12] driver_nl80211: remove signal from nl80211_mlo_signal_poll() In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192335.ce65c526036b.I8defdeb5aa3ae2ed315ef6b152978a099819bdaa@changeid> From: Johannes Berg This may sound strange, but the MLO_SIGNAL_POLL could also be used for getting the link bandwidth (for example) and the API to the kernel simply doesn't support getting per-link info yet, nor will it ever support getting it this way (getting a link station info by BSSID won't be done, it'll be the whole MLD and then with nested link info.) Fixes: b7f98d92dc31 ("MLD STA: Add per-link MLO signal poll") Signed-off-by: Johannes Berg --- src/drivers/driver_nl80211.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 8c2721d979fb..1d18759adaf5 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -10622,17 +10622,12 @@ static int nl80211_mlo_signal_poll(void *priv, mlo_si->valid_links = drv->sta_mlo_info.valid_links; for_each_link(mlo_si->valid_links, i) { - res = nl80211_get_link_signal(bss, - drv->sta_mlo_info.links[i].bssid, - &mlo_si->links[i].data); - if (res != 0) - return res; - mlo_si->links[i].center_frq1 = -1; mlo_si->links[i].center_frq2 = -1; mlo_si->links[i].chanwidth = CHAN_WIDTH_UNKNOWN; mlo_si->links[i].current_noise = WPA_INVALID_NOISE; mlo_si->links[i].frequency = drv->sta_mlo_info.links[i].freq; + mlo_si->links[i].data.signal = -WPA_INVALID_NOISE; } res = nl80211_get_links_channel_width(bss, mlo_si); -- 2.53.0 From johannes at sipsolutions.net Thu May 7 10:21:37 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:37 +0200 Subject: [PATCH v3 10/12] tests: Add two tests for UHR DBE In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192335.14f03dbf9817.I4fe7f4061db5923e2ece70e7cd9041b6d6031a98@changeid> From: Johannes Berg Add two simple tests for DBE with 80 MHz instead of the EHT 40 MHz, one where it gets used by a UHR client and one where it doesn't get used by a non-UHR client. Signed-off-by: Johannes Berg --- tests/hwsim/test_uhr.py | 115 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 3 deletions(-) diff --git a/tests/hwsim/test_uhr.py b/tests/hwsim/test_uhr.py index aaffd5f51f83..67f6e06dbf79 100644 --- a/tests/hwsim/test_uhr.py +++ b/tests/hwsim/test_uhr.py @@ -8,13 +8,14 @@ import binascii import tempfile import time +from functools import partial 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 +from test_eht import eht_verify_status, traffic_test, eht_verify_wifi_version from test_eht import eht_mld_ap_wpa2_params, eht_mld_enable_ap def uhr_verify_wifi_version(dev): @@ -26,7 +27,7 @@ def uhr_verify_wifi_version(dev): 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): +def uhr_verify_status(wpas, hapd, is_ht=False, is_vht=False, sta_expect_uhr=True): status = hapd.get_status() logger.info("hostapd STATUS: " + str(status)) @@ -54,7 +55,10 @@ def uhr_verify_status(wpas, hapd, is_ht=False, is_vht=False): if "[EHT]" not in sta['flags']: raise Exception("Missing STA flag: EHT") if "[UHR]" not in sta['flags']: - raise Exception("Missing STA flag: UHR") + if sta_expect_uhr: + raise Exception("Missing STA flag: UHR") + elif not sta_expect_uhr: + raise Exception("Erroneous STA flag: UHR") def test_uhr_simple(dev, apdev): """UHR AP with simple SAE configuration""" @@ -173,3 +177,108 @@ def run_uhr_mld_sae_single_link(dev, apdev, anti_clogging_token=False): 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) + +def uhr_5ghz_params(ssid, passphrase, channel, chanwidth, ccfs1, ccfs2=0, + he_ccfs1=None, he_oper_chanwidth=None): + if he_ccfs1 is None: + he_ccfs1 = ccfs1 + if he_oper_chanwidth is None: + he_oper_chanwidth = chanwidth + + params = uhr_mld_ap_wpa2_params(ssid, passphrase, key_mgmt="SAE", + mfp="2", pwe='2') + params.update({ + "country_code": "US", + "hw_mode": "a", + "channel": str(channel), + "ieee80211n": "1", + "ieee80211ac": "1", + "ieee80211ax": "1", + "ieee80211be": "1", + "ieee80211bn": "1", + "vht_oper_chwidth": str(he_oper_chanwidth), + "vht_oper_centr_freq_seg0_idx": str(he_ccfs1), + "vht_oper_centr_freq_seg1_idx": str(ccfs2), + "he_oper_chwidth": str(he_oper_chanwidth), + "he_oper_centr_freq_seg0_idx": str(he_ccfs1), + "he_oper_centr_freq_seg1_idx": str(ccfs2), + "eht_oper_centr_freq_seg0_idx": str(ccfs1), + "eht_oper_chwidth": str(chanwidth), + }) + + if he_oper_chanwidth == 0: + if channel < he_ccfs1: + params["ht_capab"] = "[HT40+]" + elif channel > he_ccfs1: + params["ht_capab"] = "[HT40-]" + else: + params["ht_capab"] = "[HT40+]" + if he_oper_chanwidth == 2: + params["vht_capab"] = "[VHT160]" + elif he_oper_chanwidth == 3: + params["vht_capab"] = "[VHT160-80PLUS80]" + + return params + +def _test_uhr_5ghz(channel, chanwidth, ccfs1, ccfs2=0, + callback=None, uhr_connection=True, **kw): + 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 = 'quertyiop' + params = uhr_5ghz_params('uhr', passphrase, channel, + chanwidth, ccfs1, ccfs2) + params.update(kw) + + freq = 5000 + channel * 5 + + hapd = uhr_mld_enable_ap(hapd_iface, 0, params) + wpas.set('sae_pwe', '1') + wpas.connect('uhr', sae_password=passphrase, scan_freq=str(freq), + key_mgmt="SAE", ieee80211w="2", + disable_uhr='1' if not uhr_connection else '0') + hapd.wait_sta() + + uhr_verify_status(wpas, hapd, is_ht=True, is_vht=True, + sta_expect_uhr=uhr_connection) + if uhr_connection: + uhr_verify_wifi_version(wpas) + else: + eht_verify_wifi_version(wpas) + hwsim_utils.test_connectivity(wpas, hapd) + if callback: callback(wpas, hapd) + +def _check_width(link, width, wpas, hapd): + sig = wpas.request("MLO_SIGNAL_POLL").splitlines() + logger.debug(sig) + link_data = {} + cur_link = None + for line in sig: + if line.startswith('LINK_ID='): + cur_link = int(line[8:]) + if not cur_link in link_data: + link_data[cur_link] = [] + link_data[cur_link].append(line) + assert link in link_data, f'link {link} not found' + assert f'WIDTH={width} MHz' in link_data[link], \ + f'client not connected with {width} MHz' + +def test_uhr_5ghz_dbe_80(dev, apdev): + """UHR with DBE enabled""" + try: + _test_uhr_5ghz(36, 0, 38, dbe_bandwidth="2", + callback=partial(_check_width, 0, 80)) + finally: + set_world_reg(apdev[0], None, dev[0]) + +def test_uhr_5ghz_dbe_80_not_used(dev, apdev): + """UHR with DBE enabled but client can't use it""" + try: + _test_uhr_5ghz(36, 0, 38, dbe_bandwidth="2", + callback=partial(_check_width, 0, 40), + uhr_connection=False) + finally: + set_world_reg(apdev[0], None, dev[0]) -- 2.53.0 From johannes at sipsolutions.net Thu May 7 10:21:39 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:39 +0200 Subject: [PATCH v3 12/12] AP: include enhanced critical update field in MLE for UHR In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192336.43f7f33712e3.I18eaaa1fe670d1f44cc7cae5a7b24536a2452d07@changeid> From: Johannes Berg A UHR AP has to include this field in the common info and the per-STA profiles. Do this, even if it's currently just set to zero. Signed-off-by: Johannes Berg --- src/ap/ieee802_11_eht.c | 17 +++++++++++++++++ src/common/ieee802_11_defs.h | 2 ++ 2 files changed, 19 insertions(+) diff --git a/src/ap/ieee802_11_eht.c b/src/ap/ieee802_11_eht.c index 6359303895c4..0b32fd150996 100644 --- a/src/ap/ieee802_11_eht.c +++ b/src/ap/ieee802_11_eht.c @@ -479,6 +479,12 @@ u8 * hostapd_eid_eht_basic_ml_common(struct hostapd_data *hapd, common_info_len++; } + if (hostapd_is_uhr_enabled(uhr)) { + /* Enhanced critical updates */ + control |= BASIC_MULTI_LINK_CTRL_PRES_ENH_CRIT_UPD; + common_info_len++; + } + wpabuf_put_le16(buf, control); wpabuf_put_u8(buf, common_info_len); @@ -491,6 +497,10 @@ u8 * hostapd_eid_eht_basic_ml_common(struct hostapd_data *hapd, wpabuf_put_u8(buf, hapd->eht_mld_bss_param_change); + /* Currently hard-code enhanced critical updates to zero */ + if (hostapd_is_uhr_enabled(uhr)) + wpabuf_put_u8(buf, 0); + wpa_printf(MSG_DEBUG, "MLD: EML Capabilities=0x%x", hapd->iface->mld_eml_capa); wpabuf_put_le16(buf, hapd->iface->mld_eml_capa); @@ -549,6 +559,9 @@ u8 * hostapd_eid_eht_basic_ml_common(struct hostapd_data *hapd, * frames */ if (include_bpcc) sta_info_len++; + /* enhanced critical updates */ + if (include_bpcc && hostapd_is_uhr_enabled(uhr)) + sta_info_len++; total_len = sta_info_len + link->resp_sta_profile_len; @@ -570,6 +583,8 @@ u8 * hostapd_eid_eht_basic_ml_common(struct hostapd_data *hapd, if (include_bpcc) control |= BASIC_MLE_STA_CTRL_PRES_BSS_PARAM_COUNT; + if (include_bpcc && hostapd_is_uhr_enabled(uhr)) + control |= BASIC_MLE_STA_CTRL_PRES_ENH_CRIT_UPD; wpabuf_put_le16(buf, control); @@ -594,6 +609,8 @@ u8 * hostapd_eid_eht_basic_ml_common(struct hostapd_data *hapd, /* BSS Parameters Change Count */ if (include_bpcc) wpabuf_put_u8(buf, link_bss->eht_mld_bss_param_change); + if (include_bpcc && hostapd_is_uhr_enabled(uhr)) + wpabuf_put_u8(buf, 0); if (!link->resp_sta_profile) continue; diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index 9f654b83f76f..d115578a7df1 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -2877,6 +2877,7 @@ struct ieee80211_eht_capabilities { #define BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA 0x0100 #define BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID 0x0200 #define BASIC_MULTI_LINK_CTRL_PRES_EXT_MLD_CAP 0x0400 +#define BASIC_MULTI_LINK_CTRL_PRES_ENH_CRIT_UPD 0x0800 /* * STA Control field definitions of Per-STA Profile subelement in Basic @@ -2893,6 +2894,7 @@ struct ieee80211_eht_capabilities { #define BASIC_MLE_STA_CTRL_PRES_NSTR_LINK_PAIR 0x0200 #define BASIC_MLE_STA_CTRL_NSTR_BITMAP 0x0400 #define BASIC_MLE_STA_CTRL_PRES_BSS_PARAM_COUNT 0x0800 +#define BASIC_MLE_STA_CTRL_PRES_ENH_CRIT_UPD 0x1000 #define BASIC_MLE_STA_PROF_STA_MAC_IDX 3 -- 2.53.0 From johannes at sipsolutions.net Thu May 7 10:21:38 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:38 +0200 Subject: [PATCH v3 11/12] AP: include correct EHT BSS parameter change count In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192336.de74fd0bedef.Id565f0cddfd3216e09386dd57350a5f9f69503bf@changeid> From: Johannes Berg Set the correct value according to hapd->eht_mld_bss_param_change in the multi-link element's common info rather than hard-coding the value 1. Also use the correct link_bss in the per-STA profile. Fixes: 73a6f5c37ec2 ("AP MLD: Make BSS parameter change variable") Signed-off-by: Johannes Berg --- src/ap/ieee802_11_eht.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ap/ieee802_11_eht.c b/src/ap/ieee802_11_eht.c index d111e10fa174..6359303895c4 100644 --- a/src/ap/ieee802_11_eht.c +++ b/src/ap/ieee802_11_eht.c @@ -489,8 +489,7 @@ u8 * hostapd_eid_eht_basic_ml_common(struct hostapd_data *hapd, /* Own Link ID */ wpabuf_put_u8(buf, hapd->mld_link_id); - /* Currently hard code the BSS Parameters Change Count to 0x1 */ - wpabuf_put_u8(buf, 0x1); + wpabuf_put_u8(buf, hapd->eht_mld_bss_param_change); wpa_printf(MSG_DEBUG, "MLD: EML Capabilities=0x%x", hapd->iface->mld_eml_capa); @@ -594,7 +593,7 @@ u8 * hostapd_eid_eht_basic_ml_common(struct hostapd_data *hapd, /* BSS Parameters Change Count */ if (include_bpcc) - wpabuf_put_u8(buf, hapd->eht_mld_bss_param_change); + wpabuf_put_u8(buf, link_bss->eht_mld_bss_param_change); if (!link->resp_sta_profile) continue; -- 2.53.0 From johannes at sipsolutions.net Thu May 7 10:27:39 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 07 May 2026 19:27:39 +0200 Subject: [PATCH v3 12/12] AP: include enhanced critical update field in MLE for UHR In-Reply-To: <20260507192336.43f7f33712e3.I18eaaa1fe670d1f44cc7cae5a7b24536a2452d07@changeid> References: <20260507172335.546456-14-johannes@sipsolutions.net> <20260507192336.43f7f33712e3.I18eaaa1fe670d1f44cc7cae5a7b24536a2452d07@changeid> Message-ID: <7c1073be2ce21a9eead823c20a9cd60cb089dc99.camel@sipsolutions.net> On Thu, 2026-05-07 at 19:21 +0200, Johannes Berg wrote: > > + if (hostapd_is_uhr_enabled(uhr)) { > Ahrg! I made a last-minute replacement here, that should of course be "hostapd_is_uhr_enabled(hapd)", there are 5 instances throughout this function. I'm not going to resend for that right now though. johannes From andrei.otcheretianski at intel.com Thu May 7 12:11:27 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Thu, 7 May 2026 22:11:27 +0300 Subject: [PATCH] common: Fix misaligned access in get_max_nss_capability() Message-ID: <20260507191130.36955-1-andrei.otcheretianski@intel.com> The HE capabilities optional field starts at an odd offset within the packed struct. Casting &optional[0] or &optional[4] to le16 * causes misaligned memory access, which is undefined behavior and crashes wpa_supplicant with sanitizers enabled. Properly use WPA_GET_LE16() instead. Signed-off-by: Andrei Otcheretianski --- src/common/ieee802_11_common.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c index 0dd302e11a..32342f7d99 100644 --- a/src/common/ieee802_11_common.c +++ b/src/common/ieee802_11_common.c @@ -3837,13 +3837,13 @@ unsigned int get_max_nss_capability(struct ieee802_11_elems *elems, const u8 *optional = hecaps->optional; if (bw == CHAN_WIDTH_160) { - const le16 *mcs_160 = (const le16 *) &optional[0]; - - mcs_map = parse_for_rx ? mcs_160[0] : mcs_160[1]; + mcs_map = host_to_le16( + WPA_GET_LE16(parse_for_rx ? + &optional[0] : &optional[2])); } else if (bw == CHAN_WIDTH_80P80) { - const le16 *mcs_80p80 = (const le16 *) &optional[4]; - - mcs_map = parse_for_rx ? mcs_80p80[0] : mcs_80p80[1]; + mcs_map = host_to_le16( + WPA_GET_LE16(parse_for_rx ? + &optional[4] : &optional[6])); } else { mcs_map = parse_for_rx ? hecaps->he_basic_supported_mcs_set.rx_map : -- 2.53.0 From andrei.otcheretianski at intel.com Thu May 7 12:11:28 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Thu, 7 May 2026 22:11:28 +0300 Subject: [PATCH] NAN: Fix channel peer schedule channel filtering Message-ID: <20260507191130.36955-2-andrei.otcheretianski@intel.com> Channel filtering should be done for any operating class of course. The code was misplaced. Fix it. Signed-off-by: Andrei Otcheretianski --- src/nan/nan.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/nan/nan.c b/src/nan/nan.c index 140785729d..04cc47be90 100644 --- a/src/nan/nan.c +++ b/src/nan/nan.c @@ -2244,13 +2244,13 @@ nan_peer_get_committed_avail_add(const struct nan_data *nan, freq = freq - 70 + idx * 20; /* TODO: Missing support for 80 + 80 */ - - /* Skip channels that are not in local schedule */ - if (local_sched && - !nan_peer_channel_in_local_sched(nan, freq, local_sched)) - return; } + /* Skip channels that are not in local schedule */ + if (local_sched && + !nan_peer_channel_in_local_sched(nan, freq, local_sched)) + return; + /* Assume committed for conditional slots if setup is done */ committed = (avail->type == NAN_AVAIL_ENTRY_CTRL_TYPE_COMMITTED) || (avail->type == NAN_AVAIL_ENTRY_CTRL_TYPE_COND && -- 2.53.0 From andrei.otcheretianski at intel.com Thu May 7 12:11:29 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Thu, 7 May 2026 22:11:29 +0300 Subject: [PATCH] PASN: Don't override kek_len if it is pre-configured Message-ID: <20260507191130.36955-3-andrei.otcheretianski@intel.com> kek_len may be set outside of PASN module, for example, in P2P2 pairing flows. Don't override it. Signed-off-by: Andrei Otcheretianski --- src/pasn/pasn_initiator.c | 4 +++- src/pasn/pasn_responder.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pasn/pasn_initiator.c b/src/pasn/pasn_initiator.c index 0dbc0cd18d..5ea38affa7 100644 --- a/src/pasn/pasn_initiator.c +++ b/src/pasn/pasn_initiator.c @@ -1527,7 +1527,9 @@ int wpas_parse_pasn_frame(struct pasn_data *pasn, u16 auth_type, if (pasn->derive_kek) { wpa_printf(MSG_DEBUG, "PASN: Derive PTK-KEK"); - pasn->kek_len = wpa_kek_len(pasn->akmp, pasn->pmk_len); + if (!pasn->kek_len) + pasn->kek_len = wpa_kek_len(pasn->akmp, + pasn->pmk_len); wpa_printf(MSG_DEBUG, "PASN: kek_len=%zu", pasn->kek_len); } diff --git a/src/pasn/pasn_responder.c b/src/pasn/pasn_responder.c index f9c37070c6..a46c17485b 100644 --- a/src/pasn/pasn_responder.c +++ b/src/pasn/pasn_responder.c @@ -485,7 +485,9 @@ pasn_derive_keys(struct pasn_data *pasn, own_addr = pasn->mld_addr; if (pasn->derive_kek) { - pasn->kek_len = wpa_kek_len(pasn->akmp, pasn->pmk_len); + if (!pasn->kek_len) + pasn->kek_len = wpa_kek_len(pasn->akmp, + pasn->pmk_len); wpa_printf(MSG_DEBUG, "PASN: kek_len=%zu", pasn->kek_len); } #endif /* CONFIG_ENC_ASSOC */ -- 2.53.0 From andrei.otcheretianski at intel.com Thu May 7 12:11:30 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Thu, 7 May 2026 22:11:30 +0300 Subject: [PATCH] NAN: Set new NIK before computing the new tag Message-ID: <20260507191130.36955-4-andrei.otcheretianski@intel.com> From: Ilan Peer Set the new NIK before computing the new tag, as the computation uses the stored NIK. Signed-off-by: Ilan Peer --- src/nan/nan_pairing.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nan/nan_pairing.c b/src/nan/nan_pairing.c index b03ca68dac..ad29c8427e 100644 --- a/src/nan/nan_pairing.c +++ b/src/nan/nan_pairing.c @@ -1493,6 +1493,8 @@ int nan_pairing_set_nik(struct nan_data *nan, const u8 *nik, size_t nik_len) return -1; } + os_memcpy(nan->cfg->nik, nik, NAN_NIK_LEN); + if (nan->cfg->pairing_cfg.pairing_verification) { if (nan_nira_get_tag_nonce(nan->cfg, nonce, tag) < 0) { wpa_printf(MSG_INFO, @@ -1510,8 +1512,6 @@ int nan_pairing_set_nik(struct nan_data *nan, const u8 *nik, size_t nik_len) os_memset(nan->nira_tag, 0, NAN_NIRA_TAG_LEN); } - os_memcpy(nan->cfg->nik, nik, NAN_NIK_LEN); - wpa_hexdump_key(MSG_DEBUG, "NAN: New NIK", nan->cfg->nik, NAN_NIK_LEN); return 0; -- 2.53.0 From andrei.otcheretianski at intel.com Thu May 7 13:16:36 2026 From: andrei.otcheretianski at intel.com (Otcheretianski, Andrei) Date: Thu, 7 May 2026 20:16:36 +0000 Subject: [PATCH 00/97] NAN: Group keys support, schedule update and more In-Reply-To: <20260428200639.40243-1-andrei.otcheretianski@intel.com> References: <20260428200639.40243-1-andrei.otcheretianski@intel.com> Message-ID: > The test patches are marked as RFC since full testing requires kernel NAN data > path support which is not yet complete in wireless-next. > As in previous series, iwlwifi-next/mac80211_candidate kernel can be used for > testing. > Just to let you know, all the required mac80211_hwsim patches are now available in wireless-next. The test patches can be merged as well now. Thanks, Andrei From johannes at sipsolutions.net Thu May 7 10:21:29 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Thu, 7 May 2026 19:21:29 +0200 Subject: [PATCH v3 02/12] initial UHR support In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: <20260507192335.7612031264fc.I1dd16bb1ffbcf19c38d2d73626776aca4070fbc1@changeid> From: Johannes Berg Add initial UHR support, based on a very "superficial" reading of D1.3/D1.4 (which are incomplete and not well- specified in quite a few places.) Signed-off-by: Johannes Berg --- 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 | 24 ++++ src/ap/ctrl_iface_ap.c | 16 +++ src/ap/hostapd.h | 6 + src/ap/ieee802_11.c | 52 +++++++- src/ap/ieee802_11.h | 13 ++ src/ap/ieee802_11_uhr.c | 145 +++++++++++++++++++++ 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 | 28 ++++ src/drivers/driver.h | 22 ++++ src/drivers/driver_common.c | 6 + src/drivers/driver_nl80211.c | 14 ++ src/drivers/driver_nl80211_capa.c | 19 +++ tests/hwsim/example-hostapd.config | 2 + tests/hwsim/example-wpa_supplicant.config | 1 + tests/hwsim/test_uhr.py | 152 ++++++++++++++++++++++ wpa_supplicant/Android.mk | 3 + wpa_supplicant/Makefile | 7 + 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 | 13 ++ wpa_supplicant/sme.c | 1 + wpa_supplicant/wpa_cli.c | 1 + wpa_supplicant/wpa_supplicant.c | 17 ++- wpa_supplicant/wpa_supplicant_i.h | 4 + 36 files changed, 663 insertions(+), 41 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 bff81cac4a31..492d54e50aed 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 b2420e8474be..b12a40cbc058 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 d34b4aa83950..3cb200ea358b 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -4876,6 +4876,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) { @@ -4990,6 +4992,12 @@ static int hostapd_config_fill(struct hostapd_config *conf, conf->disable_mcs15_rx = 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 20e09a9e329d..9eca437ec565 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1132,6 +1132,19 @@ wmm_ac_vo_acm=0 # is disabled. #disable_mcs15_rx=1 +##### 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 3fe2206b9133..57ee0917bbef 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -569,6 +569,7 @@ struct hostapd_bss_config { bool disable_11ac; bool disable_11ax; bool disable_11be; + bool disable_11bn; /* IEEE 802.11v */ int time_advertisement; @@ -1245,6 +1246,11 @@ struct hostapd_config { bool require_eht; #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 dc46b599cfb1..556f4b9d469a 100644 --- a/src/ap/ap_drv_ops.c +++ b/src/ap/ap_drv_ops.c @@ -477,6 +477,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, @@ -502,6 +504,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 66913ed0fca7..f09b3ad9a306 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 fba6a473396f..ecf19c7910e3 100644 --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -809,6 +809,13 @@ static size_t hostapd_probe_resp_elems_len(struct hostapd_data *hapd, } #endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211BN + if (hostapd_is_uhr_enabled(hapd)) { + buflen += hostapd_eid_uhr_capab_len(hapd, IEEE80211_MODE_AP); + buflen += 3 + IEEE80211_UHR_OPER_MAX_SIZE; + } +#endif /* CONFIG_IEEE80211BN */ + buflen += hostapd_eid_mbssid_len(hapd_probed, WLAN_FC_STYPE_PROBE_RESP, NULL, params->known_bss, @@ -981,6 +988,13 @@ static u8 * hostapd_probe_resp_fill_elems(struct hostapd_data *hapd, } #endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211BN + if (hostapd_is_uhr_enabled(hapd)) { + 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); @@ -2299,6 +2313,11 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, } #endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211BN + if (hostapd_is_uhr_enabled(hapd)) + 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 */ @@ -2481,6 +2500,11 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, } #endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211BN + if (hostapd_is_uhr_enabled(hapd)) + tailpos = hostapd_eid_uhr_operation(hapd, tailpos, true); +#endif /* CONFIG_IEEE80211BN */ + #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 90a0a49363e3..57ec4f6ec12d 100644 --- a/src/ap/ctrl_iface_ap.c +++ b/src/ap/ctrl_iface_ap.c @@ -405,6 +405,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, @@ -885,6 +899,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, @@ -896,6 +911,7 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf, hostapd_is_vht_enabled(hapd), hostapd_is_he_enabled(hapd), hostapd_is_eht_enabled(hapd), + hostapd_is_uhr_enabled(hapd), iface->conf->beacon_int, hapd->conf->dtim_period); if (os_snprintf_error(buflen - len, ret)) diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index fb06a5afd935..974964d341e9 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -965,4 +965,10 @@ hostapd_is_eht_enabled(struct hostapd_data *hapd) return hapd->iconf->ieee80211be && !hapd->conf->disable_11be; } +static inline bool +hostapd_is_uhr_enabled(struct hostapd_data *hapd) +{ + return hapd->iconf->ieee80211bn && !hapd->conf->disable_11bn; +} + #endif /* HOSTAPD_H */ diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 3abb437b002e..8a3d3208debd 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -155,6 +155,11 @@ static size_t hostapd_supp_rates(struct hostapd_data *hapd, u8 *buf) *pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_EHT_PHY; #endif /* CONFIG_IEEE80211BE */ +#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) && @@ -5542,6 +5547,23 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta, } } #endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211BN + if (hostapd_is_uhr_enabled(hapd)) { + 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"); + return WLAN_STATUS_DENIED_UHR_NOT_SUPPORTED; + } + } +#endif /* CONFIG_IEEE80211BN */ #ifdef CONFIG_P2P if (elems->p2p && ies && ies_len) { @@ -6200,6 +6222,13 @@ void ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd, } } +#ifdef CONFIG_IEEE80211BN + if (hostapd_is_uhr_enabled(hapd)) { + p = hostapd_eid_uhr_capab(hapd, p, IEEE80211_MODE_AP); + p = hostapd_eid_uhr_operation(hapd, p, false); + } +#endif /* CONFIG_IEEE80211BN */ + p = hostapd_eid_ext_capab(hapd, p, false); p = hostapd_eid_mbo(hapd, p, buf + buflen - p); p = hostapd_eid_wmm(hapd, p); @@ -6471,6 +6500,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, epp_sta = false; @@ -6552,6 +6582,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 @@ -6567,6 +6602,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, @@ -6628,6 +6665,12 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, buflen += hostapd_eid_eht_ml_tid_to_link_map_len(hapd); } #endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211BN + if (hostapd_is_uhr_enabled(hapd)) { + buflen += hostapd_eid_uhr_capab_len(hapd, IEEE80211_MODE_AP); + buflen += 3 + IEEE80211_UHR_OPER_MAX_SIZE; + } +#endif /* CONFIG_IEEE80211BN */ buf = os_zalloc(buflen); if (!buf) { @@ -6790,6 +6833,13 @@ rsnxe_done: } #endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211BN + if (hostapd_is_uhr_enabled(hapd)) { + p = hostapd_eid_uhr_capab(hapd, p, IEEE80211_MODE_AP); + p = hostapd_eid_uhr_operation(hapd, p, false); + } +#endif /* CONFIG_IEEE80211BN */ + #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) && @@ -7339,7 +7389,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, diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h index 664d585b994c..32db78f471ff 100644 --- a/src/ap/ieee802_11.h +++ b/src/ap/ieee802_11.h @@ -334,4 +334,17 @@ void ieee80211_send_eap_req(struct hostapd_data *hapd, struct sta_info *sta, struct rsn_pmksa_cache_entry *cached_pmk, const u8 *eap_req, size_t eap_req_len); +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..6538e0e0b9ea --- /dev/null +++ b/src/ap/ieee802_11_uhr.c @@ -0,0 +1,145 @@ +/* + * 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 3 /* ext elem header */ + 6 /* MAC + PHY */; +} + + +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 (!hostapd_is_uhr_enabled(hapd) || !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 2bb7e1235cb0..98f968eb1c9a 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -474,6 +474,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); @@ -602,7 +603,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) { @@ -1964,13 +1965,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]" : ""), @@ -1991,6 +1992,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]" : ""), @@ -2115,7 +2117,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, epp_sta)) { hostapd_logger(hapd, sta->addr, diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index 9810eb22f9a4..38967b2f79b3 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). */ @@ -112,7 +113,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]; @@ -226,6 +227,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 */ @@ -441,7 +444,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 0dd302e11a5b..c52241f14fd5 100644 --- a/src/common/ieee802_11_common.c +++ b/src/common/ieee802_11_common.c @@ -452,6 +452,14 @@ static int ieee802_11_parse_extension(const u8 *pos, size_t elen, elems->supported_groups = pos; elems->supported_groups_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, @@ -1026,6 +1034,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 5d9c840cee76..2751c668c9f4 100644 --- a/src/common/ieee802_11_common.h +++ b/src/common/ieee802_11_common.h @@ -131,6 +131,8 @@ struct ieee802_11_elems { const u8 *akm_suite_selector; const u8 *supported_groups; const u8 *nan_ie; + const u8 *uhr_capabilities; + const u8 *uhr_operation; u8 ssid_len; u8 supp_rates_len; @@ -205,6 +207,8 @@ struct ieee802_11_elems { u8 akm_suite_selector_len; u8 supported_groups_len; size_t nan_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 febaab8f1be3..756c36d2b439 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -246,6 +246,7 @@ #define WLAN_STATUS_DENIED_OPERATION_PARAMETER_UPDATE 141 #define WLAN_STATUS_802_1_X_AUTH_FAILED 152 #define WLAN_STATUS_802_1_X_AUTH_SUCCESS 153 +#define WLAN_STATUS_DENIED_UHR_NOT_SUPPORTED 157 /* Reason codes (IEEE Std 802.11-2020, 9.4.1.7, Table 9-90) */ #define WLAN_REASON_UNSPECIFIED 1 @@ -541,6 +542,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 #define WLAN_EID_EXT_SUPPORTED_GROUPS 161 /* Extended Capabilities field */ @@ -1413,6 +1416,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_EHT_PHY 121 #define BSS_MEMBERSHIP_SELECTOR_HE_PHY 122 #define BSS_MEMBERSHIP_SELECTOR_SAE_H2E_ONLY 123 @@ -3294,6 +3298,30 @@ 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_PEDCA_ENA 0x0004 +#define UHR_OPER_PARAMS_DBE_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; + +/* Max size in Draft P802.11bn D1.3 with DPS, NPCA, P-EDCA, DBE */ +#define IEEE80211_UHR_OPER_MAX_SIZE \ + (sizeof(struct ieee80211_uhr_operation) + 4 + 6 + 3 + 3) + #ifdef _MSC_VER #pragma pack(pop) #endif /* _MSC_VER */ diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 3807fc0f8b38..2f50265657a7 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) @@ -326,6 +333,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.11bn) capabilities + */ + struct uhr_capabilities uhr_capab[IEEE80211_MODE_NUM]; }; @@ -1403,6 +1415,11 @@ struct wpa_driver_associate_params { */ int disable_eht; + /** + * disable_uhr - Disable UHR for this connection + */ + int disable_uhr; + /* * mld_params - MLD association parameters */ @@ -2782,6 +2799,9 @@ struct hostapd_sta_add_params { const u8 *mld_link_addr; u16 eml_cap; + const struct ieee80211_uhr_capabilities *uhr_capab; + size_t uhr_capab_len; + #ifdef CONFIG_NAN /* * For a station added to a NAN Data Interface (NDI) indicate the @@ -7620,6 +7640,8 @@ bool he_supported(const struct hostapd_hw_modes *hw_mode, enum ieee80211_op_mode op_mode); bool eht_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 50a0b682d9c7..223085ac4b2c 100644 --- a/src/drivers/driver_common.c +++ b/src/drivers/driver_common.c @@ -221,6 +221,12 @@ bool eht_supported(const struct hostapd_hw_modes *hw_mode, return hw_mode->eht_capab[op_mode].eht_supported; } +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 d2d0a5cf0353..167f12069718 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -6227,6 +6227,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); @@ -7164,6 +7172,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 620cff5d8d7c..615164b0381d 100644 --- a/src/drivers/driver_nl80211_capa.c +++ b/src/drivers/driver_nl80211_capa.c @@ -2189,6 +2189,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: @@ -2296,6 +2297,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]) >= (int)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]) >= (int)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/tests/hwsim/example-hostapd.config b/tests/hwsim/example-hostapd.config index 41611b0f2f16..32ebf0870ee9 100644 --- a/tests/hwsim/example-hostapd.config +++ b/tests/hwsim/example-hostapd.config @@ -53,6 +53,8 @@ CONFIG_LIBNL3_ROUTE=y CONFIG_IEEE80211R=y CONFIG_IEEE80211AC=y CONFIG_IEEE80211AX=y +CONFIG_IEEE80211BE=y +CONFIG_IEEE80211BN=y CONFIG_OCV=y diff --git a/tests/hwsim/example-wpa_supplicant.config b/tests/hwsim/example-wpa_supplicant.config index c5b364757afa..f3c1727f60ce 100644 --- a/tests/hwsim/example-wpa_supplicant.config +++ b/tests/hwsim/example-wpa_supplicant.config @@ -169,6 +169,7 @@ CONFIG_PASN=y CONFIG_NAN_USD=y CONFIG_IEEE80211BE=y +CONFIG_IEEE80211BN=y CONFIG_PROCESS_COORDINATION=y CONFIG_NAN=y diff --git a/tests/hwsim/test_uhr.py b/tests/hwsim/test_uhr.py new file mode 100644 index 000000000000..b2a075aaae91 --- /dev/null +++ b/tests/hwsim/test_uhr.py @@ -0,0 +1,152 @@ +# 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 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 +from test_eht import eht_mld_ap_wpa2_params, eht_mld_enable_ap + +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_simple(dev, apdev): + """UHR AP with simple SAE configuration""" + 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' + params = eht_mld_ap_wpa2_params("uhr", key_mgmt="SAE", + passphrase=passphrase, + pwe='2', mfp='2') + params['ieee80211bn'] = '1' + params['require_uhr'] = '1' + try: + hapd = eht_mld_enable_ap(hapd_iface, 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") + wpas.set("sae_pwe", "1") + wpas.connect("uhr", key_mgmt="SAE", sae_password=passphrase, + ieee80211w="2", scan_freq="2412") + hapd.wait_sta() + sta = hapd.get_sta(wpas.own_addr()) + uhr_verify_status(wpas, hapd, is_ht=True) + status = wpas.request("STATUS") + if "wifi_generation=8" not in status: + raise Exception("STA STATUS did not indicate wifi_generation=8") + +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/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk index cbb839780b74..96488ae13e1f 100644 --- a/wpa_supplicant/Android.mk +++ b/wpa_supplicant/Android.mk @@ -989,6 +989,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 67f337df33da..460d4be813b1 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -1063,6 +1063,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 @@ -1085,6 +1088,10 @@ OBJS += ../src/eap_server/eap_server_methods.o ifdef CONFIG_IEEE80211AC CFLAGS += -DCONFIG_IEEE80211AC endif +ifdef CONFIG_IEEE80211BN +CONFIG_IEEE80211BE=y +CFLAGS += -DCONFIG_IEEE80211BN +endif ifdef CONFIG_IEEE80211BE CONFIG_IEEE80211AX=y CFLAGS += -DCONFIG_IEEE80211BE diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index e99036366681..b05a4d985417 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -2943,6 +2943,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)}, { BOOL(ssid_protection)}, diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 81d92b7ddf21..7360f7012ea2 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -1019,6 +1019,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 7c60d4e38cab..369b867b8b35 100644 --- a/wpa_supplicant/config_ssid.h +++ b/wpa_supplicant/config_ssid.h @@ -1300,6 +1300,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 1096fa228d06..78f101ba9d29 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -2379,12 +2379,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 2a281e4c4b06..a412361195ef 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -1064,6 +1064,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)) { @@ -3556,6 +3567,8 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s, resp_elems.eht_capabilities; if (req_elems.rrm_enabled) wpa_s->rrm.rrm_used = 1; + wpa_s->connection_uhr = req_elems.uhr_capabilities && + resp_elems.uhr_capabilities; #ifdef CONFIG_PMKSA_PRIVACY if (wpa_s->assoc_resp_encrypted && resp_elems.nonce) { diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index f110e161f067..15986067135c 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -4139,6 +4139,7 @@ mscs_fail: wpa_supplicant_apply_he_overrides(wpa_s, ssid, ¶ms); #endif /* CONFIG_HE_OVERRIDES */ wpa_supplicant_apply_eht_overrides(wpa_s, ssid, ¶ms); + wpa_supplicant_apply_uhr_overrides(wpa_s, ssid, ¶ms); #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 64d6154ee2e8..6b523ae8387c 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 c9dfe41bca41..515682f67242 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -2262,7 +2262,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 @@ -2273,7 +2273,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); @@ -4957,6 +4958,7 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) wpa_supplicant_apply_he_overrides(wpa_s, ssid, ¶ms); #endif /* CONFIG_HE_OVERRIDES */ wpa_supplicant_apply_eht_overrides(wpa_s, ssid, ¶ms); + wpa_supplicant_apply_uhr_overrides(wpa_s, ssid, ¶ms); #ifdef CONFIG_P2P /* @@ -6910,6 +6912,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 d68dd582fb75..6724bc017374 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -1054,6 +1054,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; u8 connection_max_nss_rx; u8 connection_max_nss_tx; @@ -1765,6 +1766,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.53.0 From loukot at gmail.com Thu May 7 05:11:44 2026 From: loukot at gmail.com (Louis Kotze) Date: Thu, 7 May 2026 14:11:44 +0200 Subject: [PATCH] MLD: Iterate per-link in wpa_clear_keys() for MLO group keys Message-ID: <20260507121144.1547852-1-loukot@gmail.com> When wpa_clear_keys() runs while wpa_s->valid_links is set, the DEL_KEY loop hardcodes link_id=-1. The kernel cfg80211 validator nl80211_validate_key_link_id() (introduced in commit e7a7b84e3317 ('wifi: cfg80211: Add link_id parameter to various key operations for MLO'), Linux v6.1+) rejects DEL_KEY for group keys when wdev->valid_links != 0 and link_id == -1. The DEL_KEYs fail (one per protected key type: GTK, IGTK, BIGTK), leaving stale per-link keys in the driver, and the next association is rejected by the AP with reason 9 (STA_REQ_ASSOC_WITHOUT_AUTH). wpa_supplicant retries until the SSID is temporarily disabled, then loops on backoff. The trigger path is PMKSA-cached reauth on a multi-link AP: sme_send_authentication() calls wpa_clear_keys() before sending the fast-reauth association. valid_links is still set from the prior association at that moment, so every group-key DEL_KEY hits the validator and fails. Iterate over wpa_s->valid_links and pass the corresponding link_id for each active link's group-key DEL_KEY. The non-MLO path is unchanged. Tested on a Wi-Fi 7 MLO STA (RTL8922AU + rtw89, kernel 6.19.11, wpa_supplicant 2.11-9.fc44 patched with this change) against a TP-Link Deco BE63 mesh: 9 of 9 PMKSA-cached MLO reauth attempts succeed with the patch applied; the same test on stock 2.11-9 fails intermittently (42 errors observed across 14 retries during one test session). A symmetric pattern likely applies to the BIGTK/GTK clearing in wpa_supplicant_process_1_of_2() (also hardcoded link_id=-1) for long MLO connections that rekey GTK; it is not exercised by this particular bug and is not addressed here. Note: Fedora bug 2458629 listed three commits as candidate fixes: 7a1893fd3aa8 ('MLD: Handle link reconfiguration updates from the driver'), c7139cc28a07 ('MLD: Clear group keys for removed links'), and b807ddd8ec7c ('BSS: Set valid_links for all links and return usable links'). On inspection those touch different MLO code paths (link reconfig, group-key clearing for removed AP links, BSS valid_links handling) and do not address the wpa_clear_keys() / hardcoded link_id=-1 path. wpa_clear_keys() in master is byte-identical to 2.11 in this respect. Link: https://bugzilla.redhat.com/show_bug.cgi?id=2458629 Signed-off-by: Louis Kotze --- wpa_supplicant/wpa_supplicant.c | 34 +++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 5414eab0f..d8ec85226 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -889,12 +889,34 @@ void wpa_clear_keys(struct wpa_supplicant *wpa_s, const u8 *addr) { int i, max = 6; - /* MLME-DELETEKEYS.request */ - for (i = 0; i < max; i++) { - if (wpa_s->keys_cleared & BIT(i)) - continue; - wpa_drv_set_key(wpa_s, -1, WPA_ALG_NONE, NULL, i, 0, NULL, 0, - NULL, 0, KEY_FLAG_GROUP); + /* MLME-DELETEKEYS.request + * + * For MLO connections, group keys are per-link in the kernel and the + * nl80211 validator rejects DEL_KEY for group keys with link_id=-1 + * when wdev->valid_links is set. Iterate over each valid link and + * pass the corresponding link_id so the keys are properly cleared. + */ + if (wpa_s->valid_links) { + int link_id; + + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + if (!(wpa_s->valid_links & BIT(link_id))) + continue; + for (i = 0; i < max; i++) { + if (wpa_s->keys_cleared & BIT(i)) + continue; + wpa_drv_set_key(wpa_s, link_id, WPA_ALG_NONE, + NULL, i, 0, NULL, 0, + NULL, 0, KEY_FLAG_GROUP); + } + } + } else { + for (i = 0; i < max; i++) { + if (wpa_s->keys_cleared & BIT(i)) + continue; + wpa_drv_set_key(wpa_s, -1, WPA_ALG_NONE, NULL, i, 0, + NULL, 0, NULL, 0, KEY_FLAG_GROUP); + } } /* Pairwise Key ID 1 for Extended Key ID is tracked in bit 15 */ if (~wpa_s->keys_cleared & (BIT(0) | BIT(15)) && addr && base-commit: fd96f14f36939c8f7af7c7592e331e3c10d6efe7 -- 2.54.0 From raja.mani at oss.qualcomm.com Fri May 8 04:02:34 2026 From: raja.mani at oss.qualcomm.com (Raja Mani) Date: Fri, 8 May 2026 16:32:34 +0530 Subject: [PATCH v3 1/8] initial UHR support In-Reply-To: References: <20260424071256.71834-10-johannes@sipsolutions.net> <20260424091256.c415029a0067.I1dd16bb1ffbcf19c38d2d73626776aca4070fbc1@changeid> <516527c0-4d24-4acf-bf6e-597011de4d0c@oss.qualcomm.com> <541e3229703c83a5ecf12642b44441d7f5cd0887.camel@sipsolutions.net> <05f54b1b-182e-4c30-b4df-896843d3b971@oss.qualcomm.com> Message-ID: <646445ae-0091-4c8f-8f83-885da93c00db@oss.qualcomm.com> On 5/7/2026 10:33 PM, Johannes Berg wrote: > Hi, > >>>>> + if (tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC] && >>>>> + nla_len(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]) >= (int)sizeof(uhr_capab->mac)) >>>>> + os_memcpy(uhr_capab->mac, >>>>> + nla_data(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]), >>>>> + sizeof(uhr_capab->mac)); >>>> >>>> Just to consider minimum possible mac cap size for os_memcpy(), >>>> Can this be modified like this? >>>> >>>> if (tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]) { >>>> len = nla_len(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]); >>>> >>>> if (len > sizeof(uhr_capab->mac)) >>>> len = sizeof(uhr_capab->mac); >>>> os_memcpy(uhr_capab->mac, >>>> nla_data(tb[NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC]), >>>> len); >>>> } >>>> >>>> and in the below hunk as well? >>> >>> I don't follow. Why? >> >> The suggested style is same as how HE/EHT Phy and Mac bytes copy are >> handled in the same function. Nothing new. Would that style be useful >> to handle a case when mac80211 sends lesser bytes in Mac/Phy attr than >> hostapd expected ? (for ex: mac80211 sends 1 byte info uhr phy cap >> and hostapd expects 2 byte phy cap). This is not a problem now. >> Such style helpful to handle when mac80211 and hostapd are not in sync ? > > Sorry, was making adjustments and realised I never replied to this. No problem > > I'm not sure I really agree, if it's inconsistent between the two then > something bad happened, no? Even the >= is a bit questionable to me, if > the driver thinks the MAC capabilities should be bigger, then both are > working off different drafts of the spec, and things can't really be > good anyway ... > > johannes Ok, Let's go with your logic. The whole V4 series looks fine to me. Raja From j at w1.fi Fri May 8 14:19:07 2026 From: j at w1.fi (Jouni Malinen) Date: Sat, 9 May 2026 00:19:07 +0300 Subject: [PATCH] common: Fix misaligned access in get_max_nss_capability() In-Reply-To: <20260507191130.36955-1-andrei.otcheretianski@intel.com> References: <20260507191130.36955-1-andrei.otcheretianski@intel.com> Message-ID: On Thu, May 07, 2026 at 10:11:27PM +0300, Andrei Otcheretianski wrote: > The HE capabilities optional field starts at an odd offset within > the packed struct. Casting &optional[0] or &optional[4] to > le16 * causes misaligned memory access, which is undefined behavior > and crashes wpa_supplicant with sanitizers enabled. > Properly use WPA_GET_LE16() instead. Thanks, applied. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Fri May 8 14:19:21 2026 From: j at w1.fi (Jouni Malinen) Date: Sat, 9 May 2026 00:19:21 +0300 Subject: [PATCH] NAN: Fix channel peer schedule channel filtering In-Reply-To: <20260507191130.36955-2-andrei.otcheretianski@intel.com> References: <20260507191130.36955-2-andrei.otcheretianski@intel.com> Message-ID: On Thu, May 07, 2026 at 10:11:28PM +0300, Andrei Otcheretianski wrote: > Channel filtering should be done for any operating class of course. > The code was misplaced. Fix it. Thanks, applied. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Fri May 8 14:19:33 2026 From: j at w1.fi (Jouni Malinen) Date: Sat, 9 May 2026 00:19:33 +0300 Subject: [PATCH] PASN: Don't override kek_len if it is pre-configured In-Reply-To: <20260507191130.36955-3-andrei.otcheretianski@intel.com> References: <20260507191130.36955-3-andrei.otcheretianski@intel.com> Message-ID: On Thu, May 07, 2026 at 10:11:29PM +0300, Andrei Otcheretianski wrote: > kek_len may be set outside of PASN module, for example, in P2P2 pairing > flows. Don't override it. Thanks, applied. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Fri May 8 14:19:47 2026 From: j at w1.fi (Jouni Malinen) Date: Sat, 9 May 2026 00:19:47 +0300 Subject: [PATCH] NAN: Set new NIK before computing the new tag In-Reply-To: <20260507191130.36955-4-andrei.otcheretianski@intel.com> References: <20260507191130.36955-4-andrei.otcheretianski@intel.com> Message-ID: On Thu, May 07, 2026 at 10:11:30PM +0300, Andrei Otcheretianski wrote: > Set the new NIK before computing the new tag, as the > computation uses the stored NIK. Thanks, applied. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Fri May 8 14:21:31 2026 From: j at w1.fi (Jouni Malinen) Date: Sat, 9 May 2026 00:21:31 +0300 Subject: [PATCH 00/97] NAN: Group keys support, schedule update and more In-Reply-To: <20260428200639.40243-1-andrei.otcheretianski@intel.com> References: <20260428200639.40243-1-andrei.otcheretianski@intel.com> Message-ID: On Tue, Apr 28, 2026 at 11:05:01PM +0300, Andrei Otcheretianski wrote: > This series adds various NAN improvements and bug fixes: > > - GTK/IGTK/BIGTK support > - NDPE support > - NAN schedule update handling: deferred schedule updates, peer schedule > tracking and ULW support. > - Max idle period support for NDL > - NAN channel evacuation event handling > - Follow up status tracking > - Password hex support in NDP and pairing > - Various fixes and improvements Thanks. I did not get quite to the end of the set today, but I applied patches 1-76 now with some cleanup and fixes and will continue with the remaining patches. -- Jouni Malinen PGP id EFC895FA From masashi.honma at gmail.com Fri May 8 16:18:22 2026 From: masashi.honma at gmail.com (Masashi Honma) Date: Sat, 9 May 2026 08:18:22 +0900 Subject: [PATCH] Fix mesh_secure_ocv_mix_ht failure Message-ID: <20260508231822.50787-1-masashi.honma@gmail.com> When running the tests with ./vm-run.sh -f wpas_mesh, mesh_secure_ocv_mix_ht fails. Exception during test execution: Test exception: CAC did not start Traceback (most recent call last): File "/home/honma/git/hostap/tests/hwsim/./run-tests.py", line 635, in main t(dev, apdev) File "/home/honma/git/hostap/tests/hwsim/test_wpas_mesh.py", line 515, in test_mesh_secure_ocv_mix_ht run_mesh_secure_ocv_mix_ht(dev, apdev) File "/home/honma/git/hostap/tests/hwsim/test_wpas_mesh.py", line 528, in run_mesh_secure_ocv_mix_ht check_dfs_started(dev[0]) File "/home/honma/git/hostap/tests/hwsim/test_wpas_mesh.py", line 90, in check_dfs_started raise Exception("Test exception: CAC did not start") Exception: Test exception: CAC did not start FAIL mesh_secure_ocv_mix_ht 11.329875 2026-05-08 23:09:31.926804 However, it succeeds when executed individually using ./vm-run.sh mesh_secure_ocv_mix_ht. This patch fixes the test by resetting the country code. Assisted-by: Claude:Sonnet 4.6 Signed-off-by: Masashi Honma --- tests/hwsim/test_wpas_mesh.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/hwsim/test_wpas_mesh.py b/tests/hwsim/test_wpas_mesh.py index 3be63fe0b..72a2f460a 100644 --- a/tests/hwsim/test_wpas_mesh.py +++ b/tests/hwsim/test_wpas_mesh.py @@ -477,6 +477,8 @@ def set_reg(dev, country): def clear_reg_setting(dev): dev[0].request("MESH_GROUP_REMOVE " + dev[0].ifname) dev[1].request("MESH_GROUP_REMOVE " + dev[1].ifname) + dev[0].set("country", "00") + dev[1].set("country", "00") clear_regdom_dev(dev) dev[0].flush_scan_cache() dev[1].flush_scan_cache() -- 2.43.0 From j at w1.fi Sat May 9 00:29:18 2026 From: j at w1.fi (Jouni Malinen) Date: Sat, 9 May 2026 10:29:18 +0300 Subject: [PATCH 79/97] NAN: Add support for tracking the status of transmit requests In-Reply-To: <20260428200639.40243-80-andrei.otcheretianski@intel.com> References: <20260428200639.40243-1-andrei.otcheretianski@intel.com> <20260428200639.40243-80-andrei.otcheretianski@intel.com> Message-ID: On Tue, Apr 28, 2026 at 11:06:20PM +0300, Andrei Otcheretianski wrote: > Extend the NAN Discovery Engine (DE) to track the status of > transmit requests coming from higher layers: > > - For transmit requests with valid cookie number, add the > cookie and a digest of the transmitted frame to a list. > - Once a Tx status for a transmitted frame is received, search > for the corresponding entry in the list of tracked frames, > and when found report to the higher layer whether the frame > was acknowledged or not. So this is all implementation-internal tracking? If so, why does this use CRC32? CRC was designed for error detected, but for identifying different blobs and it is not really suitable for uses where there could be issues with misidentification of two payloads either by accident or even worse, if there is any malicious construction of such payloads. > diff --git a/src/common/nan_de.c b/src/common/nan_de.c > +static u32 nan_de_track_tx_digest(const u8 *data, size_t len) > +{ > + return ieee80211_crc32(data, len); > +} This feels a bit strange and not exactly robust way of identifying whether two blobs are identical. What would happen if two different requests would end up having the same digest value? -- Jouni Malinen PGP id EFC895FA From j at w1.fi Sat May 9 01:47:56 2026 From: j at w1.fi (Jouni Malinen) Date: Sat, 9 May 2026 11:47:56 +0300 Subject: [PATCH 00/97] NAN: Group keys support, schedule update and more In-Reply-To: References: <20260428200639.40243-1-andrei.otcheretianski@intel.com> Message-ID: On Sat, May 09, 2026 at 12:21:31AM +0300, Jouni Malinen wrote: > Thanks. I did not get quite to the end of the set today, but I applied > patches 1-76 now with some cleanup and fixes and will continue with the > remaining patches. I applied the rest of the patches now as well with the exception of patches 79-81 that have an open question on the tracking digest. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Sat May 9 01:48:35 2026 From: j at w1.fi (Jouni Malinen) Date: Sat, 9 May 2026 11:48:35 +0300 Subject: [PATCH] Fix mesh_secure_ocv_mix_ht failure In-Reply-To: <20260508231822.50787-1-masashi.honma@gmail.com> References: <20260508231822.50787-1-masashi.honma@gmail.com> Message-ID: On Sat, May 09, 2026 at 08:18:22AM +0900, Masashi Honma wrote: > When running the tests with ./vm-run.sh -f wpas_mesh, mesh_secure_ocv_mix_ht > fails. > > Exception during test execution: Test exception: CAC did not start > Traceback (most recent call last): > File "/home/honma/git/hostap/tests/hwsim/./run-tests.py", line 635, in main > t(dev, apdev) > File "/home/honma/git/hostap/tests/hwsim/test_wpas_mesh.py", line 515, in test_mesh_secure_ocv_mix_ht > run_mesh_secure_ocv_mix_ht(dev, apdev) > File "/home/honma/git/hostap/tests/hwsim/test_wpas_mesh.py", line 528, in run_mesh_secure_ocv_mix_ht > check_dfs_started(dev[0]) > File "/home/honma/git/hostap/tests/hwsim/test_wpas_mesh.py", line 90, in check_dfs_started > raise Exception("Test exception: CAC did not start") > Exception: Test exception: CAC did not start > FAIL mesh_secure_ocv_mix_ht 11.329875 2026-05-08 23:09:31.926804 > > However, it succeeds when executed individually using > ./vm-run.sh mesh_secure_ocv_mix_ht. This patch fixes the test by resetting the > country code. Thanks, applied. -- Jouni Malinen PGP id EFC895FA From ilan.peer at intel.com Sun May 10 00:03:00 2026 From: ilan.peer at intel.com (Peer, Ilan) Date: Sun, 10 May 2026 07:03:00 +0000 Subject: [PATCH 79/97] NAN: Add support for tracking the status of transmit requests In-Reply-To: References: <20260428200639.40243-1-andrei.otcheretianski@intel.com> <20260428200639.40243-80-andrei.otcheretianski@intel.com> Message-ID: Hi, > -----Original Message----- > From: Jouni Malinen > Sent: Saturday, May 9, 2026 10:29 AM > To: Otcheretianski, Andrei > Cc: hostap at lists.infradead.org; vamsin at qti.qualcomm.com; > maheshkkv at google.com; Peer, Ilan > Subject: Re: [PATCH 79/97] NAN: Add support for tracking the status of > transmit requests > > On Tue, Apr 28, 2026 at 11:06:20PM +0300, Andrei Otcheretianski wrote: > > Extend the NAN Discovery Engine (DE) to track the status of transmit > > requests coming from higher layers: > > > > - For transmit requests with valid cookie number, add the > > cookie and a digest of the transmitted frame to a list. > > - Once a Tx status for a transmitted frame is received, search > > for the corresponding entry in the list of tracked frames, > > and when found report to the higher layer whether the frame > > was acknowledged or not. > > So this is all implementation-internal tracking? If so, why does this use > CRC32? CRC was designed for error detected, but for identifying different > blobs and it is not really suitable for uses where there could be issues with > misidentification of two payloads either by accident or even worse, if there > is any malicious construction of such payloads. > Understood. I'll change the implementation to use SHA-256 which is more suitable (collision resistance). I'll also add a check to reject requests for which there is already a request with identical digest. > > diff --git a/src/common/nan_de.c b/src/common/nan_de.c > > +static u32 nan_de_track_tx_digest(const u8 *data, size_t len) { > > + return ieee80211_crc32(data, len); > > +} > > This feels a bit strange and not exactly robust way of identifying whether > two blobs are identical. What would happen if two different requests would > end up having the same digest value? As I do not expect this to happen in practice, I'll add support to reject such requests. Regards, Ilan.