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. From me at prestonhunt.com Mon May 11 18:04:34 2026 From: me at prestonhunt.com (Preston Hunt) Date: Mon, 11 May 2026 18:04:34 -0700 Subject: [PATCH 0/1] Fix spelling: "send successfully" to "sent successfully" Message-ID: <20260512010509.2190609-1-me@prestonhunt.com> This is a very minor issue, but the spelling is incorrect in several messages. Preston Hunt (1): nl80211: fix spelling "send successfully" to "sent successfully" src/drivers/driver_nl80211.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) -- 2.51.2 From me at prestonhunt.com Mon May 11 18:04:35 2026 From: me at prestonhunt.com (Preston Hunt) Date: Mon, 11 May 2026 18:04:35 -0700 Subject: [PATCH 1/1] nl80211: fix spelling "send successfully" to "sent successfully" In-Reply-To: <20260512010509.2190609-1-me@prestonhunt.com> References: <20260512010509.2190609-1-me@prestonhunt.com> Message-ID: <20260512010509.2190609-2-me@prestonhunt.com> Signed-off-by: Preston Hunt --- src/drivers/driver_nl80211.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index ea89bcd8c..8c0e4abec 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -4678,7 +4678,7 @@ retry: } } else { wpa_printf(MSG_DEBUG, - "nl80211: Authentication request send successfully"); + "nl80211: Authentication request sent successfully"); } fail: @@ -7892,7 +7892,7 @@ skip_auth_type: drv->roam_indication_done = false; #endif /* CONFIG_DRIVER_NL80211_QCA */ wpa_printf(MSG_DEBUG, - "nl80211: Connect request send successfully"); + "nl80211: Connect request sent successfully"); } fail: @@ -8036,7 +8036,7 @@ static int wpa_driver_nl80211_associate( } } else { wpa_printf(MSG_DEBUG, - "nl80211: Association request send successfully"); + "nl80211: Association request sent successfully"); } fail: @@ -12968,7 +12968,7 @@ static int nl80211_join_mesh(struct i802_bss *bss, } ret = 0; drv->assoc_freq = bss->flink->freq = params->freq.freq; - wpa_printf(MSG_DEBUG, "nl80211: mesh join request send successfully"); + wpa_printf(MSG_DEBUG, "nl80211: mesh join request sent successfully"); fail: nlmsg_free(msg); @@ -13023,7 +13023,7 @@ static int wpa_driver_nl80211_leave_mesh(void *priv) ret, strerror(-ret)); } else { wpa_printf(MSG_DEBUG, - "nl80211: mesh leave request send successfully"); + "nl80211: mesh leave request sent successfully"); drv->first_bss->flink->freq = 0; } -- 2.51.2 From chung-hsien.hsu at infineon.com Tue May 12 06:15:56 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Tue, 12 May 2026 21:15:56 +0800 Subject: [PATCH 0/2] RSN: Validate GTK KDE lengths before msg 4/4 Message-ID: <20260512131558.18020-1-chung-hsien.hsu@infineon.com> Validate GTK KDE lengths in EAPOL-Key message 3/4 before transmitting message 4/4. The GTK KDE length is already checked when processing the GTK for installation. However, that validation is reached only after message 4/4 has been transmitted. This allows a malformed message 3/4 with an invalid GTK KDE length to be acknowledged even though the supplicant later rejects the GTK and fails the handshake. This series splits the early validation into non-MLO and MLO changes. Chung-Hsien Hsu (2): RSN: Reject invalid GTK KDE length in msg 3/4 RSN: Reject invalid MLO GTK KDE length in msg 3/4 src/rsn_supp/wpa.c | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) -- 2.25.1 From chung-hsien.hsu at infineon.com Tue May 12 06:15:58 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Tue, 12 May 2026 21:15:58 +0800 Subject: [PATCH 2/2] RSN: Reject invalid MLO GTK KDE length in msg 3/4 In-Reply-To: <20260512131558.18020-1-chung-hsien.hsu@infineon.com> References: <20260512131558.18020-1-chung-hsien.hsu@infineon.com> Message-ID: <20260512131558.18020-3-chung-hsien.hsu@infineon.com> Validate MLO GTK KDE lengths in EAPOL-Key message 3/4 before transmitting message 4/4. The MLO GTK KDE length is already checked when processing each per-link GTK for installation. However, that validation is reached only after message 4/4 has been transmitted. This allows a malformed message 3/4 with an invalid MLO GTK KDE length to be acknowledged even though the supplicant later rejects the GTK and fails the handshake. Reject message 3/4 before sending message 4/4 if any per-link MLO GTK KDE is too short to include the MLO GTK KDE prefix, or if the GTK payload would exceed the local GTK buffer. Reuse the same helper in the later MLO GTK installation path to keep the validation rules consistent. Signed-off-by: Chung-Hsien Hsu --- src/rsn_supp/wpa.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c index ecc0eddd45ab..acc50f4c77bd 100644 --- a/src/rsn_supp/wpa.c +++ b/src/rsn_supp/wpa.c @@ -1409,6 +1409,18 @@ static int wpa_supplicant_validate_gtk_kde_len(size_t gtk_len) } +static int wpa_supplicant_validate_mlo_gtk_kde_len(size_t gtk_len) +{ + struct wpa_gtk_data gd; + + if (gtk_len < RSN_MLO_GTK_KDE_PREFIX_LENGTH || + gtk_len - RSN_MLO_GTK_KDE_PREFIX_LENGTH > sizeof(gd.gtk)) + return -1; + + return 0; +} + + static int wpa_supplicant_install_gtk(struct wpa_sm *sm, const struct wpa_gtk_data *gd, const u8 *key_rsc, int wnm_sleep) @@ -1602,8 +1614,7 @@ static int wpa_supplicant_mlo_gtk(struct wpa_sm *sm, u8 link_id, const u8 *gtk, "RSN: received GTK in pairwise handshake", gtk, gtk_len); - if (gtk_len < RSN_MLO_GTK_KDE_PREFIX_LENGTH || - gtk_len - RSN_MLO_GTK_KDE_PREFIX_LENGTH > sizeof(gd.gtk)) + if (wpa_supplicant_validate_mlo_gtk_kde_len(gtk_len) < 0) return -1; gd.keyidx = gtk[0] & 0x3; @@ -2886,6 +2897,14 @@ static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm, goto failed; } + if (wpa_supplicant_validate_mlo_gtk_kde_len( + ie.mlo_gtk_len[i]) < 0) { + wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, + "RSN: Invalid MLO GTK KDE length %lu for link ID %u", + (unsigned long) ie.mlo_gtk_len[i], i); + goto failed; + } + if (sm->mgmt_group_cipher != WPA_CIPHER_GTK_NOT_USED && wpa_cipher_valid_mgmt_group(sm->mgmt_group_cipher) && wpa_validate_mlo_ieee80211w_kdes(sm, i, &ie) < 0) -- 2.25.1 From chung-hsien.hsu at infineon.com Tue May 12 06:15:57 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Tue, 12 May 2026 21:15:57 +0800 Subject: [PATCH 1/2] RSN: Reject invalid GTK KDE length in msg 3/4 In-Reply-To: <20260512131558.18020-1-chung-hsien.hsu@infineon.com> References: <20260512131558.18020-1-chung-hsien.hsu@infineon.com> Message-ID: <20260512131558.18020-2-chung-hsien.hsu@infineon.com> Validate the GTK KDE length in EAPOL-Key message 3/4 before transmitting message 4/4. The GTK KDE length is already checked when processing the GTK for installation. However, that validation is reached only after message 4/4 has been transmitted. This allows a malformed message 3/4 with an invalid GTK KDE length to be acknowledged even though the supplicant later rejects the GTK and fails the handshake. Reject message 3/4 before sending message 4/4 if the GTK KDE is too short to include the GTK KDE header, or if the GTK payload would exceed the local GTK buffer. Reuse the same helper in the later GTK installation path to keep the validation rules consistent. Signed-off-by: R Sanath Kumar Signed-off-by: Chung-Hsien Hsu --- src/rsn_supp/wpa.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c index 2c5ed11c8189..ecc0eddd45ab 100644 --- a/src/rsn_supp/wpa.c +++ b/src/rsn_supp/wpa.c @@ -1398,6 +1398,17 @@ struct wpa_gtk_data { }; +static int wpa_supplicant_validate_gtk_kde_len(size_t gtk_len) +{ + struct wpa_gtk_data gd; + + if (gtk_len < 2 || gtk_len - 2 > sizeof(gd.gtk)) + return -1; + + return 0; +} + + static int wpa_supplicant_install_gtk(struct wpa_sm *sm, const struct wpa_gtk_data *gd, const u8 *key_rsc, int wnm_sleep) @@ -1668,7 +1679,7 @@ static int wpa_supplicant_pairwise_gtk(struct wpa_sm *sm, wpa_hexdump_key(MSG_DEBUG, "RSN: received GTK in pairwise handshake", gtk, gtk_len); - if (gtk_len < 2 || gtk_len - 2 > sizeof(gd.gtk)) + if (wpa_supplicant_validate_gtk_kde_len(gtk_len) < 0) return -1; gd.keyidx = gtk[0] & 0x3; @@ -2892,6 +2903,15 @@ static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm, "WPA: GTK IE in unencrypted key data"); goto failed; } + + if (!mlo && ie.gtk && + wpa_supplicant_validate_gtk_kde_len(ie.gtk_len) < 0) { + wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, + "WPA: Invalid GTK KDE length %lu", + (unsigned long) ie.gtk_len); + goto failed; + } + if (!mlo && ie.igtk && !(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) { wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, "WPA: IGTK KDE in unencrypted key data"); -- 2.25.1 From chung-hsien.hsu at infineon.com Tue May 12 06:25:01 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Tue, 12 May 2026 21:25:01 +0800 Subject: [PATCH 0/2] RRM: Report driver-triggered Neighbor Report Message-ID: <20260512132503.18227-1-chung-hsien.hsu@infineon.com> Neighbor Report Request can be triggered by wpa_supplicant through the control interface or by the driver/firmware. The current receive path only reports Neighbor Report Responses that match a pending wpa_supplicant request. Responses for driver-triggered requests are treated as unexpected, or discarded due to dialog token mismatch, and are not exposed to control interface monitors. This series first moves the existing Neighbor Report control-interface event formatting into RRM code as a shared helper, then uses that helper to report unsolicited Neighbor Report Responses while preserving the existing dialog token validation and timeout handling for wpa_supplicant-triggered requests. Chung-Hsien Hsu (2): RRM: Move Neighbor Report event notification to RRM RRM: Report driver-triggered Neighbor Report responses wpa_supplicant/ctrl_iface.c | 97 +-------------- wpa_supplicant/rrm.c | 190 +++++++++++++++++++++++++++--- wpa_supplicant/wpa_supplicant_i.h | 2 + 3 files changed, 176 insertions(+), 113 deletions(-) -- 2.25.1 From chung-hsien.hsu at infineon.com Tue May 12 06:25:02 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Tue, 12 May 2026 21:25:02 +0800 Subject: [PATCH 1/2] RRM: Move Neighbor Report event notification to RRM In-Reply-To: <20260512132503.18227-1-chung-hsien.hsu@infineon.com> References: <20260512132503.18227-1-chung-hsien.hsu@infineon.com> Message-ID: <20260512132503.18227-2-chung-hsien.hsu@infineon.com> The Neighbor Report control interface event formatting is currently implemented in ctrl_iface.c as part of the NEIGHBOR_REP_REQUEST command callback. Move this formatting into RRM code so that it can be reused by other Neighbor Report Response processing paths. This is a refactoring change and is not intended to change the control-interface event format or ownership rules for the neighbor_rep buffer. Signed-off-by: Chung-Hsien Hsu --- wpa_supplicant/ctrl_iface.c | 97 +------------------------- wpa_supplicant/rrm.c | 111 ++++++++++++++++++++++++++++++ wpa_supplicant/wpa_supplicant_i.h | 2 + 3 files changed, 114 insertions(+), 96 deletions(-) diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 1096fa228d06..f1d1f8079b97 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -10868,103 +10868,8 @@ static int wpas_ctrl_vendor_elem_remove(struct wpa_supplicant *wpa_s, char *cmd) static void wpas_ctrl_neighbor_rep_cb(void *ctx, struct wpabuf *neighbor_rep) { struct wpa_supplicant *wpa_s = ctx; - size_t len; - const u8 *data; - /* - * Neighbor Report element (IEEE Std 802.11-2024, 9.4.2.35) - * BSSID[6] - * BSSID Information[4] - * Operating Class[1] - * Channel Number[1] - * PHY Type[1] - * Optional Subelements[variable] - */ -#define NR_IE_MIN_LEN (ETH_ALEN + 4 + 1 + 1 + 1) - - if (!neighbor_rep || wpabuf_len(neighbor_rep) == 0) { - wpa_msg_ctrl(wpa_s, MSG_INFO, RRM_EVENT_NEIGHBOR_REP_FAILED); - goto out; - } - - data = wpabuf_head_u8(neighbor_rep); - len = wpabuf_len(neighbor_rep); - - while (len >= 2 + NR_IE_MIN_LEN) { - const u8 *nr; - char lci[256 * 2 + 1]; - char civic[256 * 2 + 1]; - u8 nr_len = data[1]; - const u8 *pos = data, *end; - - if (pos[0] != WLAN_EID_NEIGHBOR_REPORT || - nr_len < NR_IE_MIN_LEN) { - wpa_dbg(wpa_s, MSG_DEBUG, - "CTRL: Invalid Neighbor Report element: id=%u len=%u", - data[0], nr_len); - goto out; - } - - if (2U + nr_len > len) { - wpa_dbg(wpa_s, MSG_DEBUG, - "CTRL: Invalid Neighbor Report element: id=%u len=%zu nr_len=%u", - data[0], len, nr_len); - goto out; - } - pos += 2; - end = pos + nr_len; - - nr = pos; - pos += NR_IE_MIN_LEN; - - lci[0] = '\0'; - civic[0] = '\0'; - while (end - pos > 2) { - u8 s_id, s_len; - - s_id = *pos++; - s_len = *pos++; - if (s_len > end - pos) - goto out; - if (s_id == WLAN_EID_MEASURE_REPORT && s_len > 3) { - /* Measurement Token[1] */ - /* Measurement Report Mode[1] */ - /* Measurement Type[1] */ - /* Measurement Report[variable] */ - switch (pos[2]) { - case MEASURE_TYPE_LCI: - if (lci[0]) - break; - wpa_snprintf_hex(lci, sizeof(lci), - pos, s_len); - break; - case MEASURE_TYPE_LOCATION_CIVIC: - if (civic[0]) - break; - wpa_snprintf_hex(civic, sizeof(civic), - pos, s_len); - break; - } - } - - pos += s_len; - } - - wpa_msg(wpa_s, MSG_INFO, RRM_EVENT_NEIGHBOR_REP_RXED - "bssid=" MACSTR - " info=0x%x op_class=%u chan=%u phy_type=%u%s%s%s%s", - MAC2STR(nr), WPA_GET_LE32(nr + ETH_ALEN), - nr[ETH_ALEN + 4], nr[ETH_ALEN + 5], - nr[ETH_ALEN + 6], - lci[0] ? " lci=" : "", lci, - civic[0] ? " civic=" : "", civic); - - data = end; - len -= 2 + nr_len; - } - -out: - wpabuf_free(neighbor_rep); + wpas_rrm_notify_neighbor_rep(wpa_s, neighbor_rep); } diff --git a/wpa_supplicant/rrm.c b/wpa_supplicant/rrm.c index 2430aeee7bcf..07e952dfe6fc 100644 --- a/wpa_supplicant/rrm.c +++ b/wpa_supplicant/rrm.c @@ -11,6 +11,7 @@ #include "utils/common.h" #include "utils/eloop.h" #include "common/ieee802_11_common.h" +#include "common/wpa_ctrl.h" #include "wpa_supplicant_i.h" #include "driver_i.h" #include "bss.h" @@ -53,6 +54,116 @@ void wpas_rrm_reset(struct wpa_supplicant *wpa_s) } +/* + * wpas_rrm_notify_neighbor_rep - Notify received neighbor report + * @wpa_s: Pointer to wpa_supplicant + * @neighbor_rep: Pointer to neighbor report element + */ +void wpas_rrm_notify_neighbor_rep(struct wpa_supplicant *wpa_s, + struct wpabuf *neighbor_rep) +{ + size_t len; + const u8 *data; + + /* + * Neighbor Report element (IEEE Std 802.11-2024, 9.4.2.35) + * BSSID[6] + * BSSID Information[4] + * Operating Class[1] + * Channel Number[1] + * PHY Type[1] + * Optional Subelements[variable] + */ +#define NR_IE_MIN_LEN (ETH_ALEN + 4 + 1 + 1 + 1) + + if (!neighbor_rep || wpabuf_len(neighbor_rep) == 0) { + wpa_msg_ctrl(wpa_s, MSG_INFO, RRM_EVENT_NEIGHBOR_REP_FAILED); + goto out; + } + + data = wpabuf_head_u8(neighbor_rep); + len = wpabuf_len(neighbor_rep); + + while (len >= 2 + NR_IE_MIN_LEN) { + const u8 *nr; + char lci[256 * 2 + 1]; + char civic[256 * 2 + 1]; + u8 nr_len = data[1]; + const u8 *pos = data, *end; + + if (pos[0] != WLAN_EID_NEIGHBOR_REPORT || + nr_len < NR_IE_MIN_LEN) { + wpa_dbg(wpa_s, MSG_DEBUG, + "RRM: Invalid Neighbor Report element: id=%u len=%u", + data[0], nr_len); + goto out; + } + + if (2U + nr_len > len) { + wpa_dbg(wpa_s, MSG_DEBUG, + "RRM: Invalid Neighbor Report element: id=%u len=%zu nr_len=%u", + data[0], len, nr_len); + goto out; + } + + pos += 2; + end = pos + nr_len; + nr = pos; + pos += NR_IE_MIN_LEN; + + lci[0] = '\0'; + civic[0] = '\0'; + while (end - pos > 2) { + u8 s_id, s_len; + + s_id = *pos++; + s_len = *pos++; + + if (s_len > end - pos) + goto out; + + if (s_id == WLAN_EID_MEASURE_REPORT && s_len > 3) { + /* Measurement Token[1] */ + /* Measurement Report Mode[1] */ + /* Measurement Type[1] */ + /* Measurement Report[variable] */ + switch (pos[2]) { + case MEASURE_TYPE_LCI: + if (lci[0]) + break; + wpa_snprintf_hex(lci, sizeof(lci), + pos, s_len); + break; + case MEASURE_TYPE_LOCATION_CIVIC: + if (civic[0]) + break; + wpa_snprintf_hex(civic, sizeof(civic), + pos, s_len); + break; + } + } + + pos += s_len; + } + + wpa_msg(wpa_s, MSG_INFO, RRM_EVENT_NEIGHBOR_REP_RXED + "bssid=" MACSTR + " info=0x%x op_class=%u chan=%u phy_type=%u%s%s%s%s", + MAC2STR(nr), WPA_GET_LE32(nr + ETH_ALEN), + nr[ETH_ALEN + 4], nr[ETH_ALEN + 5], + nr[ETH_ALEN + 6], + lci[0] ? " lci=" : "", lci, + civic[0] ? " civic=" : "", civic); + + data = end; + len -= 2 + nr_len; + } + +out: + wpabuf_free(neighbor_rep); +} + + /* * wpas_rrm_process_neighbor_rep - Handle incoming neighbor report * @wpa_s: Pointer to wpa_supplicant diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index d68dd582fb75..6563399928f8 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -1896,6 +1896,8 @@ void wpas_rrm_reset(struct wpa_supplicant *wpa_s); void wpas_rrm_process_neighbor_rep(struct wpa_supplicant *wpa_s, const u8 *da, const u8 *sa, const u8 *report, size_t report_len); +void wpas_rrm_notify_neighbor_rep(struct wpa_supplicant *wpa_s, + struct wpabuf *neighbor_rep); int wpas_rrm_send_neighbor_rep_request(struct wpa_supplicant *wpa_s, const struct wpa_ssid_value *ssid, int lci, int civic, -- 2.25.1 From chung-hsien.hsu at infineon.com Tue May 12 06:25:03 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Tue, 12 May 2026 21:25:03 +0800 Subject: [PATCH 2/2] RRM: Report driver-triggered Neighbor Report responses In-Reply-To: <20260512132503.18227-1-chung-hsien.hsu@infineon.com> References: <20260512132503.18227-1-chung-hsien.hsu@infineon.com> Message-ID: <20260512132503.18227-3-chung-hsien.hsu@infineon.com> Neighbor Report Request can be initiated either by wpa_supplicant or by the driver/firmware. The current processing path assumes that every Neighbor Report Response corresponds to a pending wpa_supplicant request and validates the dialog token against the locally generated token before checking for the registered callback. This drops Neighbor Report Responses for requests that were triggered by the driver/firmware since wpa_supplicant does not have a pending callback or matching dialog token for such requests. Accept Neighbor Report Responses only when they are received from the currently associated AP. For MLO connections, accept a source address that matches the AP MLD address or any valid affiliated link BSSID. Use the shared Neighbor Report notification helper to report unsolicited Neighbor Report Responses through the control interface while keeping the existing dialog token validation and timeout handling for wpa_supplicant-triggered requests. Signed-off-by: Chung-Hsien Hsu --- wpa_supplicant/rrm.c | 79 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/wpa_supplicant/rrm.c b/wpa_supplicant/rrm.c index 07e952dfe6fc..f0e0ffb994c9 100644 --- a/wpa_supplicant/rrm.c +++ b/wpa_supplicant/rrm.c @@ -164,6 +164,38 @@ out: } +/* + * wpas_rrm_neighbor_rep_from_assoc_ap - Check Neighbor Report Response source + * @wpa_s: Pointer to wpa_supplicant + * @src: Source address of the received Neighbor Report Response frame + */ +static bool wpas_rrm_neighbor_rep_from_assoc_ap(struct wpa_supplicant *wpa_s, + const u8 *src) +{ + int i; + + if (os_memcmp(src, wpa_s->bssid, ETH_ALEN) == 0) + return true; + + if (!wpa_s->valid_links) + return false; + + if (!is_zero_ether_addr(wpa_s->ap_mld_addr) && + os_memcmp(src, wpa_s->ap_mld_addr, ETH_ALEN) == 0) + return true; + + for (i = 0; i < MAX_NUM_MLD_LINKS; i++) { + if (!(wpa_s->valid_links & BIT(i))) + continue; + + if (os_memcmp(src, wpa_s->links[i].bssid, ETH_ALEN) == 0) + return true; + } + + return false; +} + + /* * wpas_rrm_process_neighbor_rep - Handle incoming neighbor report * @wpa_s: Pointer to wpa_supplicant @@ -186,38 +218,51 @@ void wpas_rrm_process_neighbor_rep(struct wpa_supplicant *wpa_s, return; } + if (!wpas_rrm_neighbor_rep_from_assoc_ap(wpa_s, sa)) { + wpa_dbg(wpa_s, MSG_DEBUG, + "RRM: Ignore Neighbor Report Response from " MACSTR + " (BSSID " MACSTR " AP MLD " MACSTR + " valid_links=0x%x)", + MAC2STR(sa), MAC2STR(wpa_s->bssid), + MAC2STR(wpa_s->ap_mld_addr), wpa_s->valid_links); + return; + } + wpa_hexdump(MSG_DEBUG, "RRM: New Neighbor Report", report, report_len); if (report_len < 1) return; - if (report[0] != wpa_s->rrm.next_neighbor_rep_token - 1) { - wpa_printf(MSG_DEBUG, - "RRM: Discarding neighbor report with token %d (expected %d)", - report[0], wpa_s->rrm.next_neighbor_rep_token - 1); - return; - } + if (wpa_s->rrm.notify_neighbor_rep) { + if (report[0] != wpa_s->rrm.next_neighbor_rep_token - 1) { + wpa_printf(MSG_DEBUG, + "RRM: Discarding neighbor report with token %d (expected %d)", + report[0], + wpa_s->rrm.next_neighbor_rep_token - 1); + return; + } - eloop_cancel_timeout(wpas_rrm_neighbor_rep_timeout_handler, &wpa_s->rrm, - NULL); - - if (!wpa_s->rrm.notify_neighbor_rep) { - wpa_msg(wpa_s, MSG_INFO, "RRM: Unexpected neighbor report"); - return; + eloop_cancel_timeout(wpas_rrm_neighbor_rep_timeout_handler, + &wpa_s->rrm, NULL); } /* skipping the first byte, which is only an id (dialog token) */ neighbor_rep = wpabuf_alloc(report_len - 1); if (!neighbor_rep) { - wpas_rrm_neighbor_rep_timeout_handler(&wpa_s->rrm, NULL); + if (wpa_s->rrm.notify_neighbor_rep) + wpas_rrm_neighbor_rep_timeout_handler(&wpa_s->rrm, NULL); return; } wpabuf_put_data(neighbor_rep, report + 1, report_len - 1); wpa_dbg(wpa_s, MSG_DEBUG, "RRM: Notifying neighbor report (token = %d)", report[0]); - wpa_s->rrm.notify_neighbor_rep(wpa_s->rrm.neighbor_rep_cb_ctx, - neighbor_rep); - wpa_s->rrm.notify_neighbor_rep = NULL; - wpa_s->rrm.neighbor_rep_cb_ctx = NULL; + if (wpa_s->rrm.notify_neighbor_rep) { + wpa_s->rrm.notify_neighbor_rep(wpa_s->rrm.neighbor_rep_cb_ctx, + neighbor_rep); + wpa_s->rrm.notify_neighbor_rep = NULL; + wpa_s->rrm.neighbor_rep_cb_ctx = NULL; + } else { + wpas_rrm_notify_neighbor_rep(wpa_s, neighbor_rep); + } } -- 2.25.1 From chung-hsien.hsu at infineon.com Tue May 12 06:26:48 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Tue, 12 May 2026 21:26:48 +0800 Subject: [PATCH 0/2] wpa_supplicant: Fix VHT/HE status indications Message-ID: <20260512132650.18420-1-chung-hsien.hsu@infineon.com> This series updates the legacy STATUS indications for VHT/HE connections. Patch 1 fixes ieee80211ac=1 so that it is reported only for actual VHT/IEEE 802.11ac associations instead of being set based only on the presence of a VHT Capabilities element in the Association Response. Patch 2 adds a corresponding ieee80211ax=1 STATUS indication for actual HE/IEEE 802.11ax associations, while excluding EHT associations. Chung-Hsien Hsu (2): wpa_supplicant: Fix ieee80211ac status indication wpa_supplicant: Add ieee80211ax status indication wpa_supplicant/ctrl_iface.c | 9 +++++++++ wpa_supplicant/events.c | 21 +++++++++++++++++---- wpa_supplicant/wpa_supplicant_i.h | 3 +++ 3 files changed, 29 insertions(+), 4 deletions(-) -- 2.25.1 From chung-hsien.hsu at infineon.com Tue May 12 06:26:50 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Tue, 12 May 2026 21:26:50 +0800 Subject: [PATCH 2/2] wpa_supplicant: Add ieee80211ax status indication In-Reply-To: <20260512132650.18420-1-chung-hsien.hsu@infineon.com> References: <20260512132650.18420-1-chung-hsien.hsu@infineon.com> Message-ID: <20260512132650.18420-3-chung-hsien.hsu@infineon.com> The STATUS output has a legacy ieee80211ac=1 indication for VHT associations, but there is no corresponding indication for HE/IEEE 802.11ax associations. Add ieee80211ax=1 to STATUS output based on the parsed connection_he state. Keep the indication limited to HE associations by excluding EHT associations so that an IEEE 802.11be connection is not reported as an IEEE 802.11ax connection. Signed-off-by: Chung-Hsien Hsu Signed-off-by: Adesh Kumar Singh --- wpa_supplicant/ctrl_iface.c | 9 +++++++++ wpa_supplicant/events.c | 10 ++++++++++ wpa_supplicant/wpa_supplicant_i.h | 3 +++ 3 files changed, 22 insertions(+) diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 1096fa228d06..42c792b7fe39 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -2600,6 +2600,15 @@ static int wpa_supplicant_ctrl_iface_status(struct wpa_supplicant *wpa_s, } #endif /* CONFIG_WPS */ +#ifdef CONFIG_IEEE80211AX + if (wpa_s->ieee80211ax) { + ret = os_snprintf(pos, end - pos, "ieee80211ax=1\n"); + if (os_snprintf_error(end - pos, ret)) + return pos - buf; + pos += ret; + } +#endif /* CONFIG_IEEE80211AX */ + if (wpa_s->ieee80211ac) { ret = os_snprintf(pos, end - pos, "ieee80211ac=1\n"); if (os_snprintf_error(end - pos, ret)) diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 9afbd4617dd5..40f682803c8e 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -439,6 +439,9 @@ void wpa_supplicant_mark_disassoc(struct wpa_supplicant *wpa_s) os_memset(wpa_s->last_tk, 0, sizeof(wpa_s->last_tk)); #endif /* CONFIG_TESTING_OPTIONS */ wpa_s->ieee80211ac = 0; +#ifdef CONFIG_IEEE80211AX + wpa_s->ieee80211ax = 0; +#endif /* CONFIG_IEEE80211AX */ if (wpa_s->enabled_4addr_mode && wpa_drv_set_4addr_mode(wpa_s, 0) == 0) wpa_s->enabled_4addr_mode = 0; @@ -3534,6 +3537,9 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s, wpa_s->connection_set = 0; wpa_s->ieee80211ac = 0; +#ifdef CONFIG_IEEE80211AX + wpa_s->ieee80211ax = 0; +#endif /* CONFIG_IEEE80211AX */ if (!req_ies || !resp_ies || ieee802_11_parse_elems(req_ies, req_ies_len, &req_elems, 0) == @@ -3560,6 +3566,10 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s, * actual VHT/IEEE 802.11ac associations. */ wpa_s->ieee80211ac = wpa_s->connection_vht && !wpa_s->connection_he && !wpa_s->connection_eht; +#ifdef CONFIG_IEEE80211AX + wpa_s->ieee80211ax = wpa_s->connection_he && + !wpa_s->connection_eht; +#endif /* CONFIG_IEEE80211AX */ if (req_elems.rrm_enabled) wpa_s->rrm.rrm_used = 1; diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index d68dd582fb75..23739aa17ce0 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -1634,6 +1634,9 @@ struct wpa_supplicant { #ifdef CONFIG_FILS unsigned int disable_fils:1; #endif /* CONFIG_FILS */ +#ifdef CONFIG_IEEE80211AX + unsigned int ieee80211ax:1; +#endif /* CONFIG_IEEE80211AX */ unsigned int ieee80211ac:1; unsigned int enabled_4addr_mode:1; unsigned int multi_bss_support:1; -- 2.25.1 From chung-hsien.hsu at infineon.com Tue May 12 06:26:49 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Tue, 12 May 2026 21:26:49 +0800 Subject: [PATCH 1/2] wpa_supplicant: Fix ieee80211ac status indication In-Reply-To: <20260512132650.18420-1-chung-hsien.hsu@infineon.com> References: <20260512132650.18420-1-chung-hsien.hsu@infineon.com> Message-ID: <20260512132650.18420-2-chung-hsien.hsu@infineon.com> The legacy ieee80211ac=1 STATUS flag is currently set based only on the presence of a VHT Capabilities element in the Association Response. This can report ieee80211ac=1 for HE/EHT associations on non-6 GHz bands where VHT capabilities are present as well. wpas_parse_connection_info() already derives the actual connection mode from both the Association Request and Response IEs and excludes the 2.4 GHz vendor VHT extension from connection_vht. Use that result for the legacy ieee80211ac STATUS flag instead of parsing the Association Response separately. This keeps ieee80211ac=1 limited to VHT/IEEE 802.11ac associations. Signed-off-by: Chung-Hsien Hsu Signed-off-by: Yu-Chien Huang --- wpa_supplicant/events.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 2a281e4c4b06..9afbd4617dd5 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -3533,6 +3533,7 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s, struct wpabuf *req_mlbuf, *resp_mlbuf; wpa_s->connection_set = 0; + wpa_s->ieee80211ac = 0; if (!req_ies || !resp_ies || ieee802_11_parse_elems(req_ies, req_ies_len, &req_elems, 0) == @@ -3554,6 +3555,12 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s, resp_elems.he_capabilities; wpa_s->connection_eht = req_elems.eht_capabilities && resp_elems.eht_capabilities; + + /* Keep the legacy ieee80211ac status indication limited to + * actual VHT/IEEE 802.11ac associations. */ + wpa_s->ieee80211ac = wpa_s->connection_vht && + !wpa_s->connection_he && !wpa_s->connection_eht; + if (req_elems.rrm_enabled) wpa_s->rrm.rrm_used = 1; @@ -3750,10 +3757,6 @@ static int wpa_supplicant_event_associnfo(struct wpa_supplicant *wpa_s, #endif /* CONFIG_WNM */ interworking_process_assoc_resp(wpa_s, data->assoc_info.resp_ies, data->assoc_info.resp_ies_len); - if ((wpa_s->hw_capab & BIT(CAPAB_VHT)) && - get_ie(data->assoc_info.resp_ies, - data->assoc_info.resp_ies_len, WLAN_EID_VHT_CAP)) - wpa_s->ieee80211ac = 1; multi_ap_process_assoc_resp(wpa_s, data->assoc_info.resp_ies, data->assoc_info.resp_ies_len); -- 2.25.1 From andrei.otcheretianski at intel.com Tue May 12 23:58:59 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Wed, 13 May 2026 09:58:59 +0300 Subject: [PATCH 1/3] NAN: Add support for tracking the status of transmit requests Message-ID: <20260513065909.12048-1-andrei.otcheretianski@intel.com> From: Ilan Peer 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. Signed-off-by: Ilan Peer --- src/ap/nan_usd_ap.c | 2 +- src/common/nan_de.c | 192 ++++++++++++++++++++++++++++++-- src/common/nan_de.h | 3 +- wpa_supplicant/nan_supplicant.c | 4 +- 4 files changed, 188 insertions(+), 13 deletions(-) diff --git a/src/ap/nan_usd_ap.c b/src/ap/nan_usd_ap.c index 8620644cb3..63696a8d19 100644 --- a/src/ap/nan_usd_ap.c +++ b/src/ap/nan_usd_ap.c @@ -277,5 +277,5 @@ int hostapd_nan_usd_transmit(struct hostapd_data *hapd, int handle, if (!hapd->nan_de) return -1; return nan_de_transmit(hapd->nan_de, handle, ssi, elems, peer_addr, - req_instance_id, NULL); + req_instance_id, NULL, NULL); } diff --git a/src/common/nan_de.c b/src/common/nan_de.c index c5b1022772..f1d23ac340 100644 --- a/src/common/nan_de.c +++ b/src/common/nan_de.c @@ -105,6 +105,19 @@ struct nan_de_service { #define NAN_DE_RSSI_CLOSE_PROXIMITY (-70) /* dBm */ +struct nan_de_tracked_tx { + struct dl_list list; + u8 dst[ETH_ALEN]; + u32 cookie; + u8 digest[SHA256_MAC_LEN]; + bool with_wait; +}; + +enum nan_de_flush_tracked_tx_reason { + NAN_DE_FLUSH_TRACKED_TX_FLUSH_ALL, + NAN_DE_FLUSH_TRACKED_TX_WAIT_EXPIRED, +}; + struct nan_de { u8 nmi[ETH_ALEN]; u8 cluster_id[ETH_ALEN]; @@ -132,6 +145,12 @@ struct nan_de { /* RSSI threshold for close proximity, or zero if not limited */ int rssi_threshold; + /* + * list of transmit requests for which the caller requested + * status indicating if the frame was acknowledged or not. + */ + struct dl_list tracked_tx; + #ifdef CONFIG_TESTING_OPTIONS /* * When set, multicast follow-up SDFs will be sent as Protected Dual of @@ -175,6 +194,7 @@ struct nan_de * nan_de_init(const u8 *nmi, bool offload, bool ap, de->cfg.n_max = NAN_DE_N_MAX; de->rssi_threshold = NAN_DE_RSSI_CLOSE_PROXIMITY; + dl_list_init(&de->tracked_tx); return de; } @@ -210,14 +230,144 @@ static void nan_de_service_deinit(struct nan_de *de, struct nan_de_service *srv, } +static void nan_de_flush_tracked_tx(struct nan_de *de, + enum nan_de_flush_tracked_tx_reason reason) +{ + struct nan_de_tracked_tx *tx, *tmp; + + dl_list_for_each_safe(tx, tmp, &de->tracked_tx, + struct nan_de_tracked_tx, + list) { + if (reason == NAN_DE_FLUSH_TRACKED_TX_WAIT_EXPIRED && + !tx->with_wait) + continue; + + de->cb.transmit_req_status(de->cb.ctx, tx->cookie, + false); + dl_list_del(&tx->list); + os_free(tx); + } +} + + static void nan_de_clear_pending(struct nan_de *de) { + nan_de_flush_tracked_tx(de, + NAN_DE_FLUSH_TRACKED_TX_FLUSH_ALL); + de->listen_freq = 0; de->tx_wait_status_freq = 0; de->tx_wait_end_freq = 0; } +static int nan_de_track_tx_digest(const u8 *data, size_t len, u8 *digest) +{ + return sha256_vector(1, &data, &len, digest); +} + + +static struct nan_de_tracked_tx * +nan_de_add_tracked_tx(struct nan_de *de, const u8 *dst, bool with_wait, + u32 cookie, const struct wpabuf *buf) +{ + struct nan_de_tracked_tx *tx; + u8 digest[SHA256_MAC_LEN]; + + if (!de->cb.transmit_req_status) { + wpa_printf(MSG_DEBUG, + "NAN: No tx_status callback, cannot track Tx"); + return NULL; + } + + if (!cookie) { + wpa_printf(MSG_DEBUG, "NAN: Invalid cookie for Tx tracking"); + return NULL; + } + + if (nan_de_track_tx_digest(wpabuf_head(buf), + wpabuf_len(buf), digest)) { + wpa_printf(MSG_DEBUG, "NAN: Failed to compute Tx digest"); + return NULL; + } + + dl_list_for_each(tx, &de->tracked_tx, struct nan_de_tracked_tx, list) { + if (!ether_addr_equal(tx->dst, dst)) + continue; + + if (tx->cookie == cookie) { + wpa_printf(MSG_DEBUG, + "NAN: Already tracking Tx cookie %u to " + MACSTR, cookie, MAC2STR(dst)); + return NULL; + } + + if (os_memcmp(tx->digest, digest, SHA256_MAC_LEN) == 0) { + wpa_printf(MSG_DEBUG, + "NAN: Already tracking identical payload to " + MACSTR " (cookie %u)", + MAC2STR(dst), tx->cookie); + return NULL; + } + } + + tx = os_zalloc(sizeof(*tx)); + if (!tx) + return NULL; + + os_memcpy(tx->dst, dst, ETH_ALEN); + tx->cookie = cookie; + tx->with_wait = with_wait; + os_memcpy(tx->digest, digest, SHA256_MAC_LEN); + + dl_list_add(&de->tracked_tx, &tx->list); + + wpa_printf(MSG_DEBUG, "NAN: Track Tx cookie %u", tx->cookie); + wpa_hexdump(MSG_DEBUG, "NAN: Track Tx digest", + tx->digest, SHA256_MAC_LEN); + + return tx; +} + + +static void nan_de_tx_status_match(struct nan_de *de, const u8 *data, + size_t len, u8 acked) +{ + struct nan_de_tracked_tx *tx; + const struct ieee80211_mgmt *mgmt = (void *)data; + const u8 *pos = (void *)&mgmt->u.action; + + if (len <= offsetof(struct ieee80211_mgmt, u.action)) + return; + + len = data + len - pos; + + { + u8 digest[SHA256_MAC_LEN]; + + if (nan_de_track_tx_digest(pos, len, digest)) + return; + + dl_list_for_each(tx, &de->tracked_tx, + struct nan_de_tracked_tx, list) { + if (!ether_addr_equal(tx->dst, mgmt->da) || + os_memcmp(tx->digest, digest, SHA256_MAC_LEN)) + continue; + + wpa_printf(MSG_DEBUG, + "NAN: Tx status for cookie=%u ack=%u", + tx->cookie, acked); + + de->cb.transmit_req_status(de->cb.ctx, tx->cookie, + acked); + dl_list_del(&tx->list); + os_free(tx); + return; + } + } +} + + void nan_de_flush(struct nan_de *de) { unsigned int i; @@ -294,16 +444,30 @@ static struct wpabuf * nan_de_alloc_sdf(struct nan_de *de, const u8 *dst, static int nan_de_tx(struct nan_de *de, unsigned int freq, unsigned int wait_time, const u8 *dst, const u8 *src, const u8 *bssid, - const struct wpabuf *buf) + const struct wpabuf *buf, + u32 *cookie) { + struct nan_de_tracked_tx *tracked_tx = NULL; int res; if (!de->cb.tx) return -1; + if (cookie) { + tracked_tx = nan_de_add_tracked_tx(de, dst, !!wait_time, + *cookie, buf); + if (!tracked_tx) + return -1; + } + res = de->cb.tx(de->cb.ctx, freq, wait_time, dst, src, bssid, buf); - if (res < 0) + if (res < 0) { + if (tracked_tx) { + dl_list_del(&tracked_tx->list); + os_free(tracked_tx); + } return res; + } de->tx_wait_status_freq = freq; de->tx_wait_end_freq = wait_time ? freq : 0; @@ -337,7 +501,8 @@ static void nan_de_tx_sdf(struct nan_de *de, struct nan_de_service *srv, enum nan_service_control_type type, const u8 *dst, const u8 *a3, u8 req_instance_id, const struct wpabuf *ssi, - const struct wpabuf *attrs) + const struct wpabuf *attrs, + u32 *cookie) { struct wpabuf *buf; size_t len = 0, sda_len, sdea_len; @@ -514,7 +679,7 @@ static void nan_de_tx_sdf(struct nan_de *de, struct nan_de_service *srv, } nan_de_tx(de, srv->sync ? 0 : srv->freq, srv->sync ? 0 : wait_time, - dst, forced_addr, a3, buf); + dst, forced_addr, a3, buf, cookie); wpabuf_free(buf); } @@ -619,7 +784,7 @@ static void nan_de_tx_multicast(struct nan_de *de, struct nan_de_service *srv, } nan_de_tx_sdf(de, srv, wait_time, type, network_id, bssid, - req_instance_id, srv->ssi, NULL); + req_instance_id, srv->ssi, NULL, NULL); os_get_reltime(&srv->last_multicast); } @@ -1058,6 +1223,8 @@ void nan_de_tx_status(struct nan_de *de, unsigned int freq, const u8 *dst, { if (freq == de->tx_wait_status_freq) de->tx_wait_status_freq = 0; + + nan_de_tx_status_match(de, data, data_len, ack); } @@ -1067,6 +1234,10 @@ void nan_de_tx_wait_ended(struct nan_de *de) wpa_printf(MSG_DEBUG, "NAN: TX wait for response ended (freq=%u)", de->tx_wait_end_freq); + + nan_de_flush_tracked_tx(de, + NAN_DE_FLUSH_TRACKED_TX_WAIT_EXPIRED); + de->tx_wait_end_freq = 0; nan_de_run_timer(de); } @@ -1525,7 +1696,7 @@ static bool nan_de_rx_publish(struct nan_de *de, struct nan_de_service *srv, * Service Specific Info field if it received a matching * unsolicited Publish message. */ nan_de_transmit(de, srv->id, NULL, NULL, peer_addr, - instance_id, NULL); + instance_id, NULL, NULL); } send_event: @@ -1630,7 +1801,8 @@ static bool nan_de_rx_subscribe(struct nan_de *de, struct nan_de_service *srv, nan_de_tx_sdf(de, srv, 100, NAN_SRV_CTRL_PUBLISH, srv->publish.solicited_multicast ? - network_id : peer_addr, a3, instance_id, srv->ssi, NULL); + network_id : peer_addr, a3, instance_id, srv->ssi, NULL, + NULL); if (!srv->is_p2p && !srv->sync) nan_de_pause_state(srv, peer_addr, instance_id); @@ -2457,7 +2629,8 @@ void nan_de_cancel_subscribe(struct nan_de *de, int subscribe_id) int nan_de_transmit(struct nan_de *de, int handle, const struct wpabuf *ssi, const struct wpabuf *elems, const u8 *peer_addr, u8 req_instance_id, - const struct wpabuf *nan_attrs) + const struct wpabuf *nan_attrs, + u32 *cookie) { struct nan_de_service *srv; const u8 *a3; @@ -2488,7 +2661,8 @@ int nan_de_transmit(struct nan_de *de, int handle, else a3 = network_id; nan_de_tx_sdf(de, srv, 100, NAN_SRV_CTRL_FOLLOW_UP, - peer_addr, a3, req_instance_id, ssi, nan_attrs); + peer_addr, a3, req_instance_id, ssi, nan_attrs, + cookie); srv->listen_stopped = false; return 0; diff --git a/src/common/nan_de.h b/src/common/nan_de.h index e3d872f38e..cbfd173d58 100644 --- a/src/common/nan_de.h +++ b/src/common/nan_de.h @@ -82,6 +82,7 @@ struct nan_callbacks { unsigned int freq); void (*add_extra_attrs)(void *ctx, struct wpabuf *buf); bool (*is_peer_paired)(void *ctx, const u8 *addr); + void (*transmit_req_status)(void *ctx, u32 cookie, bool ack); }; bool nan_de_is_nan_network_id(const u8 *addr); @@ -270,7 +271,7 @@ void nan_de_cancel_subscribe(struct nan_de *de, int subscribe_id); int nan_de_transmit(struct nan_de *de, int handle, const struct wpabuf *ssi, const struct wpabuf *elems, const u8 *peer_addr, u8 req_instance_id, - const struct wpabuf *nan_attrs); + const struct wpabuf *nan_attrs, u32 *cookie); void nan_de_dw_trigger(struct nan_de *de, int freq); void nan_de_set_cluster_id(struct nan_de *de, const u8 *cluster_id); diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c index c08c095f9e..fe20919ad4 100644 --- a/wpa_supplicant/nan_supplicant.c +++ b/wpa_supplicant/nan_supplicant.c @@ -1021,7 +1021,7 @@ static int wpas_nan_transmit_followup_cb(void *ctx, const u8 *peer_nmi, return -1; return nan_de_transmit(wpa_s->nan_de, handle, NULL, NULL, - peer_nmi, req_instance_id, attrs); + peer_nmi, req_instance_id, attrs, NULL); } @@ -4729,7 +4729,7 @@ int wpas_nan_transmit(struct wpa_supplicant *wpa_s, int handle, if (!wpa_s->nan_de) return -1; return nan_de_transmit(wpa_s->nan_de, handle, ssi, elems, peer_addr, - req_instance_id, NULL); + req_instance_id, NULL, NULL); } -- 2.53.0 From andrei.otcheretianski at intel.com Tue May 12 23:59:00 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Wed, 13 May 2026 09:59:00 +0300 Subject: [PATCH] NAN: Do not clear NAN NDL state on failure Message-ID: <20260513065909.12048-2-andrei.otcheretianski@intel.com> From: Ilan Peer When an NDP response is rejected by the subscriber a NAF with rejection status needs to be sent to the publisher. Since in the NDL processing of the NDP rejection, the peer NMI station is removed and with it the resource allocated to it, e.g., peer schedule etc., the NAF would be sent on the general resources of NAN Device and the transmission can be delayed (or even dropped). To fix this, when a NAF is expected to be sent to the peer, do not remove the peer NMI station (it would be removed on Tx status handling). This way, the NAF would be sent using the resources allocated to the station. Signed-off-by: Ilan Peer --- src/nan/nan_ndl.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/nan/nan_ndl.c b/src/nan/nan_ndl.c index c463f05595..9ee1867e1d 100644 --- a/src/nan/nan_ndl.c +++ b/src/nan/nan_ndl.c @@ -736,11 +736,15 @@ out_fail: ndl->status = NAN_NDL_STATUS_REJECTED; ndl->reason = reason; ndl->send_naf_on_error = 1; + + /* + * Do not modify the state. Full cleanup will be done on Tx + * status handling + */ + } else { + nan_ndl_clear(nan, peer); } - /* Clear the NDL info but leave the state, status, and reason. Full - * cleanup will be done on Tx status handling. */ - nan_ndl_clear(nan, peer); return -1; } -- 2.53.0 From andrei.otcheretianski at intel.com Tue May 12 23:59:01 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Wed, 13 May 2026 09:59:01 +0300 Subject: [PATCH 1/5] NAN: Refactor NAN module cleanup functions Message-ID: <20260513065909.12048-3-andrei.otcheretianski@intel.com> From: Ilan Peer - When NAN is de-initialized also flush the state. - As the NAN module maintains state even when it is not started, do not condition the flush flow with NAN being started. For example, NAN peers can be added while NAN is not started etc. - As wpas_nan_stop() might not be called, i.e., only wpas_nan_deinit() is called, clear the NAN DE cluster ID in the nan_stop() callback (which is always called). Signed-off-by: Ilan Peer --- src/nan/nan.c | 13 ++++++------- wpa_supplicant/nan_supplicant.c | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/nan/nan.c b/src/nan/nan.c index e3fe12e84e..29c6eed457 100644 --- a/src/nan/nan.c +++ b/src/nan/nan.c @@ -273,7 +273,10 @@ static void nan_peer_clear_all(struct nan_data *nan) void nan_deinit(struct nan_data *nan) { wpa_printf(MSG_DEBUG, "NAN: Deinit"); - nan_peer_clear_all(nan); + + nan_stop(nan); + nan_flush(nan); + #ifdef CONFIG_PASN pasn_initiator_pmksa_cache_deinit(nan->initiator_pmksa); pasn_responder_pmksa_cache_deinit(nan->responder_pmksa); @@ -414,12 +417,6 @@ void nan_flush(struct nan_data *nan) { wpa_printf(MSG_DEBUG, "NAN: Reset internal state"); - if (!nan->nan_started) { - wpa_printf(MSG_DEBUG, "NAN: Already stopped"); - return; - } - - nan->nan_started = 0; nan_peer_clear_all(nan); wpabuf_free(nan->sched.elems); os_memset(&nan->sched, 0, sizeof(nan->sched)); @@ -457,7 +454,9 @@ void nan_stop(struct nan_data *nan) nan->bigtk_id = 0; } + /* Even though NAN is stopping, flush internal state */ nan_flush(nan); + nan->nan_started = 0; nan->cfg->stop(nan->cfg->cb_ctx); } diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c index 5d80489411..9ffd0ff946 100644 --- a/wpa_supplicant/nan_supplicant.c +++ b/wpa_supplicant/nan_supplicant.c @@ -194,6 +194,7 @@ static void wpas_nan_stop_cb(void *ctx) } wpa_drv_nan_stop(wpa_s); + nan_de_set_cluster_id(wpa_s->nan_de, NULL); } @@ -1535,7 +1536,6 @@ int wpas_nan_stop(struct wpa_supplicant *wpa_s) return -1; nan_stop(wpa_s->nan); - nan_de_set_cluster_id(wpa_s->nan_de, NULL); return 0; } -- 2.53.0 From andrei.otcheretianski at intel.com Tue May 12 23:59:02 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Wed, 13 May 2026 09:59:02 +0300 Subject: [PATCH] wpa_supplicant: Store absolute path to P2P config file Message-ID: <20260513065909.12048-4-andrei.otcheretianski@intel.com> When wpa_supplicant is daemonized, relative paths are not valid anymore. If the P2P device is toggled (disabled/enabled) when wpa_supplicant runs with -B option, the configuration isn't found and P2P device interface can't be re-added. Signed-off-by: Andrei Otcheretianski --- wpa_supplicant/main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wpa_supplicant/main.c b/wpa_supplicant/main.c index ec8a90bc09..eeb3606ade 100644 --- a/wpa_supplicant/main.c +++ b/wpa_supplicant/main.c @@ -275,7 +275,8 @@ int main(int argc, char *argv[]) goto out; #ifdef CONFIG_P2P case 'm': - params.conf_p2p_dev = optarg; + os_free(params.conf_p2p_dev); + params.conf_p2p_dev = os_rel2abs_path(optarg); break; #endif /* CONFIG_P2P */ case 'o': @@ -432,6 +433,9 @@ out: os_free(params.match_ifaces); #endif /* CONFIG_MATCH_IFACE */ os_free(params.pid_file); +#ifdef CONFIG_P2P + os_free(params.conf_p2p_dev); +#endif /* CONFIG_P2P */ crypto_unload(); os_program_deinit(); -- 2.53.0 From andrei.otcheretianski at intel.com Tue May 12 23:59:03 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Wed, 13 May 2026 09:59:03 +0300 Subject: [PATCH 2/5] wpa_supplicant: Stop NAN when interface is removed or disabled Message-ID: <20260513065909.12048-5-andrei.otcheretianski@intel.com> From: Ilan Peer - Stop and de-initialize NAN functionality when the associated interface is removed or disabled. - Add a control interface notification indicating that NAN functionality has stopped. Signed-off-by: Ilan Peer --- src/common/wpa_ctrl.h | 1 + wpa_supplicant/nan_supplicant.c | 1 + wpa_supplicant/notify.c | 7 +++++++ wpa_supplicant/notify.h | 1 + wpa_supplicant/wpa_supplicant.c | 9 +++++++++ 5 files changed, 19 insertions(+) diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 1472553d9c..90597aa7aa 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -270,6 +270,7 @@ extern "C" { * parameters: map_id= freq= */ #define NAN_CHAN_EVACUATION "NAN-CHAN-EVACUATION " +#define NAN_STOPPED "NAN-STOPPED " /* MESH events */ #define MESH_GROUP_STARTED "MESH-GROUP-STARTED " diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c index 9ffd0ff946..bc9baa4b7f 100644 --- a/wpa_supplicant/nan_supplicant.c +++ b/wpa_supplicant/nan_supplicant.c @@ -195,6 +195,7 @@ static void wpas_nan_stop_cb(void *ctx) wpa_drv_nan_stop(wpa_s); nan_de_set_cluster_id(wpa_s->nan_de, NULL); + wpas_notify_nan_stopped(wpa_s); } diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c index 171508d12f..b816b71597 100644 --- a/wpa_supplicant/notify.c +++ b/wpa_supplicant/notify.c @@ -1531,6 +1531,13 @@ void wpas_notify_nan_chan_evacuation(struct wpa_supplicant *wpa_s, map_id, freq); } + +void wpas_notify_nan_stopped(struct wpa_supplicant *wpa_s) +{ + wpa_msg_global(wpa_s, MSG_INFO, NAN_STOPPED "ifname=%s", + wpa_s->ifname); +} + #endif /* CONFIG_NAN || CONFIG_NAN_USD */ diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h index f8ce563cd7..fafa7d80e4 100644 --- a/wpa_supplicant/notify.h +++ b/wpa_supplicant/notify.h @@ -259,5 +259,6 @@ void wpas_notify_nan_pairing_status(struct wpa_supplicant *wpa_s, u16 status, const u8 *nd_pmk); void wpas_notify_nan_chan_evacuation(struct wpa_supplicant *wpa_s, u8 map_id, int freq); +void wpas_notify_nan_stopped(struct wpa_supplicant *wpa_s); #endif /* NOTIFY_H */ diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 5414eab0fb..3c50ba368a 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -1167,6 +1167,15 @@ void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s, if (state == WPA_INTERFACE_DISABLED) { /* Assure normal scan when interface is restored */ wpa_s->normal_scans = 0; + + /* + * A NAN management interface is not expected to be disabled. If + * it disabled, it means that NAN functionality is no longer + * possible so deinit (which would also stop any ongoing NAN + * operations) + */ + if (wpa_s->nan_mgmt) + wpas_nan_deinit(wpa_s); } if (state == WPA_COMPLETED) { -- 2.53.0 From andrei.otcheretianski at intel.com Tue May 12 23:59:04 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Wed, 13 May 2026 09:59:04 +0300 Subject: [PATCH 2/3] wpa_supplicant: Support tracking NAN transmit requests Message-ID: <20260513065909.12048-6-andrei.otcheretianski@intel.com> From: Ilan Peer Extended the NAN transmit API to also include a cookie, so that when the NAN DE finishes transmitting the frame it would notify the higher layers whether the frame was acknowledged or not. Signed-off-by: Ilan Peer --- src/common/wpa_ctrl.h | 1 + wpa_supplicant/ctrl_iface.c | 17 ++++++++++++++++- wpa_supplicant/dbus/dbus_new_handlers.c | 2 +- wpa_supplicant/nan_supplicant.c | 19 +++++++++++++++++-- wpa_supplicant/nan_supplicant.h | 3 ++- wpa_supplicant/notify.c | 8 ++++++++ wpa_supplicant/notify.h | 2 ++ 7 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index b16790fe67..1472553d9c 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -246,6 +246,7 @@ extern "C" { #define NAN_PUBLISH_TERMINATED "NAN-PUBLISH-TERMINATED " #define NAN_SUBSCRIBE_TERMINATED "NAN-SUBSCRIBE-TERMINATED " #define NAN_RECEIVE "NAN-RECEIVE " +#define NAN_TRANSMIT_STATUS "NAN-TRANSMIT-STATUS " #define NAN_CLUSTER_JOIN "NAN-CLUSTER-JOIN " #define NAN_NDP_REQUEST "NAN-NDP-REQUEST " #define NAN_NDP_COUNTER_REQUEST "NAN-NDP-COUNTER-REQUEST " diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 14b4cd9d2e..fbdf72aa03 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -13266,6 +13266,8 @@ static int wpas_ctrl_nan_transmit(struct wpa_supplicant *wpa_s, char *cmd) struct wpabuf *ssi = NULL; u8 peer_addr[ETH_ALEN]; int ret = -1; + u32 cookie = 0; + u32 *cookie_ptr = NULL; os_memset(peer_addr, 0, ETH_ALEN); @@ -13291,6 +13293,19 @@ static int wpas_ctrl_nan_transmit(struct wpa_supplicant *wpa_s, char *cmd) continue; } + if (os_strncmp(token, "cookie=", 7) == 0) { + cookie = strtoul(token + 7, NULL, 0); + if (!cookie) { + wpa_printf(MSG_INFO, + "CTRL: Invalid cookie value: %s", + token + 7); + goto fail; + } + + cookie_ptr = &cookie; + continue; + } + wpa_printf(MSG_INFO, "CTRL: Invalid NAN_TRANSMIT parameter: %s", token); @@ -13310,7 +13325,7 @@ static int wpas_ctrl_nan_transmit(struct wpa_supplicant *wpa_s, char *cmd) } ret = wpas_nan_transmit(wpa_s, handle, ssi, NULL, peer_addr, - req_instance_id); + req_instance_id, cookie_ptr); fail: wpabuf_free(ssi); return ret; diff --git a/wpa_supplicant/dbus/dbus_new_handlers.c b/wpa_supplicant/dbus/dbus_new_handlers.c index cb4a8a6d38..a9f5679443 100644 --- a/wpa_supplicant/dbus/dbus_new_handlers.c +++ b/wpa_supplicant/dbus/dbus_new_handlers.c @@ -7035,7 +7035,7 @@ DBusMessage * wpas_dbus_handler_nan_transmit(DBusMessage *message, goto fail; if (wpas_nan_transmit(wpa_s, handle, ssi, NULL, peer_addr, - req_instance_id) < 0) + req_instance_id, NULL) < 0) reply = wpas_dbus_error_unknown_error( message, "failed to transmit follow-up"); out: diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c index fe20919ad4..5d80489411 100644 --- a/wpa_supplicant/nan_supplicant.c +++ b/wpa_supplicant/nan_supplicant.c @@ -4337,6 +4337,14 @@ static void wpas_nan_de_receive(void *ctx, int id, int peer_instance_id, } +static void wpas_nan_de_transmit_req_status(void *ctx, u32 cookie, bool ack) +{ + struct wpa_supplicant *wpa_s = ctx; + + wpas_notify_nan_transmit_req_status(wpa_s, cookie, ack); +} + + #ifdef CONFIG_P2P static void wpas_nan_process_p2p_usd_elems(void *ctx, const u8 *buf, u16 buf_len, const u8 *peer_addr, @@ -4388,6 +4396,7 @@ int wpas_nan_de_init(struct wpa_supplicant *wpa_s) cb.offload_cancel_publish = wpas_nan_usd_offload_cancel_publish; cb.offload_cancel_subscribe = wpas_nan_usd_offload_cancel_subscribe; cb.receive = wpas_nan_de_receive; + cb.transmit_req_status = wpas_nan_de_transmit_req_status; #ifdef CONFIG_P2P cb.process_p2p_usd_elems = wpas_nan_process_p2p_usd_elems; #endif /* CONFIG_P2P */ @@ -4724,12 +4733,13 @@ int wpas_nan_usd_subscribe_stop_listen(struct wpa_supplicant *wpa_s, int wpas_nan_transmit(struct wpa_supplicant *wpa_s, int handle, const struct wpabuf *ssi, const struct wpabuf *elems, - const u8 *peer_addr, u8 req_instance_id) + const u8 *peer_addr, u8 req_instance_id, + u32 *cookie) { if (!wpa_s->nan_de) return -1; return nan_de_transmit(wpa_s->nan_de, handle, ssi, elems, peer_addr, - req_instance_id, NULL, NULL); + req_instance_id, NULL, cookie); } @@ -4864,6 +4874,11 @@ int wpas_nan_tx_status(struct wpa_supplicant *wpa_s, (const struct ieee80211_mgmt *) data; wpa_s = wpas_nan_get_mgmt_iface(wpa_s); + + if (wpa_s->nan_de) + nan_de_tx_status(wpa_s->nan_de, 0, mgmt->da, data, data_len, + acked); + if (!wpas_nan_ndp_allowed(wpa_s)) return -1; diff --git a/wpa_supplicant/nan_supplicant.h b/wpa_supplicant/nan_supplicant.h index 9d7347f365..b8676a86c1 100644 --- a/wpa_supplicant/nan_supplicant.h +++ b/wpa_supplicant/nan_supplicant.h @@ -150,7 +150,8 @@ void wpas_nan_cancel_subscribe(struct wpa_supplicant *wpa_s, int subscribe_id); int wpas_nan_transmit(struct wpa_supplicant *wpa_s, int handle, const struct wpabuf *ssi, const struct wpabuf *elems, - const u8 *peer_addr, u8 req_instance_id); + const u8 *peer_addr, u8 req_instance_id, + u32 *cookie); int wpas_nan_tx_status(struct wpa_supplicant *wpa_s, const u8 *data, size_t data_len, int acked); diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c index d0b317ee1f..171508d12f 100644 --- a/wpa_supplicant/notify.c +++ b/wpa_supplicant/notify.c @@ -1268,6 +1268,14 @@ void wpas_notify_nan_subscribe_terminated(struct wpa_supplicant *wpa_s, } +void wpas_notify_nan_transmit_req_status(struct wpa_supplicant *wpa_s, + u32 cookie, bool acked) +{ + wpa_msg_global(wpa_s, MSG_INFO, NAN_TRANSMIT_STATUS + "cookie=%u acked=%u", cookie, acked); +} + + void wpas_notify_nan_bootstrap_request(struct wpa_supplicant *wpa_s, const u8 *peer_nmi, u16 pbm, int handle, u8 requestor_instance_id) diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h index f779a613a4..f8ce563cd7 100644 --- a/wpa_supplicant/notify.h +++ b/wpa_supplicant/notify.h @@ -201,6 +201,8 @@ void wpas_notify_nan_publish_terminated(struct wpa_supplicant *wpa_s, void wpas_notify_nan_subscribe_terminated(struct wpa_supplicant *wpa_s, int subscribe_id, enum nan_de_reason reason); +void wpas_notify_nan_transmit_req_status(struct wpa_supplicant *wpa_s, + u32 cookie, bool acked); void wpas_notify_nan_nik_received(struct wpa_supplicant *wpa_s, const u8 *nik, size_t nik_len, int cipher_ver, int akmp, -- 2.53.0 From andrei.otcheretianski at intel.com Tue May 12 23:59:05 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Wed, 13 May 2026 09:59:05 +0300 Subject: [PATCH 3/5] NAN: Handle removal of NAN interfaces Message-ID: <20260513065909.12048-7-andrei.otcheretianski@intel.com> From: Ilan Peer When the driver notifies that a NAN Data interface is removed, terminate all the NDPs which are associated with the interface. If the interface was dynamically added by upper layers, remove it. If a NAN management interface that was dynamically added is removed, remove it (NAN functionality is stopped when the interface is disabled). Signed-off-by: Ilan Peer --- src/nan/nan.c | 28 ++++++++++++++++++++++++++++ src/nan/nan.h | 2 ++ wpa_supplicant/events.c | 24 ++++++++++++++++++++++++ wpa_supplicant/nan_supplicant.c | 15 +++++++++++++++ wpa_supplicant/nan_supplicant.h | 1 + 5 files changed, 70 insertions(+) diff --git a/src/nan/nan.c b/src/nan/nan.c index 29c6eed457..cacbb2c869 100644 --- a/src/nan/nan.c +++ b/src/nan/nan.c @@ -3622,3 +3622,31 @@ int nan_peer_dump_ndps_to_buf(struct nan_data *nan, const u8 *addr, return pos - buf; } + + +/** + * nan_terminate_ndi_ndps - Terminate all NDPs with a given NDI address + * @nan: NAN module context from nan_init() + * @ndi_addr: NDI address for which all NDPs should be terminated + * + * This function terminates all NDPs that have the given NDI address as either + * initiator or responder NDI. + */ +void nan_terminate_ndi_ndps(struct nan_data *nan, const u8 *ndi_addr) +{ + struct nan_peer *peer; + + if (!nan) + return; + + dl_list_for_each(peer, &nan->peer_list, struct nan_peer, list) { + /* + * It is possible that an NDP setup in progress + * is not on the NDI that is being removed. However, + * to simplify things, stop the setup, so the other + * NDPs could be cleanly removed. + */ + nan_ndp_setup_stop(nan, peer); + nan_terminate_ndps_for_ndi(nan, peer, ndi_addr); + } +} diff --git a/src/nan/nan.h b/src/nan/nan.h index 4d5f6fc0ef..ae3c3032ab 100644 --- a/src/nan/nan.h +++ b/src/nan/nan.h @@ -876,6 +876,8 @@ bool nan_has_active_ndp(struct nan_data *nan); int nan_get_status(struct nan_data *nan, char *buf, size_t buflen); int nan_peer_dump_ndps_to_buf(struct nan_data *nan, const u8 *addr, char *buf, size_t buflen); +void nan_terminate_ndi_ndps(struct nan_data *nan, const u8 *ndi_addr); + #ifdef CONFIG_PASN int nan_pairing_add_attrs(struct nan_data *nan_data, struct wpabuf *buf); int nan_pairing_initiate_pasn_auth(struct nan_data *nan_data, const u8 *addr, diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 0684ada719..4bac52dc01 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -5344,6 +5344,30 @@ wpa_supplicant_event_interface_status(struct wpa_supplicant *wpa_s, } #endif /* CONFIG_P2P */ +#ifdef CONFIG_NAN + if (wpa_s->nan_data) { + wpa_printf(MSG_DEBUG, "%s: NAN data interface removed", + wpa_s->ifname); + + wpas_nan_data_interface_removed(wpa_s); + wpa_supplicant_remove_iface(wpa_s->global, wpa_s, 0); + break; + } + + if (wpa_s->nan_mgmt) { + wpa_printf(MSG_DEBUG, + "%s: NAN management interface removed", + wpa_s->ifname); + + /* + * Note: NAN is stopped when the interface is + * disabled + */ + wpa_supplicant_remove_iface(wpa_s->global, wpa_s, 0); + break; + } + +#endif /* CONFIG_NAN */ #ifdef CONFIG_MATCH_IFACE if (wpa_s->matched) { wpa_supplicant_remove_iface(wpa_s->global, wpa_s, 0); diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c index bc9baa4b7f..1f0de12e1b 100644 --- a/wpa_supplicant/nan_supplicant.c +++ b/wpa_supplicant/nan_supplicant.c @@ -4916,3 +4916,18 @@ void wpas_nan_rx_naf(struct wpa_supplicant *wpa_s, nan_action_rx(wpa_s->nan, mgmt, len); } #endif /* CONFIG_NAN */ + + +void wpas_nan_data_interface_removed(struct wpa_supplicant *wpa_s) +{ + struct wpa_supplicant *nan_dev_wpas = wpas_nan_get_mgmt_iface(wpa_s); + + wpa_printf(MSG_DEBUG, + "NAN: Data interface removed (%s) terminate NDPs on " MACSTR, + wpa_s->ifname, MAC2STR(wpa_s->own_addr)); + + if (!nan_dev_wpas) + return; + + nan_terminate_ndi_ndps(nan_dev_wpas->nan, wpa_s->own_addr); +} diff --git a/wpa_supplicant/nan_supplicant.h b/wpa_supplicant/nan_supplicant.h index b8676a86c1..492ac7f58f 100644 --- a/wpa_supplicant/nan_supplicant.h +++ b/wpa_supplicant/nan_supplicant.h @@ -43,6 +43,7 @@ int wpas_nan_status(struct wpa_supplicant *wpa_s, char *reply, int wpas_nan_bootstrap_request(struct wpa_supplicant *wpa_s, char *cmd); int wpas_nan_bootstrap_reset(struct wpa_supplicant *wpa_s, char *cmd); bool wpas_nan_is_peer_paired(struct wpa_supplicant *wpa_s, const u8 *peer_addr); +void wpas_nan_data_interface_removed(struct wpa_supplicant *wpa_s); int wpas_nan_pair(struct wpa_supplicant *wpa_s, const u8 *peer_addr, u8 auth_mode, int cipher, int handle, u8 peer_instance_id, -- 2.53.0 From andrei.otcheretianski at intel.com Tue May 12 23:59:06 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Wed, 13 May 2026 09:59:06 +0300 Subject: [PATCH 3/3] tests: Add NAN test to cover followup status tracking Message-ID: <20260513065909.12048-8-andrei.otcheretianski@intel.com> From: Ilan Peer Add a test to verify that when a NAN transmit is requested with a cookie, the corresponding event is sent by the wpa_supplicant with valid acknowledgment status Signed-off-by: Ilan Peer --- tests/hwsim/test_nan.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/hwsim/test_nan.py b/tests/hwsim/test_nan.py index 1c16ba47c0..691da50032 100644 --- a/tests/hwsim/test_nan.py +++ b/tests/hwsim/test_nan.py @@ -279,11 +279,15 @@ class NanDevice: if "OK" not in self.wpas.request("NAN_UPDATE_CONFIG"): raise Exception(f"{self.ifname}: failed to update NAN configuration") - def transmit(self, handle, req_instance_id, address, ssi=None): + def transmit(self, handle, req_instance_id, address, ssi=None, cookie=None): logger.info(f"Transmitting followup on {self.ifname}") cmd = f"NAN_TRANSMIT handle={handle} req_instance_id={req_instance_id} address={address}" if ssi is not None: cmd += f" ssi={ssi}" + + if cookie is not None: + cmd += f" cookie={cookie}" + if "OK" not in self.wpas.request(cmd): raise Exception(f"{self.ifname}: failed to transmit NAN followup") @@ -449,6 +453,35 @@ def test_nan_sync_followup(dev, apdev, params): if ev is None or f"id={pid}" not in ev or f"peer_instance_id={sid}" not in ev or "ssi=11223344" not in ev: raise Exception("NAN-RECEIVE followup event not seen or invalid format") +def test_nan_sync_followup_tracking(dev, apdev, params): + """NAN synchronized active subscribe and solicited publish with followup tracking""" + with hwsim_nan_radios(count=2) as [wpas1, wpas2], \ + NanDevice(wpas1, "nan0") as pub, NanDevice(wpas2, "nan1") as sub: + pid, sid, paddr, _ = nan_sync_discovery(pub, sub, "test_service", + pssi="aabbccdd", sssi="ddbbccaa", + unsolicited=0, timeout=2) + + # Check followup to the publisher. Acknowledgment is expected. + cookie = 127 + sub.transmit(handle=sid, req_instance_id=pid, address=paddr, ssi="11223344", cookie=cookie) + ev = pub.wpas.wait_event(["NAN-RECEIVE"], timeout=2) + if ev is None or f"id={pid}" not in ev or f"peer_instance_id={sid}" not in ev or "ssi=11223344" not in ev: + raise Exception("NAN-RECEIVE followup event not seen or invalid format") + + ev = sub.wpas.wait_event(["NAN-TRANSMIT-STATUS"], timeout=2) + if ev is None or f"cookie={cookie}" not in ev or "acked=1" not in ev: + raise Exception("NAN-TX-STATUS event not seen or invalid data") + + # Check followup to an invalid address. Acknowledgment is not expected. + cookie = 243 + suffix = int(paddr[-2:], 16) ^ 0xFF + addr = paddr[:-2] + f"{suffix:02x}" + + sub.transmit(handle=sid, req_instance_id=pid, address=addr, cookie=cookie) + ev = sub.wpas.wait_event(["NAN-TRANSMIT-STATUS"], timeout=2) + if ev is None or f"cookie={cookie}" not in ev or "acked=0" not in ev: + raise Exception("NAN-TX-STATUS event not seen or invalid data") + def test_nan_sync_active_subscribe_two_publishers(dev, apdev, params): """NAN synchronized active subscribe and 2 publishers""" with hwsim_nan_radios(count=3) as [wpas1, wpas2, wpas3], \ -- 2.53.0 From andrei.otcheretianski at intel.com Tue May 12 23:59:07 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Wed, 13 May 2026 09:59:07 +0300 Subject: [PATCH 4/5] nl80211: Handle removal of a NAN management interface Message-ID: <20260513065909.12048-9-andrei.otcheretianski@intel.com> From: Ilan Peer While a netdev interface removal is handled by the nl80211 driver, removal of a NAN Device interface is not. To handle NAN Device interface removal, register for receiving nl80211 configuration events, and when a NAN device interface is removed, propagate this event to the wpa_supplicant, so it could properly cleanup its state. Signed-off-by: Ilan Peer --- src/drivers/driver_nl80211.c | 10 ++++++++++ src/drivers/driver_nl80211_event.c | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index ea89bcd8cc..2c2ea72abc 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -2199,6 +2199,16 @@ static int wpa_driver_nl80211_init_nl_global(struct nl80211_global *global) nl_cb_set(global->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, process_global_event, global); + ret = nl_get_multicast_id(global, "nl80211", "config"); + if (ret >= 0) + ret = nl_socket_add_membership(global->nl_event, ret); + if (ret < 0) { + wpa_printf(MSG_ERROR, "nl80211: Could not add multicast " + "membership for config events: %d (%s)", + ret, nl_geterror(ret)); + goto err; + } + ret = nl_get_multicast_id(global, "nl80211", "scan"); if (ret >= 0) ret = nl_socket_add_membership(global->nl_event, ret); diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index a281f59536..87f90aa6ce 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -4406,6 +4406,28 @@ nl80211_nan_channel_evacuate_event(struct wpa_driver_nl80211_data *drv, wpa_supplicant_event(drv->ctx, EVENT_NAN_CHAN_EVACUATION, &data); } + +static void +nl80211_nan_del_interface_event(struct i802_bss *bss, struct nlattr **tb) +{ + union wpa_event_data event; + + os_memset(&event, 0, sizeof(event)); + + wpa_printf(MSG_DEBUG, "nl80211: NAN interface removed event for %s", + bss->ifname); + + os_strlcpy(event.interface_status.ifname, bss->ifname, + sizeof(event.interface_status.ifname)); + + event.interface_status.ievent = EVENT_INTERFACE_REMOVED; + + bss->drv->nan_started = false; + + wpa_supplicant_event(bss->ctx, EVENT_INTERFACE_STATUS, &event); +} + + #endif /* CONFIG_NAN */ @@ -4786,6 +4808,10 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd, case NL80211_CMD_NAN_CHANNEL_EVAC: nl80211_nan_channel_evacuate_event(drv, tb); break; + case NL80211_CMD_DEL_INTERFACE: + if (drv->nlmode == NL80211_IFTYPE_NAN) + nl80211_nan_del_interface_event(bss, tb); + break; #endif /* CONFIG_NAN */ case NL80211_CMD_INCUMBENT_SIGNAL_DETECT: nl80211_incumbt_sig_intf_event(bss, tb); -- 2.53.0 From andrei.otcheretianski at intel.com Tue May 12 23:59:08 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Wed, 13 May 2026 09:59:08 +0300 Subject: [PATCH 5/5] tests: Add NAN-STOPPED event tests on radio destruction Message-ID: <20260513065909.12048-10-andrei.otcheretianski@intel.com> From: Ilan Peer Add tests to verify that NAN-STOPPED is reported when the underlying radio is destroyed while NAN is active: - test_nan_stopped_on_iface_removal: Verifies NAN-STOPPED after radio destruction during service discovery, then recovers NAN on a new radio. - test_nan_stopped_on_iface_removal_with_ndp: Same scenario but with an active NDP (NAN Data Path) including connectivity verification. Signed-off-by: Ilan Peer --- tests/hwsim/test_nan.py | 144 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/tests/hwsim/test_nan.py b/tests/hwsim/test_nan.py index 691da50032..c2905c9933 100644 --- a/tests/hwsim/test_nan.py +++ b/tests/hwsim/test_nan.py @@ -10,9 +10,10 @@ logger = logging.getLogger() from utils import * import string import hwsim_utils -from hwsim import HWSimRadio +from hwsim import HWSimRadio, HWSimController from contextlib import contextmanager, ExitStack from test_p2p_channel import set_country +import time @contextmanager def hwsim_nan_radios(count=2, n_channels=3): @@ -2125,3 +2126,144 @@ def test_nan_override_potential_availability(dev, apdev, params): # expect any changes during the test execution. if potential != old_potential: raise Exception("Potential availability did not revert to original after clearing override") + +def test_nan_stopped_on_iface_removal(dev, apdev, params): + """NAN cluster and discovery followed by radio destruction reports NAN-STOPPED""" + controller = HWSimController() + radio1 = HWSimRadio(n_channels=3, use_nan=True) + radio1_id, ifname1 = radio1.__enter__() + radio1_destroyed = False + + try: + with HWSimRadio(n_channels=3, use_nan=True) as (radio2_id, ifname2): + wpas1 = WpaSupplicant(global_iface="/tmp/wpas-wlan5") + wpas1.interface_add(ifname1) + wpas2 = WpaSupplicant(global_iface="/tmp/wpas-wlan6") + wpas2.interface_add(ifname2) + + nan_ifname = "nan0" + + logger.info("Starting NAN on publisher") + pub = NanDevice(wpas1, nan_ifname) + pub.start() + + with NanDevice(wpas2, "nan1") as sub: + logger.info("Verifying service discovery") + nan_sync_discovery(pub, sub, "test_service", + pssi="aabbccdd", sssi="ddbbccaa") + + logger.info("Destroying publisher radio") + wpas1.dump_monitor() + controller.destroy_radio(radio1_id) + radio1_destroyed = True + + logger.info("Waiting for NAN-STOPPED event on publisher") + ev = wpas1.wait_global_event(["NAN-STOPPED"], timeout=5) + if ev is None: + raise Exception("NAN-STOPPED event not received after radio destruction") + if f"ifname={nan_ifname}" not in ev: + raise Exception(f"Unexpected NAN-STOPPED event content: {ev}") + + logger.info("Removing old interface and adding new radio") + sub.wpas.dump_monitor() + wpas1.interface_remove(ifname1) + + radio1 = HWSimRadio(n_channels=3, use_nan=True) + radio1_id, ifname1_new = radio1.__enter__() + radio1_destroyed = False + wpas1.interface_add(ifname1_new) + + logger.info("Starting NAN on re-added radio") + pub = NanDevice(wpas1, nan_ifname) + pub.start() + + logger.info("Verifying service discovery on re-added radio") + nan_sync_discovery(pub, sub, "test_service2", + pssi="11223344", sssi="44332211") + finally: + if not radio1_destroyed: + radio1.__exit__(None, None, None) + +def test_nan_stopped_on_iface_removal_with_ndp(dev, apdev, params): + """NAN cluster, NDP establishment, then radio destruction reports NAN-STOPPED""" + set_country("US") + controller = HWSimController() + radio1 = HWSimRadio(n_channels=3, use_nan=True) + radio1_id, ifname1 = radio1.__enter__() + radio1_destroyed = False + + try: + with HWSimRadio(n_channels=3, use_nan=True) as (radio2_id, ifname2): + wpas1 = WpaSupplicant(global_iface="/tmp/wpas-wlan5") + wpas1.interface_add(ifname1) + wpas2 = WpaSupplicant(global_iface="/tmp/wpas-wlan6") + wpas2.interface_add(ifname2) + + nan_ifname = "nan0" + ndi_name = "ndi0" + + logger.info("Starting NAN on publisher with NDI") + pub = NanDevice(wpas1, nan_ifname, ndi_name) + pub.start() + + with NanDevice(wpas2, "nan1", "ndi1") as sub: + logger.info("Establishing NDP between publisher and subscriber") + pid, sid, paddr, saddr = _nan_discover_service( + pub, sub, "test_service", "aabbccdd", "ddbbccaa", + data_path=True) + ndp_id, init_ndi = _nan_ndp_request_and_accept( + pub, sub, pid, sid, paddr, saddr, + req_ssi="aabbcc", resp_ssi="ddeeff") + + logger.info("Verifying connectivity over NDP") + _nan_test_connectivity(pub, sub) + + logger.info("Destroying publisher radio while NDP is active") + wpas1.dump_monitor() + sub.wpas.dump_monitor() + controller.destroy_radio(radio1_id) + radio1_destroyed = True + + logger.info("Waiting for NAN-STOPPED event on publisher") + ev = wpas1.wait_global_event(["NAN-STOPPED"], timeout=5) + if ev is None: + raise Exception("NAN-STOPPED event not received after radio destruction") + if f"ifname={nan_ifname}" not in ev: + raise Exception(f"Unexpected NAN-STOPPED event content: {ev}") + + logger.info("Terminating NDP on subscriber") + sub.ndp_terminate(paddr, init_ndi, ndp_id) + ev = sub.wpas.wait_event(["NAN-NDP-DISCONNECTED"], timeout=5) + if ev is None: + raise Exception("NAN-NDP-DISCONNECTED not received on subscriber") + + logger.info("Removing old interface and adding new radio") + sub.wpas.dump_monitor() + wpas1.interface_remove(ifname1) + + radio1 = HWSimRadio(n_channels=3, use_nan=True) + radio1_id, ifname1_new = radio1.__enter__() + radio1_destroyed = False + + wpas1.interface_add(ifname1_new) + + logger.info("Starting NAN on re-added radio with NDI") + pub = NanDevice(wpas1, nan_ifname, ndi_name) + pub.start() + + logger.info("Establishing NDP on re-added radio") + pid, sid, paddr, saddr = _nan_discover_service( + pub, sub, "test_service2", "11223344", "44332211", + data_path=True) + ndp_id, init_ndi = _nan_ndp_request_and_accept( + pub, sub, pid, sid, paddr, saddr, + req_ssi="aabbcc", resp_ssi="ddeeff") + + logger.info("Verifying connectivity on re-added radio") + _nan_test_connectivity(pub, sub) + + logger.info("NDP recovery after radio destruction verified successfully") + finally: + if not radio1_destroyed: + radio1.__exit__(None, None, None) + set_country("00") -- 2.53.0 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:24 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:24 +0530 Subject: [PATCH v3 00/46] PR: Add nl80211 support and ranging for Proximity Detection Message-ID: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> wpa_supplicant already had PASN authentication support for Proximity Ranging (PR). This series adds the missing nl80211 driver support and ranging to complete the PD (Proximity Detection) flow. It introduces a dedicated PD virtual interface (NL80211_IFTYPE_PD) with its own MAC address, updates nl80211_copy.h with the proposed kernel API additions for PD, improves driver capability parsing with dedicated PR-specific flags, extends the set_key path to carry LTF key seeds for secure ranging, and wires up peer measurement start and result handling through the nl80211 driver. The existing PR_PASN_START control interface command is extended with the full set of EDCA, NTB, and proximity threshold parameters and rerouted through wpas_pr_pasn_trigger() to drive the complete PASN-then-ranging flow in a single step. Support is also added for Out-of-Band peer discovery where USD is handled by an external application, and per-peer PMK and password configuration via PR_PASN_START for authenticated PASN without prior NAN USD discovery. Kernel Changes are merged into wireless-next tip. Link: https://patchwork.kernel.org/project/linux-wireless/list/?series=1083204&state=%2A&archive=both --- Changes in v3: - The earlier supplicant patchset (v2) was aligned with a previous kernel patchset series. Since that kernel series has now been merged, the supplicant has been updated again (v3) to align with the current wireless-next. - Fixed several bugs identified during internal testing. Introduced new patches in this series. - Added support for Out-of-Band (OOB) peer discovery: when USD service discovery is handled by an external application and PASN is triggered separately via PR_PASN_START, a minimal peer entry is created automatically to allow PASN to proceed without prior NAN USD discovery. --- Changes in v2: - Added the correct NL attributes. - Refactored dependent code paths to reflect the updated NL attribute handling. --- Kavita Kavita (11): nl80211: Sync with wireless-next.git include/uapi/linux/nl80211.h PR: Add CONFIG_PR to defconfig PR: Guard wpas_pr_usd_elems() against uninitialized PR context PR: Gate EDCA ranging support on ASAP capability PR: Move ranging_wrapper free inside CONFIG_PASN ifdef PR: Default pasn_type to DH19_UNAUTH|DH19_AUTH when not configured PR: Restore dev_addr to station MAC after PD wdev stop PR: Flush PMKSA cache after TK is configured for ranging PR: Track peer discovery type and handle OOB peers in PASN initiation PR: Allow PMK and password configuration in PR_PASN_START PR: Skip key installation for EDCA-based ranging Peddolla Harshavardhan Reddy (30): ctrl_iface: Add forced_addr parameter to NAN_PUBLISH and NAN_SUBSCRIBE nl80211: Add MAC address filter support for remain-on-channel nl80211: Allow NAN USD frames with foreign destination address NAN: Make NAN and P2P network IDs globally accessible NAN: Use protocol-specific network ID in USD offload to driver NAN: Pass forced_addr to remain-on-channel listen callback nl80211: Parse Peer Measurement (PMSR) capabilities PR: Use stored capabilities instead of driver flags PR: Add support to use stored 6GHz capability for PR PR: Fix wrong channel set used in NTB capabilities PR: Replace format_and_bw attribute with preamble/BW bitmaps PR: Add nl80211 driver support for PD interface nl80211: Use wdev-based message for SET_KEY on wdev-only interfaces PR: Use set_key to configure secure ranging context for PASN PR: Extend PR_PASN_START ctrl iface with src_addr and role PR: Add remain-on-channel support for PASN responder role PR: Add dedicated PD wdev for PASN with custom MAC address PR: Add interface to trigger PASN authentication for ranging PR: Add PR_PASN_NEGOTIATION_STARTED event PR: Add extended ranging parameters to PASN peer structure PR: Use op_class_to_bandwidth() in wpas_pr_ranging_params() nl80211: Add peer measurement support for proximity ranging PR: Use session time as total ROC budget for PASN responder PR: Extend PR_PASN_START ctrl iface with full ranging parameters wpa_supplicant: Add FTM peer measurement result handling PR: Handle peer measurement complete event PR: Add ranging session timeout and integrate socket cleanup PR: Add API and ctrl iface command to abort ranging session PR: store resolved peer DevIK in pr_device after DIRA match PR: use USD source address for DIRA tag computation Veerendranath Jakkam (5): nl80211: Add Proximity Detection (PD) wdev support nl80211: Route MLME TX via PD wdev based on source address nl80211: Factor out global event BSS lookup and add PD wdev event routing nl80211: Route key operations to PD wdev based on own_addr nl80211: Add dedicated PR ranging socket and stop op src/ap/nan_usd_ap.c | 2 +- src/common/nan_de.c | 13 +- src/common/nan_de.h | 6 +- src/common/nan_defs.h | 4 + src/common/proximity_ranging.c | 134 ++- src/common/proximity_ranging.h | 166 +++- src/common/wpa_ctrl.h | 11 + src/drivers/driver.h | 231 ++++- src/drivers/driver_common.c | 2 + src/drivers/driver_nl80211.c | 677 ++++++++++++++- src/drivers/driver_nl80211.h | 7 + src/drivers/driver_nl80211_capa.c | 176 ++++ src/drivers/driver_nl80211_event.c | 397 ++++++++- src/drivers/nl80211_copy.h | 303 ++++++- wpa_supplicant/ctrl_iface.c | 157 +++- wpa_supplicant/defconfig | 3 + wpa_supplicant/dpp_supplicant.c | 2 +- wpa_supplicant/driver_i.h | 60 +- wpa_supplicant/events.c | 18 + wpa_supplicant/nan_supplicant.c | 40 +- wpa_supplicant/notify.c | 45 + wpa_supplicant/notify.h | 7 + wpa_supplicant/offchannel.c | 4 +- wpa_supplicant/p2p_supplicant.c | 2 +- wpa_supplicant/pr_supplicant.c | 1280 +++++++++++++++++++++++++--- wpa_supplicant/pr_supplicant.h | 56 +- wpa_supplicant/wpa_supplicant.c | 6 +- wpa_supplicant/wpa_supplicant_i.h | 19 + 28 files changed, 3557 insertions(+), 271 deletions(-) base-commit: a034719032e92e65a18e28175e009d2598bae453 -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:25 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:25 +0530 Subject: [PATCH v3 01/46] nl80211: Sync with wireless-next.git include/uapi/linux/nl80211.h In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-2-kavita.kavita@oss.qualcomm.com> This brings in nl80211 definitions as of 2026-05-05. Signed-off-by: Kavita Kavita --- src/drivers/driver_nl80211_event.c | 2 + src/drivers/nl80211_copy.h | 303 +++++++++++++++++++++++++++-- 2 files changed, 292 insertions(+), 13 deletions(-) diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index a281f5953..86503f62e 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -197,6 +197,8 @@ static const char * nl80211_command_to_string(enum nl80211_commands cmd) C2S(NL80211_CMD_NAN_SET_PEER_SCHED) C2S(NL80211_CMD_NAN_ULW_UPDATE) C2S(NL80211_CMD_NAN_CHANNEL_EVAC) + C2S(NL80211_CMD_START_PD) + C2S(NL80211_CMD_STOP_PD) C2S(__NL80211_CMD_AFTER_LAST) } #undef C2S diff --git a/src/drivers/nl80211_copy.h b/src/drivers/nl80211_copy.h index 3d55bf4be..9998f6c0a 100644 --- a/src/drivers/nl80211_copy.h +++ b/src/drivers/nl80211_copy.h @@ -729,7 +729,9 @@ * to remain on the channel. This command is also used as an event to * notify when the requested duration starts (it may take a while for the * driver to schedule this time due to other concurrent needs for the - * radio). + * radio). An optional attribute %NL80211_ATTR_MAC can be used to filter + * incoming frames during remain-on-channel, such that frames + * addressed to the specified destination MAC are reported. * When called, this operation returns a cookie (%NL80211_ATTR_COOKIE) * that will be included with any events pertaining to this request; * the cookie is also used to cancel the request. @@ -1204,10 +1206,12 @@ * user space through the connect result as the user space would have * initiated the connection through the connect request. * - * @NL80211_CMD_STA_OPMODE_CHANGED: An event that notify station's - * ht opmode or vht opmode changes using any of %NL80211_ATTR_SMPS_MODE, - * %NL80211_ATTR_CHANNEL_WIDTH,%NL80211_ATTR_NSS attributes with its - * address(specified in %NL80211_ATTR_MAC). + * @NL80211_CMD_STA_OPMODE_CHANGED: An event that notifies that a station's + * HT opmode or VHT opmode changed using any of %NL80211_ATTR_SMPS_MODE, + * %NL80211_ATTR_CHANNEL_WIDTH, %NL80211_ATTR_NSS attributes with its + * address (specified in %NL80211_ATTR_MAC). + * Note that 80+80 and 160 MHz might not be differentiated, i.e. may + * report %NL80211_CHAN_WIDTH_160 instead of %NL80211_CHAN_WIDTH_80P80. * * @NL80211_CMD_GET_FTM_RESPONDER_STATS: Retrieve FTM responder statistics, in * the %NL80211_ATTR_FTM_RESPONDER_STATS attribute. @@ -1415,6 +1419,12 @@ * identifying the evacuated channel. * User space may reconfigure the local schedule in response to this * notification. + * @NL80211_CMD_START_PD: Start PD operation, identified by its + * %NL80211_ATTR_WDEV interface. This interface must have been previously + * created with %NL80211_CMD_NEW_INTERFACE. + * @NL80211_CMD_STOP_PD: Stop the PD operation, identified by + * its %NL80211_ATTR_WDEV interface. + * * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use */ @@ -1690,6 +1700,9 @@ enum nl80211_commands { NL80211_CMD_NAN_CHANNEL_EVAC, + NL80211_CMD_START_PD, + NL80211_CMD_STOP_PD, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ @@ -2991,11 +3004,13 @@ enum nl80211_commands { * @NL80211_ATTR_EPCS: Flag attribute indicating that EPCS is enabled for a * station interface. * - * @NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS: Extended MLD capabilities and - * operations that userspace implements to use during association/ML - * link reconfig, currently only "BTM MLD Recommendation For Multiple - * APs Support". Drivers may set additional flags that they support - * in the kernel or device. + * @NL80211_ATTR_EXT_MLD_CAPA_AND_OPS: Extended MLD capabilities and operations. + * For association and link reconfiguration, indicates extra capabilities + * that userspace implements, currently only "BTM MLD Recommendation For + * Multiple APs Support". + * For wiphy information, additional flags that drivers will set, but + * this is informational only for userspace (it's not expected to set + * these.) * * @NL80211_ATTR_WIPHY_RADIO_INDEX: (int) Integer attribute denoting the index * of the radio in interest. Internally a value of -1 is used to @@ -3140,6 +3155,16 @@ enum nl80211_commands { * association response etc., since it's abridged in the beacon. Used * for START_AP etc. * + * @NL80211_ATTR_ASSOC_ENCRYPTED: Flag attribute, used only with the + * %NL80211_CMD_CONNECT event in SME-in-driver mode. The driver should + * set this flag to indicate that both the (Re)Association Request frame + * and the corresponding (Re)Association Response frame are transmitted + * encrypted over the air. Enhanced Privacy Protection (EPP), as defined + * in IEEE P802.11bi/D4.0, mandates this encryption. + * + * @NL80211_ATTR_NPCA_PRIMARY_FREQ: NPCA primary channel (u32) + * @NL80211_ATTR_NPCA_PUNCT_BITMAP: NPCA puncturing bitmap (u32) + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3695,7 +3720,7 @@ enum nl80211_attrs { NL80211_ATTR_MLO_RECONF_REM_LINKS, NL80211_ATTR_EPCS, - NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS, + NL80211_ATTR_EXT_MLD_CAPA_AND_OPS, NL80211_ATTR_WIPHY_RADIO_INDEX, @@ -3733,6 +3758,11 @@ enum nl80211_attrs { NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME, NL80211_ATTR_NAN_PEER_MAPS, + NL80211_ATTR_ASSOC_ENCRYPTED, + + NL80211_ATTR_NPCA_PRIMARY_FREQ, + NL80211_ATTR_NPCA_PUNCT_BITMAP, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -3747,6 +3777,7 @@ enum nl80211_attrs { #define NL80211_ATTR_SAE_DATA NL80211_ATTR_AUTH_DATA #define NL80211_ATTR_CSA_C_OFF_BEACON NL80211_ATTR_CNTDWN_OFFS_BEACON #define NL80211_ATTR_CSA_C_OFF_PRESP NL80211_ATTR_CNTDWN_OFFS_PRESP +#define NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS NL80211_ATTR_EXT_MLD_CAPA_AND_OPS /* * Allow user space programs to use #ifdef on new attributes by defining them @@ -3829,6 +3860,7 @@ enum nl80211_attrs { * @NL80211_IFTYPE_NAN_DATA: NAN data interface type (netdev); NAN data * interfaces can only be brought up (IFF_UP) when a NAN interface * already exists and NAN has been started (using %NL80211_CMD_START_NAN). + * @NL80211_IFTYPE_PD: PD device interface type (not a netdev) * @NL80211_IFTYPE_MAX: highest interface type number currently defined * @NUM_NL80211_IFTYPES: number of defined interface types * @@ -3851,6 +3883,7 @@ enum nl80211_iftype { NL80211_IFTYPE_OCB, NL80211_IFTYPE_NAN, NL80211_IFTYPE_NAN_DATA, + NL80211_IFTYPE_PD, /* keep last */ NUM_NL80211_IFTYPES, @@ -5790,6 +5823,18 @@ enum nl80211_key_default_types { * @NL80211_KEY_MODE: the mode from enum nl80211_key_mode. * Defaults to @NL80211_KEY_RX_TX. * @NL80211_KEY_DEFAULT_BEACON: flag indicating default Beacon frame key + * @NL80211_KEY_LTF_SEED: LTF key seed is used by the driver to generate + * secure LTF keys used in case of peer measurement request with FTM + * request type as either %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED + * or %NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED. Secure LTF key seeds + * will help enable PHY security in peer measurement session. + * The LTF key seed is installed along with the TK (Temporal Key) using + * %NL80211_CMD_NEW_KEY. The TK is configured using the + * %NL80211_ATTR_KEY_DATA attribute, while the LTF key seed is configured + * using this attribute. Both keys must be configured before initiation + * of peer measurement to ensure peer measurement session is secure. + * Only valid if %NL80211_EXT_FEATURE_SET_KEY_LTF_SEED is set. This + * attribute is restricted to pairwise keys (%NL80211_KEYTYPE_PAIRWISE). * * @__NL80211_KEY_AFTER_LAST: internal * @NL80211_KEY_MAX: highest key attribute @@ -5806,6 +5851,7 @@ enum nl80211_key_attributes { NL80211_KEY_DEFAULT_TYPES, NL80211_KEY_MODE, NL80211_KEY_DEFAULT_BEACON, + NL80211_KEY_LTF_SEED, /* keep last */ __NL80211_KEY_AFTER_LAST, @@ -7029,6 +7075,16 @@ enum nl80211_feature_flags { * (NL80211_CMD_AUTHENTICATE) in non-AP STA mode, as specified in * "IEEE P802.11bi/D4.0, 12.16.5". * + * @NL80211_EXT_FEATURE_ROC_ADDR_FILTER: Driver supports MAC address + * filtering during remain-on-channel. When %NL80211_ATTR_MAC is + * provided with %NL80211_CMD_REMAIN_ON_CHANNEL, the driver will + * forward frames with a matching MAC address to userspace during + * the off-channel period. + * + * @NL80211_EXT_FEATURE_SET_KEY_LTF_SEED: Driver supports installing the + * LTF key seed via %NL80211_KEY_LTF_SEED. The seed is used to generate + * secure LTF keys for secure LTF measurement sessions. + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -7108,6 +7164,8 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_EPPKE, NL80211_EXT_FEATURE_ASSOC_FRAME_ENCRYPTION, NL80211_EXT_FEATURE_IEEE8021X_AUTH, + NL80211_EXT_FEATURE_ROC_ADDR_FILTER, + NL80211_EXT_FEATURE_SET_KEY_LTF_SEED, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, @@ -7960,6 +8018,26 @@ enum nl80211_peer_measurement_resp { NL80211_PMSR_RESP_ATTR_MAX = NUM_NL80211_PMSR_RESP_ATTRS - 1 }; +/** + * enum nl80211_peer_measurement_ftm_req_type - FTM ranging request type, + * used with %NL80211_PMSR_PEER_ATTR_REQ_TYPE + * + * @NL80211_PMSR_FTM_REQ_TYPE_INFRA: infrastructure ranging, i.e. STA-to-AP + * @NL80211_PMSR_FTM_REQ_TYPE_PD: peer-to-peer ranging as defined in the + * Wi-Fi Alliance specification "Proximity Ranging (PR) Implementation + * Consideration Draft 1.9 Rev 1" + * @NUM_NL80211_PMSR_FTM_REQ_TYPE: internal + * @NL80211_PMSR_FTM_REQ_TYPE_MAX: highest request type value + */ +enum nl80211_peer_measurement_ftm_req_type { + NL80211_PMSR_FTM_REQ_TYPE_INFRA, + NL80211_PMSR_FTM_REQ_TYPE_PD, + + /* keep last */ + NUM_NL80211_PMSR_FTM_REQ_TYPE, + NL80211_PMSR_FTM_REQ_TYPE_MAX = NUM_NL80211_PMSR_FTM_REQ_TYPE - 1 +}; + /** * enum nl80211_peer_measurement_peer_attrs - peer attributes for measurement * @__NL80211_PMSR_PEER_ATTR_INVALID: invalid @@ -7973,6 +8051,9 @@ enum nl80211_peer_measurement_resp { * @NL80211_PMSR_PEER_ATTR_RESP: This is a nested attribute indexed by * measurement type, with attributes from the * &enum nl80211_peer_measurement_resp inside. + * @NL80211_PMSR_PEER_ATTR_REQ_TYPE: u32 attribute specifying the ranging + * request type, using values from &enum nl80211_peer_measurement_ftm_req_type. + * If absent, defaults to %NL80211_PMSR_FTM_REQ_TYPE_INFRA. * * @NUM_NL80211_PMSR_PEER_ATTRS: internal * @NL80211_PMSR_PEER_ATTR_MAX: highest attribute number @@ -7984,6 +8065,7 @@ enum nl80211_peer_measurement_peer_attrs { NL80211_PMSR_PEER_ATTR_CHAN, NL80211_PMSR_PEER_ATTR_REQ, NL80211_PMSR_PEER_ATTR_RESP, + NL80211_PMSR_PEER_ATTR_REQ_TYPE, /* keep last */ NUM_NL80211_PMSR_PEER_ATTRS, @@ -8010,6 +8092,18 @@ enum nl80211_peer_measurement_peer_attrs { * meaningless, just a list of peers to measure with, with the * sub-attributes taken from * &enum nl80211_peer_measurement_peer_attrs. + * @NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE: u32 attribute indicating the + * maximum number of peers supported when the device operates in the + * ISTA (Initiator STA) role. If absent, no role-specific peer limit + * applies. The sum of %NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE and + * %NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE is enforced when the device + * supports concurrent ISTA/RSTA operation. + * @NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE: u32 attribute indicating the + * maximum number of peers supported when the device operates in the + * RSTA (Responder STA) role. If absent, no role-specific peer limit + * applies. The sum of %NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE and + * %NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE is enforced when the device + * supports concurrent ISTA/RSTA operation. * * @NUM_NL80211_PMSR_ATTR: internal * @NL80211_PMSR_ATTR_MAX: highest attribute number @@ -8022,6 +8116,8 @@ enum nl80211_peer_measurement_attrs { NL80211_PMSR_ATTR_RANDOMIZE_MAC_ADDR, NL80211_PMSR_ATTR_TYPE_CAPA, NL80211_PMSR_ATTR_PEERS, + NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE, + NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE, /* keep last */ NUM_NL80211_PMSR_ATTR, @@ -8080,6 +8176,54 @@ enum nl80211_peer_measurement_attrs { * This limits the allowed combinations of LTF repetitions and STS. * @NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT: flag attribute indicating the * device supports operating as the RSTA in PMSR FTM request + * @NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS: nested attribute containing ISTA + * (initiator) role capabilities. Uses the same sub-attributes as + * %NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS. + * @NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS: nested attribute containing RSTA + * (responder) role capabilities. + * @NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_NTB: flag attribute (used inside + * %NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS or + * %NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS) indicating NTB ranging support. + * @NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_TB: flag attribute (used inside + * %NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS or + * %NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS) indicating TB ranging support. + * @NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA: flag attribute (used inside + * %NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS or + * %NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS) indicating EDCA based ranging + * support. + * @NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS: nested attribute containing ranging + * type capabilities. Uses sub-attributes from + * &enum nl80211_peer_measurement_ftm_type_capa. + * @NL80211_PMSR_FTM_CAPA_ATTR_CONCURRENT_ISTA_RSTA_SUPPORT: flag attribute + * indicating that the device can simultaneously act as initiator and + * responder in a multi-peer measurement request. Only valid if + * @NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT is set. + * @NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_TX_ANTENNAS: u32 attribute indicating + * the maximum number of transmit antennas supported for EDCA based ranging + * (0 means unknown) + * @NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_RX_ANTENNAS: u32 attribute indicating + * the maximum number of receive antennas supported for EDCA based ranging + * (0 means unknown) + * @NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_EDCA: u32 attribute indicating + * the minimum EDCA ranging interval supported by the device + * in milli seconds. (0 means unknown). Applications can use this value + * to estimate the burst period to be given in the FTM request for the + * EDCA based ranging case. If non-zero, this value will be used to + * validate the burst period in the FTM request. + * @NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_NTB: u32 attribute indicating + * the minimum NTB ranging interval supported by the device + * in milli seconds. (0 means unknown). Applications can use this value + * to estimate the burst period to be given in the FTM request for the + * NTB ranging case. If non-zero, this value will be used to validate + * the nominal time in the FTM request. + * @NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES: u32 bitmap of values from + * &enum nl80211_preamble indicating the supported preambles for PD + * ranging requests. Only valid if %NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT + * is set. + * @NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS: u32 bitmap of values from + * &enum nl80211_chan_width indicating the supported channel bandwidths + * for PD ranging requests. Only valid if + * %NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT is set. * * @NUM_NL80211_PMSR_FTM_CAPA_ATTR: internal * @NL80211_PMSR_FTM_CAPA_ATTR_MAX: highest attribute number @@ -8105,12 +8249,52 @@ enum nl80211_peer_measurement_ftm_capa { NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_TX, NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX, NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT, + NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS, + NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS, + NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_NTB, + NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_TB, + NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA, + NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS, + NL80211_PMSR_FTM_CAPA_ATTR_CONCURRENT_ISTA_RSTA_SUPPORT, + NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_TX_ANTENNAS, + NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_RX_ANTENNAS, + NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_EDCA, + NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_NTB, + NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES, + NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS, /* keep last */ NUM_NL80211_PMSR_FTM_CAPA_ATTR, NL80211_PMSR_FTM_CAPA_ATTR_MAX = NUM_NL80211_PMSR_FTM_CAPA_ATTR - 1 }; +/** + * enum nl80211_peer_measurement_ftm_type_capa - FTM ranging type capability + * sub-attributes, used inside %NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS + * @__NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INVALID: invalid + * + * @NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INFRA_SUPPORT: flag attribute indicating + * that the device supports infrastructure ranging (STA-to-AP or + * AP-to-STA) as part of Proximity Detection + * @NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT: flag attribute indicating that + * the device supports peer-to-peer ranging as mentioned in the + * specification "PR Implementation Consideration Draft 1.9 rev 1" where + * PD stands for proximity detection + * + * @NUM_NL80211_PMSR_FTM_TYPE_CAPA_ATTR: internal + * @NL80211_PMSR_FTM_TYPE_CAPA_ATTR_MAX: highest attribute number + */ +enum nl80211_peer_measurement_ftm_type_capa { + __NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INVALID, + + NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INFRA_SUPPORT, + NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT, + + /* keep last */ + NUM_NL80211_PMSR_FTM_TYPE_CAPA_ATTR, + NL80211_PMSR_FTM_TYPE_CAPA_ATTR_MAX = NUM_NL80211_PMSR_FTM_TYPE_CAPA_ATTR - 1 +}; + /** * enum nl80211_peer_measurement_ftm_req - FTM request attributes * @__NL80211_PMSR_FTM_REQ_ATTR_INVALID: invalid @@ -8129,9 +8313,11 @@ enum nl80211_peer_measurement_ftm_capa { * default 15 i.e. "no preference"). For non-EDCA ranging, this is the * burst duration in milliseconds (optional with default 0, i.e. let the * device decide). - * @NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST: number of successful FTM frames - * requested per burst + * @NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST: (Optional) number of successful + * FTM frames requested per burst * (u8, 0-31, optional with default 0 i.e. "no preference") + * If the attribute is absent ("no preference"), the driver or firmware can + * choose a suitable value. * @NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES: number of FTMR frame retries * (u8, default 3) * @NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI: request LCI data (flag) @@ -8165,6 +8351,50 @@ enum nl80211_peer_measurement_ftm_capa { * Only valid if %NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK is set (so the * RSTA will have the measurement results to report back in the FTM * response). + * @NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS: minimum time + * between two consecutive range measurements in units of 100 microseconds, + * for non-trigger based ranging (u32). Should be set as short as possible + * to minimize turnaround time, since two-way ranging with delayed LMR + * requires two measurements. Only valid if + * %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set. + * @NL80211_PMSR_FTM_REQ_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS: maximum time + * between two consecutive range measurements in units of 10 milliseconds, + * for non-trigger based ranging (u32). Acts as a session timeout; if + * exceeded, the ranging session should be terminated. Only valid if + * %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set. + * @NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME: The nominal time field shall be + * set to the nominal duration between adjacent Availability Windows in + * units of milli seconds (u32). Mandatory if + * %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set. + * @NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION: (Optional) The AW duration field + * shall be set to the duration of the AW in units of 1ms (0-255 ms) (u32). + * Only valid if %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set. + * If the attribute is absent ("no preference"), the driver or firmware + * can choose a suitable value. + * @NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS: (Optional) number of + * Availability Windows (AWs) to schedule for non-trigger-based ranging. + * Each AW may contain multiple FTM exchanges as configured by + * %NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST. Only valid if + * %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set. + * If the attribute is absent ("no preference"), the driver or firmware + * can choose a suitable value. + * @NL80211_PMSR_FTM_REQ_ATTR_PAD: ignore, for u64/s64 padding only. + * @NL80211_PMSR_FTM_REQ_ATTR_INGRESS: optional u64 attribute in units of mm. + * When specified, the measurement result of the peer needs to be + * indicated if the device moves into this range. + * @NL80211_PMSR_FTM_REQ_ATTR_EGRESS: optional u64 attribute in units of mm. + * When specified, the measurement result of the peer needs to be + * indicated if the device moves out of this range. + * If neither or only one of @NL80211_PMSR_FTM_REQ_ATTR_INGRESS and + * @NL80211_PMSR_FTM_REQ_ATTR_EGRESS is specified, only the specified + * threshold is used. If both are specified, both thresholds are applied. + * If neither is specified, results are reported without threshold + * filtering. + * @NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS: Flag to suppress ranging + * results for PD requests. When set, ranging measurements are performed + * but results are not reported to userspace, regardless of ranging role + * or type. Only valid when %NL80211_PMSR_PEER_ATTR_REQ_TYPE is set to + * %NL80211_PMSR_FTM_REQ_TYPE_PD. * * @NUM_NL80211_PMSR_FTM_REQ_ATTR: internal * @NL80211_PMSR_FTM_REQ_ATTR_MAX: highest attribute number @@ -8186,6 +8416,15 @@ enum nl80211_peer_measurement_ftm_req { NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK, NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR, NL80211_PMSR_FTM_REQ_ATTR_RSTA, + NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS, + NL80211_PMSR_FTM_REQ_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS, + NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME, + NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION, + NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS, + NL80211_PMSR_FTM_REQ_ATTR_PAD, + NL80211_PMSR_FTM_REQ_ATTR_INGRESS, + NL80211_PMSR_FTM_REQ_ATTR_EGRESS, + NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS, /* keep last */ NUM_NL80211_PMSR_FTM_REQ_ATTR, @@ -8272,6 +8511,33 @@ enum nl80211_peer_measurement_ftm_failure_reasons { * @NL80211_PMSR_FTM_RESP_ATTR_PAD: ignore, for u64/s64 padding only * @NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD: actual burst period used by * the responder (similar to request, u16) + * @NL80211_PMSR_FTM_RESP_ATTR_TX_LTF_REPETITION_COUNT: negotiated value of + * number of tx ltf repetitions in NDP frames (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_RX_LTF_REPETITION_COUNT: negotiated value of + * number of rx ltf repetitions in NDP frames (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS: negotiated value + * where latest time by which the ISTA needs to complete the next round of + * measurements, in units of 10 ms (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS: negotiated + * minimum time between two consecutive range measurements initiated by an + * ISTA, in units of 100 us (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_NUM_TX_SPATIAL_STREAMS: number of Tx space-time + * streams used in NDP frames during the measurement sounding phase + * (u32, optional). + * @NL80211_PMSR_FTM_RESP_ATTR_NUM_RX_SPATIAL_STREAMS: number of Rx space-time + * streams used in the NDP frames during the measurement sounding phase + * (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_NOMINAL_TIME: negotiated nominal time used in + * this session in milliseconds. (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_AVAILABILITY_WINDOW: negotiated availability + * window time used in this session, in units of milli seconds. + * (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_CHANNEL_WIDTH: u32 attribute indicating channel + * width used for measurement, see &enum nl80211_chan_width (optional). + * @NL80211_PMSR_FTM_RESP_ATTR_PREAMBLE: u32 attribute indicating the preamble + * type used for the measurement, see &enum nl80211_preamble (optional). + * @NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR: flag, indicates if the + * current result is delayed LMR data. * * @NUM_NL80211_PMSR_FTM_RESP_ATTR: internal * @NL80211_PMSR_FTM_RESP_ATTR_MAX: highest attribute number @@ -8301,6 +8567,17 @@ enum nl80211_peer_measurement_ftm_resp { NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC, NL80211_PMSR_FTM_RESP_ATTR_PAD, NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD, + NL80211_PMSR_FTM_RESP_ATTR_TX_LTF_REPETITION_COUNT, + NL80211_PMSR_FTM_RESP_ATTR_RX_LTF_REPETITION_COUNT, + NL80211_PMSR_FTM_RESP_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS, + NL80211_PMSR_FTM_RESP_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS, + NL80211_PMSR_FTM_RESP_ATTR_NUM_TX_SPATIAL_STREAMS, + NL80211_PMSR_FTM_RESP_ATTR_NUM_RX_SPATIAL_STREAMS, + NL80211_PMSR_FTM_RESP_ATTR_NOMINAL_TIME, + NL80211_PMSR_FTM_RESP_ATTR_AVAILABILITY_WINDOW, + NL80211_PMSR_FTM_RESP_ATTR_CHANNEL_WIDTH, + NL80211_PMSR_FTM_RESP_ATTR_PREAMBLE, + NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR, /* keep last */ NUM_NL80211_PMSR_FTM_RESP_ATTR, -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:26 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:26 +0530 Subject: [PATCH v3 02/46] PR: Add CONFIG_PR to defconfig In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-3-kavita.kavita@oss.qualcomm.com> Add build option for Proximity Ranging support. Signed-off-by: Kavita Kavita --- wpa_supplicant/defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig index 15512b915..75832dbba 100644 --- a/wpa_supplicant/defconfig +++ b/wpa_supplicant/defconfig @@ -681,6 +681,9 @@ CONFIG_DPP2=y # Wi-Fi Aware (NAN) support #CONFIG_NAN=y +# Proximity Ranging (PR) support +#CONFIG_PR=y + # Enhanced Privacy Protection (IEEE P802.11bi) # Note: These features are experimental and still changing; do not enable for # production use. -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:27 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:27 +0530 Subject: [PATCH v3 03/46] PR: Guard wpas_pr_usd_elems() against uninitialized PR context In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-4-kavita.kavita@oss.qualcomm.com> global->pr is initialized only when the driver advertises proximity ranging support. Add a NULL check in wpas_pr_usd_elems() to return NULL when the PR context is not available. Fixes: a760532f76ef ("PR: USD for Proximity Ranging") Signed-off-by: Kavita Kavita --- wpa_supplicant/pr_supplicant.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index c50d4bd92..f47407e5c 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -315,6 +315,9 @@ static void wpas_pr_pasn_clear_keys(void *ctx, const u8 *own_addr, struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s) { + if (!wpa_s->global->pr) + return NULL; + return pr_prepare_usd_elems(wpa_s->global->pr); } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:28 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:28 +0530 Subject: [PATCH v3 04/46] ctrl_iface: Add forced_addr parameter to NAN_PUBLISH and NAN_SUBSCRIBE In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-5-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy The NAN_PUBLISH and NAN_SUBSCRIBE control interface commands had no way to specify a per-service transmitter address for NAN Service Discovery Frames. Add a forced_addr parameter to both commands to allow the caller to pass the desired transmitter address into the NAN Discovery Engine. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/ctrl_iface.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 14b4cd9d2..d31acbef4 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -12795,6 +12795,7 @@ static int wpas_ctrl_nan_publish(struct wpa_supplicant *wpa_s, char *cmd, int *cipher_list = NULL; u8 nd_pmk[PMK_LEN]; bool p2p = false; + u8 forced_addr[ETH_ALEN]; os_memset(¶ms, 0, sizeof(params)); /* USD shall use both solicited and unsolicited transmissions */ @@ -12946,6 +12947,16 @@ static int wpas_ctrl_nan_publish(struct wpa_supplicant *wpa_s, char *cmd, continue; } + if (os_strncmp(token, "forced_addr=", 12) == 0) { + if (hwaddr_aton(token + 12, forced_addr)) { + wpa_printf(MSG_INFO, + "CTRL: Invalid forced_addr"); + goto fail; + } + params.forced_addr = forced_addr; + continue; + } + wpa_printf(MSG_INFO, "CTRL: Invalid NAN_PUBLISH parameter: %s", token); goto fail; @@ -13074,6 +13085,7 @@ static int wpas_ctrl_nan_subscribe(struct wpa_supplicant *wpa_s, char *cmd, enum nan_service_protocol_type srv_proto_type = 0; int *freq_list = NULL; bool p2p = false; + u8 forced_addr[ETH_ALEN]; os_memset(¶ms, 0, sizeof(params)); params.freq = NAN_USD_DEFAULT_FREQ; @@ -13189,6 +13201,16 @@ static int wpas_ctrl_nan_subscribe(struct wpa_supplicant *wpa_s, char *cmd, continue; } + if (os_strncmp(token, "forced_addr=", 12) == 0) { + if (hwaddr_aton(token + 12, forced_addr)) { + wpa_printf(MSG_INFO, + "CTRL: Invalid forced_addr"); + goto fail; + } + params.forced_addr = forced_addr; + continue; + } + wpa_printf(MSG_INFO, "CTRL: Invalid NAN_SUBSCRIBE parameter: %s", token); -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:29 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:29 +0530 Subject: [PATCH v3 05/46] nl80211: Add MAC address filter support for remain-on-channel In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-6-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Currently, the remain_on_channel driver interface does not support filtering received frames by a specific MAC address. This prevents use cases like Proximity Ranging from listening for frames addressed to a custom MAC address that differs from the interface address. Add a filter_addr parameter to the remain_on_channel driver operation and its wpa_drv_remain_on_channel() wrapper. When provided, the nl80211 driver adds NL80211_ATTR_MAC to the remain-on-channel command to configure hardware-level frame filtering. A new driver capability flag WPA_DRIVER_FLAGS2_ROC_ADDR_FILTER, detected from the kernel's NL80211_EXT_FEATURE_ROC_ADDR_FILTER, guards this behavior. All existing callers are updated to pass NULL to maintain backward compatibility. Signed-off-by: Peddolla Harshavardhan Reddy --- src/drivers/driver.h | 10 +++++++++- src/drivers/driver_nl80211.c | 27 +++++++++++++++++++++++---- src/drivers/driver_nl80211_capa.c | 3 +++ wpa_supplicant/dpp_supplicant.c | 2 +- wpa_supplicant/driver_i.h | 5 +++-- wpa_supplicant/nan_supplicant.c | 2 +- wpa_supplicant/offchannel.c | 4 ++-- wpa_supplicant/p2p_supplicant.c | 2 +- 8 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index b8e01d2d7..69231d468 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2529,6 +2529,8 @@ struct wpa_driver_capa { #define WPA_DRIVER_FLAGS2_802_1X_AUTH 0x0000000800000000ULL /** Driver supports PMKSA caching privacy */ #define WPA_DRIVER_FLAGS2_PMKSA_PRIVACY 0x0000001000000000ULL +/** Driver supports MAC address filter for remain-on-channel */ +#define WPA_DRIVER_FLAGS2_ROC_ADDR_FILTER 0x0000002000000000ULL u64 flags2; #define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \ @@ -4490,6 +4492,8 @@ struct wpa_driver_ops { * @priv: Private driver interface data * @freq: Frequency (in MHz) of the channel * @duration: Duration in milliseconds + * @filter_addr: MAC address to filter received frames (NULL for no + * filter) * Returns: 0 on success, -1 on failure * * This command is used to request the driver to remain awake on the @@ -4498,6 +4502,10 @@ struct wpa_driver_ops { * Probe Request frames may also be requested to be reported by calling * probe_req_report(). These will be reported with EVENT_RX_PROBE_REQ. * + * If filter_addr is provided, the driver will configure an additional MAC + * address for frame filtering. i.e., this new address would be used in + * addition to the interface address. + * * The driver may not be at the requested channel when this function * returns, i.e., the return code is only indicating whether the * request was accepted. The caller will need to wait until the @@ -4508,7 +4516,7 @@ struct wpa_driver_ops { * executed. */ int (*remain_on_channel)(void *priv, unsigned int freq, - unsigned int duration); + unsigned int duration, const u8 *filter_addr); /** * cancel_remain_on_channel - Cancel remain-on-channel operation diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index ea89bcd8c..6adc7c853 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -10039,7 +10039,8 @@ static int nl80211_put_any_link_id(struct nl_msg *msg, static int wpa_driver_nl80211_remain_on_channel(void *priv, unsigned int freq, - unsigned int duration) + unsigned int duration, + const u8 *filter_addr) { struct i802_bss *bss = priv; struct wpa_driver_nl80211_data *drv = bss->drv; @@ -10055,12 +10056,30 @@ static int wpa_driver_nl80211_remain_on_channel(void *priv, unsigned int freq, return -1; } + /* Add MAC address filter if provided and supported */ + if (filter_addr) { + if (!(drv->capa.flags2 & WPA_DRIVER_FLAGS2_ROC_ADDR_FILTER)) { + wpa_printf(MSG_ERROR, + "nl80211: Driver does not support ROC address filter"); + nlmsg_free(msg); + return -1; + } + + if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, filter_addr)) { + wpa_printf(MSG_ERROR, + "nl80211: Failed to add MAC address filter"); + nlmsg_free(msg); + return -1; + } + } + cookie = 0; ret = send_and_recv_resp(drv, msg, cookie_handler, &cookie); if (ret == 0) { - wpa_printf(MSG_DEBUG, "nl80211: Remain-on-channel cookie " - "0x%llx for freq=%u MHz duration=%u", - (long long unsigned int) cookie, freq, duration); + wpa_printf(MSG_DEBUG, + "nl80211: Remain-on-channel cookie 0x%llx for freq=%u MHz duration=%u%s", + (unsigned long long) cookie, freq, duration, + filter_addr ? " (with MAC filter)" : ""); drv->remain_on_chan_cookie = cookie; drv->pending_remain_on_chan = 1; return 0; diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c index 178bf8774..1799c5395 100644 --- a/src/drivers/driver_nl80211_capa.c +++ b/src/drivers/driver_nl80211_capa.c @@ -602,6 +602,9 @@ static void wiphy_info_ext_feature_flags(struct wiphy_info_data *info, if (ext_feature_isset(ext_features, len, NL80211_EXT_FEATURE_MGMT_TX_RANDOM_TA_CONNECTED)) capa->flags |= WPA_DRIVER_FLAGS_MGMT_TX_RANDOM_TA_CONNECTED; + if (ext_feature_isset(ext_features, len, + NL80211_EXT_FEATURE_ROC_ADDR_FILTER)) + capa->flags2 |= WPA_DRIVER_FLAGS2_ROC_ADDR_FILTER; if (ext_feature_isset(ext_features, len, NL80211_EXT_FEATURE_SCHED_SCAN_RELATIVE_RSSI)) capa->flags |= WPA_DRIVER_FLAGS_SCHED_SCAN_RELATIVE_RSSI; diff --git a/wpa_supplicant/dpp_supplicant.c b/wpa_supplicant/dpp_supplicant.c index ab9d81593..a55d0e7ab 100644 --- a/wpa_supplicant/dpp_supplicant.c +++ b/wpa_supplicant/dpp_supplicant.c @@ -1034,7 +1034,7 @@ static void dpp_start_listen_cb(struct wpa_radio_work *work, int deinit) wpa_s->dpp_pending_listen_freq = lwork->freq; if (wpa_drv_remain_on_channel(wpa_s, lwork->freq, - wpa_s->max_remain_on_chan) < 0) { + wpa_s->max_remain_on_chan, NULL) < 0) { wpa_printf(MSG_DEBUG, "DPP: Failed to request the driver to remain on channel (%u MHz) for listen", lwork->freq); diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index 2166d86e3..f92a96e02 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -460,11 +460,12 @@ static inline int wpa_drv_if_remove(struct wpa_supplicant *wpa_s, static inline int wpa_drv_remain_on_channel(struct wpa_supplicant *wpa_s, unsigned int freq, - unsigned int duration) + unsigned int duration, + const u8 *filter_addr) { if (wpa_s->driver->remain_on_channel) return wpa_s->driver->remain_on_channel(wpa_s->drv_priv, freq, - duration); + duration, filter_addr); return -1; } diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c index c08c095f9..3a578334c 100644 --- a/wpa_supplicant/nan_supplicant.c +++ b/wpa_supplicant/nan_supplicant.c @@ -4224,7 +4224,7 @@ static void wpas_nan_usd_start_listen_cb(struct wpa_radio_work *work, duration = wpa_s->max_remain_on_chan; wpa_printf(MSG_DEBUG, "NAN: Start listen on %u MHz for %u ms", lwork->freq, duration); - if (wpa_drv_remain_on_channel(wpa_s, lwork->freq, duration) < 0) { + if (wpa_drv_remain_on_channel(wpa_s, lwork->freq, duration, NULL) < 0) { wpa_printf(MSG_DEBUG, "NAN: Failed to request the driver to remain on channel (%u MHz) for listen", lwork->freq); diff --git a/wpa_supplicant/offchannel.c b/wpa_supplicant/offchannel.c index 9e591d7d6..688718cd7 100644 --- a/wpa_supplicant/offchannel.c +++ b/wpa_supplicant/offchannel.c @@ -124,7 +124,7 @@ static void wpas_send_action_cb(void *eloop_ctx, void *timeout_ctx) #endif /* CONFIG_TESTING_OPTIONS */ if (wpa_drv_remain_on_channel( wpa_s, wpa_s->pending_action_freq, - duration) < 0) { + duration, NULL) < 0) { wpa_printf(MSG_DEBUG, "Off-channel: Failed to " "request driver to remain on " "channel (%u MHz) for Action Frame " @@ -369,7 +369,7 @@ int offchannel_send_action(struct wpa_supplicant *wpa_s, unsigned int freq, wait_time += wpa_s->extra_roc_dur; } #endif /* CONFIG_TESTING_OPTIONS */ - if (wpa_drv_remain_on_channel(wpa_s, freq, wait_time) < 0) { + if (wpa_drv_remain_on_channel(wpa_s, freq, wait_time, NULL) < 0) { wpa_printf(MSG_DEBUG, "Off-channel: Failed to request driver " "to remain on channel (%u MHz) for Action " "Frame TX", freq); diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c index 81f1cad41..7ae584a5a 100644 --- a/wpa_supplicant/p2p_supplicant.c +++ b/wpa_supplicant/p2p_supplicant.c @@ -3265,7 +3265,7 @@ static void wpas_start_listen_cb(struct wpa_radio_work *work, int deinit) } #endif /* CONFIG_TESTING_OPTIONS */ - if (wpa_drv_remain_on_channel(wpa_s, lwork->freq, duration) < 0) { + if (wpa_drv_remain_on_channel(wpa_s, lwork->freq, duration, NULL) < 0) { wpa_printf(MSG_DEBUG, "P2P: Failed to request the driver " "to remain on channel (%u MHz) for Listen " "state", lwork->freq); -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:30 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:30 +0530 Subject: [PATCH v3 06/46] nl80211: Allow NAN USD frames with foreign destination address In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-7-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy NAN USD (Unsynchronized Service Discovery) frames are Public Action / Vendor Specific frames with NAN SDF OUI (50:6f:9a:13). They can arrive addressed to: - The NAN Network ID (51:6f:9a:01:00:00) for multicast publish/subscribe - A unicast forced_addr for solicited publish replies and follow-ups Both cases have a destination address different from bss->addr or bss->rand_addr and were being silently dropped by the foreign address filter in the MLME event handler. Fix by checking the frame content (Action category + PA Vendor Specific subtype + NAN SDF OUI) instead of the destination address, alongside the existing PASN exception. Signed-off-by: Peddolla Harshavardhan Reddy --- src/drivers/driver_nl80211_event.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index 86503f62e..b60de54c1 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -1849,6 +1849,7 @@ static void mlme_event(struct i802_bss *bss, size_t len; int link_id = -1; struct i802_link *mld_link = NULL; + const struct ieee80211_mgmt *mgmt; if (timed_out && addr) { mlme_timeout_event(drv, cmd, addr); @@ -1889,9 +1890,11 @@ static void mlme_event(struct i802_bss *bss, /* PASN Authentication frame can be received with a different source MAC * address. Allow NL80211_CMD_FRAME event with foreign addresses also. + * NAN USD frames (Public Action/Vendor Specific with NAN SDF OUI) may + * be addressed to the NAN Network ID or unicast to a forced address + * (e.g. solicited publish replies, follow-ups), allow those too. */ if (cmd == NL80211_CMD_FRAME && len >= 24) { - const struct ieee80211_mgmt *mgmt; u16 fc; mgmt = (const struct ieee80211_mgmt *) data; @@ -1905,6 +1908,19 @@ static void mlme_event(struct i802_bss *bss, wpa_printf(MSG_DEBUG, "nl80211: %s: Allow PASN frame for foreign address", bss->ifname); +#ifdef CONFIG_NAN_USD + } else if (cmd == NL80211_CMD_FRAME && + stype == WLAN_FC_STYPE_ACTION && + len >= offsetof(struct ieee80211_mgmt, + u.action.u.vs_public_action.variable) + 1 && + mgmt->u.action.category == WLAN_ACTION_PUBLIC && + mgmt->u.action.u.vs_public_action.action == + WLAN_PA_VENDOR_SPECIFIC && + WPA_GET_BE32(mgmt->u.action.u.vs_public_action.oui) == + NAN_SDF_VENDOR_TYPE) { + wpa_printf(MSG_DEBUG, "nl80211: %s: Allow NAN USD frame", + bss->ifname); +#endif /* CONFIG_NAN_USD */ } else if (cmd != NL80211_CMD_FRAME_TX_STATUS && !(data[4] & 0x01) && !ether_addr_equal(bss->addr, data + 4) && -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:31 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:31 +0530 Subject: [PATCH v3 07/46] NAN: Make NAN and P2P network IDs globally accessible In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-8-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Remove the static qualifier from nan_network_id and p2p_network_id and add extern declarations in nan_de.h and nan_defs.h so other modules can reference these network IDs directly. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/nan_de.c | 4 ++-- src/common/nan_de.h | 3 +++ src/common/nan_defs.h | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/common/nan_de.c b/src/common/nan_de.c index c5b102277..72ee6fe47 100644 --- a/src/common/nan_de.c +++ b/src/common/nan_de.c @@ -19,7 +19,7 @@ #include "nan_defs.h" #include "nan_de.h" -static const u8 nan_network_id[ETH_ALEN] = +const u8 nan_network_id[ETH_ALEN] = { 0x51, 0x6f, 0x9a, 0x01, 0x00, 0x00 }; enum nan_de_service_type { @@ -27,7 +27,7 @@ enum nan_de_service_type { NAN_DE_SUBSCRIBE, }; -static const u8 p2p_network_id[ETH_ALEN] = +const u8 p2p_network_id[ETH_ALEN] = { 0x51, 0x6f, 0x9a, 0x02, 0x00, 0x00 }; static const u8 wildcard_bssid[ETH_ALEN] = diff --git a/src/common/nan_de.h b/src/common/nan_de.h index e3d872f38..95ca161f2 100644 --- a/src/common/nan_de.h +++ b/src/common/nan_de.h @@ -84,6 +84,9 @@ struct nan_callbacks { bool (*is_peer_paired)(void *ctx, const u8 *addr); }; +extern const u8 nan_network_id[ETH_ALEN]; +extern const u8 p2p_network_id[ETH_ALEN]; + bool nan_de_is_nan_network_id(const u8 *addr); bool nan_de_is_p2p_network_id(const u8 *addr); struct nan_de * nan_de_init(const u8 *nmi, bool offload, bool ap, diff --git a/src/common/nan_defs.h b/src/common/nan_defs.h index 88b46eaf3..f0c804553 100644 --- a/src/common/nan_defs.h +++ b/src/common/nan_defs.h @@ -173,6 +173,10 @@ enum nan_pairing_bootstrapping_method { #define NAN_USD_DEFAULT_FREQ 2437 +extern const u8 p2p_network_id[ETH_ALEN]; + +extern const u8 nan_network_id[ETH_ALEN]; + /* Map ID * Wi-Fi Aware spec v4.0, Table 79 (Device Capability attribute format) */ #define NAN_DEV_CAPA_MAP_ID_DONT_APPLY_ALL BIT(0) -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:32 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:32 +0530 Subject: [PATCH v3 08/46] NAN: Use protocol-specific network ID in USD offload to driver In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-9-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add network_id parameter to nan_publish() and nan_subscribe() driver operations and pass the appropriate network ID (nan_network_id or p2p_network_id) based on the protocol being used, so the driver can include the correct network ID in publish/subscribe frames during USD offload. Signed-off-by: Peddolla Harshavardhan Reddy --- src/drivers/driver.h | 6 ++++-- src/drivers/driver_nl80211.c | 14 ++++++++++---- wpa_supplicant/driver_i.h | 11 +++++++---- wpa_supplicant/nan_supplicant.c | 18 ++++++++++++++++-- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 69231d468..bbab06e7b 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -5683,7 +5683,8 @@ struct wpa_driver_ops { const char *service_name, const u8 *service_id, enum nan_service_protocol_type srv_proto_type, const struct wpabuf *ssi, const struct wpabuf *elems, - struct nan_publish_params *params); + struct nan_publish_params *params, + const u8 *network_id); /** * nan_cancel_publish - NAN offload for CancelPublish() @@ -5720,7 +5721,8 @@ struct wpa_driver_ops { enum nan_service_protocol_type srv_proto_type, const struct wpabuf *ssi, const struct wpabuf *elems, - struct nan_subscribe_params *params); + struct nan_subscribe_params *params, + const u8 *network_id); /** * nan_cancel_subscribe - NAN offload for CancelSubscribe() diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 6adc7c853..3b50ffca3 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -14395,7 +14395,8 @@ static int nl80211_nan_publish(void *priv, const u8 *src, int publish_id, enum nan_service_protocol_type srv_proto_type, const struct wpabuf *ssi, const struct wpabuf *elems, - struct nan_publish_params *params) + struct nan_publish_params *params, + const u8 *network_id) { struct i802_bss *bss = priv; struct wpa_driver_nl80211_data *drv = bss->drv; @@ -14431,7 +14432,9 @@ static int nl80211_nan_publish(void *priv, const u8 *src, int publish_id, (elems && nla_put(msg, QCA_WLAN_VENDOR_ATTR_USD_ELEMENT_CONTAINER, wpabuf_len(elems), wpabuf_head(elems))) || (ssi && nla_put(msg, QCA_WLAN_VENDOR_ATTR_USD_SSI, - wpabuf_len(ssi), wpabuf_head(ssi)))) + wpabuf_len(ssi), wpabuf_head(ssi))) || + (network_id && nla_put(msg, QCA_WLAN_VENDOR_ATTR_USD_NETWORK_ID, + ETH_ALEN, network_id))) goto fail; attr = nla_nest_start(msg, QCA_WLAN_VENDOR_ATTR_USD_CHAN_CONFIG); @@ -14547,7 +14550,8 @@ static int nl80211_nan_subscribe(void *priv, const u8 *src, int subscribe_id, enum nan_service_protocol_type srv_proto_type, const struct wpabuf *ssi, const struct wpabuf *elems, - struct nan_subscribe_params *params) + struct nan_subscribe_params *params, + const u8 *network_id) { struct i802_bss *bss = priv; struct wpa_driver_nl80211_data *drv = bss->drv; @@ -14584,7 +14588,9 @@ static int nl80211_nan_subscribe(void *priv, const u8 *src, int subscribe_id, (elems && nla_put(msg, QCA_WLAN_VENDOR_ATTR_USD_ELEMENT_CONTAINER, wpabuf_len(elems), wpabuf_head(elems))) || (ssi && nla_put(msg, QCA_WLAN_VENDOR_ATTR_USD_SSI, - wpabuf_len(ssi), wpabuf_head(ssi)))) + wpabuf_len(ssi), wpabuf_head(ssi))) || + (network_id && nla_put(msg, QCA_WLAN_VENDOR_ATTR_USD_NETWORK_ID, + ETH_ALEN, network_id))) goto fail; attr = nla_nest_start(msg, QCA_WLAN_VENDOR_ATTR_USD_CHAN_CONFIG); diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index f92a96e02..034827488 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -1222,14 +1222,15 @@ wpas_drv_nan_publish(struct wpa_supplicant *wpa_s, const u8 *addr, const u8 *service_id, enum nan_service_protocol_type srv_proto_type, const struct wpabuf *ssi, const struct wpabuf *elems, - struct nan_publish_params *params) + struct nan_publish_params *params, const u8 *network_id) { if (!wpa_s->driver->nan_publish) return 0; return wpa_s->driver->nan_publish(wpa_s->drv_priv, addr, publish_id, service_name, service_id, - srv_proto_type, ssi, elems, params); + srv_proto_type, ssi, elems, params, + network_id); } static inline int @@ -1258,14 +1259,16 @@ wpas_drv_nan_subscribe(struct wpa_supplicant *wpa_s, const u8 *addr, const u8 *service_id, enum nan_service_protocol_type srv_proto_type, const struct wpabuf *ssi, const struct wpabuf *elems, - struct nan_subscribe_params *params) + struct nan_subscribe_params *params, + const u8 *network_id) { if (!wpa_s->driver->nan_subscribe) return 0; return wpa_s->driver->nan_subscribe(wpa_s->drv_priv, addr, subscribe_id, service_name, service_id, - srv_proto_type, ssi, elems, params); + srv_proto_type, ssi, elems, params, + network_id); } static inline int diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c index 3a578334c..5c162ad8b 100644 --- a/wpa_supplicant/nan_supplicant.c +++ b/wpa_supplicant/nan_supplicant.c @@ -4460,6 +4460,7 @@ int wpas_nan_publish(struct wpa_supplicant *wpa_s, const char *service_name, int publish_id; struct wpabuf *elems = NULL; const u8 *addr; + const u8 *network_id = NULL; if (!wpa_s->nan_de) return -1; @@ -4501,6 +4502,11 @@ int wpas_nan_publish(struct wpa_supplicant *wpa_s, const char *service_name, } #endif /* CONFIG_NAN */ + if (p2p) + network_id = p2p_network_id; + else + network_id = nan_network_id; + if (p2p) { elems = wpas_p2p_usd_elems(wpa_s, service_name); addr = wpa_s->global->p2p_dev_addr; @@ -4523,7 +4529,8 @@ int wpas_nan_publish(struct wpa_supplicant *wpa_s, const char *service_name, wpas_drv_nan_publish(wpa_s, addr, publish_id, service_name, nan_de_get_service_id(wpa_s->nan_de, publish_id), - srv_proto_type, ssi, elems, params) < 0) { + srv_proto_type, ssi, elems, params, + network_id) < 0) { nan_de_cancel_publish(wpa_s->nan_de, publish_id); publish_id = -1; } @@ -4620,6 +4627,7 @@ int wpas_nan_subscribe(struct wpa_supplicant *wpa_s, int subscribe_id; struct wpabuf *elems = NULL; const u8 *addr; + const u8 *network_id = NULL; if (!wpa_s->nan_de) return -1; @@ -4660,6 +4668,11 @@ int wpas_nan_subscribe(struct wpa_supplicant *wpa_s, } #endif /* CONFIG_NAN */ + if (p2p) + network_id = p2p_network_id; + else + network_id = nan_network_id; + if (p2p) { elems = wpas_p2p_usd_elems(wpa_s, service_name); addr = wpa_s->global->p2p_dev_addr; @@ -4683,7 +4696,8 @@ int wpas_nan_subscribe(struct wpa_supplicant *wpa_s, wpas_drv_nan_subscribe(wpa_s, addr, subscribe_id, service_name, nan_de_get_service_id(wpa_s->nan_de, subscribe_id), - srv_proto_type, ssi, elems, params) < 0) { + srv_proto_type, ssi, elems, params, + network_id) < 0) { nan_de_cancel_subscribe(wpa_s->nan_de, subscribe_id); subscribe_id = -1; } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:33 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:33 +0530 Subject: [PATCH v3 09/46] NAN: Pass forced_addr to remain-on-channel listen callback In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-10-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Extend the nan_callbacks.listen() signature to accept an optional forced_addr parameter, allowing a per-service source MAC address to be propagated to the driver's remain-on-channel request. In nan_de_timer(), pass srv->forced_addr (or NULL if not set) to de->cb.listen() at both call sites. In the supplicant, store the address in wpas_nan_usd_listen_work and pass it as filter_addr to wpa_drv_remain_on_channel() when the driver advertises WPA_DRIVER_FLAGS2_ROC_ADDR_FILTER support. Update hostapd_nan_de_listen() stub signature to match. Signed-off-by: Peddolla Harshavardhan Reddy --- src/ap/nan_usd_ap.c | 2 +- src/common/nan_de.c | 9 ++++++--- src/common/nan_de.h | 3 ++- wpa_supplicant/nan_supplicant.c | 14 ++++++++++++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/ap/nan_usd_ap.c b/src/ap/nan_usd_ap.c index 8620644cb..86183deb3 100644 --- a/src/ap/nan_usd_ap.c +++ b/src/ap/nan_usd_ap.c @@ -37,7 +37,7 @@ static int hostapd_nan_de_tx(void *ctx, unsigned int freq, static int hostapd_nan_de_listen(void *ctx, unsigned int freq, - unsigned int duration) + unsigned int duration, const u8 *forced_addr) { return 0; } diff --git a/src/common/nan_de.c b/src/common/nan_de.c index 72ee6fe47..f620da808 100644 --- a/src/common/nan_de.c +++ b/src/common/nan_de.c @@ -958,8 +958,9 @@ static void nan_de_timer(void *eloop_ctx, void *timeout_ctx) } if ((unsigned int) duration > de->max_listen) duration = de->max_listen; - if (de->cb.listen(de->cb.ctx, srv->freq, duration) == - 0) { + if (de->cb.listen(de->cb.ctx, srv->freq, duration, + srv->forced_addr_set ? + srv->forced_addr : NULL) == 0) { wpa_printf(MSG_DEBUG, "NAN: Publisher in pauseState - started listen on %u MHz", srv->freq); @@ -994,7 +995,9 @@ static void nan_de_timer(void *eloop_ctx, void *timeout_ctx) duration = nan_de_listen_duration(de, srv); started = true; - if (de->cb.listen(de->cb.ctx, srv->freq, duration) == 0) + if (de->cb.listen(de->cb.ctx, srv->freq, duration, + srv->forced_addr_set ? + srv->forced_addr : NULL) == 0) de->listen_freq = srv->freq; } diff --git a/src/common/nan_de.h b/src/common/nan_de.h index 95ca161f2..6e06f61ea 100644 --- a/src/common/nan_de.h +++ b/src/common/nan_de.h @@ -49,7 +49,8 @@ struct nan_callbacks { int (*tx)(void *ctx, unsigned int freq, unsigned int wait_time, const u8 *dst, const u8 *src, const u8 *bssid, const struct wpabuf *buf); - int (*listen)(void *ctx, unsigned int freq, unsigned int duration); + int (*listen)(void *ctx, unsigned int freq, unsigned int duration, + const u8 *forced_addr); /* NAN DE Events */ void (*discovery_result)(void *ctx, struct nan_discovery_result *res); diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c index 5c162ad8b..705643ca7 100644 --- a/wpa_supplicant/nan_supplicant.c +++ b/wpa_supplicant/nan_supplicant.c @@ -4174,6 +4174,8 @@ static int wpas_nan_de_tx(void *ctx, unsigned int freq, unsigned int wait_time, struct wpas_nan_usd_listen_work { unsigned int freq; unsigned int duration; + u8 forced_addr[ETH_ALEN]; + bool forced_addr_set; }; @@ -4224,7 +4226,11 @@ static void wpas_nan_usd_start_listen_cb(struct wpa_radio_work *work, duration = wpa_s->max_remain_on_chan; wpa_printf(MSG_DEBUG, "NAN: Start listen on %u MHz for %u ms", lwork->freq, duration); - if (wpa_drv_remain_on_channel(wpa_s, lwork->freq, duration, NULL) < 0) { + if (wpa_drv_remain_on_channel(wpa_s, lwork->freq, duration, + (lwork->forced_addr_set && + (wpa_s->drv_flags2 & + WPA_DRIVER_FLAGS2_ROC_ADDR_FILTER)) ? + lwork->forced_addr : NULL) < 0) { wpa_printf(MSG_DEBUG, "NAN: Failed to request the driver to remain on channel (%u MHz) for listen", lwork->freq); @@ -4241,7 +4247,7 @@ static void wpas_nan_usd_start_listen_cb(struct wpa_radio_work *work, static int wpas_nan_de_listen(void *ctx, unsigned int freq, - unsigned int duration) + unsigned int duration, const u8 *forced_addr) { struct wpa_supplicant *wpa_s = ctx; struct wpas_nan_usd_listen_work *lwork; @@ -4251,6 +4257,10 @@ static int wpas_nan_de_listen(void *ctx, unsigned int freq, return -1; lwork->freq = freq; lwork->duration = duration; + if (forced_addr) { + os_memcpy(lwork->forced_addr, forced_addr, ETH_ALEN); + lwork->forced_addr_set = true; + } if (!radio_add_work(wpa_s, freq, "nan-usd-listen", 0, wpas_nan_usd_start_listen_cb, lwork)) { -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:34 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:34 +0530 Subject: [PATCH v3 10/46] nl80211: Parse Peer Measurement (PMSR) capabilities In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-11-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add support for parsing FTM capabilities for Proximity Ranging (PR). Parse PMSR attributes and store them in nested structs (ista, rsta, ranging_type). This includes support for: - ISTA and RSTA role capabilities (NTB, TB, EDCA support) - Per-role peer limits - Ranging type support (infrastructure and proximity detection) - Concurrent ISTA/RSTA support - 6GHz support - Proximity Detection preambles and bandwidths - Minimum ranging intervals for EDCA and NTB The parsed values are stored in wpa_driver_capa. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.h | 14 +++ src/drivers/driver.h | 29 +++++ src/drivers/driver_nl80211_capa.c | 170 ++++++++++++++++++++++++++++++ wpa_supplicant/pr_supplicant.c | 8 ++ wpa_supplicant/wpa_supplicant.c | 6 +- 5 files changed, 226 insertions(+), 1 deletion(-) diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 14106d2fb..85fc40770 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -358,6 +358,8 @@ struct pr_config { u8 edca_format_and_bw; + u32 edca_min_ranging_interval; + u8 max_tx_antenna; u8 max_rx_antenna; @@ -368,6 +370,16 @@ struct pr_config { bool ntb_rsta_support; + bool concurrent_ista_rsta; + + u32 pmsr_max_peers; + + u32 pr_max_peer_ista_role; + + u32 pr_max_peer_rsta_role; + + u8 max_ftms_per_burst; + bool secure_he_ltf; u8 max_tx_ltf_repetations; @@ -388,6 +400,8 @@ struct pr_config { u8 ntb_format_and_bw; + u32 ntb_min_ranging_interval; + struct pr_channels ntb_channels; bool support_6ghz; diff --git a/src/drivers/driver.h b/src/drivers/driver.h index bbab06e7b..32cdbe800 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2660,6 +2660,7 @@ struct wpa_driver_capa { u8 edca_format_and_bw; u8 max_tx_antenna; u8 max_rx_antenna; + u32 edca_min_ranging_interval; /* NTB based ranging capabilities */ u8 ntb_format_and_bw; @@ -2671,6 +2672,34 @@ struct wpa_driver_capa { u8 max_rx_sts_gt_80; u8 max_tx_sts_le_80; u8 max_tx_sts_gt_80; + u32 ntb_min_ranging_interval; + + /* Peer measurement capabilities */ + u32 pmsr_max_peers; + u8 max_ftms_per_burst; + bool concurrent_ista_rsta; + bool support_6ghz; + u32 pd_preambles; + u32 pd_bandwidths; + + struct { + bool support_ntb; + bool support_tb; + bool support_edca; + u32 max_peers; + } ista; + + struct { + bool support_ntb; + bool support_tb; + bool support_edca; + u32 max_peers; + } rsta; + + struct { + bool infra_support; + bool pd_support; + } ranging_type; #ifdef CONFIG_NAN struct nan_capa nan_capa; diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c index 1799c5395..281f06d22 100644 --- a/src/drivers/driver_nl80211_capa.c +++ b/src/drivers/driver_nl80211_capa.c @@ -18,6 +18,7 @@ #include "common/qca-vendor-attr.h" #include "common/brcm_vendor.h" #include "driver_nl80211.h" +#include "common/proximity_ranging.h" static int protocol_feature_handler(struct nl_msg *msg, void *arg) @@ -1136,6 +1137,170 @@ out: #endif /* CONFIG_NAN */ +#ifdef CONFIG_PR + +static void pmsr_type_ftm_handler(struct wpa_driver_nl80211_data *drv, + struct nlattr *capa) +{ + u32 max_rx_sts, max_tx_sts; + struct nlattr *tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX + 1]; + + if (nla_parse_nested(tb, NL80211_PMSR_FTM_CAPA_ATTR_MAX, capa, NULL)) + return; + + /* Parse ISTA capabilities */ + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS]) { + struct nlattr *ista_caps[NL80211_PMSR_FTM_CAPA_ATTR_MAX + 1]; + + if (!nla_parse_nested(ista_caps, NL80211_PMSR_FTM_CAPA_ATTR_MAX, + tb[NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS], + NULL)) { + drv->capa.ista.support_ntb = + !!ista_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_NTB]; + drv->capa.ista.support_tb = + !!ista_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_TB]; + drv->capa.ista.support_edca = + !!ista_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA]; + if (ista_caps[NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE]) + drv->capa.ista.max_peers = + nla_get_u32(ista_caps[NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE]); + } + } + + /* Parse ranging type capabilities */ + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS]) { + struct nlattr *type_caps[NL80211_PMSR_FTM_TYPE_CAPA_ATTR_MAX + 1]; + + if (!nla_parse_nested(type_caps, + NL80211_PMSR_FTM_TYPE_CAPA_ATTR_MAX, + tb[NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS], + NULL)) { + drv->capa.ranging_type.infra_support = + !!type_caps[NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INFRA_SUPPORT]; + drv->capa.ranging_type.pd_support = + !!type_caps[NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT]; + } + } + + drv->capa.concurrent_ista_rsta = + !!tb[NL80211_PMSR_FTM_CAPA_ATTR_CONCURRENT_ISTA_RSTA_SUPPORT]; + + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_LTF_REP]) + drv->capa.max_tx_ltf_repetations = + nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_LTF_REP]); + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_LTF_REP]) + drv->capa.max_rx_ltf_repetations = + nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_LTF_REP]); + + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_STS]) { + max_rx_sts = nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_STS]); + drv->capa.max_rx_sts_le_80 = max_rx_sts; + drv->capa.max_rx_sts_gt_80 = max_rx_sts; + } + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_STS]) { + max_tx_sts = nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_STS]); + drv->capa.max_tx_sts_le_80 = max_tx_sts; + drv->capa.max_tx_sts_gt_80 = max_tx_sts; + } + + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_TX]) + drv->capa.max_tx_ltf_total = + nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_TX]); + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX]) + drv->capa.max_rx_ltf_total = + nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX]); + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_FTMS_PER_BURST]) + drv->capa.max_ftms_per_burst = + nla_get_u8(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_FTMS_PER_BURST]); + + /* Parse RSTA capabilities */ + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT] && + tb[NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS]) { + struct nlattr *rsta_caps[NL80211_PMSR_FTM_CAPA_ATTR_MAX + 1]; + + if (!nla_parse_nested(rsta_caps, NL80211_PMSR_FTM_CAPA_ATTR_MAX, + tb[NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS], + NULL)) { + drv->capa.rsta.support_ntb = + !!rsta_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_NTB]; + drv->capa.rsta.support_tb = + !!rsta_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_TB]; + drv->capa.rsta.support_edca = + !!rsta_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA]; + if (rsta_caps[NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE]) + drv->capa.rsta.max_peers = + nla_get_u32(rsta_caps[NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE]); + } + } + + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_TX_ANTENNAS]) + drv->capa.max_tx_antenna = + nla_get_u8(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_TX_ANTENNAS]); + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_RX_ANTENNAS]) + drv->capa.max_rx_antenna = + nla_get_u8(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_RX_ANTENNAS]); + + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_EDCA]) + drv->capa.edca_min_ranging_interval = + nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_EDCA]); + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_NTB]) + drv->capa.ntb_min_ranging_interval = + nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_NTB]); + + /* Parse additional ranging capabilities */ + drv->capa.support_6ghz = + !!tb[NL80211_PMSR_FTM_CAPA_ATTR_6GHZ_SUPPORT]; + + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES]) + drv->capa.pd_preambles = + nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES]); + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS]) + drv->capa.pd_bandwidths = + nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS]); +} + + +static void wiphy_info_pmsr_type_capa(struct wpa_driver_nl80211_data *drv, + struct nlattr *attr) +{ + struct nlattr *pos; + int rem; + + nla_for_each_nested(pos, attr, rem) { + if (nla_type(pos) == NL80211_PMSR_TYPE_FTM) + pmsr_type_ftm_handler(drv, pos); + } +} + + +static void wiphy_info_pmsr_capa(struct wpa_driver_nl80211_data *drv, + struct nlattr *tb[]) +{ + struct nlattr *pmsr_capa[NL80211_PMSR_ATTR_MAX + 1]; + static struct nla_policy + pmsr_policy[NL80211_PMSR_ATTR_MAX + 1] = { + [NL80211_PMSR_ATTR_MAX_PEERS] = { .type = NLA_U32 }, + [NL80211_PMSR_ATTR_TYPE_CAPA] = { .type = NLA_NESTED }, + }; + + if (nla_parse_nested(pmsr_capa, NL80211_PMSR_ATTR_MAX, + tb[NL80211_ATTR_PEER_MEASUREMENTS], + pmsr_policy)) { + wpa_printf(MSG_DEBUG, "nl80211: Failed to parse PMSR capabilities"); + return; + } + + if (pmsr_capa[NL80211_PMSR_ATTR_MAX_PEERS]) + drv->capa.pmsr_max_peers = + nla_get_u32(pmsr_capa[NL80211_PMSR_ATTR_MAX_PEERS]); + if (pmsr_capa[NL80211_PMSR_ATTR_TYPE_CAPA]) + wiphy_info_pmsr_type_capa(drv, + pmsr_capa[NL80211_PMSR_ATTR_TYPE_CAPA]); +} + +#endif /* CONFIG_PR */ + + static int wiphy_info_handler(struct nl_msg *msg, void *arg) { struct nlattr *tb[NL80211_ATTR_MAX + 1]; @@ -1262,6 +1427,11 @@ static int wiphy_info_handler(struct nl_msg *msg, void *arg) wiphy_info_extended_capab(drv, tb[NL80211_ATTR_IFTYPE_EXT_CAPA]); +#ifdef CONFIG_PR + if (tb[NL80211_ATTR_PEER_MEASUREMENTS]) + wiphy_info_pmsr_capa(drv, tb); +#endif /* CONFIG_PR */ + if (tb[NL80211_ATTR_VENDOR_DATA]) { struct nlattr *nl; int rem; diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index f47407e5c..0af25f5d4 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -375,6 +375,14 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, pr.max_tx_sts_le_80 = capa->max_tx_sts_le_80; pr.max_tx_sts_gt_80 = capa->max_tx_sts_gt_80; + pr.edca_min_ranging_interval = capa->edca_min_ranging_interval; + pr.ntb_min_ranging_interval = capa->ntb_min_ranging_interval; + pr.concurrent_ista_rsta = capa->concurrent_ista_rsta; + pr.pmsr_max_peers = capa->pmsr_max_peers; + pr.pr_max_peer_ista_role = capa->ista.max_peers; + pr.pr_max_peer_rsta_role = capa->rsta.max_peers; + pr.max_ftms_per_burst = capa->max_ftms_per_burst; + pr.support_6ghz = wpas_is_6ghz_supported(wpa_s, true); pr.pasn_send_mgmt = wpas_pr_pasn_send_mgmt; diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 5414eab0f..f54cb02ed 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -8071,8 +8071,12 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, return -1; } - if (wpas_pr_init(wpa_s->global, wpa_s, &capa) < 0) + if (!capa.ranging_type.pd_support) { + wpa_printf(MSG_DEBUG, + "PR: Driver does not support Proximity Ranging - PR disabled"); + } else if (wpas_pr_init(wpa_s->global, wpa_s, &capa) < 0) { return -1; + } if (wpa_bss_init(wpa_s) < 0) return -1; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:35 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:35 +0530 Subject: [PATCH v3 11/46] PR: Use stored capabilities instead of driver flags In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-12-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy When driver flags were initially added to determine EDCA and NTB support for ISTA and RSTA roles, they were not being set anywhere in the code. Now that we parse and store PMSR capabilities from the driver in nested structs (ista, rsta), use those stored capabilities directly instead of relying on driver flags. Replace driver flag checks: - WPA_DRIVER_FLAGS2_FTM_INITIATOR -> capa->ista.support_edca - WPA_DRIVER_FLAGS_FTM_RESPONDER -> capa->rsta.support_edca - WPA_DRIVER_FLAGS2_NON_TRIGGER_BASED_INITIATOR -> capa->ista.support_ntb - WPA_DRIVER_FLAGS2_NON_TRIGGER_BASED_RESPONDER -> capa->rsta.support_ntb Fixes: f45cc220e4bb ("PR: Update PR device configs and capabilities from driver") Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/pr_supplicant.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 0af25f5d4..544656bb3 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -350,10 +350,8 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, pr.pasn_type = wpa_s->conf->pr_pasn_type; pr.preferred_ranging_role = wpa_s->conf->pr_preferred_role; - pr.edca_ista_support = wpa_s->drv_flags2 & - WPA_DRIVER_FLAGS2_FTM_INITIATOR; - pr.edca_rsta_support = wpa_s->drv_flags & - WPA_DRIVER_FLAGS_FTM_RESPONDER; + pr.edca_ista_support = capa->ista.support_edca; + pr.edca_rsta_support = capa->rsta.support_edca; pr.edca_format_and_bw = capa->edca_format_and_bw; pr.max_rx_antenna = capa->max_rx_antenna; pr.max_tx_antenna = capa->max_tx_antenna; @@ -361,10 +359,8 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, wpas_pr_setup_edca_channels(wpa_s, &pr.edca_channels, pr.edca_format_and_bw); - pr.ntb_ista_support = wpa_s->drv_flags2 & - WPA_DRIVER_FLAGS2_NON_TRIGGER_BASED_INITIATOR; - pr.ntb_rsta_support = wpa_s->drv_flags2 & - WPA_DRIVER_FLAGS2_NON_TRIGGER_BASED_RESPONDER; + pr.ntb_ista_support = capa->ista.support_ntb; + pr.ntb_rsta_support = capa->rsta.support_ntb; pr.ntb_format_and_bw = capa->ntb_format_and_bw; pr.max_tx_ltf_repetations = capa->max_tx_ltf_repetations; pr.max_rx_ltf_repetations = capa->max_rx_ltf_repetations; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:36 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:36 +0530 Subject: [PATCH v3 12/46] PR: Add support to use stored 6GHz capability for PR In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-13-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Currently, wpas_pr_init uses a generic function wpas_is_6ghz_supported() to determine Proximity Ranging (PR) 6 GHz support. This can be inaccurate as the generic check may be true for reasons unrelated to PR. Use the stored PMSR capability (capa->support_6ghz) which is parsed from NL80211_PMSR_FTM_CAPA_ATTR_6GHZ_SUPPORT attribute. This ensures that PR 6GHz capability is determined by its specific PMSR attribute rather than a generic system setting. Fixes: f45cc220e4bb ("PR: Update PR device configs and capabilities from driver") Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/pr_supplicant.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 544656bb3..be5477b44 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -379,7 +379,7 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, pr.pr_max_peer_rsta_role = capa->rsta.max_peers; pr.max_ftms_per_burst = capa->max_ftms_per_burst; - pr.support_6ghz = wpas_is_6ghz_supported(wpa_s, true); + pr.support_6ghz = capa->support_6ghz; pr.pasn_send_mgmt = wpas_pr_pasn_send_mgmt; pr.pasn_result = wpas_pr_pasn_result; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:37 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:37 +0530 Subject: [PATCH v3 13/46] PR: Fix wrong channel set used in NTB capabilities In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-14-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Currently when filling NTB capabilities, pr_get_ntb_capabilities() copies edca_channels into the capabilities structure. This is incorrect as NTB ranging uses a separate channel set from EDCA ranging. To address this issue, replace the edca_channels reference with ntb_channels so that the NTB capability advertisement reflects the correct supported channels. Fixes: fa6cd439890d ("PR: Add NTB capabilities in USD PR element") Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index 96cb8194d..ea229eaef 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -345,7 +345,7 @@ static void pr_get_ntb_capabilities(struct pr_data *pr, MAX_TX_STS_GT_80; capab->ntb_hw_caps = ntb_hw_caps; - os_memcpy(&capab->channels, &pr->cfg->edca_channels, + os_memcpy(&capab->channels, &pr->cfg->ntb_channels, sizeof(struct pr_channels)); } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:38 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:38 +0530 Subject: [PATCH v3 14/46] PR: Replace format_and_bw attribute with preamble/BW bitmaps In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-15-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy The PD ranging capability used a single format-and-bandwidth attribute in wpa_driver_capa to represent the best supported combination. This only considered the highest supported bandwidth and assumed all lower bandwidths were also supported, which is not always the case. The channel population logic inherited the same assumption. Replace the attribute with separate preamble and bandwidth bitmaps so each supported combination is explicitly represented. Parse the corresponding nl80211 attributes to populate these bitmaps and remove the now redundant attribute from driver.h. Update the channel population logic to check each operating class bandwidth directly against the bitmap rather than inferring support from the highest supported value. Fixes: f45cc220e4bb ("PR: Update PR device configs and capabilities from driver") Fixes: cfe7a215db0b ("PR: Determine channels that are supported to perform ranging") Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.h | 8 ++ src/drivers/driver.h | 24 ++++- wpa_supplicant/pr_supplicant.c | 181 ++++++++++++++++++--------------- 3 files changed, 127 insertions(+), 86 deletions(-) diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 85fc40770..2643f8534 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -98,6 +98,7 @@ enum edca_format_and_bw_value { EDCA_FORMAT_AND_BW_VHT80P80 = 14, EDCA_FORMAT_AND_BW_VHT160_DUAL_LO = 15, EDCA_FORMAT_AND_BW_VHT160_SINGLE_LO = 16, + EDCA_FORMAT_AND_BW_MAX, }; /** @@ -114,6 +115,7 @@ enum ntb_format_and_bw_value { NTB_FORMAT_AND_BW_HE80P80 = 3, NTB_FORMAT_AND_BW_HE160_DUAL_LO = 4, NTB_FORMAT_AND_BW_HE160_SINGLE_LO = 5, + NTB_FORMAT_AND_BW_MAX, }; struct pr_capabilities { @@ -356,6 +358,7 @@ struct pr_config { bool edca_rsta_support; + /* Best single format_bw value derived from pd_format_bw_bitmap */ u8 edca_format_and_bw; u32 edca_min_ranging_interval; @@ -398,6 +401,11 @@ struct pr_config { u8 max_tx_sts_gt_80; + /* PD ranging preamble and bandwidth bitmaps (shared by EDCA and NTB) */ + u32 pd_preamble_bitmap; + u32 pd_format_bw_bitmap; + + /* Best single format_bw value derived from pd_format_bw_bitmap */ u8 ntb_format_and_bw; u32 ntb_min_ranging_interval; diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 32cdbe800..a1f922f9a 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2246,6 +2246,28 @@ struct nan_capa { bool he_valid; }; +/** + * Channel width index values used in PD ranging capability bitmaps + * (pd_bandwidths). The bit position in the bitmap corresponds to the + * channel width; these values match the nl80211_chan_width enum so + * that drivers can use them directly. + */ +#define WPA_PR_CHAN_WIDTH_20 1 +#define WPA_PR_CHAN_WIDTH_40 2 +#define WPA_PR_CHAN_WIDTH_80 3 +#define WPA_PR_CHAN_WIDTH_80P80 4 +#define WPA_PR_CHAN_WIDTH_160 5 + +/** + * Preamble index values used in PD ranging capability bitmaps + * (pd_preambles). The bit position in the bitmap corresponds to the + * preamble type; these values match the nl80211_preamble enum so + * that drivers can use them directly. + */ +#define WPA_PR_PREAMBLE_HT 1 +#define WPA_PR_PREAMBLE_VHT 2 +#define WPA_PR_PREAMBLE_HE 4 + /** * struct wpa_driver_capa - Driver capability information */ @@ -2657,13 +2679,11 @@ struct wpa_driver_capa { size_t max_probe_req_ie_len; /* EDCA based ranging capabilities */ - u8 edca_format_and_bw; u8 max_tx_antenna; u8 max_rx_antenna; u32 edca_min_ranging_interval; /* NTB based ranging capabilities */ - u8 ntb_format_and_bw; u8 max_tx_ltf_repetations; u8 max_rx_ltf_repetations; u8 max_tx_ltf_total; diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index be5477b44..f50712a96 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -24,115 +24,118 @@ static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx); #endif /* CONFIG_PASN */ -static int wpas_pr_edca_get_bw(enum edca_format_and_bw_value format_and_bw) +static enum edca_format_and_bw_value +wpas_pr_best_edca_format_bw(u32 bw_bitmap, u32 preamble_bitmap) { - switch (format_and_bw) { - case EDCA_FORMAT_AND_BW_VHT20: - return 20; - case EDCA_FORMAT_AND_BW_HT40: - case EDCA_FORMAT_AND_BW_VHT40: - return 40; - case EDCA_FORMAT_AND_BW_VHT80: - return 80; - case EDCA_FORMAT_AND_BW_VHT80P80: - case EDCA_FORMAT_AND_BW_VHT160_DUAL_LO: - case EDCA_FORMAT_AND_BW_VHT160_SINGLE_LO: - return 160; - default: - return 0; - } + /* Prefer highest bandwidth first */ + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_160)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT))) + return EDCA_FORMAT_AND_BW_VHT160_DUAL_LO; + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80P80)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT))) + return EDCA_FORMAT_AND_BW_VHT80P80; + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT))) + return EDCA_FORMAT_AND_BW_VHT80; + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_40)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT))) + return EDCA_FORMAT_AND_BW_VHT40; + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_40)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_HT))) + return EDCA_FORMAT_AND_BW_HT40; + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_20)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT))) + return EDCA_FORMAT_AND_BW_VHT20; + return EDCA_FORMAT_AND_BW_MAX; } -static int wpas_pr_ntb_get_bw(enum ntb_format_and_bw_value format_and_bw) +static enum ntb_format_and_bw_value +wpas_pr_best_ntb_format_bw(u32 bw_bitmap, u32 preamble_bitmap) { - switch (format_and_bw) { - case NTB_FORMAT_AND_BW_HE20: - return 20; - case NTB_FORMAT_AND_BW_HE40: - return 40; - case NTB_FORMAT_AND_BW_HE80: - return 80; - case NTB_FORMAT_AND_BW_HE80P80: - case NTB_FORMAT_AND_BW_HE160_DUAL_LO: - case NTB_FORMAT_AND_BW_HE160_SINGLE_LO: - return 160; - default: - return 0; - } + if (!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_HE))) + return NTB_FORMAT_AND_BW_MAX; + + if (bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_160)) + return NTB_FORMAT_AND_BW_HE160_SINGLE_LO; + if (bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80P80)) + return NTB_FORMAT_AND_BW_HE80P80; + if (bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80)) + return NTB_FORMAT_AND_BW_HE80; + if (bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_40)) + return NTB_FORMAT_AND_BW_HE40; + if (bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_20)) + return NTB_FORMAT_AND_BW_HE20; + return NTB_FORMAT_AND_BW_MAX; } static bool -wpas_pr_edca_is_valid_op_class(enum edca_format_and_bw_value format_and_bw, +wpas_pr_edca_is_valid_op_class(u32 bw_bitmap, u32 preamble_bitmap, const struct oper_class_map *op_class_map) { - int bw = 0, op_class_bw = 0; - if (!op_class_map) return false; - op_class_bw = oper_class_bw_to_int(op_class_map); - bw = wpas_pr_edca_get_bw(format_and_bw); - - if (!op_class_bw || !bw) + switch (op_class_map->bw) { + case BW20: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_20)) && + !!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT)); + case BW40PLUS: + case BW40MINUS: + case BW40: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_40)) && + !!(preamble_bitmap & (BIT(WPA_PR_PREAMBLE_VHT) | + BIT(WPA_PR_PREAMBLE_HT))); + case BW80: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80)) && + !!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT)); + case BW80P80: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80P80)) && + !!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT)); + case BW160: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_160)) && + !!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT)); + default: return false; - - if (format_and_bw <= EDCA_FORMAT_AND_BW_VHT80 && - format_and_bw >= EDCA_FORMAT_AND_BW_VHT20 && - op_class_bw <= bw) - return true; - - if (format_and_bw == EDCA_FORMAT_AND_BW_VHT80P80 && - (op_class_bw < bw || op_class_map->bw == BW80P80)) - return true; - - if ((format_and_bw == EDCA_FORMAT_AND_BW_VHT160_DUAL_LO || - format_and_bw == EDCA_FORMAT_AND_BW_VHT160_SINGLE_LO) && - (op_class_bw < bw || op_class_map->bw == BW160)) - return true; - - return false; + } } static bool -wpas_pr_ntb_is_valid_op_class(enum ntb_format_and_bw_value format_and_bw, +wpas_pr_ntb_is_valid_op_class(u32 bw_bitmap, u32 preamble_bitmap, const struct oper_class_map *op_class_map) { - int bw = 0, op_class_bw = 0; - if (!op_class_map) return false; - op_class_bw = oper_class_bw_to_int(op_class_map); - bw = wpas_pr_ntb_get_bw(format_and_bw); - - if (!op_class_bw || !bw) + /* NTB ranging requires HE preamble */ + if (!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_HE))) return false; - if (format_and_bw <= NTB_FORMAT_AND_BW_HE80 && - format_and_bw >= NTB_FORMAT_AND_BW_HE20 && - op_class_bw <= bw) - return true; - - if (format_and_bw == NTB_FORMAT_AND_BW_HE80P80 && - (op_class_bw < bw || op_class_map->bw == BW80P80)) - return true; - - if ((format_and_bw == NTB_FORMAT_AND_BW_HE160_DUAL_LO || - format_and_bw == NTB_FORMAT_AND_BW_HE160_SINGLE_LO) && - (op_class_bw < bw || op_class_map->bw == BW160)) - return true; - - return false; + switch (op_class_map->bw) { + case BW20: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_20)); + case BW40PLUS: + case BW40MINUS: + case BW40: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_40)); + case BW80: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80)); + case BW80P80: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80P80)); + case BW160: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_160)); + default: + return false; + } } static void wpas_pr_setup_edca_channels(struct wpa_supplicant *wpa_s, struct pr_channels *chan, - enum edca_format_and_bw_value format_and_bw) + u32 bw_bitmap, u32 preamble_bitmap) { struct hostapd_hw_modes *mode; int cla = 0, i; @@ -145,7 +148,8 @@ wpas_pr_setup_edca_channels(struct wpa_supplicant *wpa_s, mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, o->mode, is_6ghz_op_class(o->op_class)); if (!mode || is_6ghz_op_class(o->op_class) || - !wpas_pr_edca_is_valid_op_class(format_and_bw, o)) + !wpas_pr_edca_is_valid_op_class(bw_bitmap, preamble_bitmap, + o)) continue; for (ch = o->min_chan; ch <= o->max_chan; ch += o->inc) { @@ -191,7 +195,7 @@ wpas_pr_setup_edca_channels(struct wpa_supplicant *wpa_s, static void wpas_pr_setup_ntb_channels(struct wpa_supplicant *wpa_s, struct pr_channels *chan, - enum ntb_format_and_bw_value format_and_bw, + u32 bw_bitmap, u32 preamble_bitmap, bool allow_6ghz) { int cla = 0, i; @@ -205,7 +209,8 @@ wpas_pr_setup_ntb_channels(struct wpa_supplicant *wpa_s, mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, o->mode, is_6ghz_op_class(o->op_class)); if (!mode || (!allow_6ghz && is_6ghz_op_class(o->op_class)) || - !wpas_pr_ntb_is_valid_op_class(format_and_bw, o)) + !wpas_pr_ntb_is_valid_op_class(bw_bitmap, preamble_bitmap, + o)) continue; for (ch = o->min_chan; ch <= o->max_chan; ch += o->inc) { @@ -352,16 +357,23 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, pr.edca_ista_support = capa->ista.support_edca; pr.edca_rsta_support = capa->rsta.support_edca; - pr.edca_format_and_bw = capa->edca_format_and_bw; + pr.pd_format_bw_bitmap = capa->pd_bandwidths; + pr.pd_preamble_bitmap = capa->pd_preambles; + pr.edca_format_and_bw = + wpas_pr_best_edca_format_bw(capa->pd_bandwidths, + capa->pd_preambles); pr.max_rx_antenna = capa->max_rx_antenna; pr.max_tx_antenna = capa->max_tx_antenna; wpas_pr_setup_edca_channels(wpa_s, &pr.edca_channels, - pr.edca_format_and_bw); + capa->pd_bandwidths, + capa->pd_preambles); pr.ntb_ista_support = capa->ista.support_ntb; pr.ntb_rsta_support = capa->rsta.support_ntb; - pr.ntb_format_and_bw = capa->ntb_format_and_bw; + pr.ntb_format_and_bw = + wpas_pr_best_ntb_format_bw(capa->pd_bandwidths, + capa->pd_preambles); pr.max_tx_ltf_repetations = capa->max_tx_ltf_repetations; pr.max_rx_ltf_repetations = capa->max_rx_ltf_repetations; pr.max_tx_ltf_total = capa->max_tx_ltf_total; @@ -390,7 +402,8 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, pr.secure_he_ltf = wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SEC_LTF_STA; wpas_pr_setup_ntb_channels(wpa_s, &pr.ntb_channels, - pr.ntb_format_and_bw, + capa->pd_bandwidths, + capa->pd_preambles, pr.support_6ghz); if (wpa_s->conf->country[0] && wpa_s->conf->country[1]) { -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:39 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:39 +0530 Subject: [PATCH v3 15/46] PR: Gate EDCA ranging support on ASAP capability In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-16-kavita.kavita@oss.qualcomm.com> Parse NL80211_PMSR_FTM_CAPA_ATTR_ASAP from the driver and store it in capa->asap_support. Gate both pr.edca_ista_support and pr.edca_rsta_support on ASAP support since ASAP is mandatory for EDCA-based ranging. This ensures EDCA ranging requests are only sent when the driver advertises ASAP support, preventing invalid ranging attempts. Signed-off-by: Kavita Kavita --- src/drivers/driver.h | 1 + src/drivers/driver_nl80211_capa.c | 3 +++ wpa_supplicant/pr_supplicant.c | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index a1f922f9a..18c51b75c 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2699,6 +2699,7 @@ struct wpa_driver_capa { u8 max_ftms_per_burst; bool concurrent_ista_rsta; bool support_6ghz; + bool asap_support; u32 pd_preambles; u32 pd_bandwidths; diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c index 281f06d22..6051a0801 100644 --- a/src/drivers/driver_nl80211_capa.c +++ b/src/drivers/driver_nl80211_capa.c @@ -1251,6 +1251,9 @@ static void pmsr_type_ftm_handler(struct wpa_driver_nl80211_data *drv, drv->capa.support_6ghz = !!tb[NL80211_PMSR_FTM_CAPA_ATTR_6GHZ_SUPPORT]; + drv->capa.asap_support = + !!tb[NL80211_PMSR_FTM_CAPA_ATTR_ASAP]; + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES]) drv->capa.pd_preambles = nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES]); diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index f50712a96..9b3e09d82 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -355,8 +355,8 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, pr.pasn_type = wpa_s->conf->pr_pasn_type; pr.preferred_ranging_role = wpa_s->conf->pr_preferred_role; - pr.edca_ista_support = capa->ista.support_edca; - pr.edca_rsta_support = capa->rsta.support_edca; + pr.edca_ista_support = capa->ista.support_edca && capa->asap_support; + pr.edca_rsta_support = capa->rsta.support_edca && capa->asap_support; pr.pd_format_bw_bitmap = capa->pd_bandwidths; pr.pd_preamble_bitmap = capa->pd_preambles; pr.edca_format_and_bw = -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:40 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:40 +0530 Subject: [PATCH v3 16/46] PR: Add nl80211 driver support for PD interface In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-17-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Proximity Detection (PD) ranging requires a dedicated virtual interface with its own MAC address, separate from the station interface. Add WPA_IF_PD to the driver interface type enum and wire it through the nl80211 driver. A dedicated netlink socket (nl_pr) is created when the PD interface is brought up, mirroring the existing nl_nan socket used for NAN. The new nl80211_set_pr_dev() function sends NL80211_CMD_START_PD and NL80211_CMD_STOP_PD to the kernel. Interface creation routes the PD type through the nl_pr socket and registers it for eloop event reception. Signed-off-by: Peddolla Harshavardhan Reddy --- src/drivers/driver.h | 5 +++++ src/drivers/driver_nl80211.c | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 18c51b75c..ba53dc60d 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2169,6 +2169,11 @@ enum wpa_driver_if_type { */ WPA_IF_NAN_DATA, + /* + * WPA_IF_PD - PD Device + */ + WPA_IF_PD, + /* keep last */ WPA_IF_MAX }; diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 3b50ffca3..a5b44d9d6 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -248,7 +248,8 @@ static int is_p2p_net_interface(enum nl80211_iftype nlmode) static bool nl80211_is_netdev_iftype(enum nl80211_iftype t) { - return t != NL80211_IFTYPE_P2P_DEVICE && t != NL80211_IFTYPE_NAN; + return t != NL80211_IFTYPE_P2P_DEVICE && t != NL80211_IFTYPE_NAN && + t != NL80211_IFTYPE_PD; } @@ -3239,6 +3240,22 @@ static int nl80211_set_p2pdev(struct i802_bss *bss, int start) return ret; } +static int nl80211_set_pr_dev(struct i802_bss *bss, int start) +{ + struct nl_msg *msg; + int ret; + + msg = nl80211_cmd_msg(bss, 0, start ? NL80211_CMD_START_PD : + NL80211_CMD_STOP_PD); + ret = send_and_recv_cmd(bss->drv, msg); + + wpa_printf(MSG_DEBUG, "nl80211: %s PD Device %s (0x%llx): %s", + start ? "Start" : "Stop", + bss->ifname, (unsigned long long)bss->wdev_id, + strerror(-ret)); + return ret; +} + #ifdef CONFIG_NAN static void nl80211_nan_stop(struct i802_bss *bss) @@ -3297,6 +3314,11 @@ static int i802_set_iface_flags(struct i802_bss *bss, int up) if (nlmode == NL80211_IFTYPE_NAN) return nl80211_set_nandev(bss, up); + if (nlmode == NL80211_IFTYPE_PD) { + /* PR Device has start/stop which is equivalent */ + return nl80211_set_pr_dev(bss, up); + } + return linux_set_iface_flags(bss->drv->global->ioctl_sock, bss->ifname, up); } @@ -3492,7 +3514,8 @@ wpa_driver_nl80211_finish_drv_init(struct i802_bss *bss, const u8 *set_addr, nl80211_disable_11b_rates(bss->drv, bss->drv->ifindex, 1); - if (nlmode == NL80211_IFTYPE_P2P_DEVICE) + if (nlmode == NL80211_IFTYPE_P2P_DEVICE || + nlmode == NL80211_IFTYPE_PD) return ret; } else { wpa_printf(MSG_DEBUG, "nl80211: Could not yet enable " @@ -4856,7 +4879,8 @@ static int wpa_driver_nl80211_send_mlme(struct i802_bss *bss, const u8 *data, } if ((is_sta_interface(drv->nlmode) || - drv->nlmode == NL80211_IFTYPE_P2P_DEVICE) && + drv->nlmode == NL80211_IFTYPE_P2P_DEVICE || + drv->nlmode == NL80211_IFTYPE_PD) && WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT && WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_AUTH) { if (freq == 0 && @@ -6662,6 +6686,8 @@ const char * nl80211_iftype_str(enum nl80211_iftype mode) return "NAN DEVICE"; case NL80211_IFTYPE_NAN_DATA: return "NAN_DATA"; + case NL80211_IFTYPE_PD: + return "PD DEVICE"; default: return "unknown"; } @@ -6699,6 +6725,7 @@ static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv, goto fail; if ((addr && (iftype == NL80211_IFTYPE_P2P_DEVICE || + iftype == NL80211_IFTYPE_PD || iftype == NL80211_IFTYPE_NAN)) && nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr)) goto fail; @@ -9454,6 +9481,8 @@ static enum nl80211_iftype wpa_driver_nl80211_if_type( return NL80211_IFTYPE_NAN; case WPA_IF_NAN_DATA: return NL80211_IFTYPE_NAN_DATA; + case WPA_IF_PD: + return NL80211_IFTYPE_PD; default: return -1; } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:41 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:41 +0530 Subject: [PATCH v3 17/46] nl80211: Use wdev-based message for SET_KEY on wdev-only interfaces In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-18-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy The pairwise RX/TX modify path in wpa_driver_nl80211_set_key() always used nl80211_ifindex_msg() which includes NL80211_ATTR_IFINDEX. Non-netdev interfaces such as PD (Proximity Detection) have no valid ifindex, causing SET_KEY to fail. Use nl80211_cmd_msg() instead when the interface is not a netdev type so that NL80211_ATTR_WDEV is included in the SET_KEY command. Signed-off-by: Peddolla Harshavardhan Reddy --- src/drivers/driver_nl80211.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index a5b44d9d6..ea214b74b 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -4012,7 +4012,11 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, KEY_FLAG_PAIRWISE_RX_TX_MODIFY) { wpa_printf(MSG_DEBUG, "nl80211: SET_KEY (pairwise RX/TX modify)"); - msg = nl80211_ifindex_msg(drv, ifindex, 0, NL80211_CMD_SET_KEY); + if (!nl80211_is_netdev_iftype(drv->nlmode)) + msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_SET_KEY); + else + msg = nl80211_ifindex_msg(drv, ifindex, 0, + NL80211_CMD_SET_KEY); if (!msg) goto fail2; } else if (alg == WPA_ALG_NONE && (key_flag & KEY_FLAG_RX_TX)) { -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:42 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:42 +0530 Subject: [PATCH v3 18/46] nl80211: Add Proximity Detection (PD) wdev support In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-19-kavita.kavita@oss.qualcomm.com> From: Veerendranath Jakkam Introduce driver and nl80211 support for a Proximity Detection (PD) virtual interface, enabled under CONFIG_PR. Two new driver ops are added: - pd_start(): create a non-netdev PD wdev (NL80211_IFTYPE_PD), activate it via NL80211_CMD_START_PD, and return the effective PD MAC address - pd_stop(): deactivate the PD wdev via NL80211_CMD_STOP_PD, then destroy and delete it On the nl80211 side, nl80211_pd_start() allocates a dedicated i802_bss (drv->pd_bss) outside the regular BSS list, creates the PD wdev via nl80211_create_iface(), and activates it. nl80211_pd_stop() tears it down in the correct order: deactivate first, then destroy BSS resources, then delete the wdev. The driver deinit path calls nl80211_pd_stop() if a PD wdev is still active. The wdev_info struct and its netlink callback are generalized and renamed to nl80211_wdev_handler_info to capture both wdev_id and MAC address, replacing the previous nl80211_wdev_handler. This handler is shared between PD wdev creation and the existing non-netdev interface path in wpa_driver_nl80211_if_add(). wpa_supplicant is updated with wpa_drv_pd_start() and wpa_drv_pd_stop() wrappers that call into the driver if CONFIG_PR is enabled. Signed-off-by: Veerendranath Jakkam --- src/drivers/driver.h | 28 ++++++ src/drivers/driver_nl80211.c | 178 +++++++++++++++++++++++++++++------ src/drivers/driver_nl80211.h | 3 + wpa_supplicant/driver_i.h | 20 ++++ 4 files changed, 202 insertions(+), 27 deletions(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index ba53dc60d..ff8d2fd27 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -5897,6 +5897,34 @@ struct wpa_driver_ops { const struct wpabuf *ulw, struct nan_peer_schedule_config *sched); #endif /* CONFIG_NAN */ + +#ifdef CONFIG_PR + /** + * pd_start - Create and activate a PD (Proximity Detection) wdev + * @priv: Private driver interface data + * @addr: Requested MAC address for the PD wdev + * @pd_addr: Buffer (ETH_ALEN) to receive the actual assigned MAC + * Returns: 0 on success, -1 on failure + * + * Creates a PD virtual interface in the driver and activates it via + * NL80211_CMD_START_PD. The PD wdev is associated with the parent + * BSS; all PD operations (key install, TX, peer measurement) are + * routed through it by matching the PD MAC address. + */ + int (*pd_start)(void *priv, const u8 *addr, u8 *pd_addr); + + /** + * pd_stop - Stop and destroy the PD wdev + * @priv: Private driver interface data + * + * Sends NL80211_CMD_STOP_PD, deletes the PD wdev via + * NL80211_CMD_DEL_INTERFACE, and frees all associated driver + * resources. Safe to call when no PD wdev is active (no-op). + */ + void (*pd_stop)(void *priv); + +#endif /* CONFIG_PR */ + }; /** diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index ea214b74b..c392a4b64 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -199,6 +199,9 @@ static int nl80211_put_mesh_config(struct nl_msg *msg, #endif /* CONFIG_MESH */ static int i802_sta_disassoc(void *priv, const u8 *own_addr, const u8 *addr, u16 reason, int link_id); +#ifdef CONFIG_PR +static void nl80211_pd_stop(void *priv); +#endif /* CONFIG_PR */ /* Converts nl80211_chan_width to a common format */ @@ -3240,6 +3243,31 @@ static int nl80211_set_p2pdev(struct i802_bss *bss, int start) return ret; } +struct wdev_info { + u64 wdev_id; + int wdev_id_set; + u8 macaddr[ETH_ALEN]; +}; + +static int nl80211_wdev_handler_info(struct nl_msg *msg, void *arg) +{ + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct wdev_info *wi = arg; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + if (tb[NL80211_ATTR_WDEV]) { + wi->wdev_id = nla_get_u64(tb[NL80211_ATTR_WDEV]); + wi->wdev_id_set = 1; + } + if (tb[NL80211_ATTR_MAC]) + os_memcpy(wi->macaddr, nla_data(tb[NL80211_ATTR_MAC]), + ETH_ALEN); + return NL_SKIP; +} + + static int nl80211_set_pr_dev(struct i802_bss *bss, int start) { struct nl_msg *msg; @@ -3257,6 +3285,120 @@ static int nl80211_set_pr_dev(struct i802_bss *bss, int start) } +#ifdef CONFIG_PR +/** + * nl80211_pd_start - Create and activate a PD wdev + * + * Creates a PD virtual interface (NL80211_IFTYPE_PD) in the kernel and + * activates it via NL80211_CMD_START_PD. The resulting i802_bss is stored + * in drv->pd_bss with ctx pointing to the parent wpa_s so that events + * arriving on the PD wdev_id are routed back to the parent. + */ +static int nl80211_pd_start(void *priv, const u8 *addr, u8 *pd_addr) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + struct i802_bss *pd_bss; + struct wdev_info info; + int ret; + + if (drv->pd_bss) { + wpa_printf(MSG_DEBUG, "nl80211: PD wdev already active"); + if (pd_addr) + os_memcpy(pd_addr, drv->pd_bss->addr, ETH_ALEN); + return 0; + } + + pd_bss = os_zalloc(sizeof(*pd_bss)); + if (!pd_bss) + return -1; + + pd_bss->drv = drv; + pd_bss->ctx = bss->ctx; /* parent wpa_s */ + pd_bss->flink = &pd_bss->links[0]; + os_strlcpy(pd_bss->ifname, "pd-wdev", sizeof(pd_bss->ifname)); + + os_memset(&info, 0, sizeof(info)); + ret = nl80211_create_iface(drv, "pd-wdev", NL80211_IFTYPE_PD, addr, + 0, nl80211_wdev_handler_info, &info, 0); + if (ret || !info.wdev_id_set) { + wpa_printf(MSG_ERROR, + "nl80211: Failed to create PD wdev (ret=%d)", ret); + os_free(pd_bss); + return -1; + } + + pd_bss->wdev_id = info.wdev_id; + pd_bss->wdev_id_set = 1; + if (!is_zero_ether_addr(info.macaddr)) + os_memcpy(pd_bss->addr, info.macaddr, ETH_ALEN); + else if (addr) + os_memcpy(pd_bss->addr, addr, ETH_ALEN); + os_memcpy(pd_bss->flink->addr, pd_bss->addr, ETH_ALEN); + + /* Activate the PD device */ + ret = nl80211_set_pr_dev(pd_bss, 1); + if (ret) { + wpa_printf(MSG_ERROR, + "nl80211: Failed to start PD device (ret=%d)", ret); + goto failed; + } + + if (nl80211_init_bss(pd_bss)) { + wpa_printf(MSG_ERROR, + "nl80211: Failed to init PD BSS"); + goto failed_stop; + } + + if (nl80211_mgmt_subscribe_non_ap(pd_bss)) + wpa_printf(MSG_DEBUG, + "nl80211: Failed to register frame processing for PD interface - ignore for now"); + + drv->pd_bss = pd_bss; + + if (pd_addr) + os_memcpy(pd_addr, pd_bss->addr, ETH_ALEN); + + wpa_printf(MSG_DEBUG, + "nl80211: PD wdev created wdev_id=0x%llx addr=" MACSTR, + (unsigned long long) pd_bss->wdev_id, + MAC2STR(pd_bss->addr)); + return 0; + +failed_stop: + nl80211_set_pr_dev(pd_bss, 0); +failed: + nl80211_del_non_netdev(pd_bss); + os_free(pd_bss); + return -1; +} + + +/** + * nl80211_pd_stop - Stop and destroy the PD wdev + */ +static void nl80211_pd_stop(void *priv) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + + if (!drv->pd_bss) + return; + + wpa_printf(MSG_DEBUG, + "nl80211: Stopping PD wdev wdev_id=0x%llx addr=" MACSTR, + (unsigned long long) drv->pd_bss->wdev_id, + MAC2STR(drv->pd_bss->addr)); + + nl80211_set_pr_dev(drv->pd_bss, 0); + nl80211_destroy_bss(drv->pd_bss); + nl80211_del_non_netdev(drv->pd_bss); + os_free(drv->pd_bss); + drv->pd_bss = NULL; +} +#endif /* CONFIG_PR */ + + #ifdef CONFIG_NAN static void nl80211_nan_stop(struct i802_bss *bss) { @@ -3621,6 +3763,10 @@ static void wpa_driver_nl80211_deinit(struct i802_bss *bss) eloop_unregister_read_sock(drv->eapol_tx_sock); if (drv->eapol_tx_sock >= 0) close(drv->eapol_tx_sock); +#ifdef CONFIG_PR + if (drv->pd_bss) + nl80211_pd_stop(bss); +#endif /* CONFIG_PR */ if (bss->nl_preq) wpa_driver_nl80211_probe_req_report(bss, 0); @@ -9529,32 +9675,6 @@ static int nl80211_vif_addr(struct wpa_driver_nl80211_data *drv, u8 *new_addr) } -struct wdev_info { - u64 wdev_id; - int wdev_id_set; - u8 macaddr[ETH_ALEN]; -}; - -static int nl80211_wdev_handler(struct nl_msg *msg, void *arg) -{ - struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); - struct nlattr *tb[NL80211_ATTR_MAX + 1]; - struct wdev_info *wi = arg; - - nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), - genlmsg_attrlen(gnlh, 0), NULL); - if (tb[NL80211_ATTR_WDEV]) { - wi->wdev_id = nla_get_u64(tb[NL80211_ATTR_WDEV]); - wi->wdev_id_set = 1; - } - - if (tb[NL80211_ATTR_MAC]) - os_memcpy(wi->macaddr, nla_data(tb[NL80211_ATTR_MAC]), - ETH_ALEN); - - return NL_SKIP; -} - static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type, const char *ifname, const u8 *addr, @@ -9577,7 +9697,7 @@ static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type, os_memset(&nonnetdev_info, 0, sizeof(nonnetdev_info)); ifidx = nl80211_create_iface(drv, ifname, nlmode, addr, - 0, nl80211_wdev_handler, + 0, nl80211_wdev_handler_info, &nonnetdev_info, use_existing); if (!nonnetdev_info.wdev_id_set || ifidx != 0) { wpa_printf(MSG_ERROR, @@ -16230,4 +16350,8 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { .nan_config_schedule = wpa_driver_nl80211_nan_config_schedule, .nan_config_peer_schedule = wpa_driver_nl80211_nan_config_peer_schedule, #endif /* CONFIG_NAN */ +#ifdef CONFIG_PR + .pd_start = nl80211_pd_start, + .pd_stop = nl80211_pd_stop, +#endif /* CONFIG_PR */ }; diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h index 0f03da642..d1133a1fe 100644 --- a/src/drivers/driver_nl80211.h +++ b/src/drivers/driver_nl80211.h @@ -287,6 +287,9 @@ struct wpa_driver_nl80211_data { #ifdef CONFIG_NAN unsigned int nan_started:1; #endif /* CONFIG_NAN */ +#ifdef CONFIG_PR + struct i802_bss *pd_bss; /* PD wdev; not in the BSS list */ +#endif /* CONFIG_PR */ }; struct nl80211_err_info { diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index 034827488..c19c834b5 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -738,6 +738,26 @@ static inline int wpa_drv_wowlan(struct wpa_supplicant *wpa_s, return wpa_s->driver->set_wowlan(wpa_s->drv_priv, triggers); } +#ifdef CONFIG_PR + +static inline int +wpa_drv_pd_start(struct wpa_supplicant *wpa_s, const u8 *addr, u8 *pd_addr) +{ + if (!wpa_s->driver->pd_start) + return -1; + return wpa_s->driver->pd_start(wpa_s->drv_priv, addr, pd_addr); +} + +static inline void +wpa_drv_pd_stop(struct wpa_supplicant *wpa_s) +{ + if (!wpa_s->driver->pd_stop) + return; + wpa_s->driver->pd_stop(wpa_s->drv_priv); +} + +#endif /* CONFIG_PR */ + static inline int wpa_drv_vendor_cmd(struct wpa_supplicant *wpa_s, int vendor_id, int subcmd, const u8 *data, size_t data_len, -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:45 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:45 +0530 Subject: [PATCH v3 21/46] PR: Use set_key to configure secure ranging context for PASN In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-22-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy The PR PASN implementation uses wpa_drv_set_secure_ranging_ctx() to install and clear the pairwise key and LTF keyseed after PASN authentication. Replace this with the standard set_key driver operation by extending wpa_driver_set_key_params with ltf_keyseed and ltf_keyseed_len fields and adding NL80211_KEY_LTF_SEED support to the nl80211 set_key implementation. Signed-off-by: Peddolla Harshavardhan Reddy --- src/drivers/driver.h | 15 +++++++++++++ src/drivers/driver_nl80211.c | 10 +++++++++ wpa_supplicant/pr_supplicant.c | 40 +++++++++++++++++++++++++++++----- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index ff8d2fd27..4a13953b4 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2099,6 +2099,21 @@ struct wpa_driver_set_key_params { * * Set to a valid Link ID (0-14) when applicable, otherwise -1. */ int link_id; + + /** + * ltf_keyseed_len - Length of the LTF keyseed in octets. + * + * Set to 0 if no LTF keyseed is provided. + */ + u8 ltf_keyseed_len; + + /** + * ltf_keyseed - LTF keyseed for secure ranging (802.11az). + * + * Used to configure the secure LTF key seed for a peer measurement + * session. Set to NULL if not applicable. + */ + const u8 *ltf_keyseed; }; enum wpa_driver_if_type { diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 4b3b8d1c4..4d5484422 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -4238,6 +4238,16 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, wpa_hexdump(MSG_DEBUG, "nl80211: KEY_SEQ", seq, seq_len); } + + if (params->ltf_keyseed_len && params->ltf_keyseed) { + if (nla_put(key_msg, NL80211_KEY_LTF_SEED, + params->ltf_keyseed_len, + params->ltf_keyseed)) + goto fail; + wpa_hexdump_key(MSG_DEBUG, "nl80211: KEY_LTF_SEED", + params->ltf_keyseed, + params->ltf_keyseed_len); + } } if (addr && !is_broadcast_ether_addr(addr)) { diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 9b3e09d82..e38d7243e 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -296,13 +296,29 @@ static void wpas_pr_pasn_set_keys(void *ctx, const u8 *own_addr, struct wpa_ptk *ptk) { struct wpa_supplicant *wpa_s = ctx; + struct wpa_driver_set_key_params params; wpa_printf(MSG_DEBUG, "PR PASN: Set secure ranging context for " MACSTR, MAC2STR(peer_addr)); - wpa_drv_set_secure_ranging_ctx(wpa_s, own_addr, peer_addr, cipher, - ptk->tk_len, ptk->tk, - ptk->ltf_keyseed_len, - ptk->ltf_keyseed, 0); + + if (!wpa_s->driver->set_key) + return; + + os_memset(¶ms, 0, sizeof(params)); + params.ifname = wpa_s->ifname; + params.alg = wpa_cipher_to_alg(cipher); + params.addr = peer_addr; + params.key_idx = 0; + params.set_tx = 1; + params.key = ptk->tk; + params.key_len = ptk->tk_len; + params.key_flag = KEY_FLAG_PAIRWISE_RX_TX; + params.link_id = -1; + params.ltf_keyseed = ptk->ltf_keyseed; + params.ltf_keyseed_len = ptk->ltf_keyseed_len; + + if (wpa_s->driver->set_key(wpa_s->drv_priv, ¶ms) < 0) + wpa_printf(MSG_ERROR, "PR PASN: Failed to set PTK"); } @@ -310,11 +326,23 @@ static void wpas_pr_pasn_clear_keys(void *ctx, const u8 *own_addr, const u8 *peer_addr) { struct wpa_supplicant *wpa_s = ctx; + struct wpa_driver_set_key_params params; wpa_printf(MSG_DEBUG, "PR PASN: Clear secure ranging context for " MACSTR, MAC2STR(peer_addr)); - wpa_drv_set_secure_ranging_ctx(wpa_s, own_addr, peer_addr, 0, 0, NULL, - 0, NULL, 1); + + if (!wpa_s->driver->set_key) + return; + + os_memset(¶ms, 0, sizeof(params)); + params.ifname = wpa_s->ifname; + params.alg = WPA_ALG_NONE; + params.addr = peer_addr; + params.key_idx = 0; + params.link_id = -1; + + if (wpa_s->driver->set_key(wpa_s->drv_priv, ¶ms) < 0) + wpa_printf(MSG_ERROR, "PR PASN: Failed to clear PTK"); } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:46 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:46 +0530 Subject: [PATCH v3 22/46] nl80211: Route key operations to PD wdev based on own_addr In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-23-kavita.kavita@oss.qualcomm.com> From: Veerendranath Jakkam When installing or removing keys for PD-only wdevs, the key operation must be sent using the PD wdev_id instead of the main interface ifindex. Add own_addr field to struct wpa_driver_set_key_params to identify the virtual interface for the key operation. In the nl80211 driver, if own_addr matches the active PD wdev MAC address, route the operation to the PD BSS with ifindex = 0. Update NEW_KEY, DEL_KEY, and SET_KEY message construction to use nl80211_cmd_msg() (wdev-based) instead of nl80211_ifindex_msg() when operating on the PD wdev. Populate own_addr in wpas_pr_pasn_set_keys() and wpas_pr_pasn_clear_keys() so key install and removal are correctly directed to the PD wdev. Signed-off-by: Veerendranath Jakkam --- src/drivers/driver.h | 6 ++++++ src/drivers/driver_nl80211.c | 21 ++++++++++++++++++--- wpa_supplicant/pr_supplicant.c | 2 ++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 4a13953b4..375ac7da2 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -1978,6 +1978,12 @@ struct wpa_driver_set_key_params { * ifname - Interface name (for multi-SSID/VLAN support) */ const char *ifname; + /** + * own_addr - Own MAC address identifying the virtual interface for + * the key operation, or %NULL to use the default interface (ifname) + */ + const u8 *own_addr; + /** * alg - Encryption algorithm * diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 4d5484422..8397dc5ce 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -4125,6 +4125,7 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, struct nl_msg *key_msg; int ret; int skip_set_key = 1; + int is_pd_bss = 0; const char *ifname = params->ifname; enum wpa_alg alg = params->alg; const u8 *addr = params->addr; @@ -4143,6 +4144,20 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, return 0; ifindex = if_nametoindex(ifname); + +#ifdef CONFIG_PR + /* Route key operation to PD wdev if own_addr matches */ + if (drv->pd_bss && params->own_addr && + ether_addr_equal(params->own_addr, drv->pd_bss->addr)) { + bss = drv->pd_bss; + is_pd_bss = 1; + wpa_printf(MSG_DEBUG, + "nl80211: set_key: routing to PD wdev " MACSTR, + MAC2STR(bss->addr)); + ifindex = 0; /* PD wdev has no ifindex */ + } +#endif /* CONFIG_PR */ + wpa_printf(MSG_DEBUG, "%s: ifindex=%d (%s) alg=%d addr=%p key_idx=%d " "set_tx=%d seq_len=%lu key_len=%lu key_flag=0x%x link_id=%d", __func__, ifindex, ifname, alg, addr, key_idx, set_tx, @@ -4189,7 +4204,7 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, KEY_FLAG_PAIRWISE_RX_TX_MODIFY) { wpa_printf(MSG_DEBUG, "nl80211: SET_KEY (pairwise RX/TX modify)"); - if (!nl80211_is_netdev_iftype(drv->nlmode)) + if (!nl80211_is_netdev_iftype(drv->nlmode) || is_pd_bss) msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_SET_KEY); else msg = nl80211_ifindex_msg(drv, ifindex, 0, @@ -4203,7 +4218,7 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, goto fail2; } else if (alg == WPA_ALG_NONE) { wpa_printf(MSG_DEBUG, "nl80211: DEL_KEY"); - if (!nl80211_is_netdev_iftype(drv->nlmode)) + if (!nl80211_is_netdev_iftype(drv->nlmode) || is_pd_bss) msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_DEL_KEY); else msg = nl80211_ifindex_msg(drv, ifindex, 0, @@ -4219,7 +4234,7 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, goto fail2; } wpa_printf(MSG_DEBUG, "nl80211: NEW_KEY"); - if (!nl80211_is_netdev_iftype(drv->nlmode)) + if (!nl80211_is_netdev_iftype(drv->nlmode) || is_pd_bss) msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_NEW_KEY); else msg = nl80211_ifindex_msg(drv, ifindex, 0, diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index e38d7243e..6b1cffa4c 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -306,6 +306,7 @@ static void wpas_pr_pasn_set_keys(void *ctx, const u8 *own_addr, os_memset(¶ms, 0, sizeof(params)); params.ifname = wpa_s->ifname; + params.own_addr = own_addr; params.alg = wpa_cipher_to_alg(cipher); params.addr = peer_addr; params.key_idx = 0; @@ -336,6 +337,7 @@ static void wpas_pr_pasn_clear_keys(void *ctx, const u8 *own_addr, os_memset(¶ms, 0, sizeof(params)); params.ifname = wpa_s->ifname; + params.own_addr = own_addr; params.alg = WPA_ALG_NONE; params.addr = peer_addr; params.key_idx = 0; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:47 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:47 +0530 Subject: [PATCH v3 23/46] PR: Extend PR_PASN_START ctrl iface with src_addr and role In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-24-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy The PR_PASN_START control interface command had no way to specify a source MAC address for the dedicated PR interface or to select the PASN role. Extend wpas_ctrl_iface_pr_pasn_start() to parse two new optional parameters. The src_addr parameter accepts a MAC address that is passed to wpas_pr_initiate_pasn_auth() for use when creating the dedicated PR interface. The pasn_role parameter accepts INITIATOR or RESPONDER and selects the corresponding pr_pasn_role value, defaulting to INITIATOR when omitted. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/ctrl_iface.c | 22 ++++++++++++++++++---- wpa_supplicant/pr_supplicant.c | 3 ++- wpa_supplicant/pr_supplicant.h | 9 +++++++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index d31acbef4..6dd444349 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -11564,6 +11564,8 @@ static int wpas_ctrl_iface_pr_pasn_start(struct wpa_supplicant *wpa_s, int freq = 0, forced_pr_freq = 0; u8 ranging_type = 0, role = 0, auth_mode = 0; bool got_addr = false; + u8 src_addr[ETH_ALEN], *p_src_addr = NULL; + enum pr_pasn_role pasn_role = PR_ROLE_PASN_INITIATOR; while ((token = str_token(cmd, " ", &context))) { if (os_strncmp(token, "addr=", 5) == 0) { @@ -11586,6 +11588,14 @@ static int wpas_ctrl_iface_pr_pasn_start(struct wpa_supplicant *wpa_s, auth_mode = atoi(token + 5); } else if (os_strncmp(token, "forced_pr_freq=", 15) == 0) { forced_pr_freq = atoi(token + 15); + } else if (os_strncmp(token, "src_addr=", 9) == 0) { + if (hwaddr_aton(token + 9, src_addr)) + return -1; + p_src_addr = src_addr; + } else if (os_strcmp(token, "pasn_role=INITIATOR") == 0) { + pasn_role = PR_ROLE_PASN_INITIATOR; + } else if (os_strcmp(token, "pasn_role=RESPONDER") == 0) { + pasn_role = PR_ROLE_PASN_RESPONDER; } else { wpa_printf(MSG_DEBUG, "CTRL: PASN invalid parameter: '%s'", @@ -11599,12 +11609,16 @@ static int wpas_ctrl_iface_pr_pasn_start(struct wpa_supplicant *wpa_s, "CTRL: Proximity Ranging PASN missing parameter"); return -1; } + wpa_printf(MSG_DEBUG, - "CTRL: PR PASN params: ranging type=0x%x, role=0x%x, auth_mode=%d, forced pr freq=%d, addr=" MACSTR, - ranging_type, role, auth_mode, forced_pr_freq, - MAC2STR(addr)); + "CTRL: PR PASN params: ranging type=0x%x, role=0x%x, " + "pasn_role=%d, auth_mode=%d, forced pr freq=%d, addr=" + MACSTR " src_addr=" MACSTR, + ranging_type, role, pasn_role, auth_mode, forced_pr_freq, + MAC2STR(addr), MAC2STR(p_src_addr ? p_src_addr : addr)); return wpas_pr_initiate_pasn_auth(wpa_s, addr, freq, auth_mode, role, - ranging_type, forced_pr_freq); + ranging_type, forced_pr_freq, + p_src_addr, pasn_role); } diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 6b1cffa4c..217b8dddc 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -624,7 +624,8 @@ fail: int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, const u8 *peer_addr, int freq, u8 auth_mode, u8 ranging_role, u8 ranging_type, - int forced_pr_freq) + int forced_pr_freq, const u8 *src_addr, + enum pr_pasn_role pasn_role) { struct wpa_pr_pasn_auth_work *awork; diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index 24c369e30..ee40a9251 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -9,6 +9,8 @@ #ifndef PR_SUPPLICANT_H #define PR_SUPPLICANT_H +#include "common/proximity_ranging.h" + #ifdef CONFIG_PR int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, @@ -27,7 +29,8 @@ void wpas_pr_process_usd_elems(struct wpa_supplicant *wpa_s, const u8 *buf, int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, const u8 *peer_addr, int freq, u8 auth_mode, u8 ranging_role, u8 ranging_type, - int forced_pr_freq); + int forced_pr_freq, const u8 *src_addr, + enum pr_pasn_role pasn_role); int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data, size_t data_len, bool acked); int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s, @@ -74,7 +77,9 @@ static inline int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, const u8 *peer_addr, int freq, u8 auth_mode, u8 ranging_role, u8 ranging_type, - int forced_pr_freq) + int forced_pr_freq, + const u8 *src_addr, + enum pr_pasn_role pasn_role) { return 0; } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:53 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:53 +0530 Subject: [PATCH v3 29/46] PR: Use op_class_to_bandwidth() in wpas_pr_ranging_params() In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-30-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Replace oper_class_bw_to_int(get_oper_class(NULL, op_class)) with the simpler and more direct op_class_to_bandwidth(op_class) which achieves the same result without the intermediate get_oper_class() lookup. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/pr_supplicant.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index f3f3ad11d..f1120c8a2 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -299,7 +299,7 @@ static void wpas_pr_ranging_params(void *ctx, const u8 *dev_addr, struct wpa_supplicant *wpa_s = ctx; int bw, format_bw, freq; - bw = oper_class_bw_to_int(get_oper_class(NULL, op_class)); + bw = op_class_to_bandwidth(op_class); format_bw = self_format_bw < peer_format_bw ? self_format_bw : peer_format_bw; freq = ieee80211_chan_to_freq(NULL, op_class, op_channel); -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:55 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:55 +0530 Subject: [PATCH v3 31/46] PR: Use session time as total ROC budget for PASN responder In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-32-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy When continuous_ranging_session_time is configured, use it as the total ROC budget for the PASN responder instead of the fixed PR_PASN_RESPONDER_ROC_DURATION default. This ensures the responder stops listening once the intended session duration expires. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/pr_supplicant.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 923fe9d0f..7a0557ea7 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -1073,6 +1073,8 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, if (pasn_role == PR_ROLE_PASN_RESPONDER) { struct wpa_pr_pasn_roc_work *rwork; + struct pr_data *pr = wpa_s->global->pr; + unsigned int roc_time_ms = PR_PASN_RESPONDER_ROC_DURATION; bool has_src_addr = src_addr && !is_zero_ether_addr(src_addr); wpa_printf(MSG_DEBUG, @@ -1112,9 +1114,16 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, /* * Register the total-budget timer. When it fires it clears * pr_responder_mode so the cancel callback stops restarting - * chunks. + * chunks. Defaults to PR_PASN_RESPONDER_ROC_DURATION; overridden + * by continuous_ranging_session_time when non-zero. */ - eloop_register_timeout(0, PR_PASN_RESPONDER_ROC_DURATION * 1000, + if (pr && pr->pr_pasn_params && + pr->pr_pasn_params->continuous_ranging_session_time > 0) + roc_time_ms = + pr->pr_pasn_params->continuous_ranging_session_time; + + eloop_register_timeout(roc_time_ms / 1000, + (roc_time_ms % 1000) * 1000, wpas_pr_pasn_roc_total_timeout, wpa_s, NULL); return 0; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:49 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:49 +0530 Subject: [PATCH v3 25/46] PR: Add dedicated PD wdev for PASN with custom MAC address In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-26-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy When PR PASN is triggered with a custom source address, create a dedicated PD virtual interface (WPA_IF_PD) carrying that MAC before the PASN exchange begins. For the initiator role, the PD wdev is created via wpas_pr_start_pd() before queuing the radio work. For the responder role, the ROC started on the existing interface receives the incoming Auth1 frame, and the PD wdev is created lazily on first M1 reception when a custom MAC was requested. When no custom MAC is provided both roles fall back to the existing interface. wpas_pr_pd_stop() tears down the PD wdev on completion or failure. A pd_addr field tracks the active PD wdev address and is used to route set_key and TX status operations to the correct interface. pr_responder_src_addr stores the requested MAC so it is available when M1 arrives. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/pr_supplicant.c | 125 +++++++++++++++++++++++++++++- wpa_supplicant/pr_supplicant.h | 5 ++ wpa_supplicant/wpa_supplicant_i.h | 10 +++ 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 0c20be22f..6272c0b7c 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -514,6 +514,22 @@ void wpas_pr_deinit(struct wpa_supplicant *wpa_s) } +void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s) +{ + if (is_zero_ether_addr(wpa_s->pd_addr)) { + wpa_printf(MSG_DEBUG, "PR: pd_stop: no active PD wdev"); + return; + } + + wpa_printf(MSG_DEBUG, "PR: Stopping PD wdev addr=" MACSTR, + MAC2STR(wpa_s->pd_addr)); + + wpa_drv_pd_stop(wpa_s); + os_memset(wpa_s->pd_addr, 0, ETH_ALEN); + wpa_printf(MSG_DEBUG, "PR: PD wdev stopped"); +} + + void wpas_pr_update_dev_addr(struct wpa_supplicant *wpa_s) { pr_set_dev_addr(wpa_s->global->pr, wpa_s->own_addr); @@ -546,6 +562,41 @@ void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, #ifdef CONFIG_PASN +static int wpas_pr_start_pd(struct wpa_supplicant *wpa_s, const u8 *src_addr) +{ + u8 pd_addr[ETH_ALEN]; + + if (!src_addr || is_zero_ether_addr(src_addr)) { + wpa_printf(MSG_ERROR, "PR: Invalid MAC address for PD wdev"); + return -1; + } + + if (!is_zero_ether_addr(wpa_s->pd_addr)) { + wpa_printf(MSG_ERROR, + "PR: PD wdev already active addr=" MACSTR, + MAC2STR(wpa_s->pd_addr)); + return -1; + } + + wpa_printf(MSG_DEBUG, + "PR: Creating PD wdev with MAC " MACSTR, + MAC2STR(src_addr)); + + os_memset(pd_addr, 0, ETH_ALEN); + if (wpa_drv_pd_start(wpa_s, src_addr, pd_addr) < 0) { + wpa_printf(MSG_ERROR, "PR: Failed to create PD wdev"); + return -1; + } + + os_memcpy(wpa_s->pd_addr, pd_addr, ETH_ALEN); + pr_set_dev_addr(wpa_s->global->pr, pd_addr); + + wpa_printf(MSG_DEBUG, + "PR: PD wdev created addr=" MACSTR, MAC2STR(pd_addr)); + return 0; +} + + struct wpa_pr_pasn_auth_work { u8 peer_addr[ETH_ALEN]; u8 auth_mode; @@ -628,6 +679,7 @@ static void wpas_pr_schedule_responder_roc(struct wpa_supplicant *wpa_s, fail: wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s, NULL); } @@ -644,6 +696,7 @@ static void wpas_pr_pasn_roc_total_timeout(void *eloop_ctx, "PR PASN: Total ROC budget expired, " "stopping responder listen"); wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); if (wpa_s->pr_roc_work) { wpa_drv_cancel_remain_on_channel(wpa_s); @@ -683,6 +736,7 @@ static void wpas_pr_pasn_roc_start_cb(struct wpa_radio_work *work, int deinit) eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s, NULL); wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); os_free(rwork); work->ctx = NULL; return; @@ -707,6 +761,7 @@ static void wpas_pr_pasn_roc_start_cb(struct wpa_radio_work *work, int deinit) eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s, NULL); wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); os_free(rwork); work->ctx = NULL; radio_work_done(work); @@ -755,6 +810,16 @@ static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx) wpas_pr_pasn_cancel_auth_work(wpa_s); wpa_s->pr_pasn_auth_work = NULL; } + + /* + * Stop the PD wdev only after radio_work_done() has + * fully returned. Calling wpas_pr_pd_stop() from inside + * the radio-work deinit callback would trigger a re-entrant + * radio_remove_works() ? radio_work_free() on the same work item, + * causing a use-after-free / SIGSEGV. + */ + wpas_pr_pd_stop(wpa_s); + wpa_printf(MSG_DEBUG, "PR: PASN timed out"); } @@ -797,6 +862,8 @@ fail: wpas_pr_pasn_free_auth_work(awork); work->ctx = NULL; radio_work_done(work); + /* Stop PD wdev after radio_work_done() to avoid use-after-free */ + wpas_pr_pd_stop(wpa_s); } @@ -810,14 +877,31 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, if (pasn_role == PR_ROLE_PASN_RESPONDER) { struct wpa_pr_pasn_roc_work *rwork; + bool has_src_addr = src_addr && !is_zero_ether_addr(src_addr); + + wpa_printf(MSG_DEBUG, + "PR PASN: Scheduling ROC at freq %d for responder role%s", + freq, has_src_addr ? " with custom MAC" : ""); rwork = os_zalloc(sizeof(*rwork)); if (!rwork) return -1; rwork->freq = freq; + if (has_src_addr) + os_memcpy(rwork->src_addr, src_addr, ETH_ALEN); + /* else rwork->src_addr stays all-zeros (no MAC filter on ROC) */ + /* + * Store state so wpas_pr_pasn_auth_rx can create the PD + * interface when M1 arrives. When no custom MAC is given the + * PD wdev is skipped and the existing interface is used. + */ wpa_s->pr_responder_mode = true; + if (has_src_addr) + os_memcpy(wpa_s->pr_responder_src_addr, src_addr, + ETH_ALEN); + /* else pr_responder_src_addr stays all-zeros */ if (radio_add_work(wpa_s, freq, "pr-pasn-roc", 0, wpas_pr_pasn_roc_start_cb, rwork) < 0) { @@ -825,6 +909,7 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, "PR PASN: Failed to schedule ROC for responder"); os_free(rwork); wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); return -1; } @@ -839,12 +924,26 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, return 0; } + /* + * PASN initiator role: create the PD wdev if src_addr is provided, + * then queue the radio work to send M1. + */ + if (src_addr && !is_zero_ether_addr(src_addr)) { + if (wpas_pr_start_pd(wpa_s, src_addr) < 0) { + wpa_printf(MSG_ERROR, + "PR PASN: Failed to create PD wdev"); + return -1; + } + } + wpas_pr_pasn_cancel_auth_work(wpa_s); wpa_s->pr_pasn_auth_work = NULL; awork = os_zalloc(sizeof(*awork)); - if (!awork) + if (!awork) { + wpas_pr_pd_stop(wpa_s); return -1; + } awork->freq = freq; os_memcpy(awork->peer_addr, peer_addr, ETH_ALEN); @@ -856,6 +955,7 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, if (!radio_add_work(wpa_s, freq, "pr-pasn-start-auth", 1, wpas_pr_pasn_auth_start_cb, awork)) { wpas_pr_pasn_free_auth_work(awork); + wpas_pr_pd_stop(wpa_s); return -1; } @@ -870,7 +970,8 @@ int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data, { struct pr_data *pr = wpa_s->global->pr; - if (!wpa_s->pr_pasn_auth_work) + if (!wpa_s->pr_pasn_auth_work && + is_zero_ether_addr(wpa_s->pd_addr)) return -1; return pr_pasn_auth_tx_status(pr, data, data_len, acked); @@ -898,6 +999,25 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s, le_to_host16(mgmt->u.auth.auth_transaction); if (auth_transaction == WLAN_AUTH_TR_SEQ_PASN_AUTH1) { + if (!is_zero_ether_addr(wpa_s->pr_responder_src_addr)) { + wpa_printf(MSG_DEBUG, + "PR PASN: M1 received in responder " + "mode, creating PD wdev"); + + if (wpas_pr_start_pd(wpa_s, + wpa_s->pr_responder_src_addr) < 0) { + wpa_printf(MSG_ERROR, + "PR PASN: Failed to create " + "PD wdev for responder"); + return -1; + } + } else { + wpa_printf(MSG_DEBUG, + "PR PASN: M1 received in responder " + "mode, no custom MAC - using " + "existing interface"); + } + /* * Cancel the total-budget timer first so it does not * fire after we have already handed off to the PASN @@ -924,6 +1044,7 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s, /* Clear responder mode */ wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); wpa_printf(MSG_DEBUG, "PR PASN: M1 processed, proceeding with " diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index 7cf29705e..747bb900a 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -17,6 +17,7 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, const struct wpa_driver_capa *capa); void wpas_pr_flush(struct wpa_supplicant *wpa_s); void wpas_pr_deinit(struct wpa_supplicant *wpa_s); +void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s); void wpas_pr_update_dev_addr(struct wpa_supplicant *wpa_s); void wpas_pr_clear_dev_iks(struct wpa_supplicant *wpa_s); void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, @@ -56,6 +57,10 @@ static inline void wpas_pr_deinit(struct wpa_supplicant *wpa_s) { } +static inline void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s) +{ +} + static inline void wpas_pr_update_dev_addr(struct wpa_supplicant *wpa_s) { } diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 85994de80..3394ab4cc 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -1758,6 +1758,8 @@ struct wpa_supplicant { u8 pmkid_anonce[NONCE_LEN]; bool pmkid_anonce_set; #endif /* CONFIG_PMKSA_PRIVACY */ + u8 pd_addr[ETH_ALEN]; + /** * pr_responder_mode - Waiting for PASN M1 as responder * @@ -1766,6 +1768,14 @@ struct wpa_supplicant { * is created on M1 reception. */ bool pr_responder_mode; + + /** + * pr_responder_src_addr - Source MAC address used for responder ROC + * + * Stored when responder mode is activated so that the dedicated PR + * interface can be created with the same address when M1 arrives. + */ + u8 pr_responder_src_addr[ETH_ALEN]; }; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:54 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:54 +0530 Subject: [PATCH v3 30/46] nl80211: Add peer measurement support for proximity ranging In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-31-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add nl80211_start_peer_measurement() to build and send NL80211_CMD_PEER_MEASUREMENT_START for FTM-based proximity ranging after successful PASN authentication. The function constructs nested nl80211 attributes for channel configuration, preamble selection, and EDCA/NTB-specific FTM parameters, and routes the command via the PD wdev when src_addr matches the active PD wdev. Add wpas_pr_trigger_ranging() as the entry point called from the wpas_pr_ranging_params() callback after PASN completes. It derives center frequencies and channel width from the operating class via wpas_pr_op_class_to_chan_params() and calls the driver op. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.h | 7 + src/drivers/driver.h | 20 +++ src/drivers/driver_nl80211.c | 302 +++++++++++++++++++++++++++++++++ wpa_supplicant/driver_i.h | 16 ++ wpa_supplicant/pr_supplicant.c | 185 ++++++++++++++++++++ 5 files changed, 530 insertions(+) diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 3cee96133..b8e49ab77 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -273,6 +273,7 @@ struct pr_pasn_ranging_params { u8 pr_pasn_status; u8 auth_mode; int freq; + u32 ranging_timeout; u8 src_addr[ETH_ALEN]; enum pr_pasn_role pasn_role; @@ -350,6 +351,12 @@ struct pr_pasn_ranging_params { u32 continuous_ranging_session_time; int forced_pr_freq; + u8 ranging_op_class; + u16 channel_width; /* channel width in MHz (20/40/80/160/320) */ + u8 format_bw; + u32 center_freq1; + u32 center_freq2; + u64 cookie; }; struct pr_dev_ik { diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 375ac7da2..ee710fad8 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -30,6 +30,7 @@ struct nan_subscribe_params; struct nan_publish_params; +struct pr_pasn_ranging_params; #define HOSTAPD_CHAN_DISABLED 0x00000001 #define HOSTAPD_CHAN_NO_IR 0x00000002 @@ -5845,6 +5846,25 @@ struct wpa_driver_ops { struct hostapd_multi_hw_info * (*get_multi_hw_info)(void *priv, unsigned int *num_multi_hws); +#ifdef CONFIG_PR + /** + * start_peer_measurement - Start peer measurement (FTM ranging) + * @priv: Private driver interface data + * @peer_addr: Peer MAC address + * @freq: Operating frequency in MHz + * @channel: Operating channel number + * @bw: Channel bandwidth in MHz + * @params: Ranging parameters (EDCA/NTB specific) + * Returns: 0 on success, -1 on failure + * + * This function triggers peer measurement (FTM ranging) after + * successful PASN authentication for proximity ranging. + */ + int (*start_peer_measurement)(void *priv, const u8 *peer_addr, + int freq, u8 channel, int bw, + struct pr_pasn_ranging_params *params); +#endif /* CONFIG_PR */ + #ifdef CONFIG_NAN /** * nan_start - Start NAN operation diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 8397dc5ce..8af3efe98 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -41,6 +41,7 @@ #include "radiotap_iter.h" #include "rfkill.h" #include "driver_nl80211.h" +#include "common/proximity_ranging.h" #ifndef NETLINK_CAP_ACK @@ -9276,6 +9277,306 @@ static int i802_sta_disassoc(void *priv, const u8 *own_addr, const u8 *addr, 0, NULL, 0, 0, link_id); } +#ifdef CONFIG_PR + +static u8 get_pr_preamble(u8 ranging_type, u8 format_bw) +{ + /* Determine preamble based on ranging type and format_bw */ + if (ranging_type & PR_EDCA_BASED_RANGING) { + /* EDCA format_bw maps to different preambles */ + switch (format_bw) { + case EDCA_FORMAT_AND_BW_HT40: + return NL80211_PREAMBLE_HT; + case EDCA_FORMAT_AND_BW_VHT20: + case EDCA_FORMAT_AND_BW_VHT40: + case EDCA_FORMAT_AND_BW_VHT80: + case EDCA_FORMAT_AND_BW_VHT80P80: + case EDCA_FORMAT_AND_BW_VHT160_DUAL_LO: + case EDCA_FORMAT_AND_BW_VHT160_SINGLE_LO: + return NL80211_PREAMBLE_VHT; + default: + return NL80211_PREAMBLE_VHT; + } + } else if (ranging_type & (PR_NTB_SECURE_LTF_BASED_RANGING | PR_NTB_OPEN_BASED_RANGING)) { + /* NTB format_bw all use HE preamble */ + return NL80211_PREAMBLE_HE; + } + + /* Default fallback */ + return NL80211_PREAMBLE_VHT; +} + + +/** + * nl80211_start_peer_measurement - Send NL80211_CMD_PEER_MEASUREMENT_START + */ +static int nl80211_start_peer_measurement(void *priv, const u8 *peer_addr, + int freq, u8 channel, int bw, + struct pr_pasn_ranging_params *params) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + struct nl_msg *msg; + struct nlattr *pmsr_attr, *peers_attr, *peer_attr, *chan_attr, *req_attr, *ftm_attr; + struct nlattr *data_attr; + int ret = -1, center_freq1 = 0, center_freq2 = 0; + struct nl80211_ack_ext_arg ack_arg; + enum nl80211_chan_width width; + u32 preamble; + u64 cookie; + + if (!peer_addr || !params) { + wpa_printf(MSG_ERROR, "nl80211: Invalid parameters for peer measurement"); + return -1; + } + + /* Route via PD wdev if src_addr matches */ + if (drv->pd_bss && !is_zero_ether_addr(params->src_addr) && + ether_addr_equal(params->src_addr, drv->pd_bss->addr)) { + wpa_printf(MSG_DEBUG, + "nl80211: Peer measurement routed via PD wdev addr=" MACSTR, + MAC2STR(drv->pd_bss->addr)); + bss = drv->pd_bss; + } + + wpa_printf(MSG_DEBUG, + "nl80211: Start peer measurement for " MACSTR " freq=%d ch=%u bw=%d", + MAC2STR(peer_addr), freq, channel, bw); + + /* Use center frequencies from params (calculated in wpas_pr_trigger_ranging) */ + center_freq1 = params->center_freq1; + center_freq2 = params->center_freq2; + + /* channel_width is in MHz */ + switch (params->channel_width) { + case 20: + width = NL80211_CHAN_WIDTH_20; + break; + case 40: + width = NL80211_CHAN_WIDTH_40; + break; + case 80: + width = NL80211_CHAN_WIDTH_80; + break; + case 160: + width = NL80211_CHAN_WIDTH_160; + break; + case 320: + width = NL80211_CHAN_WIDTH_320; + break; + default: + wpa_printf(MSG_ERROR, "nl80211: Unsupported channel width %u MHz", + params->channel_width); + return -1; + } + + wpa_printf(MSG_DEBUG, + "nl80211: Using center_freq1=%u, center_freq2=%u, width=%d", + center_freq1, center_freq2, width); + /* + * PD interfaces are non-netdev (ifindex=0); use nl80211_cmd_msg() + * which emits NL80211_ATTR_WDEV instead of NL80211_ATTR_IFINDEX. + */ + msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_PEER_MEASUREMENT_START); + if (!msg) + return -1; + + /* Add timeout if specified */ + if (params->ranging_timeout > 0) { + if (nla_put_u32(msg, NL80211_ATTR_TIMEOUT, params->ranging_timeout)) + goto fail; + wpa_printf(MSG_DEBUG, "nl80211: Added timeout %u ms", params->ranging_timeout); + } + + /* Add peer measurements attribute */ + pmsr_attr = nla_nest_start(msg, NL80211_ATTR_PEER_MEASUREMENTS); + if (!pmsr_attr) + goto fail; + + /* Add peers array */ + peers_attr = nla_nest_start(msg, NL80211_PMSR_ATTR_PEERS); + if (!peers_attr) + goto fail; + + /* Add single peer */ + peer_attr = nla_nest_start(msg, 0); + if (!peer_attr) + goto fail; + + /* Peer MAC address */ + if (nla_put(msg, NL80211_PMSR_PEER_ATTR_ADDR, ETH_ALEN, peer_addr)) + goto fail; + + /* Add proximity detection request type */ + if (nla_put_u32(msg, NL80211_PMSR_PEER_ATTR_REQ_TYPE, + NL80211_PMSR_FTM_REQ_TYPE_PD)) + goto fail; + + /* Channel information */ + chan_attr = nla_nest_start(msg, NL80211_PMSR_PEER_ATTR_CHAN); + if (!chan_attr) + goto fail; + + if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, freq) || + nla_put_u32(msg, NL80211_ATTR_CHANNEL_WIDTH, width) || + nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1, center_freq1)) + goto fail; + + if (center_freq2 && nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ2, center_freq2)) + goto fail; + + nla_nest_end(msg, chan_attr); + + + /* Request attributes */ + req_attr = nla_nest_start(msg, NL80211_PMSR_PEER_ATTR_REQ); + if (!req_attr) + goto fail; + + /* Add DATA attribute as required by NL80211 specification */ + data_attr = nla_nest_start(msg, NL80211_PMSR_REQ_ATTR_DATA); + + if (!data_attr) + goto fail; + + /* FTM request */ + ftm_attr = nla_nest_start(msg, NL80211_PMSR_TYPE_FTM); + if (!ftm_attr) + goto fail; + + preamble = get_pr_preamble(params->ranging_type, params->format_bw); + if (nla_put_u32(msg, NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE, preamble)) + goto fail; + + /* Map ranging parameters to NL80211 attributes based on protocol type */ + if (params->ranging_type & PR_EDCA_BASED_RANGING) { + wpa_printf(MSG_DEBUG, "nl80211: Adding EDCA ranging parameters"); + + /* ASAP and BURST_PERIOD are always set for EDCA */ + if (nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_ASAP) || + nla_put_u16(msg, NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD, + params->burst_period)) + goto fail; + + /* Other EDCA parameters are only set for ISTA */ + if (params->ranging_role == PR_ISTA_SUPPORT) { + if (nla_put_u8(msg, NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP, + params->num_bursts_exp) || + nla_put_u8(msg, NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST, + params->ftms_per_burst) || + nla_put_u8(msg, NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES, + params->ftmr_retries) || + nla_put_u8(msg, NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION, + params->burst_duration)) + goto fail; + } + } + + if (params->ranging_type & (PR_NTB_SECURE_LTF_BASED_RANGING | PR_NTB_OPEN_BASED_RANGING)) { + wpa_printf(MSG_DEBUG, "nl80211: Adding NTB ranging parameters"); + + /* Set non-trigger based flag */ + if (nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED)) + goto fail; + + if (nla_put_u32(msg, NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME, + params->nominal_time)) + goto fail; + + /* ISTA-specific NTB parameters */ + if (params->ranging_role == PR_ISTA_SUPPORT && + (nla_put_u32(msg, NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS, + params->min_time_between_measurements) || + nla_put_u32(msg, NL80211_PMSR_FTM_REQ_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS, + params->max_time_between_measurements) || + nla_put_u32(msg, NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION, + params->availability_window) || + nla_put_u8(msg, NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST, + params->ftms_per_burst))) + goto fail; + } + + /* Location requests */ + if (params->request_lci && + nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI)) + goto fail; + + if (params->request_civicloc && + nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC)) + goto fail; + + /* RSTA mode if responder role */ + if (params->ranging_role == PR_RSTA_SUPPORT && + nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_RSTA)) + goto fail; + + /* + * LMR feedback: set for RSTA role in NTB ranging so that + * the RSTA reports its measurement results back to the ISTA. + * Only valid when NON_TRIGGER_BASED is set. + */ + if (params->ranging_role == PR_RSTA_SUPPORT && + (params->ranging_type & (PR_NTB_SECURE_LTF_BASED_RANGING | + PR_NTB_OPEN_BASED_RANGING)) && + nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK)) + goto fail; + + /* + * PD ingress/egress thresholds: only valid when + * NL80211_PMSR_PEER_ATTR_REQ_TYPE is NL80211_PMSR_FTM_REQ_TYPE_PD + * (always the case here). Thresholds are in millimeters. + */ + if (params->ingress_threshold && + nla_put_u64(msg, NL80211_PMSR_FTM_REQ_ATTR_INGRESS, + params->ingress_threshold)) + goto fail; + + if (params->egress_threshold && + nla_put_u64(msg, NL80211_PMSR_FTM_REQ_ATTR_EGRESS, + params->egress_threshold)) + goto fail; + + /* + * PD suppress results: suppress ranging results for PD requests. + */ + if (params->pr_suppress_results && + nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS)) + goto fail; + + nla_nest_end(msg, ftm_attr); + nla_nest_end(msg, data_attr); + nla_nest_end(msg, req_attr); + nla_nest_end(msg, peer_attr); + nla_nest_end(msg, peers_attr); + nla_nest_end(msg, pmsr_attr); + + cookie = 0; + os_memset(&ack_arg, 0, sizeof(struct nl80211_ack_ext_arg)); + ack_arg.ext_data = &cookie; + ret = send_and_recv(drv, drv->global->nl, msg, NULL, NULL, + ack_handler_cookie, &ack_arg, NULL); + + if (ret < 0) { + wpa_printf(MSG_ERROR, + "nl80211: Peer measurement start failed: ret=%d (%s)", + ret, strerror(-ret)); + } else { + wpa_printf(MSG_DEBUG, + "nl80211: Peer measurement started successfully addr=" MACSTR + " cookie=%llu", MAC2STR(peer_addr), + (unsigned long long) cookie); + params->cookie = cookie; + } + + return ret; + +fail: + wpa_printf(MSG_ERROR, "nl80211: Failed to build peer measurement message"); + nlmsg_free(msg); + return -1; +} + +#endif /* CONFIG_PR */ static void dump_ifidx(struct wpa_driver_nl80211_data *drv) { @@ -16420,5 +16721,6 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { #ifdef CONFIG_PR .pd_start = nl80211_pd_start, .pd_stop = nl80211_pd_stop, + .start_peer_measurement = nl80211_start_peer_measurement, #endif /* CONFIG_PR */ }; diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index c19c834b5..c111224a6 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -758,6 +758,22 @@ wpa_drv_pd_stop(struct wpa_supplicant *wpa_s) #endif /* CONFIG_PR */ +#ifdef CONFIG_PR + +static inline int +wpa_drv_start_peer_measurement(struct wpa_supplicant *wpa_s, const u8 *peer, + int freq, u8 channel, int bw, + struct pr_pasn_ranging_params *params) +{ + if (!wpa_s->driver->start_peer_measurement) + return -1; + return wpa_s->driver->start_peer_measurement(wpa_s->drv_priv, peer, + freq, channel, bw, + params); +} + +#endif /* CONFIG_PR */ + static inline int wpa_drv_vendor_cmd(struct wpa_supplicant *wpa_s, int vendor_id, int subcmd, const u8 *data, size_t data_len, diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index f1120c8a2..923fe9d0f 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -11,6 +11,7 @@ #include "utils/common.h" #include "utils/eloop.h" #include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" #include "common/proximity_ranging.h" #include "p2p/p2p.h" #include "wpa_supplicant_i.h" @@ -141,6 +142,94 @@ wpas_pr_ntb_is_valid_op_class(u32 bw_bitmap, u32 preamble_bitmap, } +/** + * wpas_pr_op_class_to_chan_params - Derive center freq and width from op_class + */ +static int wpas_pr_op_class_to_chan_params(u8 op_class, u8 op_channel, + u32 *center_freq1, + u32 *center_freq2, + u16 *channel_width) +{ + int control_freq, bw, offset = 0; + int half_bw, block_pos; + + if (!center_freq1 || !center_freq2 || !channel_width) + return -1; + + *center_freq1 = 0; + *center_freq2 = 0; + *channel_width = 0; + + /* 80+80 MHz: center_freq2 is unknown from primary channel alone */ + if (op_class == 130 || op_class == 135) { + wpa_printf(MSG_DEBUG, + "PR: op_class %u (80+80 MHz) not supported, " + "center_freq2 cannot be determined", op_class); + return -1; + } + + control_freq = ieee80211_chan_to_freq(NULL, op_class, op_channel); + if (control_freq < 0) { + wpa_printf(MSG_DEBUG, "PR: Invalid op_class=%u channel=%u", + op_class, op_channel); + return -1; + } + + bw = op_class_to_bandwidth(op_class); + + /* + * 2 GHz 40 MHz: channels are 5 MHz apart so the generic offset + * formula does not apply. Handle upper (op_class 83) and lower + * (op_class 84) secondary channel directions explicitly. + */ + if (op_class == 83) { + *center_freq1 = control_freq + 10; + *channel_width = 40; + return 0; + } + if (op_class == 84) { + *center_freq1 = control_freq - 10; + *channel_width = 40; + return 0; + } + + if (bw == 20) { + *center_freq1 = control_freq; + *channel_width = 20; + return 0; + } + + /* + * For 5 GHz and 6 GHz wider bandwidths, compute the primary channel's + * position (in 20 MHz steps) within its band segment, then derive + * center_freq1 using the formula: + * center_freq1 = control_freq + (bw/2 - 10) - (offset & (bw/20 - 1)) * 20 + */ + if (control_freq >= 5955) + offset = (control_freq - 5955) / 20; + else if (control_freq >= 5745) + offset = (control_freq - 5745) / 20; + else if (control_freq >= 5180) + offset = (control_freq - 5180) / 20; + + /* Distance from the primary channel to the center of the BW block */ + half_bw = bw / 2 - 10; + + /* Position of the primary channel within its BW block (in 20 MHz steps) */ + block_pos = offset & (bw / 20 - 1); + + /* Center = primary channel + half-BW offset - position within block */ + *center_freq1 = control_freq + half_bw - block_pos * 20; + *channel_width = bw; + + wpa_printf(MSG_DEBUG, + "PR: op_class=%u ch=%u -> freq=%d cf1=%u bw=%d", + op_class, op_channel, control_freq, *center_freq1, bw); + + return 0; +} + + static void wpas_pr_setup_edca_channels(struct wpa_supplicant *wpa_s, struct pr_channels *chan, @@ -291,6 +380,98 @@ static void wpas_pr_pasn_result(void *ctx, u8 role, u8 protocol_type, } +/** + * wpas_pr_trigger_ranging - Trigger FTM ranging after successful PASN auth + */ +static int wpas_pr_trigger_ranging(struct wpa_supplicant *wpa_s, + const u8 *peer_addr, int freq, u8 op_class, + u8 op_channel, u8 format_bw, + u8 protocol_type) +{ + struct pr_data *pr = wpa_s->global->pr; + struct pr_pasn_ranging_params *params; + u16 channel_width = 0; + u32 center_freq1 = 0, center_freq2 = 0; + int ret; + + if (!pr || !pr->pr_pasn_params) { + wpa_printf(MSG_DEBUG, + "PR: No ranging params available for " MACSTR, + MAC2STR(peer_addr)); + return -1; + } + + params = pr->pr_pasn_params; + + wpa_printf(MSG_DEBUG, + "PR: Triggering ranging for " MACSTR " freq=%d ch=%u", + MAC2STR(peer_addr), freq, op_channel); + + /* Derive center frequencies and channel width (MHz) from op_class */ + ret = wpas_pr_op_class_to_chan_params(op_class, op_channel, + ¢er_freq1, ¢er_freq2, + &channel_width); + if (ret < 0) { + wpa_printf(MSG_ERROR, + "PR: Failed to derive channel params for " + "op_class=%u ch=%u", op_class, op_channel); + goto fail; + } + + /* Populate ranging params */ + params->ranging_op_class = op_class; + params->channel_width = channel_width; + params->format_bw = format_bw; + params->center_freq1 = center_freq1; + params->center_freq2 = center_freq2; + + /* Validate format_bw is within enum limits before setting preamble */ + if (protocol_type & PR_EDCA_BASED_RANGING) { + if (format_bw < EDCA_FORMAT_AND_BW_VHT20 || + format_bw >= EDCA_FORMAT_AND_BW_MAX) { + wpa_printf(MSG_ERROR, + "PR: Invalid EDCA format_bw %u (valid range: %u-%u)", + format_bw, EDCA_FORMAT_AND_BW_VHT20, + EDCA_FORMAT_AND_BW_MAX - 1); + goto fail; + } + } else if (protocol_type & (PR_NTB_SECURE_LTF_BASED_RANGING | + PR_NTB_OPEN_BASED_RANGING)) { + if (format_bw >= NTB_FORMAT_AND_BW_MAX) { + wpa_printf(MSG_ERROR, + "PR: Invalid NTB format_bw %u (valid range: 0-%u)", + format_bw, NTB_FORMAT_AND_BW_MAX - 1); + goto fail; + } + } else { + wpa_printf(MSG_ERROR, "PR: Unknown protocol_type %u", + protocol_type); + goto fail; + } + + wpa_printf(MSG_DEBUG, + "PR: Ranging params - op_class=%u, ch_width=%u, format_bw=%u, cf1=%u, cf2=%u", + params->ranging_op_class, params->channel_width, + params->format_bw, params->center_freq1, params->center_freq2); + + /* Call driver operation to start peer measurement */ + if (wpa_drv_start_peer_measurement(wpa_s, peer_addr, freq, op_channel, + channel_width, params) < 0) { + wpa_printf(MSG_ERROR, "PR: Failed to start peer measurement"); + goto fail; + } + + wpa_printf(MSG_DEBUG, "PR: Successfully triggered ranging measurement"); + + return 0; + +fail: + os_free(pr->pr_pasn_params); + pr->pr_pasn_params = NULL; + return -1; +} + + static void wpas_pr_ranging_params(void *ctx, const u8 *dev_addr, const u8 *peer_addr, u8 ranging_role, u8 protocol_type, u8 op_class, u8 op_channel, @@ -307,6 +488,10 @@ static void wpas_pr_ranging_params(void *ctx, const u8 *dev_addr, wpas_notify_pr_ranging_params(wpa_s, dev_addr, peer_addr, ranging_role, protocol_type, freq, op_channel, bw, format_bw); + + /* Trigger ranging measurement after successful PASN authentication */ + wpas_pr_trigger_ranging(wpa_s, peer_addr, freq, op_class, op_channel, + format_bw, protocol_type); } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:48 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:48 +0530 Subject: [PATCH v3 24/46] PR: Add remain-on-channel support for PASN responder role In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-25-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy When PR PASN is triggered in the responder role the existing code queues a radio work item to send M1, but there is no mechanism to keep the radio on the target frequency long enough to receive the incoming Auth1 frame from the initiator. Add a dedicated radio work item ("pr-pasn-roc") that starts a remain-on-channel operation on the requested frequency before the PASN exchange begins. A new pr_responder_mode flag is set while the ROC is active so that wpas_pr_pasn_auth_rx() can recognise the first Auth1 frame, cancel the ROC, and hand control to the common PASN layer. The ROC cancel/expiry event is routed through a new wpas_pr_cancel_remain_on_channel_cb() hook, which clears the responder state and releases the radio work slot if M1 has not yet arrived. This change makes the responder path functional on the existing interface without requiring a dedicated virtual interface, and serves as the foundation on which the PD wdev support is built. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/events.c | 4 + wpa_supplicant/pr_supplicant.c | 256 ++++++++++++++++++++++++++++++ wpa_supplicant/pr_supplicant.h | 8 + wpa_supplicant/wpa_supplicant_i.h | 9 ++ 4 files changed, 277 insertions(+) diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 0684ada71..22d6ce196 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -7293,6 +7293,10 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, #endif /* CONFIG_DPP */ wpas_nan_usd_cancel_remain_on_channel_cb( wpa_s, data->remain_on_channel.freq); +#ifdef CONFIG_PR + wpas_pr_cancel_remain_on_channel_cb( + wpa_s, data->remain_on_channel.freq); +#endif /* CONFIG_PR */ break; case EVENT_EAPOL_RX: wpa_supplicant_rx_eapol(wpa_s, data->eapol_rx.src, diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 217b8dddc..0c20be22f 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -21,6 +21,15 @@ #ifdef CONFIG_PASN static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx); + +/* Total listen window (ms) for the PASN responder ROC */ +#define PR_PASN_RESPONDER_ROC_DURATION 5000 +static void wpas_pr_pasn_roc_total_timeout(void *eloop_ctx, + void *timeout_ctx); +static void wpas_pr_schedule_responder_roc(struct wpa_supplicant *wpa_s, + unsigned int freq); +static void wpas_pr_pasn_roc_start_cb(struct wpa_radio_work *work, + int deinit); #endif /* CONFIG_PASN */ @@ -551,6 +560,12 @@ struct wpa_pr_pasn_auth_work { }; +struct wpa_pr_pasn_roc_work { + unsigned int freq; + u8 src_addr[ETH_ALEN]; +}; + + static void wpas_pr_pasn_free_auth_work(struct wpa_pr_pasn_auth_work *awork) { if (!awork) @@ -569,6 +584,169 @@ static void wpas_pr_pasn_cancel_auth_work(struct wpa_supplicant *wpa_s) } +/** + * wpas_pr_pasn_roc_work_done - Idempotent helper to complete ROC radio work + */ +static void wpas_pr_pasn_roc_work_done(struct wpa_supplicant *wpa_s) +{ + struct wpa_pr_pasn_roc_work *rwork; + + if (!wpa_s->pr_roc_work) + return; + + rwork = wpa_s->pr_roc_work->ctx; + os_free(rwork); + wpa_s->pr_roc_work->ctx = NULL; + radio_work_done(wpa_s->pr_roc_work); + wpa_s->pr_roc_work = NULL; +} + + +/** + * wpas_pr_schedule_responder_roc - Queue next ROC chunk for the responder + */ +static void wpas_pr_schedule_responder_roc(struct wpa_supplicant *wpa_s, + unsigned int freq) +{ + struct wpa_pr_pasn_roc_work *rwork; + + rwork = os_zalloc(sizeof(*rwork)); + if (!rwork) { + wpa_printf(MSG_ERROR, "PR PASN: OOM restarting ROC"); + goto fail; + } + rwork->freq = freq; + + if (radio_add_work(wpa_s, freq, "pr-pasn-roc", 0, + wpas_pr_pasn_roc_start_cb, rwork) < 0) { + wpa_printf(MSG_ERROR, + "PR PASN: Failed to reschedule ROC"); + os_free(rwork); + goto fail; + } + return; + +fail: + wpa_s->pr_responder_mode = false; + eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s, NULL); +} + + +/** + * wpas_pr_pasn_roc_total_timeout - Total ROC budget expiry; stop responder + */ +static void wpas_pr_pasn_roc_total_timeout(void *eloop_ctx, + void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + + wpa_printf(MSG_DEBUG, + "PR PASN: Total ROC budget expired, " + "stopping responder listen"); + wpa_s->pr_responder_mode = false; + + if (wpa_s->pr_roc_work) { + wpa_drv_cancel_remain_on_channel(wpa_s); + wpa_s->off_channel_freq = 0; + wpa_s->roc_waiting_drv_freq = 0; + wpas_pr_pasn_roc_work_done(wpa_s); + } +} + + +/** + * wpas_pr_pasn_roc_start_cb - Radio work callback to start the responder ROC + */ +static void wpas_pr_pasn_roc_start_cb(struct wpa_radio_work *work, int deinit) +{ + struct wpa_supplicant *wpa_s = work->wpa_s; + struct wpa_pr_pasn_roc_work *rwork = work->ctx; + unsigned int chunk_ms; + + if (deinit) { + if (work->started) { + /* + * ROC was already started but the work is being + * cancelled (e.g., interface removal). Cancel the + * driver ROC and clear the channel state. + */ + wpa_s->pr_roc_work = NULL; + wpa_drv_cancel_remain_on_channel(wpa_s); + wpa_s->off_channel_freq = 0; + wpa_s->roc_waiting_drv_freq = 0; + } + /* + * Clear responder state and cancel the total-budget timer + * regardless of whether the work was started or not ? the + * ROC will never fire now. + */ + eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, + wpa_s, NULL); + wpa_s->pr_responder_mode = false; + os_free(rwork); + work->ctx = NULL; + return; + } + + wpa_s->pr_roc_work = work; + + /* Use max_remain_on_chan as per-chunk duration, matching DPP/P2P */ + chunk_ms = wpa_s->max_remain_on_chan; + + wpa_printf(MSG_DEBUG, + "PR PASN: Starting ROC chunk at freq %u MHz duration %u ms%s", + rwork->freq, chunk_ms, + is_zero_ether_addr(rwork->src_addr) ? "" : + " with MAC filter"); + + if (wpa_drv_remain_on_channel(wpa_s, rwork->freq, chunk_ms, + is_zero_ether_addr(rwork->src_addr) ? + NULL : rwork->src_addr) < 0) { + wpa_printf(MSG_ERROR, + "PR PASN: Failed to start ROC for responder"); + eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, + wpa_s, NULL); + wpa_s->pr_responder_mode = false; + os_free(rwork); + work->ctx = NULL; + radio_work_done(work); + wpa_s->pr_roc_work = NULL; + return; + } + + wpa_s->off_channel_freq = 0; + wpa_s->roc_waiting_drv_freq = rwork->freq; +} + + +/** + * wpas_pr_cancel_remain_on_channel_cb - ROC cancel/expiry callback for PR + */ +void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, + unsigned int freq) +{ + wpa_printf(MSG_DEBUG, + "PR PASN: Remain on channel cancel for %u MHz", freq); + + if (!wpa_s->pr_roc_work) + return; + + wpas_pr_pasn_roc_work_done(wpa_s); + + if (wpa_s->pr_responder_mode) { + /* Total-budget timer still live ? restart another chunk */ + wpa_printf(MSG_DEBUG, + "PR PASN: ROC chunk expired, " + "restarting for next chunk"); + wpas_pr_schedule_responder_roc(wpa_s, freq); + return; + } + + wpa_printf(MSG_DEBUG, + "PR PASN: ROC total timeout reached, responder done"); +} + + static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx) { struct wpa_supplicant *wpa_s = eloop_ctx; @@ -594,6 +772,7 @@ static void wpas_pr_pasn_auth_start_cb(struct wpa_radio_work *work, int deinit) eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL); wpas_pr_pasn_free_auth_work(awork); + work->ctx = NULL; return; } @@ -629,6 +808,37 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, { struct wpa_pr_pasn_auth_work *awork; + if (pasn_role == PR_ROLE_PASN_RESPONDER) { + struct wpa_pr_pasn_roc_work *rwork; + + rwork = os_zalloc(sizeof(*rwork)); + if (!rwork) + return -1; + + rwork->freq = freq; + + wpa_s->pr_responder_mode = true; + + if (radio_add_work(wpa_s, freq, "pr-pasn-roc", 0, + wpas_pr_pasn_roc_start_cb, rwork) < 0) { + wpa_printf(MSG_ERROR, + "PR PASN: Failed to schedule ROC for responder"); + os_free(rwork); + wpa_s->pr_responder_mode = false; + return -1; + } + + /* + * Register the total-budget timer. When it fires it clears + * pr_responder_mode so the cancel callback stops restarting + * chunks. + */ + eloop_register_timeout(0, PR_PASN_RESPONDER_ROC_DURATION * 1000, + wpas_pr_pasn_roc_total_timeout, + wpa_s, NULL); + return 0; + } + wpas_pr_pasn_cancel_auth_work(wpa_s); wpa_s->pr_pasn_auth_work = NULL; @@ -672,9 +882,55 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s, int freq) { struct pr_data *pr = wpa_s->global->pr; + u16 auth_transaction; if (!pr) return -2; + + /* + * Responder path: when we are waiting for PASN M1 on the parent + * interface ROC, create the PD wdev on first receipt of Auth1 before + * handing the frame to the common layer. + */ + if (wpa_s->pr_responder_mode && + len >= offsetof(struct ieee80211_mgmt, u.auth.variable)) { + auth_transaction = + le_to_host16(mgmt->u.auth.auth_transaction); + + if (auth_transaction == WLAN_AUTH_TR_SEQ_PASN_AUTH1) { + /* + * Cancel the total-budget timer first so it does not + * fire after we have already handed off to the PASN + * layer. + */ + eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, + wpa_s, NULL); + + /* + * Cancel ROC on the listening interface; the dedicated + * PR interface (if created) will handle all subsequent + * frames. Then complete the radio work item so the + * radio is released for other operations. + * + * wpas_pr_cancel_remain_on_channel_cb() may also fire + * asynchronously when the driver processes the cancel + * request, but wpas_pr_pasn_roc_work_done() is + * idempotent (no-op if pr_roc_work is already NULL). + */ + wpa_drv_cancel_remain_on_channel(wpa_s); + wpa_s->off_channel_freq = 0; + wpa_s->roc_waiting_drv_freq = 0; + wpas_pr_pasn_roc_work_done(wpa_s); + + /* Clear responder mode */ + wpa_s->pr_responder_mode = false; + + wpa_printf(MSG_DEBUG, + "PR PASN: M1 processed, proceeding with " + "PASN"); + } + } + return pr_pasn_auth_rx(pr, mgmt, len, freq); } diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index ee40a9251..7cf29705e 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -36,6 +36,8 @@ int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data, int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s, const struct ieee80211_mgmt *mgmt, size_t len, int freq); +void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, + unsigned int freq); #else /* CONFIG_PR */ @@ -98,6 +100,12 @@ static inline int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s, return 0; } +static inline void +wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, + unsigned int freq) +{ +} + #endif /* CONFIG_PR */ #endif /* PR_SUPPLICANT_H */ diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 828795b19..85994de80 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -1681,6 +1681,7 @@ struct wpa_supplicant { struct wpa_radio_work *p2p_pasn_auth_work; #endif /* CONFIG_P2P */ struct wpa_radio_work *pr_pasn_auth_work; + struct wpa_radio_work *pr_roc_work; #endif /* CONFIG_PASN */ bool is_6ghz_enabled; @@ -1757,6 +1758,14 @@ struct wpa_supplicant { u8 pmkid_anonce[NONCE_LEN]; bool pmkid_anonce_set; #endif /* CONFIG_PMKSA_PRIVACY */ + /** + * pr_responder_mode - Waiting for PASN M1 as responder + * + * Set when ROC has been started on this interface to listen for an + * incoming PASN Auth1 frame. Cleared once the dedicated PR interface + * is created on M1 reception. + */ + bool pr_responder_mode; }; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:52 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:52 +0530 Subject: [PATCH v3 28/46] PR: Add extended ranging parameters to PASN peer structure In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-29-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Extend pr_pasn_ranging_params with EDCA, NTB, continuous ranging, and PD-specific parameters: - EDCA: burst_period, num_bursts_exp, ftms_per_burst, ftmr_retries, burst_duration - NTB: min_time_between_measurements, max_time_between_measurements, availability_window, nominal_time - PD-specific: lmr_feedback, ingress_threshold, egress_threshold, pr_suppress_results, continuous_ranging_session_time, forced_pr_freq Location: request_lci, request_civicloc Add wpas_pr_validate_ranging_request() to validate the request against device capabilities and parameter constraints before triggering PASN. Pass forced_pr_freq from params to wpas_pr_initiate_pasn_auth(). Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.h | 75 ++++++++++++++++ wpa_supplicant/pr_supplicant.c | 159 ++++++++++++++++++++++++++++++++- 2 files changed, 233 insertions(+), 1 deletion(-) diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index a9d7ed16a..3cee96133 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -275,6 +275,81 @@ struct pr_pasn_ranging_params { int freq; u8 src_addr[ETH_ALEN]; enum pr_pasn_role pasn_role; + + /** + * EDCA based ranging specific parameters + * + * @burst_period: Burst period in units of 100 ms + * @num_bursts_exp: Number of bursts exponent + * @ftms_per_burst: Number of FTM frames per burst + * @ftmr_retries: Number of retries for FTM Request frame + * @burst_duration: Burst duration as defined in IEEE 2024 std + * Table 9-322?Burst Duration subfield encoding. + */ + u16 burst_period; + u8 num_bursts_exp; + u8 ftms_per_burst; + u8 ftmr_retries; + u8 burst_duration; + + /** + * NTB ranging specific parameters + * + * @min_time_between_measurements: Minimum time between two consecutive + * range measurements in units of 100 micro seconds. + * @max_time_between_measurements: Maximum time between two consecutive + * range measurements in units of 10 milli seconds, to avoid FTM + * negotiation. + * @availability_window: Duration of the Availability Window (AW) in + * units of 1 millisecond (0-255 ms). + * @nominal_time: Nominal duration between adjacent Availability Windows + * in units of milliseconds. + */ + u32 min_time_between_measurements; + u32 max_time_between_measurements; + u8 availability_window; + u32 nominal_time; + + /** + * @request_lci: Whether to request LCI + * @request_civicloc: Whether to request civic location + */ + bool request_lci; + bool request_civicloc; + + /** + * @lmr_feedback: Negotiate LMR feedback for NTB ranging. + * Only valid when NTB ranging type is set. Required for RSTA + * to report measurement results back to the initiator. + */ + bool lmr_feedback; + + /** + * @ingress_threshold: Ingress range threshold in millimeters. + * Only valid when NL80211_PMSR_PEER_ATTR_PD_REQUEST is set. + * The kernel reports a measurement result when the device + * moves INTO this range. + */ + u64 ingress_threshold; + + /** + * @egress_threshold: Egress range threshold in millimeters. + * Only valid when NL80211_PMSR_PEER_ATTR_PD_REQUEST is set. + * The kernel reports a measurement result when the device + * moves OUT OF this range. + */ + u64 egress_threshold; + + /** + * @pr_suppress_results: Suppress ranging results for PD requests. + * Only valid when NL80211_PMSR_PEER_ATTR_PD_REQUEST is set. + * Cannot be used with range_report or lmr_feedback. + */ + bool pr_suppress_results; + + u32 continuous_ranging_session_time; + + int forced_pr_freq; }; struct pr_dev_ik { diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 77e35aa1a..f3f3ad11d 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -976,6 +976,122 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, } +/** + * wpas_pr_validate_ranging_request - Validate PR ranging request parameters + */ +static int wpas_pr_validate_ranging_request(struct wpa_supplicant *wpa_s, + struct pr_pasn_ranging_params *pr_pasn_params) +{ + struct pr_data *pr = wpa_s->global->pr; + struct pr_config *cfg; + bool is_edca, is_ntb, is_ista; + + if (!pr || !pr->cfg) { + wpa_printf(MSG_DEBUG, "PR: PR not initialized"); + return -1; + } + + cfg = pr->cfg; + + /* Peer address must not be all-zeros */ + if (is_zero_ether_addr(pr_pasn_params->peer_addr)) { + wpa_printf(MSG_DEBUG, "PR: Invalid peer address (all zeros)"); + return -1; + } + + /* Frequency must be set */ + if (!pr_pasn_params->freq) { + wpa_printf(MSG_DEBUG, "PR: Invalid frequency (zero)"); + return -1; + } + + /* ranging_type must have at least one valid bit and no unknown bits */ + if (!pr_pasn_params->ranging_type || + (pr_pasn_params->ranging_type & + ~(PR_EDCA_BASED_RANGING | PR_NTB_SECURE_LTF_BASED_RANGING | + PR_NTB_OPEN_BASED_RANGING))) { + wpa_printf(MSG_DEBUG, + "PR: Invalid ranging_type=0x%x", + pr_pasn_params->ranging_type); + return -1; + } + + /* ranging_role must have at least one valid bit and no unknown bits */ + if (!pr_pasn_params->ranging_role || + (pr_pasn_params->ranging_role & + ~(PR_ISTA_SUPPORT | PR_RSTA_SUPPORT))) { + wpa_printf(MSG_DEBUG, + "PR: Invalid ranging_role=0x%x", + pr_pasn_params->ranging_role); + return -1; + } + + is_edca = !!(pr_pasn_params->ranging_type & PR_EDCA_BASED_RANGING); + is_ntb = !!(pr_pasn_params->ranging_type & + (PR_NTB_SECURE_LTF_BASED_RANGING | + PR_NTB_OPEN_BASED_RANGING)); + is_ista = !!(pr_pasn_params->ranging_role & PR_ISTA_SUPPORT); + + /* Validate ranging type against device capabilities */ + if (is_edca) { + if (is_ista && !cfg->edca_ista_support) { + wpa_printf(MSG_DEBUG, + "PR: EDCA ISTA ranging not supported by device"); + return -1; + } + if (!is_ista && !cfg->edca_rsta_support) { + wpa_printf(MSG_DEBUG, + "PR: EDCA RSTA ranging not supported by device"); + return -1; + } + } + + if (is_ntb) { + if (is_ista && !cfg->ntb_ista_support) { + wpa_printf(MSG_DEBUG, + "PR: NTB ISTA ranging not supported by device"); + return -1; + } + if (!is_ista && !cfg->ntb_rsta_support) { + wpa_printf(MSG_DEBUG, + "PR: NTB RSTA ranging not supported by device"); + return -1; + } + + /* Secure LTF requires explicit device support */ + if ((pr_pasn_params->ranging_type & + PR_NTB_SECURE_LTF_BASED_RANGING) && + !cfg->secure_he_ltf) { + wpa_printf(MSG_DEBUG, + "PR: Secure HE-LTF NTB ranging not supported by device"); + return -1; + } + + /* min must not exceed max time between measurements */ + if (pr_pasn_params->min_time_between_measurements && + pr_pasn_params->max_time_between_measurements && + pr_pasn_params->min_time_between_measurements > + pr_pasn_params->max_time_between_measurements * 100) { + wpa_printf(MSG_DEBUG, + "PR: min_time_between_measurements=%u > max=%u (units: 100us vs 10ms)", + pr_pasn_params->min_time_between_measurements, + pr_pasn_params->max_time_between_measurements); + return -1; + } + } + + /* lmr_feedback is only valid for NTB ranging */ + if (pr_pasn_params->lmr_feedback && !is_ntb) { + wpa_printf(MSG_DEBUG, + "PR: lmr_feedback is only valid for NTB ranging"); + return -1; + } + + wpa_printf(MSG_DEBUG, "PR: Ranging request validation successful"); + return 0; +} + + /** * wpas_pr_pasn_trigger - Entry point to trigger PASN authentication for PR */ @@ -1001,6 +1117,13 @@ void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, return; } + /* Validate request before proceeding */ + if (wpas_pr_validate_ranging_request(wpa_s, pr_pasn_params) < 0) { + wpa_printf(MSG_DEBUG, "PR PASN: Request validation failed"); + pr_pasn_params->pr_pasn_status = PASN_STATUS_FAILURE; + return; + } + if (pr_pasn_params->action == PR_PASN_AND_RANGING) { wpa_printf(MSG_DEBUG, "PR PASN: Triggering PASN authentication for " MACSTR @@ -1023,12 +1146,46 @@ void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, os_memcpy(pr->pr_pasn_params, pr_pasn_params, sizeof(*pr->pr_pasn_params)); + /* Log EDCA parameters if applicable */ + if (pr_pasn_params->ranging_type & PR_EDCA_BASED_RANGING) { + wpa_printf(MSG_DEBUG, + "PR PASN: EDCA params - burst_period=%u num_bursts_exp=%u " + "ftms_per_burst=%u ftmr_retries=%u burst_duration=%u", + pr_pasn_params->burst_period, + pr_pasn_params->num_bursts_exp, + pr_pasn_params->ftms_per_burst, + pr_pasn_params->ftmr_retries, + pr_pasn_params->burst_duration); + } + + /* Log NTB parameters if applicable */ + if (pr_pasn_params->ranging_type & (PR_NTB_SECURE_LTF_BASED_RANGING | + PR_NTB_OPEN_BASED_RANGING)) { + wpa_printf(MSG_DEBUG, + "PR PASN: NTB params - min_time=%u max_time=%u " + "aw=%u nominal_time=%u", + pr_pasn_params->min_time_between_measurements, + pr_pasn_params->max_time_between_measurements, + pr_pasn_params->availability_window, + pr_pasn_params->nominal_time); + } + + /* Log location request parameters */ + if (pr_pasn_params->request_lci || + pr_pasn_params->request_civicloc) { + wpa_printf(MSG_DEBUG, + "PR PASN: Location requests - LCI=%d CivicLoc=%d", + pr_pasn_params->request_lci, + pr_pasn_params->request_civicloc); + } + /* Initiate PASN authentication for the peer */ if (wpas_pr_initiate_pasn_auth(wpa_s, pr_pasn_params->peer_addr, pr_pasn_params->freq, pr_pasn_params->auth_mode, pr_pasn_params->ranging_role, - pr_pasn_params->ranging_type, 0, + pr_pasn_params->ranging_type, + pr_pasn_params->forced_pr_freq, pr_pasn_params->src_addr, pr_pasn_params->pasn_role)) { wpa_printf(MSG_DEBUG, -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:51 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:51 +0530 Subject: [PATCH v3 27/46] PR: Add PR_PASN_NEGOTIATION_STARTED event In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-28-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add a new supplicant event to signal the start of PASN negotiation for proximity ranging. Fire the event at the earliest point where both peers have committed to the exchange: - Initiator: after Auth frame 1 (M1) is successfully sent - Responder: after M1 is received and Auth frame 2 (M2) is sent back This allows the upper layer to know when a peer has initiated PASN on the responder side, since the ROC runs silently in the background without any prior indication. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.c | 15 ++++++++++++++- src/common/proximity_ranging.h | 13 +++++++++++++ src/common/wpa_ctrl.h | 5 +++++ wpa_supplicant/notify.c | 10 ++++++++++ wpa_supplicant/notify.h | 3 +++ wpa_supplicant/pr_supplicant.c | 11 +++++++++++ 6 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index 4aa8ebee0..0ca9c95fe 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -1975,8 +1975,15 @@ int pr_initiate_pasn_auth(struct pr_data *pr, const u8 *addr, int freq, pasn->group, pasn->freq, NULL, 0, NULL, 0, NULL); } - if (ret) + if (ret) { wpa_printf(MSG_INFO, "PR PASN: Failed to start PASN"); + } else { + /* M1 sent successfully ? notify that negotiation has started */ + if (pr->cfg->negotiation_started) + pr->cfg->negotiation_started(pr->cfg->cb_ctx, addr, + ranging_role, + ranging_type); + } out: wpabuf_free(extra_ies); @@ -2394,6 +2401,12 @@ static int pr_pasn_handle_auth_1(struct pr_data *pr, struct pr_device *dev, pr->cfg->set_keys(pr->cfg->cb_ctx, pr->cfg->dev_addr, dev->pr_device_addr, dev->pasn->cipher, dev->pasn->akmp, &dev->pasn->ptk); + + /* M1 received and M2 sent ? notify that negotiation has started */ + if (pr->cfg->negotiation_started) + pr->cfg->negotiation_started(pr->cfg->cb_ctx, mgmt->sa, + dev->ranging_role, + dev->protocol_type); ret = 0; fail: diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 91fa3f652..a9d7ed16a 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -468,6 +468,19 @@ struct pr_config { int (*pasn_send_mgmt)(void *ctx, const u8 *data, size_t data_len, int noack, unsigned int freq, unsigned int wait); + /** + * negotiation_started - Called when PASN negotiation begins + * @ctx: Callback context from cb_ctx + * @peer_addr: MAC address of the peer + * @role: Ranging role (initiator or responder) + * @protocol_type: Ranging protocol type + * + * Fired on the initiator after Auth frame 1 (M1) is sent, and on the + * responder after Auth frame 1 (M1) is received and M2 is sent back. + */ + void (*negotiation_started)(void *ctx, const u8 *peer_addr, u8 role, + u8 protocol_type); + void (*pasn_result)(void *ctx, u8 role, u8 protocol_type, u8 op_class, u8 op_channel, const char *country); diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index b16790fe6..922d5f82a 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -494,6 +494,11 @@ extern "C" { /* PASN authentication status */ #define PASN_AUTH_STATUS "PASN-AUTH-STATUS " +/* Proximity Ranging PASN negotiation started + * peer_addr= role= protocol= + */ +#define PR_PASN_NEGOTIATION_STARTED "PR-PASN-NEGOTIATION-STARTED " + /* Result of PASN performed for Proximity Ranging * role= protocol= opclass= channel= cc= */ diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c index d0b317ee1..59fce8e9b 100644 --- a/wpa_supplicant/notify.c +++ b/wpa_supplicant/notify.c @@ -1539,6 +1539,16 @@ void wpas_notify_pr_pasn_result(struct wpa_supplicant *wpa_s, u8 role, } +void wpas_notify_pr_negotiation_started(struct wpa_supplicant *wpa_s, + const u8 *peer_addr, u8 role, + u8 protocol_type) +{ + wpa_msg_global(wpa_s, MSG_INFO, PR_PASN_NEGOTIATION_STARTED + "peer_addr=" MACSTR " role=%u protocol=%u", + MAC2STR(peer_addr), role, protocol_type); +} + + void wpas_notify_pr_ranging_params(struct wpa_supplicant *wpa_s, const u8 *dev_addr, const u8 *peer_addr, u8 ranging_role, u8 protocol_type, int freq, diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h index f779a613a..eb4ce89a7 100644 --- a/wpa_supplicant/notify.h +++ b/wpa_supplicant/notify.h @@ -209,6 +209,9 @@ void wpas_notify_nan_nik_received(struct wpa_supplicant *wpa_s, void wpas_notify_pr_pasn_result(struct wpa_supplicant *wpa_s, u8 role, u8 protocol_type, u8 op_class, u8 op_channel, const char *country); +void wpas_notify_pr_negotiation_started(struct wpa_supplicant *wpa_s, + const u8 *peer_addr, u8 role, + u8 protocol_type); void wpas_notify_pr_ranging_params(struct wpa_supplicant *wpa_s, const u8 *dev_addr, const u8 *peer_addr, u8 role, u8 protocol, int freq, int channel, diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index b47434eae..77e35aa1a 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -271,6 +271,16 @@ static int wpas_pr_pasn_send_mgmt(void *ctx, const u8 *data, size_t data_len, } +static void wpas_pr_pasn_negotiation_started(void *ctx, const u8 *peer_addr, + u8 role, u8 protocol_type) +{ + struct wpa_supplicant *wpa_s = ctx; + + wpas_notify_pr_negotiation_started(wpa_s, peer_addr, role, + protocol_type); +} + + static void wpas_pr_pasn_result(void *ctx, u8 role, u8 protocol_type, u8 op_class, u8 op_channel, const char *country) { @@ -433,6 +443,7 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, pr.support_6ghz = capa->support_6ghz; pr.pasn_send_mgmt = wpas_pr_pasn_send_mgmt; + pr.negotiation_started = wpas_pr_pasn_negotiation_started; pr.pasn_result = wpas_pr_pasn_result; pr.get_ranging_params = wpas_pr_ranging_params; pr.set_keys = wpas_pr_pasn_set_keys; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:59 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:59 +0530 Subject: [PATCH v3 35/46] wpa_supplicant: Add FTM peer measurement result handling In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-36-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add support for receiving and processing FTM ranging measurement results from the kernel via NL80211_CMD_PEER_MEASUREMENT_RESULT. Add EVENT_PEER_MEASUREMENT_RESULT event and peer_measurement_result structure containing RTT, distance, RSSI, burst parameters, LCI, civic location, and NTB-specific fields. nl80211_peer_measurement_result_event() parses the netlink message and fires the event. wpas_pr_measurement_result() validates the cookie against the pending session and forwards all results including failures to the upper layer via PR-PEER-MEASUREMENT ctrl event. On final result, ranging_final_received is set to block any further results for the session. Session cleanup is handled separately on the COMPLETE event. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.c | 1 + src/common/proximity_ranging.h | 3 + src/common/wpa_ctrl.h | 3 + src/drivers/driver.h | 63 +++++++ src/drivers/driver_common.c | 1 + src/drivers/driver_nl80211_event.c | 257 +++++++++++++++++++++++++++++ wpa_supplicant/events.c | 7 + wpa_supplicant/notify.c | 28 ++++ wpa_supplicant/notify.h | 2 + wpa_supplicant/pr_supplicant.c | 47 ++++++ wpa_supplicant/pr_supplicant.h | 8 + 11 files changed, 420 insertions(+) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index 06ee58b3d..5b5f3bf8a 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -151,6 +151,7 @@ void pr_deinit(struct pr_data *pr) #ifdef CONFIG_PASN os_free(pr->pr_pasn_params); pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; pasn_initiator_pmksa_cache_deinit(pr->initiator_pmksa); pasn_responder_pmksa_cache_deinit(pr->responder_pmksa); diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index b8e49ab77..52a9620ec 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -596,6 +596,9 @@ struct pr_data { /* PR PASN request tracking - similar to pasn_params in wpa_supplicant */ struct pr_pasn_ranging_params *pr_pasn_params; + + /* Set when final measurement result received; blocks further results */ + bool ranging_final_received; }; /* PR Device Identity Resolution Attribute parameters */ diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 922d5f82a..70fe22576 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -507,6 +507,9 @@ extern "C" { /* Proximity Ranging parameters to use in ranging */ #define PR_RANGING_PARAMS "PR-RANGING-PARAMS " +/* Proximity Ranging measurement result */ +#define PR_EVENT_PEER_MEASUREMENT "PR-PEER-MEASUREMENT " + /* BSS command information masks */ #define WPA_BSS_MASK_ALL 0xFFFDFFFF diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 4689bc5b0..22a4ad02e 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -6668,6 +6668,13 @@ enum wpa_event_type { * activities). */ EVENT_NAN_CHAN_EVACUATION, + + /** + * EVENT_PEER_MEASUREMENT_RESULT - Ranging measurement result + * + * This event is used to indicate a ranging measurement result. + */ + EVENT_PEER_MEASUREMENT_RESULT, }; @@ -7719,6 +7726,62 @@ union wpa_event_data { struct nan_chan_evacuation_info { int freq; } nan_chan_evacuation_info; + + /** + * struct peer_measurement_result - Ranging measurement result + */ + struct peer_measurement_result { + u8 addr[ETH_ALEN]; + u32 status; + u64 host_time; + u64 ap_tsf; + u8 final; + u64 cookie; + + /** + * struct peer_measurement_ftm_result - + * FTM-specific measurement results + */ + struct peer_measurement_ftm_result { + u8 fail; + u32 fail_reason; + u32 burst_index; + u32 num_ftmr_attempts; + u32 num_ftmr_successes; + u32 busy_retry_time; + u32 num_bursts_exp; + u32 burst_duration; + u32 ftms_per_burst; + s32 rssi_avg; + s32 rssi_spread; + s64 rtt_avg; + u64 rtt_variance; + u64 rtt_spread; + s64 dist_avg; + u64 dist_variance; + u64 dist_spread; + const u8 *lci; + size_t lci_len; + const u8 *civicloc; + size_t civicloc_len; + /* Additional FTM parameters */ + u32 burst_period; + u32 tx_ltf_repetition_count; + u32 rx_ltf_repetition_count; + u32 max_time_between_measurements; + u32 min_time_between_measurements; + u32 num_tx_spatial_streams; + u32 num_rx_spatial_streams; + u32 nominal_time; + u8 availability_window; + u32 band_width; + u32 preamble; + u8 is_delayed_lmr; + /* Flag indicating if FTM data is present */ + u8 has_data; + } ftm; + + } peer_measurement_result; }; /** diff --git a/src/drivers/driver_common.c b/src/drivers/driver_common.c index f04f737c0..170821423 100644 --- a/src/drivers/driver_common.c +++ b/src/drivers/driver_common.c @@ -108,6 +108,7 @@ const char * event_to_string(enum wpa_event_type event) E2S(NAN_SCHED_UPDATE_DONE); E2S(NAN_ULW_UPDATE); E2S(NAN_CHAN_EVACUATION); + E2S(PEER_MEASUREMENT_RESULT); } return "UNKNOWN"; diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index 2f5d352e2..c16508404 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -4258,6 +4258,258 @@ static void nl80211_assoc_comeback(struct wpa_driver_nl80211_data *drv, MAC2STR((u8 *) nla_data(mac)), nla_get_u32(timeout)); } +#ifdef CONFIG_PR + +static void +nl80211_parse_peer_ftm_result(struct peer_measurement_ftm_result *ftm, + struct nlattr *ftm_data) +{ + struct nlattr *ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MAX + 1]; + + if (nla_parse_nested(ftm_tb, NL80211_PMSR_FTM_RESP_ATTR_MAX, + ftm_data, NULL)) + return; + + ftm->has_data = 1; + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON]) { + ftm->fail = 1; + ftm->fail_reason = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON]); + wpa_printf(MSG_DEBUG, + "nl80211: Ranging failed with reason %u", + ftm->fail_reason); + return; + } + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_INDEX]) + ftm->burst_index = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_INDEX]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_ATTEMPTS]) + ftm->num_ftmr_attempts = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_ATTEMPTS]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_SUCCESSES]) + ftm->num_ftmr_successes = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_SUCCESSES]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME]) + ftm->busy_retry_time = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_BURSTS_EXP]) + ftm->num_bursts_exp = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_BURSTS_EXP]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_DURATION]) + ftm->burst_duration = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_DURATION]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FTMS_PER_BURST]) + ftm->ftms_per_burst = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FTMS_PER_BURST]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_AVG]) + ftm->rssi_avg = + nla_get_s32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_AVG]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_SPREAD]) + ftm->rssi_spread = + nla_get_s32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_SPREAD]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_AVG]) + ftm->rtt_avg = + nla_get_s64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_AVG]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_VARIANCE]) + ftm->rtt_variance = + nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_VARIANCE]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_SPREAD]) + ftm->rtt_spread = + nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_SPREAD]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_AVG]) + ftm->dist_avg = + nla_get_s64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_AVG]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_VARIANCE]) + ftm->dist_variance = + nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_VARIANCE]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_SPREAD]) + ftm->dist_spread = + nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_SPREAD]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI]) { + ftm->lci = os_memdup( + nla_data(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI]), + nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI])); + ftm->lci_len = nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI]); + } + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]) { + ftm->civicloc = os_memdup( + nla_data(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]), + nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC])); + ftm->civicloc_len = + nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]); + } + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD]) + ftm->burst_period = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_TX_LTF_REPETITION_COUNT]) + ftm->tx_ltf_repetition_count = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_TX_LTF_REPETITION_COUNT]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RX_LTF_REPETITION_COUNT]) + ftm->rx_ltf_repetition_count = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RX_LTF_REPETITION_COUNT]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS]) + ftm->max_time_between_measurements = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS]) + ftm->min_time_between_measurements = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_TX_SPATIAL_STREAMS]) + ftm->num_tx_spatial_streams = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_TX_SPATIAL_STREAMS]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_RX_SPATIAL_STREAMS]) + ftm->num_rx_spatial_streams = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_RX_SPATIAL_STREAMS]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NOMINAL_TIME]) + ftm->nominal_time = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NOMINAL_TIME]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_AVAILABILITY_WINDOW]) + ftm->availability_window = + nla_get_u8(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_AVAILABILITY_WINDOW]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CHANNEL_WIDTH]) + ftm->band_width = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CHANNEL_WIDTH]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_PREAMBLE]) + ftm->preamble = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_PREAMBLE]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR]) + ftm->is_delayed_lmr = + !!ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR]; +} + + +static void nl80211_peer_measurement_result_event(struct i802_bss *bss, + struct nlattr **tb) +{ + union wpa_event_data data; + struct nlattr *pmsr[NL80211_PMSR_ATTR_MAX + 1]; + struct nlattr *peer; + u64 cookie = 0; + int rem; + struct nla_policy pmsr_policy[NL80211_PMSR_ATTR_MAX + 1] = { + [NL80211_PMSR_ATTR_PEERS] = { .type = NLA_NESTED }, + }; + struct nla_policy peer_policy[NL80211_PMSR_PEER_ATTR_MAX + 1] = { + [NL80211_PMSR_PEER_ATTR_ADDR] = { + .minlen = ETH_ALEN, + .maxlen = ETH_ALEN, + }, + [NL80211_PMSR_PEER_ATTR_RESP] = { .type = NLA_NESTED }, + }; + struct nla_policy resp_policy[NL80211_PMSR_RESP_ATTR_MAX + 1] = { + [NL80211_PMSR_RESP_ATTR_DATA] = { .type = NLA_NESTED }, + [NL80211_PMSR_RESP_ATTR_STATUS] = { .type = NLA_U32 }, + [NL80211_PMSR_RESP_ATTR_HOST_TIME] = { .type = NLA_U64 }, + [NL80211_PMSR_RESP_ATTR_AP_TSF] = { .type = NLA_U64 }, + [NL80211_PMSR_RESP_ATTR_FINAL] = { .type = NLA_FLAG }, + }; + + os_memset(&data, 0, sizeof(data)); + if (tb[NL80211_ATTR_COOKIE]) { + cookie = nla_get_u64(tb[NL80211_ATTR_COOKIE]); + wpa_printf(MSG_DEBUG, + "nl80211: PR: Peer measurement cookie: %llu", + (unsigned long long) cookie); + } + + if (!tb[NL80211_ATTR_PEER_MEASUREMENTS] || + nla_parse_nested(pmsr, NL80211_PMSR_ATTR_MAX, + tb[NL80211_ATTR_PEER_MEASUREMENTS], pmsr_policy)) + return; + + if (!pmsr[NL80211_PMSR_ATTR_PEERS]) + return; + + nla_for_each_nested(peer, pmsr[NL80211_PMSR_ATTR_PEERS], rem) { + struct nlattr *peer_tb[NL80211_PMSR_PEER_ATTR_MAX + 1]; + struct nlattr *resp_tb[NL80211_PMSR_RESP_ATTR_MAX + 1]; + + if (nla_parse_nested(peer_tb, NL80211_PMSR_PEER_ATTR_MAX, + peer, peer_policy)) + continue; + + if (!peer_tb[NL80211_PMSR_PEER_ATTR_ADDR] || + !peer_tb[NL80211_PMSR_PEER_ATTR_RESP]) + continue; + + os_memset(&data.peer_measurement_result, 0, + sizeof(data.peer_measurement_result)); + data.peer_measurement_result.cookie = cookie; + + os_memcpy(data.peer_measurement_result.addr, + nla_data(peer_tb[NL80211_PMSR_PEER_ATTR_ADDR]), + ETH_ALEN); + + if (nla_parse_nested(resp_tb, NL80211_PMSR_RESP_ATTR_MAX, + peer_tb[NL80211_PMSR_PEER_ATTR_RESP], + resp_policy)) + continue; + + if (resp_tb[NL80211_PMSR_RESP_ATTR_STATUS]) + data.peer_measurement_result.status = + nla_get_u32(resp_tb[NL80211_PMSR_RESP_ATTR_STATUS]); + + if (resp_tb[NL80211_PMSR_RESP_ATTR_HOST_TIME]) + data.peer_measurement_result.host_time = + nla_get_u64(resp_tb[NL80211_PMSR_RESP_ATTR_HOST_TIME]); + + if (resp_tb[NL80211_PMSR_RESP_ATTR_AP_TSF]) + data.peer_measurement_result.ap_tsf = + nla_get_u64(resp_tb[NL80211_PMSR_RESP_ATTR_AP_TSF]); + + if (resp_tb[NL80211_PMSR_RESP_ATTR_FINAL]) + data.peer_measurement_result.final = 1; + + if (resp_tb[NL80211_PMSR_RESP_ATTR_DATA]) { + struct nlattr *data_type_tb[NL80211_PMSR_TYPE_MAX + 1]; + + if (nla_parse_nested(data_type_tb, NL80211_PMSR_TYPE_MAX, + resp_tb[NL80211_PMSR_RESP_ATTR_DATA], + NULL)) + continue; + + if (data_type_tb[NL80211_PMSR_TYPE_FTM]) + nl80211_parse_peer_ftm_result(&data.peer_measurement_result.ftm, + data_type_tb[NL80211_PMSR_TYPE_FTM]); + } + + wpa_supplicant_event(bss->ctx, EVENT_PEER_MEASUREMENT_RESULT, &data); + /* Free deep-copied LCI/civic location data */ + os_free((void *) data.peer_measurement_result.ftm.lci); + os_free((void *) data.peer_measurement_result.ftm.civicloc); + } +} + +#endif /* CONFIG_PR */ #ifdef CONFIG_IEEE80211AX @@ -4808,6 +5060,11 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd, case NL80211_CMD_INCUMBENT_SIGNAL_DETECT: nl80211_incumbt_sig_intf_event(bss, tb); break; +#ifdef CONFIG_PR + case NL80211_CMD_PEER_MEASUREMENT_RESULT: + nl80211_peer_measurement_result_event(bss, tb); + break; +#endif /* CONFIG_PR */ default: wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Ignored unknown event " "(cmd=%d)", cmd); diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 22d6ce196..1fcc57ee1 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -7648,6 +7648,13 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, if (data) wpas_setup_link_reconfig(wpa_s, &data->reconfig_info); break; + case EVENT_PEER_MEASUREMENT_RESULT: +#ifdef CONFIG_PR + if (data) + wpas_pr_measurement_result(wpa_s, + &data->peer_measurement_result); +#endif /* CONFIG_PR */ + break; #ifdef CONFIG_NAN case EVENT_NAN_CLUSTER_JOIN: wpas_nan_cluster_join(wpa_s, data->nan_cluster_join_info.bssid, diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c index 59fce8e9b..58661c7ec 100644 --- a/wpa_supplicant/notify.c +++ b/wpa_supplicant/notify.c @@ -1561,4 +1561,32 @@ void wpas_notify_pr_ranging_params(struct wpa_supplicant *wpa_s, protocol_type, freq, channel, bw, format_bw); } + +void wpas_notify_pr_measurement_result(struct wpa_supplicant *wpa_s, + const struct peer_measurement_result *result) +{ + char rtt[32] = ""; + char dist[32] = ""; + + if (result->ftm.fail) { + wpa_msg_global(wpa_s, MSG_INFO, PR_EVENT_PEER_MEASUREMENT + "addr=" MACSTR " status=%u burst_index=%u fail=1 fail_reason=%u", + MAC2STR(result->addr), result->status, + result->ftm.burst_index, result->ftm.fail_reason); + return; + } + + if (result->ftm.rtt_avg || result->ftm.has_data) + os_snprintf(rtt, sizeof(rtt), " rtt_avg=%lld", + (long long) result->ftm.rtt_avg); + if (result->ftm.dist_avg || result->ftm.has_data) + os_snprintf(dist, sizeof(dist), " dist_avg=%lld", + (long long) result->ftm.dist_avg); + + wpa_msg_global(wpa_s, MSG_INFO, PR_EVENT_PEER_MEASUREMENT + "addr=" MACSTR " status=%u burst_index=%u%s%s", + MAC2STR(result->addr), result->status, + result->ftm.burst_index, rtt, dist); +} + #endif /* CONFIG_PR */ diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h index eb4ce89a7..1ed9978b9 100644 --- a/wpa_supplicant/notify.h +++ b/wpa_supplicant/notify.h @@ -216,6 +216,8 @@ void wpas_notify_pr_ranging_params(struct wpa_supplicant *wpa_s, const u8 *dev_addr, const u8 *peer_addr, u8 role, u8 protocol, int freq, int channel, int bw, int format_bw); +void wpas_notify_pr_measurement_result(struct wpa_supplicant *wpa_s, + const struct peer_measurement_result *result); void wpas_notify_nan_bootstrap_request(struct wpa_supplicant *wpa_s, const u8 *peer_addr, u16 pbm, int handle, u8 requestor_instance_id); diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 7a0557ea7..e28b68c13 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -468,6 +468,7 @@ static int wpas_pr_trigger_ranging(struct wpa_supplicant *wpa_s, fail: os_free(pr->pr_pasn_params); pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; return -1; } @@ -756,6 +757,51 @@ void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, } +void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, + struct peer_measurement_result *result) +{ + struct pr_data *pr = wpa_s->global->pr; + + if (!result) { + wpa_printf(MSG_ERROR, "PR: Invalid measurement result"); + return; + } + + /* Drop results after final has been received */ + if (pr && pr->ranging_final_received) { + wpa_printf(MSG_DEBUG, + "PR: Ignoring result after final for " MACSTR, + MAC2STR(result->addr)); + return; + } + + /* Validate cookie if we have a pending ranging request */ + if (pr && pr->pr_pasn_params && result->cookie != 0) { + if (pr->pr_pasn_params->cookie != result->cookie) { + wpa_printf(MSG_WARNING, + "PR: Cookie mismatch - expected %llu, got %llu. Ignoring result.", + (unsigned long long) pr->pr_pasn_params->cookie, + (unsigned long long) result->cookie); + return; + } + } + + /* Forward result to upper layer ? includes failures and final */ + if (result->ftm.has_data || result->ftm.fail) + wpas_notify_pr_measurement_result(wpa_s, result); + + /* After final result, mark session done ? no more results accepted */ + if (result->final) { + wpa_printf(MSG_DEBUG, + "PR: Final result received for " MACSTR + " ? no further results will be processed", + MAC2STR(result->addr)); + if (pr) + pr->ranging_final_received = true; + } +} + + #ifdef CONFIG_PASN static int wpas_pr_start_pd(struct wpa_supplicant *wpa_s, const u8 *src_addr) @@ -1388,6 +1434,7 @@ void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, pr_pasn_params->pr_pasn_status = PASN_STATUS_FAILURE; os_free(pr->pr_pasn_params); pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; return; } } else { diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index f8b114985..679c755e2 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -41,6 +41,8 @@ void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, unsigned int freq); void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, struct pr_pasn_ranging_params *pr_pasn_params); +void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, + struct peer_measurement_result *result); #else /* CONFIG_PR */ @@ -118,6 +120,12 @@ static inline void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, { } +static inline void +wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, + struct peer_measurement_result *result) +{ +} + #endif /* CONFIG_PR */ #endif /* PR_SUPPLICANT_H */ -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 03:00:01 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:30:01 +0530 Subject: [PATCH v3 37/46] PR: Add ranging session timeout and integrate socket cleanup In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-38-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add wpas_pr_ranging_session_timeout() to automatically clean up when a continuous ranging session expires. When continuous_ranging_session_time is set, a timer is started after ranging begins; on expiry it stops the peer measurement, frees pr_pasn_params, and stops the PD wdev. Cancel the PASN timeout when ranging starts to avoid premature cleanup while ranging is in progress. Cancel the ranging timeout on measurement completion. On final measurement result, only ranging_final_received is set to block further results. Actual cleanup is deferred to the COMPLETE event or session timeout to avoid premature teardown. Integrate wpa_drv_stop_peer_measurement() into wpas_pr_pd_stop() so the nl_pr socket is always destroyed when the PD wdev is stopped, ensuring proper cleanup on all termination paths. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/driver_i.h | 8 +++++ wpa_supplicant/pr_supplicant.c | 55 ++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index c111224a6..7ff9f454c 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -772,6 +772,14 @@ wpa_drv_start_peer_measurement(struct wpa_supplicant *wpa_s, const u8 *peer, params); } +static inline void +wpa_drv_stop_peer_measurement(struct wpa_supplicant *wpa_s) +{ + if (!wpa_s->driver->stop_peer_measurement) + return; + wpa_s->driver->stop_peer_measurement(wpa_s->drv_priv); +} + #endif /* CONFIG_PR */ static inline int wpa_drv_vendor_cmd(struct wpa_supplicant *wpa_s, diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 60a8406cc..a753db9a1 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -380,6 +380,28 @@ static void wpas_pr_pasn_result(void *ctx, u8 role, u8 protocol_type, } +static void wpas_pr_ranging_session_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + struct pr_data *pr = wpa_s->global->pr; + + wpa_printf(MSG_DEBUG, + "PR: Ranging session timeout - cleaning up"); + + /* Stop peer measurement and cleanup ranging socket */ + wpa_drv_stop_peer_measurement(wpa_s); + + /* Free ranging params */ + if (pr && pr->pr_pasn_params) { + os_free(pr->pr_pasn_params); + pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; + } + + wpas_pr_pd_stop(wpa_s); +} + + /** * wpas_pr_trigger_ranging - Trigger FTM ranging after successful PASN auth */ @@ -463,6 +485,20 @@ static int wpas_pr_trigger_ranging(struct wpa_supplicant *wpa_s, wpa_printf(MSG_DEBUG, "PR: Successfully triggered ranging measurement"); + /* Start session timeout timer if continuous ranging session time is set */ + if (params->continuous_ranging_session_time > 0) { + unsigned int timeout_sec = params->continuous_ranging_session_time / 1000; + unsigned int timeout_usec = (params->continuous_ranging_session_time % 1000) * 1000; + + wpa_printf(MSG_DEBUG, + "PR: Starting ranging session timeout timer for %u ms", + params->continuous_ranging_session_time); + + eloop_cancel_timeout(wpas_pr_ranging_session_timeout, wpa_s, NULL); + eloop_register_timeout(timeout_sec, timeout_usec, + wpas_pr_ranging_session_timeout, wpa_s, NULL); + } + return 0; fail: @@ -490,9 +526,20 @@ static void wpas_pr_ranging_params(void *ctx, const u8 *dev_addr, protocol_type, freq, op_channel, bw, format_bw); + /* + * PASN succeeded - cancel the PASN timeout so it does not fire and + * prematurely stop the PD wdev while ranging is in progress. + * Cleanup happens on COMPLETE event or session timeout. + */ + eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL); + /* Trigger ranging measurement after successful PASN authentication */ - wpas_pr_trigger_ranging(wpa_s, peer_addr, freq, op_class, op_channel, - format_bw, protocol_type); + if (wpas_pr_trigger_ranging(wpa_s, peer_addr, freq, op_class, op_channel, + format_bw, protocol_type) < 0) { + wpa_printf(MSG_DEBUG, + "PR: Failed to trigger ranging, stopping PD wdev"); + wpas_pr_pd_stop(wpa_s); + } } @@ -713,6 +760,10 @@ void wpas_pr_deinit(struct wpa_supplicant *wpa_s) void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s) { + /* Cancel ranging session timeout and stop peer measurement */ + eloop_cancel_timeout(wpas_pr_ranging_session_timeout, wpa_s, NULL); + wpa_drv_stop_peer_measurement(wpa_s); + if (is_zero_ether_addr(wpa_s->pd_addr)) { wpa_printf(MSG_DEBUG, "PR: pd_stop: no active PD wdev"); return; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:57 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:57 +0530 Subject: [PATCH v3 33/46] PR: Extend PR_PASN_START ctrl iface with full ranging parameters In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-34-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Refactor wpas_ctrl_iface_pr_pasn_start() to build a pr_pasn_ranging_params struct and route through wpas_pr_pasn_trigger() which performs capability validation before initiating PASN. Extend the command to accept EDCA burst parameters (burst_duration, num_bursts_exp, ftms_per_burst, ftmr_retries, burst_duration), NTB availability window settings (min/max_time_between_meas, availability_window, nominal_time), proximity thresholds (ingress_threshold, egress_threshold), lmr_feedback, pd_suppress_results, and continuous_session_time for session lifetime control. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/ctrl_iface.c | 102 ++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 29 deletions(-) diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 6dd444349..b2cb6746e 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -11560,65 +11560,108 @@ static int wpas_ctrl_iface_pr_pasn_start(struct wpa_supplicant *wpa_s, char *cmd) { char *token, *context = NULL; - u8 addr[ETH_ALEN]; - int freq = 0, forced_pr_freq = 0; - u8 ranging_type = 0, role = 0, auth_mode = 0; + struct pr_pasn_ranging_params params; bool got_addr = false; - u8 src_addr[ETH_ALEN], *p_src_addr = NULL; - enum pr_pasn_role pasn_role = PR_ROLE_PASN_INITIATOR; + int interval_time = 0; + + os_memset(¶ms, 0, sizeof(params)); + params.action = PR_PASN_AND_RANGING; while ((token = str_token(cmd, " ", &context))) { if (os_strncmp(token, "addr=", 5) == 0) { - if (hwaddr_aton(token + 5, addr)) + if (hwaddr_aton(token + 5, params.peer_addr)) return -1; got_addr = true; } else if (os_strcmp(token, "role=ISTA") == 0) { - role |= PR_ISTA_SUPPORT; + params.ranging_role |= PR_ISTA_SUPPORT; } else if (os_strcmp(token, "role=RSTA") == 0) { - role |= PR_RSTA_SUPPORT; + params.ranging_role |= PR_RSTA_SUPPORT; } else if (os_strcmp(token, "ranging_type=EDCA") == 0) { - ranging_type |= PR_EDCA_BASED_RANGING; + params.ranging_type |= PR_EDCA_BASED_RANGING; } else if (os_strcmp(token, "ranging_type=NTB-OPEN-PHY") == 0) { - ranging_type |= PR_NTB_OPEN_BASED_RANGING; + params.ranging_type |= PR_NTB_OPEN_BASED_RANGING; } else if (os_strcmp(token, "ranging_type=NTB-SEC-PHY") == 0) { - ranging_type |= PR_NTB_SECURE_LTF_BASED_RANGING; + params.ranging_type |= PR_NTB_SECURE_LTF_BASED_RANGING; } else if (os_strncmp(token, "freq=", 5) == 0) { - freq = atoi(token + 5); + params.freq = atoi(token + 5); } else if (os_strncmp(token, "auth=", 5) == 0) { - auth_mode = atoi(token + 5); + params.auth_mode = atoi(token + 5); } else if (os_strncmp(token, "forced_pr_freq=", 15) == 0) { - forced_pr_freq = atoi(token + 15); + params.forced_pr_freq = atoi(token + 15); + } else if (os_strncmp(token, "num_bursts_exp=", 15) == 0) { + params.num_bursts_exp = atoi(token + 15); + } else if (os_strncmp(token, "ftmr_retries=", 13) == 0) { + params.ftmr_retries = atoi(token + 13); + } else if (os_strncmp(token, "burst_duration=", 15) == 0) { + params.burst_duration = atoi(token + 15); + } else if (os_strncmp(token, "ftms_per_burst=", 15) == 0) { + params.ftms_per_burst = atoi(token + 15); + } else if (os_strncmp(token, "interval_time=", 14) == 0) { + interval_time = atoi(token + 14); + } else if (os_strncmp(token, "min_time_between_meas=", 22) == 0) { + params.min_time_between_measurements = atoi(token + 22); + } else if (os_strncmp(token, "max_time_between_meas=", 22) == 0) { + params.max_time_between_measurements = atoi(token + 22); + } else if (os_strncmp(token, "availability_window=", 20) == 0) { + params.availability_window = atoi(token + 20); + } else if (os_strcmp(token, "request_lci=1") == 0) { + params.request_lci = true; + } else if (os_strcmp(token, "request_civicloc=1") == 0) { + params.request_civicloc = true; + } else if (os_strncmp(token, "continuous_session_time=", 24) == 0) { + params.continuous_ranging_session_time = atoi(token + 24); } else if (os_strncmp(token, "src_addr=", 9) == 0) { - if (hwaddr_aton(token + 9, src_addr)) + if (hwaddr_aton(token + 9, params.src_addr)) return -1; - p_src_addr = src_addr; } else if (os_strcmp(token, "pasn_role=INITIATOR") == 0) { - pasn_role = PR_ROLE_PASN_INITIATOR; + params.pasn_role = PR_ROLE_PASN_INITIATOR; } else if (os_strcmp(token, "pasn_role=RESPONDER") == 0) { - pasn_role = PR_ROLE_PASN_RESPONDER; + params.pasn_role = PR_ROLE_PASN_RESPONDER; + } else if (os_strcmp(token, "lmr_feedback=1") == 0) { + params.lmr_feedback = true; + } else if (os_strncmp(token, "ingress_threshold=", 18) == 0) { + params.ingress_threshold = atoll(token + 18); + } else if (os_strncmp(token, "egress_threshold=", 17) == 0) { + params.egress_threshold = atoll(token + 17); + } else if (os_strcmp(token, "pd_suppress_results=1") == 0) { + params.pr_suppress_results = true; } else { wpa_printf(MSG_DEBUG, - "CTRL: PASN invalid parameter: '%s'", + "CTRL: PR_PASN_START invalid parameter: '%s'", token); return -1; } } - if (!got_addr || ranging_type == 0 || role == 0 || freq == 0) { + /* + * Map interval_time to the appropriate protocol-specific field: + * - EDCA: burst_period + * - NTB: nominal_time (nominal time between measurements) + */ + if (interval_time > 0) { + if (params.ranging_type & PR_EDCA_BASED_RANGING) + params.burst_period = interval_time; + else if (params.ranging_type & (PR_NTB_OPEN_BASED_RANGING | + PR_NTB_SECURE_LTF_BASED_RANGING)) + params.nominal_time = interval_time; + } + + if (!got_addr || params.ranging_type == 0 || params.ranging_role == 0 || + params.freq == 0) { wpa_printf(MSG_DEBUG, - "CTRL: Proximity Ranging PASN missing parameter"); + "CTRL: PR_PASN_START missing parameter"); return -1; } wpa_printf(MSG_DEBUG, - "CTRL: PR PASN params: ranging type=0x%x, role=0x%x, " - "pasn_role=%d, auth_mode=%d, forced pr freq=%d, addr=" - MACSTR " src_addr=" MACSTR, - ranging_type, role, pasn_role, auth_mode, forced_pr_freq, - MAC2STR(addr), MAC2STR(p_src_addr ? p_src_addr : addr)); - return wpas_pr_initiate_pasn_auth(wpa_s, addr, freq, auth_mode, role, - ranging_type, forced_pr_freq, - p_src_addr, pasn_role); + "CTRL: PR_PASN_START params: ranging type=0x%x, role=0x%x," + " auth_mode=%d, freq=%d, addr=" MACSTR " src_addr=" MACSTR " pasn_role=%d", + params.ranging_type, params.ranging_role, params.auth_mode, + params.freq, MAC2STR(params.peer_addr), + MAC2STR(params.src_addr), params.pasn_role); + + wpas_pr_pasn_trigger(wpa_s, ¶ms); + return 0; } @@ -11673,6 +11716,7 @@ fail: return ret; } + #endif /* CONFIG_PR */ -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:58 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:58 +0530 Subject: [PATCH v3 34/46] nl80211: Add dedicated PR ranging socket and stop op In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-35-kavita.kavita@oss.qualcomm.com> From: Veerendranath Jakkam Create a dedicated netlink socket (nl_pr) in nl80211_global for PR peer measurements. The socket is created when start_peer_measurement() is called, used to send NL80211_CMD_PEER_MEASUREMENT_START, and registered with eloop to receive RESULT and COMPLETE events. Add stop_peer_measurement() driver op implemented by nl80211_stop_peer_measurement() which destroys the nl_pr socket and unregisters it from eloop. Also add cleanup in nl80211_global_deinit() for the case where stop was never called. Signed-off-by: Veerendranath Jakkam --- src/drivers/driver.h | 9 +++++++ src/drivers/driver_nl80211.c | 46 +++++++++++++++++++++++++++++++++++- src/drivers/driver_nl80211.h | 4 ++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index ee710fad8..4689bc5b0 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -5863,6 +5863,15 @@ struct wpa_driver_ops { int (*start_peer_measurement)(void *priv, const u8 *peer_addr, int freq, u8 channel, int bw, struct pr_pasn_ranging_params *params); + + /** + * stop_peer_measurement - Stop peer measurement and destroy ranging socket + * @priv: Private driver interface data + * + * Unregisters the ranging socket from eloop and frees all associated + * resources. Safe to call when no ranging session is active (no-op). + */ + void (*stop_peer_measurement)(void *priv); #endif /* CONFIG_PR */ #ifdef CONFIG_NAN diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 8af3efe98..4435b915d 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -9330,6 +9330,20 @@ static int nl80211_start_peer_measurement(void *priv, const u8 *peer_addr, return -1; } + /* Create dedicated ranging socket if not already created */ + if (drv->global->nl_pr) { + wpa_printf(MSG_ERROR, + "nl80211: PR ranging socket already in use"); + return -1; + } + + drv->global->nl_pr = nl_create_handle(drv->global->nl_cb, "pr"); + if (!drv->global->nl_pr) { + wpa_printf(MSG_ERROR, + "nl80211: Failed to create PR ranging socket"); + return -1; + } + /* Route via PD wdev if src_addr matches */ if (drv->pd_bss && !is_zero_ether_addr(params->src_addr) && ether_addr_equal(params->src_addr, drv->pd_bss->addr)) { @@ -9553,19 +9567,29 @@ static int nl80211_start_peer_measurement(void *priv, const u8 *peer_addr, cookie = 0; os_memset(&ack_arg, 0, sizeof(struct nl80211_ack_ext_arg)); ack_arg.ext_data = &cookie; - ret = send_and_recv(drv, drv->global->nl, msg, NULL, NULL, + ret = send_and_recv(drv, drv->global->nl_pr, msg, NULL, NULL, ack_handler_cookie, &ack_arg, NULL); if (ret < 0) { wpa_printf(MSG_ERROR, "nl80211: Peer measurement start failed: ret=%d (%s)", ret, strerror(-ret)); + nl_destroy_handles(&drv->global->nl_pr); } else { wpa_printf(MSG_DEBUG, "nl80211: Peer measurement started successfully addr=" MACSTR " cookie=%llu", MAC2STR(peer_addr), (unsigned long long) cookie); params->cookie = cookie; + /* + * Register the PR socket with eloop so that + * NL80211_CMD_PEER_MEASUREMENT_RESULT and + * NL80211_CMD_PEER_MEASUREMENT_COMPLETE events are delivered + * to the existing nl80211 event handler. + */ + nl80211_register_eloop_read(&drv->global->nl_pr, + wpa_driver_nl80211_event_receive, + drv->global->nl_cb, 1); } return ret; @@ -9573,9 +9597,23 @@ static int nl80211_start_peer_measurement(void *priv, const u8 *peer_addr, fail: wpa_printf(MSG_ERROR, "nl80211: Failed to build peer measurement message"); nlmsg_free(msg); + nl_destroy_handles(&drv->global->nl_pr); return -1; } + +static void nl80211_stop_peer_measurement(void *priv) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + + if (!drv->global->nl_pr) + return; + + wpa_printf(MSG_DEBUG, "nl80211: Stopping PR ranging socket"); + nl80211_destroy_eloop_handle(&drv->global->nl_pr, 1); +} + #endif /* CONFIG_PR */ static void dump_ifidx(struct wpa_driver_nl80211_data *drv) @@ -11354,6 +11392,11 @@ static void nl80211_global_deinit(void *priv) nl_cb_put(global->nl_cb); +#ifdef CONFIG_PR + if (global->nl_pr) + nl80211_destroy_eloop_handle(&global->nl_pr, 1); +#endif /* CONFIG_PR */ + if (global->ioctl_sock >= 0) close(global->ioctl_sock); @@ -16722,5 +16765,6 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { .pd_start = nl80211_pd_start, .pd_stop = nl80211_pd_stop, .start_peer_measurement = nl80211_start_peer_measurement, + .stop_peer_measurement = nl80211_stop_peer_measurement, #endif /* CONFIG_PR */ }; diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h index d1133a1fe..9c871ce18 100644 --- a/src/drivers/driver_nl80211.h +++ b/src/drivers/driver_nl80211.h @@ -52,6 +52,10 @@ struct nl80211_global { /* Dedicated socket for NAN interface creation and events */ struct nl_sock *nl_nan; #endif /* CONFIG_NAN */ +#ifdef CONFIG_PR + /* Dedicated socket for PR peer measurement commands and events */ + struct nl_sock *nl_pr; +#endif /* CONFIG_PR */ }; struct nl80211_wiphy_data { -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 03:00:03 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:30:03 +0530 Subject: [PATCH v3 39/46] PR: Default pasn_type to DH19_UNAUTH|DH19_AUTH when not configured In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-40-kavita.kavita@oss.qualcomm.com> When pr_pasn_type is not configured in wpa_supplicant.conf, default to PR_PASN_DH19_UNAUTH | PR_PASN_DH19_AUTH (0x03) to support both unauthenticated and authenticated PASN with DH group 19. DH group 19 (P-256) is mandatory per IEEE 802.11az. Advertising AUTH support does not force authenticated PASN ? the actual mode is determined at session time based on the auth parameter in PR_PASN_START and credential availability. Signed-off-by: Kavita Kavita --- wpa_supplicant/pr_supplicant.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 8d1a1b632..3cd4f3cc6 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -636,7 +636,9 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, os_memcpy(pr.dev_addr, wpa_s->own_addr, ETH_ALEN); pr.cb_ctx = wpa_s; pr.dev_name = wpa_s->conf->device_name; - pr.pasn_type = wpa_s->conf->pr_pasn_type; + pr.pasn_type = wpa_s->conf->pr_pasn_type ? + wpa_s->conf->pr_pasn_type : + (PR_PASN_DH19_UNAUTH | PR_PASN_DH19_AUTH); pr.preferred_ranging_role = wpa_s->conf->pr_preferred_role; pr.edca_ista_support = capa->ista.support_edca && capa->asap_support; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 03:00:04 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:30:04 +0530 Subject: [PATCH v3 40/46] PR: Restore dev_addr to station MAC after PD wdev stop In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-41-kavita.kavita@oss.qualcomm.com> When a PD wdev is created with a custom MAC, pr_set_dev_addr() updates pr->cfg->dev_addr to the PD MAC address. pr_pasn_auth_rx() validates incoming PASN frames against pr->cfg->dev_addr, so without restoring it, subsequent ranging sessions using the station MAC would have their PASN frames rejected. Call pr_set_dev_addr() with wpa_s->own_addr in wpas_pr_pd_stop() so that pr->cfg->dev_addr reflects the station MAC once the PD wdev is torn down. Signed-off-by: Kavita Kavita --- wpa_supplicant/pr_supplicant.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 3cd4f3cc6..f37d1915e 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -764,6 +764,8 @@ void wpas_pr_deinit(struct wpa_supplicant *wpa_s) void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s) { + struct pr_data *pr = wpa_s->global->pr; + /* Cancel ranging session timeout and stop peer measurement */ eloop_cancel_timeout(wpas_pr_ranging_session_timeout, wpa_s, NULL); wpa_drv_stop_peer_measurement(wpa_s); @@ -778,7 +780,13 @@ void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s) wpa_drv_pd_stop(wpa_s); os_memset(wpa_s->pd_addr, 0, ETH_ALEN); - wpa_printf(MSG_DEBUG, "PR: PD wdev stopped"); + + /* Restore dev_addr to station MAC now that PD wdev is gone */ + if (pr) + pr_set_dev_addr(pr, wpa_s->own_addr); + + wpa_printf(MSG_DEBUG, "PR: PD wdev stopped, dev_addr restored to " MACSTR, + MAC2STR(wpa_s->own_addr)); } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 03:00:05 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:30:05 +0530 Subject: [PATCH v3 41/46] PR: Flush PMKSA cache after TK is configured for ranging In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-42-kavita.kavita@oss.qualcomm.com> In the ranging use case, PASN is performed solely to derive and install the encryption keys (TK) into the driver. Once set_keys() has been called, the PMK and any cached PMKSA entry serve no further purpose. Flush the PMKSA cache after get_ranging_params() is invoked on both the initiator and responder to ensure the PMKID is not included in M1 on any subsequent PASN attempt. Signed-off-by: Kavita Kavita --- src/common/proximity_ranging.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index 5b5f3bf8a..d728e0030 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -2048,6 +2048,7 @@ int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len, dev->final_op_channel, self_format_bw, peer_format_bw); + pr_flush(pr); } out: @@ -2516,6 +2517,7 @@ static int pr_pasn_handle_auth_3(struct pr_data *pr, struct pr_device *dev, dev->final_op_channel, self_format_bw, peer_format_bw); + pr_flush(pr); return 0; fail: -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 03:00:06 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:30:06 +0530 Subject: [PATCH v3 42/46] PR: Track peer discovery type and handle OOB peers in PASN initiation In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-43-kavita.kavita@oss.qualcomm.com> Add discovery_type to struct pr_device to distinguish peers discovered via NAN USD from peers added out-of-band. Mark peers as NAN-discovered when found via USD and as OOB when added without prior NAN discovery. When PR_PASN_START is issued for a peer that is not in the discovery list, create a minimal OOB peer entry so that PASN can proceed without requiring prior NAN USD discovery. This is done at PR_PASN_START time for both initiator and responder roles. For OOB peers, skip capability comparison and PASN parameter validation since their capabilities are not known. Per spec, all PR devices must support unauthenticated DH group 19, so cipher and group selection defaults to the mandatory baseline. Signed-off-by: Kavita Kavita --- src/common/proximity_ranging.c | 52 +++++++++++++++++++++++++++++++--- src/common/proximity_ranging.h | 6 ++++ wpa_supplicant/pr_supplicant.c | 6 +++- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index d728e0030..2624b8d97 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -1140,6 +1140,7 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len, os_get_reltime(&dev->last_seen); dev->listen_freq = freq; + dev->discovery_type = PR_DISCOVERY_TYPE_USD; pr_process_ranging_capabilities(msg.pr_capability, msg.pr_capability_len, &dev->pr_caps); @@ -1162,11 +1163,45 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len, } +/** + * pr_ensure_oob_peer - Add a minimal OOB peer entry if not already present + */ +int pr_ensure_oob_peer(struct pr_data *pr, const u8 *addr, int freq) +{ + struct pr_device *dev; + + if (!pr || !addr) + return -1; + + dev = pr_get_device(pr, addr); + if (dev) + return 0; + + dev = pr_create_device(pr, addr); + if (!dev) { + wpa_printf(MSG_DEBUG, "PR: Failed to create OOB peer " MACSTR, + MAC2STR(addr)); + return -1; + } + + dev->discovery_type = PR_DISCOVERY_TYPE_OOB; + if (freq) + dev->listen_freq = freq; + + wpa_printf(MSG_DEBUG, "PR: OOB peer " MACSTR " created at PASN_START", + MAC2STR(addr)); + return 0; +} + + #ifdef CONFIG_PASN static bool pr_eq_ranging_capa_params(const struct pr_device *dev, const struct pr_capabilities *caps) { + if (dev->discovery_type == PR_DISCOVERY_TYPE_OOB) + return true; + return dev->pr_caps.edca_support == caps->edca_support && dev->pr_caps.ntb_support == caps->ntb_support && dev->pr_caps.pasn_type == caps->pasn_type && @@ -1179,6 +1214,9 @@ static bool pr_eq_ranging_capa_params(const struct pr_device *dev, static bool pr_eq_edca_params(const struct pr_device *dev, const struct edca_capabilities *edca_caps) { + if (dev->discovery_type == PR_DISCOVERY_TYPE_OOB) + return true; + return dev->edca_caps.ista_support == edca_caps->ista_support && dev->edca_caps.rsta_support == edca_caps->rsta_support && dev->edca_caps.edca_hw_caps == edca_caps->edca_hw_caps && @@ -1189,6 +1227,9 @@ static bool pr_eq_edca_params(const struct pr_device *dev, static bool pr_eq_ntb_params(const struct pr_device *dev, const struct ntb_capabilities *ntb_caps) { + if (dev->discovery_type == PR_DISCOVERY_TYPE_OOB) + return true; + return dev->ntb_caps.ista_support == ntb_caps->ista_support && dev->ntb_caps.rsta_support == ntb_caps->rsta_support && dev->ntb_caps.ntb_hw_caps == ntb_caps->ntb_hw_caps && @@ -1717,9 +1758,11 @@ static int pr_pasn_initialize(struct pr_data *pr, struct pr_device *dev, /* As specified in Proximity Ranging Implementation Considerations for * P2P Operation D1.8, unauthenticated mode PASN with DH group 19 - * should be supported by all P2P proximity ranging devices. */ - if (!(pr->cfg->pasn_type & BIT(0)) || - !(dev->pr_caps.pasn_type & BIT(0))) { + * should be supported by all P2P proximity ranging devices. Skip + * this check for OOB peers whose capabilities are not known. */ + if (dev->discovery_type != PR_DISCOVERY_TYPE_OOB && + (!(pr->cfg->pasn_type & BIT(0)) || + !(dev->pr_caps.pasn_type & BIT(0)))) { wpa_printf(MSG_DEBUG, "PR PASN: Unauthenticated DH group 19 NOT supported, PASN type of self 0x%x, peer 0x%x", pr->cfg->pasn_type, dev->pr_caps.pasn_type); @@ -1927,7 +1970,8 @@ int pr_initiate_pasn_auth(struct pr_data *pr, const u8 *addr, int freq, return -1; } - if (pr_validate_pasn_request(pr, dev, auth_mode, ranging_role, + if (dev->discovery_type != PR_DISCOVERY_TYPE_OOB && + pr_validate_pasn_request(pr, dev, auth_mode, ranging_role, ranging_type) < 0) { wpa_printf(MSG_INFO, "PR PASN: Invalid parameters to initiate authentication"); diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 52a9620ec..908827c83 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -258,6 +258,10 @@ enum pr_attr_id { #define PR_PASN_AUTH_MODE_SAE 1 #define PR_PASN_AUTH_MODE_PMK 2 +/* Peer discovery type */ +#define PR_DISCOVERY_TYPE_USD 0 +#define PR_DISCOVERY_TYPE_OOB 1 + /** * struct pr_pasn_ranging_params - peer parameters to be used in PASN * then to trigger ranging in case where PR PASN is successful. @@ -410,6 +414,7 @@ struct pr_device { u8 protocol_type; u8 final_op_class; u8 final_op_channel; + u8 discovery_type; }; @@ -637,6 +642,7 @@ void pr_add_dev_ik(struct pr_data *pr, const u8 *dik, const char *password, struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr); void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len, const u8 *peer_addr, unsigned int freq); +int pr_ensure_oob_peer(struct pr_data *pr, const u8 *addr, int freq); int pr_initiate_pasn_auth(struct pr_data *pr, const u8 *addr, int freq, u8 auth_mode, u8 ranging_role, u8 ranging_type, int forced_pr_freq); diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index f37d1915e..7f0061c0c 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -1261,10 +1261,14 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, enum pr_pasn_role pasn_role) { struct wpa_pr_pasn_auth_work *awork; + struct pr_data *pr = wpa_s->global->pr; + + /* Add OOB peer if not already in the discovery list */ + if (pr && pr_ensure_oob_peer(pr, peer_addr, freq) < 0) + return -1; if (pasn_role == PR_ROLE_PASN_RESPONDER) { struct wpa_pr_pasn_roc_work *rwork; - struct pr_data *pr = wpa_s->global->pr; unsigned int roc_time_ms = PR_PASN_RESPONDER_ROC_DURATION; bool has_src_addr = src_addr && !is_zero_ether_addr(src_addr); -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 03:00:07 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:30:07 +0530 Subject: [PATCH v3 43/46] PR: Allow PMK and password configuration in PR_PASN_START In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-44-kavita.kavita@oss.qualcomm.com> Add optional pmk and password parameters to the PR_PASN_START ctrl_iface command to allow per-peer credentials to be configured directly at session initiation time. When provided, the credentials are stored in the peer's pr_device entry and used for the PASN exchange. This works for both USD-discovered peers (overriding DIRA-resolved credentials) and OOB peers (where DIRA resolution never occurs). The credentials persist for future sessions with the same peer. The peer device entry is ensured to exist before applying credentials so that PR_PASN_START with pmk or password works even when no prior NAN USD discovery has taken place. Passing pmk or password with auth=0 (unauthenticated PASN) is rejected since credentials are not applicable in that mode. Signed-off-by: Peddolla Harshavardhan Reddy Signed-off-by: Kavita Kavita --- src/common/proximity_ranging.c | 32 +++++++++++++++++++++++++++++++ src/common/proximity_ranging.h | 9 +++++++++ wpa_supplicant/ctrl_iface.c | 35 ++++++++++++++++++++++++++++++++++ wpa_supplicant/pr_supplicant.c | 13 +++++++++++++ 4 files changed, 89 insertions(+) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index 2624b8d97..72f70597d 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -238,6 +238,38 @@ void pr_add_dev_ik(struct pr_data *pr, const u8 *dik, const char *password, } +void pr_set_peer_credentials(struct pr_data *pr, const u8 *addr, + const u8 *pmk, size_t pmk_len, + const char *password) +{ + struct pr_device *dev; + + if (!pr || !addr) + return; + + dev = pr_get_device(pr, addr); + if (!dev) { + wpa_printf(MSG_DEBUG, "PR: set_peer_credentials: " MACSTR + " not found", MAC2STR(addr)); + return; + } + + if (pmk && pmk_len) { + os_memcpy(dev->pmk, pmk, pmk_len); + dev->pmk_len = pmk_len; + dev->pmk_valid = true; + wpa_printf(MSG_DEBUG, "PR: PMK set for " MACSTR, MAC2STR(addr)); + } + + if (password) { + os_strlcpy(dev->password, password, sizeof(dev->password)); + dev->password_valid = true; + wpa_printf(MSG_DEBUG, "PR: password set for " MACSTR, + MAC2STR(addr)); + } +} + + static struct wpabuf * pr_encaps_elem(const struct wpabuf *subelems, u32 ie_type) { diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 908827c83..4b98ca612 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -361,6 +361,12 @@ struct pr_pasn_ranging_params { u32 center_freq1; u32 center_freq2; u64 cookie; + + /* Per-session credentials ? if set, used instead of stored ones */ + char password[100]; + bool password_valid; + u8 pmk[PMK_LEN_MAX]; + size_t pmk_len; }; struct pr_dev_ik { @@ -639,6 +645,9 @@ void pr_set_dev_addr(struct pr_data *pr, const u8 *addr); void pr_clear_dev_iks(struct pr_data *pr); void pr_add_dev_ik(struct pr_data *pr, const u8 *dik, const char *password, const u8 *pmk, size_t pmk_len, bool own); +void pr_set_peer_credentials(struct pr_data *pr, const u8 *addr, + const u8 *pmk, size_t pmk_len, + const char *password); struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr); void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len, const u8 *peer_addr, unsigned int freq); diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 8238229f0..ffcd6173a 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -11625,6 +11625,33 @@ static int wpas_ctrl_iface_pr_pasn_start(struct wpa_supplicant *wpa_s, params.egress_threshold = atoll(token + 17); } else if (os_strcmp(token, "pd_suppress_results=1") == 0) { params.pr_suppress_results = true; + } else if (os_strncmp(token, "password=", 9) == 0) { + size_t pwd_len = os_strlen(token + 9); + + if (pwd_len == 0 || pwd_len >= sizeof(params.password)) { + wpa_printf(MSG_DEBUG, + "CTRL: PR_PASN_START invalid password length %zu", + pwd_len); + return -1; + } + os_strlcpy(params.password, token + 9, + sizeof(params.password)); + params.password_valid = true; + } else if (os_strncmp(token, "pmk=", 4) == 0) { + size_t pmk_len = os_strlen(token + 4) / 2; + + if (pmk_len != 32 && pmk_len != 48 && pmk_len != 64) { + wpa_printf(MSG_DEBUG, + "CTRL: PR_PASN_START invalid PMK length %zu", + pmk_len); + return -1; + } + if (hexstr2bin(token + 4, params.pmk, pmk_len)) { + wpa_printf(MSG_DEBUG, + "CTRL: PR_PASN_START invalid PMK"); + return -1; + } + params.pmk_len = pmk_len; } else { wpa_printf(MSG_DEBUG, "CTRL: PR_PASN_START invalid parameter: '%s'", @@ -11653,6 +11680,14 @@ static int wpas_ctrl_iface_pr_pasn_start(struct wpa_supplicant *wpa_s, return -1; } + /* pmk and password are only valid for authenticated modes */ + if (params.auth_mode == PR_PASN_AUTH_MODE_PASN && + (params.pmk_len > 0 || params.password_valid)) { + wpa_printf(MSG_DEBUG, + "CTRL: PR_PASN_START pmk/password not applicable for unauthenticated mode"); + return -1; + } + wpa_printf(MSG_DEBUG, "CTRL: PR_PASN_START params: ranging type=0x%x, role=0x%x," " auth_mode=%d, freq=%d, addr=" MACSTR " src_addr=" MACSTR " pasn_role=%d", diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 7f0061c0c..a6137f1a4 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -1535,6 +1535,19 @@ void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, os_memcpy(pr->pr_pasn_params, pr_pasn_params, sizeof(*pr->pr_pasn_params)); + /* Ensure peer device entry exists before setting credentials */ + if (pr_pasn_params->pmk_len > 0 || pr_pasn_params->password_valid) { + pr_ensure_oob_peer(pr, pr_pasn_params->peer_addr, + pr_pasn_params->freq); + pr_set_peer_credentials(pr, + pr_pasn_params->peer_addr, + pr_pasn_params->pmk_len > 0 ? + pr_pasn_params->pmk : NULL, + pr_pasn_params->pmk_len, + pr_pasn_params->password_valid ? + pr_pasn_params->password : NULL); + } + /* Log EDCA parameters if applicable */ if (pr_pasn_params->ranging_type & PR_EDCA_BASED_RANGING) { wpa_printf(MSG_DEBUG, -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 03:00:08 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:30:08 +0530 Subject: [PATCH v3 44/46] PR: store resolved peer DevIK in pr_device after DIRA match In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-45-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Currently when pr_validate_dira() successfully matches a peer's DIRA tag, the matched DevIK is not stored on the pr_device. This makes it impossible to report the resolved peer identity back to the framework after USD discovery. To address this issue, add dik[DEVICE_IDENTITY_KEY_LEN] and dik_valid fields to struct pr_device, following the same pattern as the existing pmk and password fields. In pr_validate_dira(), reset dik and dik_valid at entry so stale state from a previous match is never visible, then copy dev_ik->dik and set dik_valid=true only when the DIRA tag comparison succeeds. In pr_clear_dev_iks(), clear dik and dik_valid alongside password to prevent dangling state after the dev_iks list is freed. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.c | 8 ++++++++ src/common/proximity_ranging.h | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index 72f70597d..365c828a0 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -182,6 +182,8 @@ void pr_clear_dev_iks(struct pr_data *pr) dl_list_for_each(dev, &pr->devices, struct pr_device, list) { dev->password_valid = false; os_memset(dev->password, 0, sizeof(dev->password)); + dev->dik_valid = false; + os_memset(dev->dik, 0, DEVICE_IDENTITY_KEY_LEN); } pr_deinit_dev_iks(pr); @@ -452,6 +454,10 @@ static int pr_validate_dira(struct pr_data *pr, struct pr_device *dev, const char *label = "DIR"; const u8 *dira_nonce, *dira_tag; + /* Reset DevIK state ? set only if DIRA verification succeeds */ + os_memset(dev->dik, 0, DEVICE_IDENTITY_KEY_LEN); + dev->dik_valid = false; + if (dira_len < 1 + DEVICE_IDENTITY_NONCE_LEN + DEVICE_IDENTITY_TAG_LEN) { wpa_printf(MSG_DEBUG, "PR: Truncated DIRA (length %u)", @@ -503,6 +509,8 @@ static int pr_validate_dira(struct pr_data *pr, struct pr_device *dev, dev->pmk_len = dev_ik->pmk_len; dev->pmk_valid = true; } + os_memcpy(dev->dik, dev_ik->dik, DEVICE_IDENTITY_KEY_LEN); + dev->dik_valid = true; return 0; } } diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 4b98ca612..771b109af 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -409,6 +409,13 @@ struct pr_device { size_t pmk_len; bool pmk_valid; + /* DevIK of the peer resolved via DIRA verification. + * Set to the matched dev_ik->dik when pr_validate_dira() succeeds. + * Cleared by pr_clear_dev_iks(). + */ + u8 dik[DEVICE_IDENTITY_KEY_LEN]; + bool dik_valid; + #ifdef CONFIG_PASN /* PASN data structure */ struct pasn_data *pasn; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 03:00:09 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:30:09 +0530 Subject: [PATCH v3 45/46] PR: use USD source address for DIRA tag computation In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-46-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Currently when a USD publish or subscribe session is started with a forced (random) PR MAC address, pr_prepare_usd_elems() calls pr_derive_dira() which uses pr->cfg->dev_addr (the regular interface MAC) to compute the DIRA tag. The USD SDF is transmitted with the forced address as the source, so the receiving peer uses that address in pr_validate_dira(). The two addresses differ, causing the HMAC to never match and DIRA verification to always fail. To address this issue, add a src_addr parameter to pr_derive_dira() and pr_prepare_usd_elems() so the caller can supply the address that will appear as the SDF source on air. Thread this through wpas_pr_usd_elems() and use params->forced_addr (falling back to wpa_s->own_addr when no forced address is set) at the two call sites in wpas_nan_publish() and wpas_nan_subscribe(). The PASN path in pr_prepare_pasn_pr_elem() is unchanged and continues to pass pr->cfg->dev_addr, which is already set to the PD wdev address before any PASN DIRA computation occurs. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.c | 11 ++++++----- src/common/proximity_ranging.h | 2 +- wpa_supplicant/nan_supplicant.c | 8 ++++++-- wpa_supplicant/pr_supplicant.c | 5 +++-- wpa_supplicant/pr_supplicant.h | 6 ++++-- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index 365c828a0..57e3f7b44 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -388,7 +388,8 @@ static void pr_get_ntb_capabilities(struct pr_data *pr, } -static int pr_derive_dira(struct pr_data *pr, struct pr_dira *dira) +static int pr_derive_dira(struct pr_data *pr, const u8 *src_addr, + struct pr_dira *dira) { u8 nonce[DEVICE_IDENTITY_NONCE_LEN]; u8 tag[DEVICE_MAX_HASH_LEN]; @@ -417,7 +418,7 @@ static int pr_derive_dira(struct pr_data *pr, struct pr_dira *dira) * Nonce)) */ os_memcpy(data, "DIR", DIR_STR_LEN); - os_memcpy(&data[DIR_STR_LEN], pr->cfg->dev_addr, ETH_ALEN); + os_memcpy(&data[DIR_STR_LEN], src_addr, ETH_ALEN); os_memcpy(&data[DIR_STR_LEN + ETH_ALEN], nonce, DEVICE_IDENTITY_NONCE_LEN); @@ -745,7 +746,7 @@ static void pr_buf_add_dira(struct wpabuf *buf, const struct pr_dira *dira) } -struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr) +struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const u8 *src_addr) { u32 ie_type; struct wpabuf *buf, *buf2; @@ -773,7 +774,7 @@ struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr) pr_buf_add_ntb_capa_info(buf, &ntb_caps); } - if (!pr_derive_dira(pr, &dira)) + if (!pr_derive_dira(pr, src_addr, &dira)) pr_buf_add_dira(buf, &dira); ie_type = (OUI_WFA << 8) | PR_OUI_TYPE; @@ -1684,7 +1685,7 @@ static int pr_prepare_pasn_pr_elem(struct pr_data *pr, struct wpabuf *extra_ies, pr_buf_add_operation_mode(buf, &op_mode); /* PR Device Identity Resolution attribute */ - if (!pr_derive_dira(pr, &dira)) + if (!pr_derive_dira(pr, pr->cfg->dev_addr, &dira)) pr_buf_add_dira(buf, &dira); ie_type = (OUI_WFA << 8) | PR_OUI_TYPE; diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 771b109af..0685e1d6e 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -655,7 +655,7 @@ void pr_add_dev_ik(struct pr_data *pr, const u8 *dik, const char *password, void pr_set_peer_credentials(struct pr_data *pr, const u8 *addr, const u8 *pmk, size_t pmk_len, const char *password); -struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr); +struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const u8 *src_addr); void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len, const u8 *peer_addr, unsigned int freq); int pr_ensure_oob_peer(struct pr_data *pr, const u8 *addr, int freq); diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c index 705643ca7..13969ef1c 100644 --- a/wpa_supplicant/nan_supplicant.c +++ b/wpa_supplicant/nan_supplicant.c @@ -4521,7 +4521,9 @@ int wpas_nan_publish(struct wpa_supplicant *wpa_s, const char *service_name, elems = wpas_p2p_usd_elems(wpa_s, service_name); addr = wpa_s->global->p2p_dev_addr; } else if (params->proximity_ranging) { - elems = wpas_pr_usd_elems(wpa_s); + const u8 *src = params->forced_addr ? + params->forced_addr : wpa_s->own_addr; + elems = wpas_pr_usd_elems(wpa_s, src); } if (params->forced_addr) { @@ -4687,7 +4689,9 @@ int wpas_nan_subscribe(struct wpa_supplicant *wpa_s, elems = wpas_p2p_usd_elems(wpa_s, service_name); addr = wpa_s->global->p2p_dev_addr; } else if (params->proximity_ranging) { - elems = wpas_pr_usd_elems(wpa_s); + const u8 *src = params->forced_addr ? + params->forced_addr : wpa_s->own_addr; + elems = wpas_pr_usd_elems(wpa_s, src); } if (params->forced_addr) { diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index a6137f1a4..aa4bf027f 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -602,12 +602,13 @@ static void wpas_pr_pasn_clear_keys(void *ctx, const u8 *own_addr, } -struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s) +struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s, + const u8 *src_addr) { if (!wpa_s->global->pr) return NULL; - return pr_prepare_usd_elems(wpa_s->global->pr); + return pr_prepare_usd_elems(wpa_s->global->pr, src_addr); } diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index 941a4a2f8..4a158d9af 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -23,7 +23,8 @@ void wpas_pr_clear_dev_iks(struct wpa_supplicant *wpa_s); void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, const char *password, const u8 *pmk, size_t pmk_len, bool own); -struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s); +struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s, + const u8 *src_addr); void wpas_pr_process_usd_elems(struct wpa_supplicant *wpa_s, const u8 *buf, u16 buf_len, const u8 *peer_addr, unsigned int freq); @@ -82,7 +83,8 @@ static inline void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, { } -static inline struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s) +static inline struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s, + const u8 *src_addr) { return NULL; } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 03:00:10 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:30:10 +0530 Subject: [PATCH v3 46/46] PR: Skip key installation for EDCA-based ranging In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-47-kavita.kavita@oss.qualcomm.com> IEEE 802.11az-2022 section 11.21.6.3 defines a secure FTM session as one where a PTKSA is established and Protected FTM frames are exchanged. This applies to TB ranging, non-TB ranging, and EDCA-based ranging only when the Format And Bandwidth field (Table 9-280) indicates DMG or EDMG format. For EDCA-based ranging, no encryption mechanism is defined for FTM timestamp frames and the spec explicitly states that an ISTA shall not set up a secure FTM session in this case. Skip key installation for EDCA-based ranging since there is no point in installing keys to the firmware when they will never be used. Signed-off-by: Kavita Kavita --- src/common/proximity_ranging.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index 57e3f7b44..3b154d1b6 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -2484,7 +2484,8 @@ static int pr_pasn_handle_auth_1(struct pr_data *pr, struct pr_device *dev, goto fail; } - if (pr->cfg->set_keys) + if (!(dev->protocol_type & PR_EDCA_BASED_RANGING) && + pr->cfg->set_keys) pr->cfg->set_keys(pr->cfg->cb_ctx, pr->cfg->dev_addr, dev->pr_device_addr, dev->pasn->cipher, dev->pasn->akmp, &dev->pasn->ptk); @@ -2533,7 +2534,8 @@ static int pr_pasn_handle_auth_2(struct pr_data *pr, struct pr_device *dev, goto fail; } - if (pr->cfg->set_keys) + if (!(dev->protocol_type & PR_EDCA_BASED_RANGING) && + pr->cfg->set_keys) pr->cfg->set_keys(pr->cfg->cb_ctx, pr->cfg->dev_addr, dev->pr_device_addr, dev->pasn->cipher, dev->pasn->akmp, &dev->pasn->ptk); -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:43 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:43 +0530 Subject: [PATCH v3 19/46] nl80211: Route MLME TX via PD wdev based on source address In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-20-kavita.kavita@oss.qualcomm.com> From: Veerendranath Jakkam When sending MLME frames, check if the source address matches the active PD wdev MAC address and if so, route the transmission via the PD BSS with its associated link reference. This ensures management frames are sent on the correct wdev when a PD interface is active. Signed-off-by: Veerendranath Jakkam --- src/drivers/driver_nl80211.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index c392a4b64..75178fd03 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -4994,6 +4994,17 @@ static int wpa_driver_nl80211_send_mlme(struct i802_bss *bss, const u8 *data, noack, freq, no_cck, offchanok, wait_time, no_encrypt, fc, fc2str(fc), drv->nlmode); +#ifdef CONFIG_PR + /* Route MLME TX via PD wdev if source address matches */ + if (drv->pd_bss && ether_addr_equal(mgmt->sa, drv->pd_bss->addr)) { + bss = drv->pd_bss; + wpa_printf(MSG_DEBUG, + "nl80211: send_mlme - route via PD wdev sa=" MACSTR, + MAC2STR(mgmt->sa)); + link = bss->flink; + } +#endif /* CONFIG_PR */ + if ((is_sta_interface(drv->nlmode) || drv->nlmode == NL80211_IFTYPE_P2P_DEVICE) && WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT && -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:44 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:44 +0530 Subject: [PATCH v3 20/46] nl80211: Factor out global event BSS lookup and add PD wdev event routing In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-21-kavita.kavita@oss.qualcomm.com> From: Veerendranath Jakkam Introduce nl80211_get_event_bss() to centralize BSS selection for process_global_event(). The helper performs an exact ifindex match first, then checks if the wdev_id matches the active PD wdev so that events arriving on the PD wdev are delivered to pd_bss which is not part of the regular BSS list, and finally falls back to the existing bridge/wiphy/wdev matching rules. The wiphy_idx fallback applies only when wdev_id is not set so that wdev-specific events match exclusively by exact wdev_id. wdev_id is set during creation for wdev-only interfaces (P2P Device, NAN, PD). For netdev interfaces it is not set via if_add_wdevid, so fetch it from the kernel via GET_INTERFACE and store it in the BSS. This ensures events that carry NL80211_ATTR_WDEV without NL80211_ATTR_IFINDEX are routed by exact wdev_id match. Signed-off-by: Veerendranath Jakkam --- src/drivers/driver_nl80211.c | 31 ++++++++++ src/drivers/driver_nl80211_event.c | 94 ++++++++++++++++-------------- 2 files changed, 81 insertions(+), 44 deletions(-) diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 75178fd03..4b3b8d1c4 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -891,6 +891,8 @@ struct wiphy_idx_data { enum nl80211_iftype nlmode; u8 *macaddr; u8 use_4addr; + u64 wdev_id; + int wdev_id_set; }; @@ -916,6 +918,11 @@ static int netdev_info_handler(struct nl_msg *msg, void *arg) if (tb[NL80211_ATTR_4ADDR]) info->use_4addr = nla_get_u8(tb[NL80211_ATTR_4ADDR]); + if (tb[NL80211_ATTR_WDEV]) { + info->wdev_id = nla_get_u64(tb[NL80211_ATTR_WDEV]); + info->wdev_id_set = 1; + } + return NL_SKIP; } @@ -3591,6 +3598,30 @@ wpa_driver_nl80211_finish_drv_init(struct i802_bss *bss, const u8 *set_addr, bss->wdev_id = drv->global->if_add_wdevid; bss->wdev_id_set = drv->global->if_add_wdevid_set; + /* + * wdev_id is set for wdev-only interfaces during creation. For netdev + * interfaces it is not set, so fetch it via GET_INTERFACE to allow + * event routing by exact wdev_id match. + */ + if (!bss->wdev_id_set) { + struct nl_msg *msg; + struct wiphy_idx_data info; + + os_memset(&info, 0, sizeof(info)); + msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_GET_INTERFACE); + if (msg && + send_and_recv_resp(drv, msg, netdev_info_handler, + &info) == 0 && + info.wdev_id_set) { + bss->wdev_id = info.wdev_id; + bss->wdev_id_set = 1; + wpa_printf(MSG_DEBUG, + "nl80211: %s wdev_id=0x%llx", + bss->ifname, + (unsigned long long) bss->wdev_id); + } + } + bss->if_dynamic = drv->ifindex == drv->global->if_add_ifindex; bss->if_dynamic = bss->if_dynamic || drv->global->if_add_wdevid_set; if (first) diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index b60de54c1..2f5d352e2 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -4831,13 +4831,51 @@ static bool nl80211_drv_in_list(struct nl80211_global *global, } +static struct i802_bss * +nl80211_get_event_bss(struct wpa_driver_nl80211_data *drv, int ifidx, + int wiphy_idx_rx, int wiphy_idx_set, + u64 wdev_id, int wdev_id_set) +{ + struct i802_bss *bss; + int wiphy_idx = -1; + + if (ifidx != -1) { + for (bss = drv->first_bss; bss; bss = bss->next) { + if (ifidx == bss->ifindex) + return bss; + } + } + +#ifdef CONFIG_PR + if (wdev_id_set && drv->pd_bss && drv->pd_bss->wdev_id_set && + wdev_id == drv->pd_bss->wdev_id) + return drv->pd_bss; +#endif /* CONFIG_PR */ + + for (bss = drv->first_bss; bss; bss = bss->next) { + if (wiphy_idx_set) + wiphy_idx = nl80211_get_wiphy_index(bss); + if ((ifidx == -1 && !wiphy_idx_set && !wdev_id_set) || + (bss->br_ifindex > 0 && + nl80211_has_ifidx(drv, bss->br_ifindex, ifidx)) || + (!wdev_id_set && wiphy_idx_set && + wiphy_idx == wiphy_idx_rx) || + (wdev_id_set && bss->wdev_id_set && + wdev_id == bss->wdev_id)) + return bss; + } + + return NULL; +} + + int process_global_event(struct nl_msg *msg, void *arg) { struct nl80211_global *global = arg; struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); struct nlattr *tb[NL80211_ATTR_MAX + 1]; struct wpa_driver_nl80211_data *drv, *tmp; - int ifidx = -1, wiphy_idx = -1, wiphy_idx_rx = -1; + int ifidx = -1, wiphy_idx_rx = -1; struct i802_bss *bss; u64 wdev_id = 0; int wdev_id_set = 0; @@ -4890,50 +4928,18 @@ int process_global_event(struct nl_msg *msg, void *arg) struct wpa_driver_nl80211_data, list) { unsigned int unique_drv_id = drv->unique_drv_id; - /* First pass: Check for exact ifindex match for events directed - * to a specific interface to avoid incorrect selection based on - * matching rules for bridged interfaces. */ - if (ifidx != -1) { - for (bss = drv->first_bss; bss; bss = bss->next) { - if (ifidx == bss->ifindex) { - do_process_drv_event(bss, gnlh->cmd, - tb); - return NL_SKIP; - } - } - } + bss = nl80211_get_event_bss(drv, ifidx, wiphy_idx_rx, + wiphy_idx_set, wdev_id, + wdev_id_set); + if (!bss) + continue; - /* Second pass: Check all other conditions including bridge */ - for (bss = drv->first_bss; bss; bss = bss->next) { - if (wiphy_idx_set) - wiphy_idx = nl80211_get_wiphy_index(bss); - if ((ifidx == -1 && !wiphy_idx_set && !wdev_id_set) || - (bss->br_ifindex > 0 && - nl80211_has_ifidx(drv, bss->br_ifindex, ifidx)) || - (wiphy_idx_set && wiphy_idx == wiphy_idx_rx) || - (wdev_id_set && bss->wdev_id_set && - wdev_id == bss->wdev_id)) { - processed = true; - do_process_drv_event(bss, gnlh->cmd, tb); - /* There are two types of events that may need - * to be delivered to multiple interfaces: - * 1. Events for a wiphy, as it can have - * multiple interfaces. - * 2. "Global" events, like - * NL80211_CMD_REG_CHANGE. - * - * Terminate early only if the event is directed - * to a specific interface or wdev. */ - if (ifidx != -1 || wdev_id_set) - return NL_SKIP; - /* The driver instance could have been removed, - * e.g., due to NL80211_CMD_RADAR_DETECT event, - * so need to stop the loop if that has - * happened. */ - if (!nl80211_drv_in_list(global, unique_drv_id)) - break; - } - } + processed = true; + do_process_drv_event(bss, gnlh->cmd, tb); + if (ifidx != -1 || wdev_id_set) + return NL_SKIP; + if (!nl80211_drv_in_list(global, unique_drv_id)) + break; } if (processed) -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:56 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:56 +0530 Subject: [PATCH v3 32/46] PR: Move ranging_wrapper free inside CONFIG_PASN ifdef In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-33-kavita.kavita@oss.qualcomm.com> The ranging_wrapper member of struct pr_device is only defined when CONFIG_PASN is enabled. Move wpabuf_free(dev->ranging_wrapper) inside the CONFIG_PASN ifdef block to fix compilation errors when CONFIG_PASN is not defined. Fixes: ef1b1587d315 ("PR: Handle PR element in PASN Auth M1 and prepare M2") Signed-off-by: Kavita Kavita --- src/common/proximity_ranging.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index 0ca9c95fe..06ee58b3d 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -24,8 +24,8 @@ static bool valid_country_ch(char c) static void pr_device_free(struct pr_data *pr, struct pr_device *dev) { - wpabuf_free(dev->ranging_wrapper); #ifdef CONFIG_PASN + wpabuf_free(dev->ranging_wrapper); if (dev->pasn) { wpa_pasn_reset(dev->pasn); pasn_data_deinit(dev->pasn); -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 03:00:00 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:30:00 +0530 Subject: [PATCH v3 36/46] PR: Handle peer measurement complete event In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-37-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add support for NL80211_CMD_PEER_MEASUREMENT_COMPLETE which signals the entire ranging session has ended. Add EVENT_PEER_MEASUREMENT_COMPLETE event and peer_measurement_complete struct. wpas_pr_measurement_complete() validates the cookie, emits PR-RANGING-COMPLETE ctrl event, frees pr_pasn_params, resets ranging_final_received, and stops the PD wdev. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/wpa_ctrl.h | 3 +++ src/drivers/driver.h | 15 +++++++++++++ src/drivers/driver_common.c | 1 + src/drivers/driver_nl80211_event.c | 26 +++++++++++++++++++++ wpa_supplicant/events.c | 7 ++++++ wpa_supplicant/notify.c | 7 ++++++ wpa_supplicant/notify.h | 2 ++ wpa_supplicant/pr_supplicant.c | 36 ++++++++++++++++++++++++++++++ wpa_supplicant/pr_supplicant.h | 8 +++++++ 9 files changed, 105 insertions(+) diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 70fe22576..664a6c825 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -510,6 +510,9 @@ extern "C" { /* Proximity Ranging measurement result */ #define PR_EVENT_PEER_MEASUREMENT "PR-PEER-MEASUREMENT " +/* Proximity Ranging measurement session complete */ +#define PR_EVENT_RANGING_COMPLETE "PR-RANGING-COMPLETE " + /* BSS command information masks */ #define WPA_BSS_MASK_ALL 0xFFFDFFFF diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 22a4ad02e..43394afed 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -6675,6 +6675,14 @@ enum wpa_event_type { * This event is used to indicate a ranging measurement result. */ EVENT_PEER_MEASUREMENT_RESULT, + + /** + * EVENT_PEER_MEASUREMENT_COMPLETE - Ranging session fully done + * + * This event is used to notify that the entire peer + * measurement session identified by the cookie has ended. + */ + EVENT_PEER_MEASUREMENT_COMPLETE, }; @@ -7782,6 +7790,13 @@ union wpa_event_data { } ftm; } peer_measurement_result; + + /** + * struct peer_measurement_complete - Ranging session complete + */ + struct peer_measurement_complete { + u64 cookie; + } peer_measurement_complete; }; /** diff --git a/src/drivers/driver_common.c b/src/drivers/driver_common.c index 170821423..812305cae 100644 --- a/src/drivers/driver_common.c +++ b/src/drivers/driver_common.c @@ -109,6 +109,7 @@ const char * event_to_string(enum wpa_event_type event) E2S(NAN_ULW_UPDATE); E2S(NAN_CHAN_EVACUATION); E2S(PEER_MEASUREMENT_RESULT); + E2S(PEER_MEASUREMENT_COMPLETE); } return "UNKNOWN"; diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index c16508404..f9db161bc 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -4407,6 +4407,29 @@ nl80211_parse_peer_ftm_result(struct peer_measurement_ftm_result *ftm, } +static void nl80211_peer_measurement_complete_event(struct i802_bss *bss, + struct nlattr **tb) +{ + union wpa_event_data data; + u64 cookie = 0; + + os_memset(&data, 0, sizeof(data)); + + if (tb[NL80211_ATTR_COOKIE]) { + cookie = nla_get_u64(tb[NL80211_ATTR_COOKIE]); + wpa_printf(MSG_DEBUG, + "nl80211: PR: Peer measurement complete cookie: %llu", + (unsigned long long) cookie); + } else { + wpa_printf(MSG_DEBUG, + "nl80211: PR: Peer measurement complete (no cookie)"); + } + + data.peer_measurement_complete.cookie = cookie; + wpa_supplicant_event(bss->ctx, EVENT_PEER_MEASUREMENT_COMPLETE, &data); +} + + static void nl80211_peer_measurement_result_event(struct i802_bss *bss, struct nlattr **tb) { @@ -5064,6 +5087,9 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd, case NL80211_CMD_PEER_MEASUREMENT_RESULT: nl80211_peer_measurement_result_event(bss, tb); break; + case NL80211_CMD_PEER_MEASUREMENT_COMPLETE: + nl80211_peer_measurement_complete_event(bss, tb); + break; #endif /* CONFIG_PR */ default: wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Ignored unknown event " diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 1fcc57ee1..f771f9658 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -7653,6 +7653,13 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, if (data) wpas_pr_measurement_result(wpa_s, &data->peer_measurement_result); +#endif /* CONFIG_PR */ + break; + case EVENT_PEER_MEASUREMENT_COMPLETE: +#ifdef CONFIG_PR + if (data) + wpas_pr_measurement_complete(wpa_s, + &data->peer_measurement_complete); #endif /* CONFIG_PR */ break; #ifdef CONFIG_NAN diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c index 58661c7ec..c26203d5b 100644 --- a/wpa_supplicant/notify.c +++ b/wpa_supplicant/notify.c @@ -1589,4 +1589,11 @@ void wpas_notify_pr_measurement_result(struct wpa_supplicant *wpa_s, result->ftm.burst_index, rtt, dist); } + +void wpas_notify_pr_ranging_complete(struct wpa_supplicant *wpa_s, u64 cookie) +{ + wpa_msg_global(wpa_s, MSG_INFO, PR_EVENT_RANGING_COMPLETE + "cookie=%llu", (unsigned long long) cookie); +} + #endif /* CONFIG_PR */ diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h index 1ed9978b9..fe8375f09 100644 --- a/wpa_supplicant/notify.h +++ b/wpa_supplicant/notify.h @@ -218,6 +218,8 @@ void wpas_notify_pr_ranging_params(struct wpa_supplicant *wpa_s, int bw, int format_bw); void wpas_notify_pr_measurement_result(struct wpa_supplicant *wpa_s, const struct peer_measurement_result *result); +void wpas_notify_pr_ranging_complete(struct wpa_supplicant *wpa_s, + u64 cookie); void wpas_notify_nan_bootstrap_request(struct wpa_supplicant *wpa_s, const u8 *peer_addr, u16 pbm, int handle, u8 requestor_instance_id); diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index e28b68c13..60a8406cc 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -757,6 +757,42 @@ void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, } +void wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, + struct peer_measurement_complete *complete) +{ + struct pr_data *pr = wpa_s->global->pr; + + if (!complete) { + wpa_printf(MSG_ERROR, + "PR: Invalid measurement complete event"); + return; + } + + wpa_printf(MSG_DEBUG, + "PR: Peer measurement complete cookie=%llu", + (unsigned long long) complete->cookie); + + /* Validate cookie if we have a pending ranging request */ + if (pr && pr->pr_pasn_params && complete->cookie != 0) { + if (pr->pr_pasn_params->cookie != complete->cookie) { + wpa_printf(MSG_WARNING, + "PR: Complete cookie mismatch - expected %llu, got %llu. Ignoring.", + (unsigned long long) pr->pr_pasn_params->cookie, + (unsigned long long) complete->cookie); + return; + } + } + + wpas_notify_pr_ranging_complete(wpa_s, complete->cookie); + if (pr) { + os_free(pr->pr_pasn_params); + pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; + } + wpas_pr_pd_stop(wpa_s); +} + + void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, struct peer_measurement_result *result) { diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index 679c755e2..9a4009819 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -41,6 +41,8 @@ void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, unsigned int freq); void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, struct pr_pasn_ranging_params *pr_pasn_params); +void wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, + struct peer_measurement_complete *complete); void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, struct peer_measurement_result *result); @@ -120,6 +122,12 @@ static inline void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, { } +static inline void +wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, + struct peer_measurement_complete *complete) +{ +} + static inline void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, struct peer_measurement_result *result) -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 03:00:02 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:30:02 +0530 Subject: [PATCH v3 38/46] PR: Add API and ctrl iface command to abort ranging session In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-39-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add wpas_pr_abort_ranging() and PR_RANGING_ABORT ctrl_iface command to allow early termination of an ongoing ranging session. The function cancels any active PASN work and ROC (for the responder path where no PD wdev exists yet), then calls wpas_pr_pd_stop() to clean up all ranging resources including the nl_pr socket, session timeout, and PD wdev. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/ctrl_iface.c | 2 ++ wpa_supplicant/pr_supplicant.c | 48 ++++++++++++++++++++++++++++++++++ wpa_supplicant/pr_supplicant.h | 5 ++++ 3 files changed, 55 insertions(+) diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index b2cb6746e..8238229f0 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -14505,6 +14505,8 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, reply_len = -1; } else if (os_strcmp(buf, "PR_CLEAR_DIK_CONTEXT") == 0) { wpas_pr_clear_dev_iks(wpa_s); + } else if (os_strcmp(buf, "PR_RANGING_ABORT") == 0) { + wpas_pr_abort_ranging(wpa_s); #endif /* CONFIG_PR */ #ifdef CONFIG_TESTING_OPTIONS } else if (os_strncmp(buf, "PASN_DRIVER ", 12) == 0) { diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index a753db9a1..8d1a1b632 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -22,6 +22,8 @@ #ifdef CONFIG_PASN static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx); +static void wpas_pr_pasn_cancel_auth_work(struct wpa_supplicant *wpa_s); +static void wpas_pr_pasn_roc_work_done(struct wpa_supplicant *wpa_s); /* Total listen window (ms) for the PASN responder ROC */ #define PR_PASN_RESPONDER_ROC_DURATION 5000 @@ -808,6 +810,52 @@ void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, } +void wpas_pr_abort_ranging(struct wpa_supplicant *wpa_s) +{ + struct pr_data *pr = wpa_s->global->pr; + + if (!pr) { + wpa_printf(MSG_DEBUG, "PR: abort_ranging: PR not initialized"); + return; + } + + /* Check if there's an active ranging session */ + if (is_zero_ether_addr(wpa_s->pd_addr) && !pr->pr_pasn_params) { + wpa_printf(MSG_DEBUG, + "PR: abort_ranging: no active ranging session"); + return; + } + + wpa_printf(MSG_DEBUG, "PR: Aborting ranging session"); + + /* + * Cancel PASN and ROC in case PD wdev is not yet created + * (PASN still in progress or responder ROC active). + */ + wpas_pr_pasn_cancel_auth_work(wpa_s); + wpa_s->pr_pasn_auth_work = NULL; + if (wpa_s->pr_responder_mode) { + eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s, NULL); + wpa_drv_cancel_remain_on_channel(wpa_s); + wpa_s->off_channel_freq = 0; + wpa_s->roc_waiting_drv_freq = 0; + wpas_pr_pasn_roc_work_done(wpa_s); + wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); + } + + /* Stop PD wdev and cleanup all ranging resources */ + wpas_pr_pd_stop(wpa_s); + + /* Free ranging params so a new session can be started */ + if (pr->pr_pasn_params) { + os_free(pr->pr_pasn_params); + pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; + } +} + + void wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, struct peer_measurement_complete *complete) { diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index 9a4009819..941a4a2f8 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -41,6 +41,7 @@ void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, unsigned int freq); void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, struct pr_pasn_ranging_params *pr_pasn_params); +void wpas_pr_abort_ranging(struct wpa_supplicant *wpa_s); void wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, struct peer_measurement_complete *complete); void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, @@ -122,6 +123,10 @@ static inline void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, { } +static inline void wpas_pr_abort_ranging(struct wpa_supplicant *wpa_s) +{ +} + static inline void wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, struct peer_measurement_complete *complete) -- 2.34.1 From kavita.kavita at oss.qualcomm.com Wed May 13 02:59:50 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Wed, 13 May 2026 15:29:50 +0530 Subject: [PATCH v3 26/46] PR: Add interface to trigger PASN authentication for ranging In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260513100010.1947710-27-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add wpas_pr_pasn_trigger() as the entry point for initiating PASN authentication for a single peer in the PR ranging workflow. The function stores the request parameters in pr_data to track the in-progress session and calls wpas_pr_initiate_pasn_auth() to start the exchange. Add pr_pasn_ranging_params struct to carry per-peer PASN and ranging parameters including peer address, frequency, auth mode, ranging role and type, source address, and PASN role. pr_pasn_params is freed in pr_deinit() under CONFIG_PASN and on failure in wpas_pr_pasn_trigger(). Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.c | 3 ++ src/common/proximity_ranging.h | 22 +++++++++++ wpa_supplicant/pr_supplicant.c | 72 ++++++++++++++++++++++++++++++++++ wpa_supplicant/pr_supplicant.h | 7 ++++ 4 files changed, 104 insertions(+) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index ea229eaef..4aa8ebee0 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -149,6 +149,9 @@ void pr_deinit(struct pr_data *pr) pr_deinit_dev_iks(pr); #ifdef CONFIG_PASN + os_free(pr->pr_pasn_params); + pr->pr_pasn_params = NULL; + pasn_initiator_pmksa_cache_deinit(pr->initiator_pmksa); pasn_responder_pmksa_cache_deinit(pr->responder_pmksa); #endif /* CONFIG_PASN */ diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 2643f8534..91fa3f652 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -258,6 +258,25 @@ enum pr_attr_id { #define PR_PASN_AUTH_MODE_SAE 1 #define PR_PASN_AUTH_MODE_PMK 2 +/** + * struct pr_pasn_ranging_params - peer parameters to be used in PASN + * then to trigger ranging in case where PR PASN is successful. + */ +struct pr_pasn_ranging_params { + enum { + PR_PASN_AND_RANGING, + PR_STOP_RANGING, + } action; + u8 peer_addr[ETH_ALEN]; + u8 ranging_type; + u8 ranging_role; + u8 pr_pasn_status; + u8 auth_mode; + int freq; + u8 src_addr[ETH_ALEN]; + enum pr_pasn_role pasn_role; +}; + struct pr_dev_ik { struct dl_list list; u8 dik[DEVICE_IDENTITY_KEY_LEN]; @@ -479,6 +498,9 @@ struct pr_data { /* PMKSA cache for PASN-PMK authentication */ struct rsn_pmksa_cache *initiator_pmksa; struct rsn_pmksa_cache *responder_pmksa; + + /* PR PASN request tracking - similar to pasn_params in wpa_supplicant */ + struct pr_pasn_ranging_params *pr_pasn_params; }; /* PR Device Identity Resolution Attribute parameters */ diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 6272c0b7c..b47434eae 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -965,6 +965,78 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, } +/** + * wpas_pr_pasn_trigger - Entry point to trigger PASN authentication for PR + */ +void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, + struct pr_pasn_ranging_params *pr_pasn_params) +{ + struct pr_data *pr = wpa_s->global->pr; + + if (!pr_pasn_params) { + wpa_printf(MSG_DEBUG, "PR PASN: trigger: NULL params"); + return; + } + + if (!pr) { + wpa_printf(MSG_DEBUG, "PR PASN: trigger: PR not initialized"); + return; + } + + if (pr->pr_pasn_params) { + wpa_printf(MSG_DEBUG, + "PR PASN: auth_trigger: Already in progress"); + pr_pasn_params->pr_pasn_status = PASN_STATUS_FAILURE; + return; + } + + if (pr_pasn_params->action == PR_PASN_AND_RANGING) { + wpa_printf(MSG_DEBUG, + "PR PASN: Triggering PASN authentication for " MACSTR + " type=%u role=%u mode=%u freq=%d", + MAC2STR(pr_pasn_params->peer_addr), + pr_pasn_params->ranging_type, + pr_pasn_params->ranging_role, + pr_pasn_params->auth_mode, + pr_pasn_params->freq); + + /* Allocate and store the params to track the request */ + pr->pr_pasn_params = os_zalloc(sizeof(*pr->pr_pasn_params)); + if (!pr->pr_pasn_params) { + wpa_printf(MSG_DEBUG, + "PR PASN: Failed to allocate params"); + pr_pasn_params->pr_pasn_status = PASN_STATUS_FAILURE; + return; + } + + os_memcpy(pr->pr_pasn_params, pr_pasn_params, + sizeof(*pr->pr_pasn_params)); + + /* Initiate PASN authentication for the peer */ + if (wpas_pr_initiate_pasn_auth(wpa_s, pr_pasn_params->peer_addr, + pr_pasn_params->freq, + pr_pasn_params->auth_mode, + pr_pasn_params->ranging_role, + pr_pasn_params->ranging_type, 0, + pr_pasn_params->src_addr, + pr_pasn_params->pasn_role)) { + wpa_printf(MSG_DEBUG, + "PR PASN: Failed to initiate PASN for " MACSTR, + MAC2STR(pr_pasn_params->peer_addr)); + pr_pasn_params->pr_pasn_status = PASN_STATUS_FAILURE; + os_free(pr->pr_pasn_params); + pr->pr_pasn_params = NULL; + return; + } + } else { + wpa_printf(MSG_WARNING, + "PR PASN: Unsupported action %u, ignoring request", + pr_pasn_params->action); + pr_pasn_params->pr_pasn_status = PASN_STATUS_FAILURE; + } +} + + int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data, size_t data_len, bool acked) { diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index 747bb900a..f8b114985 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -39,6 +39,8 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s, int freq); void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, unsigned int freq); +void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, + struct pr_pasn_ranging_params *pr_pasn_params); #else /* CONFIG_PR */ @@ -111,6 +113,11 @@ wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, { } +static inline void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, + struct pr_pasn_ranging_params *pr_pasn_params) +{ +} + #endif /* CONFIG_PR */ #endif /* PR_SUPPLICANT_H */ -- 2.34.1 From frederik.vanbogaert at mind.be Wed May 13 04:50:14 2026 From: frederik.vanbogaert at mind.be (Frederik Van Bogaert) Date: Wed, 13 May 2026 13:50:14 +0200 Subject: [PATCH] wpa_supplicant: events: stop processing scan results when aborting Message-ID: <9463b75d-3b82-489f-aa21-09e98e06a96d@mind.be> A problem was seen on NXP boards with an offloaded Wi-Fi stack where the CPU could get stuck processing scan results over and over again in cases where the scan is aborted. This is reproducable on the target stack by starting a connection to a non-existent SSID (thereby starting a scan) and then performing a "Wi-Fi disconnect" command. That results in the thread getting stuck processing the scan results over and over. uart:~$ wifi connect -s nonexistent -p testtesttest -k 10 Connection requested uart:~$ kernel sleep 1000 uart:~$ wifi disconnect This one-line patch solves the issue by stopping scan processing early without triggering follow-up events when the scan is aborted. Platform: NXP RW610 running hostap on Zephyr v4.3 The issue has not been tested on other platforms, but it is submitted upsteam as it seems to be a valuable sanity check. This is what my AI has to say about it: Overall Assessment: APPROVE This is a well-targeted bugfix that addresses an infinite loop issue when ???aborting scan operations. ISSUE ANALYSIS: * Problem: When connecting to a non-existent Wi-Fi network and then calling ?wifi disconnect, the stack gets stuck in an infinite loop continually ?processing the same scan results. * Root Cause: The function _wpa_supplicant_event_scan_results() was not checking ?if the scan was aborted before proceeding with scan result processing. ?This allowed the code to continue processing results from an aborted scan, ?leading to the infinite loop. CODE CHANGE REVIEW: * Correctness: The fix is correct. By checking data->scan_info.aborted, the ???function now exits early when processing aborted scan results, preventing ???further processing that would lead to the loop. * Defensive Programming: The check includes proper null-pointer protection (data &&) ???before accessing data->scan_info.aborted. * Minimal Impact: The fix is minimal and surgical - only one line changed, ???reducing risk of introducing new bugs. * Return Value: Returns 1, which per the function documentation (lines 2414-2421) ???means "scan results may be shared with other virtual interfaces but may not ???trigger any operations" - appropriate for aborted scans. Signed-off-by: Frederik Van Bogaert --- wpa_supplicant/events.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 0684ada71..b84e956d5 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -2582,7 +2582,7 @@ static int _wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s, wpa_s->last_scan_num_ssids = 0; } -if (update_only) { +if (update_only || (data && data->scan_info.aborted)) { ret = 1; goto scan_work_done; } -- 2.47.3 From andrei.otcheretianski at intel.com Thu May 14 01:34:17 2026 From: andrei.otcheretianski at intel.com (Andrei Otcheretianski) Date: Thu, 14 May 2026 11:34:17 +0300 Subject: [PATCH] NAN: Fix setting PBM in NAN discovery event Message-ID: <20260514083418.34498-1-andrei.otcheretianski@intel.com> From: Ilan Peer The pairing and bootstrapping value was set before the NAN discovery object was memset. Fix this by moving the memset() call earlier. Signed-off-by: Ilan Peer --- src/common/nan_de.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/nan_de.c b/src/common/nan_de.c index c5b1022772..ba61412bd9 100644 --- a/src/common/nan_de.c +++ b/src/common/nan_de.c @@ -1529,6 +1529,7 @@ static bool nan_de_rx_publish(struct nan_de *de, struct nan_de_service *srv, } send_event: + os_memset(&res, 0, sizeof(res)); if (buf && buf_len > 0) { /* Parse Cipher Suite Information Attribute */ cipher_suite_count = nan_de_parse_csia( @@ -1552,7 +1553,6 @@ send_event: res.pbm = nan_de_get_advertise_pbm(buf, buf_len); } - os_memset(&res, 0, sizeof(res)); res.subscribe_id = srv->id; res.srv_proto_type = srv_proto_type; res.ssi = ssi; -- 2.53.0 From Jason.Huang2 at infineon.com Thu May 14 19:44:28 2026 From: Jason.Huang2 at infineon.com (Jason Huang) Date: Fri, 15 May 2026 10:44:28 +0800 Subject: [PATCH] AP: Disconnect station when PMKSA entry expires Message-ID: <20260515024428.646368-1-Jason.Huang2@infineon.com> From: Kurt Lee When a PMKSA cache entry expires for a currently associated station, the AP may keep the link up until another trigger causes reauthentication. This delays key refresh and can leave the station connected with stale PMKSA state. Fix this by disconnecting the station when its PMKSA entry is freed on expiration. The disconnect causes the STA to immediately start a new authentication exchange and complete a fresh association/4-way handshake. Also add plumbing for dot11RSNAConfigPMKLifetime into the authenticator context so the configured PMK lifetime is propagated to the lower layers. Signed-off-by: Kurt Lee Signed-off-by: Jason Huang --- hostapd/config_file.c | 2 ++ src/ap/ap_config.h | 1 + src/ap/ieee802_11.c | 1 + src/ap/wpa_auth.c | 12 ++++++++++-- src/ap/wpa_auth.h | 2 ++ src/ap/wpa_auth_i.h | 1 + wpa_supplicant/ap.c | 2 ++ 7 files changed, 19 insertions(+), 2 deletions(-) diff --git a/hostapd/config_file.c b/hostapd/config_file.c index d34b4aa83..9568090b1 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -3756,6 +3756,8 @@ static int hostapd_config_fill(struct hostapd_config *conf, bss->max_listen_interval = atoi(pos); } else if (os_strcmp(buf, "disable_pmksa_caching") == 0) { bss->disable_pmksa_caching = atoi(pos); + } else if (os_strcmp(buf, "dot11RSNAConfigPMKLifetime") == 0) { + bss->dot11RSNAConfigPMKLifetime = atoi(pos); } else if (os_strcmp(buf, "okc") == 0) { bss->okc = atoi(pos); #ifdef CONFIG_WPS diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 3fe2206b9..79904c4e3 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -1007,6 +1007,7 @@ struct hostapd_bss_config { unsigned int pmksa_caching_privacy:1; unsigned int eap_using_authentication_frames:1; #endif /* CONFIG_ENC_ASSOC */ + unsigned int dot11RSNAConfigPMKLifetime; }; /** diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index d01f42d82..8ba912fd7 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -1340,6 +1340,7 @@ void sae_accept_sta(struct hostapd_data *hapd, struct sta_info *sta) crypto_bignum_deinit(sta->sae->peer_commit_scalar_accepted, 0); sta->sae->peer_commit_scalar_accepted = sta->sae->peer_commit_scalar; sta->sae->peer_commit_scalar = NULL; + wpa_auth_set_pmk_life_time(hapd->wpa_auth,hapd->conf->dot11RSNAConfigPMKLifetime); wpa_auth_pmksa_add_sae(hapd->wpa_auth, sta->addr, sta->sae->pmk, sta->sae->pmk_len, sta->sae->pmkid, sta->sae->akmp, diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c index 42344a2bd..e1326a170 100644 --- a/src/ap/wpa_auth.c +++ b/src/ap/wpa_auth.c @@ -701,6 +701,7 @@ static void wpa_auth_pmksa_free_cb(struct rsn_pmksa_cache_entry *entry, entry); } #else /* CONFIG_IEEE80211BE */ + wpa_sta_disconnect(wpa_auth, entry->spa, WLAN_REASON_PREV_AUTH_NOT_VALID); wpa_auth_for_each_sta(wpa_auth, wpa_auth_pmksa_clear_cb, entry); #endif /* CONFIG_IEEE80211BE */ @@ -6855,6 +6856,12 @@ int wpa_auth_pmksa_add_preauth(struct wpa_authenticator *wpa_auth, } +void wpa_auth_set_pmk_life_time(struct wpa_authenticator *wpa_auth, unsigned int pmk_life_time) +{ + wpa_auth->pmk_life_time = pmk_life_time; +} + + int wpa_auth_pmksa_add_sae(struct wpa_authenticator *wpa_auth, const u8 *addr, const u8 *pmk, size_t pmk_len, const u8 *pmkid, int akmp, bool is_ml, int vlan_id) @@ -6877,8 +6884,9 @@ int wpa_auth_pmksa_add_sae(struct wpa_authenticator *wpa_auth, const u8 *addr, } #endif /* CONFIG_IEEE80211BE */ - entry = pmksa_cache_auth_add(pmksa, pmk, pmk_len, pmkid, NULL, 0, - aa, addr, 0, NULL, akmp); + entry = pmksa_cache_auth_add(wpa_auth->pmksa, pmk, pmk_len, pmkid, + NULL, 0, wpa_auth->addr, addr, + wpa_auth->pmk_life_time, NULL, akmp); if (!entry) return -1; diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index 97f55e67a..97e48789a 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -531,6 +531,8 @@ int wpa_auth_pmksa_add_preauth(struct wpa_authenticator *wpa_auth, const u8 *pmk, size_t len, const u8 *sta_addr, int session_timeout, struct eapol_state_machine *eapol); +void wpa_auth_set_pmk_life_time(struct wpa_authenticator *wpa_auth, + unsigned int pmk_life_time); int wpa_auth_pmksa_add_sae(struct wpa_authenticator *wpa_auth, const u8 *addr, const u8 *pmk, size_t pmk_len, const u8 *pmkid, int akmp, bool is_ml, int vlan_id); diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h index c9427bb72..3661d3b3e 100644 --- a/src/ap/wpa_auth_i.h +++ b/src/ap/wpa_auth_i.h @@ -279,6 +279,7 @@ struct wpa_authenticator { u8 link_id; bool primary_auth; #endif /* CONFIG_IEEE80211BE */ + unsigned int pmk_life_time; }; diff --git a/wpa_supplicant/ap.c b/wpa_supplicant/ap.c index d0a1a96fd..39ab1adb3 100644 --- a/wpa_supplicant/ap.c +++ b/wpa_supplicant/ap.c @@ -649,6 +649,8 @@ static int wpa_supplicant_conf_ap(struct wpa_supplicant *wpa_s, } bss->sae_pwe = wpas_get_ssid_sae_pwe(wpa_s, ssid); + + bss->dot11RSNAConfigPMKLifetime = wpa_s->conf->dot11RSNAConfigPMKLifetime; #endif /* CONFIG_SAE */ if (wpa_s->conf->go_interworking) { -- 2.25.1 From Jason.Huang2 at infineon.com Thu May 14 19:49:56 2026 From: Jason.Huang2 at infineon.com (Jason Huang) Date: Fri, 15 May 2026 10:49:56 +0800 Subject: [PATCH] wpa_supplicant: add option to suppress deauth on PMKSA expiry Message-ID: <20260515024956.677001-1-Jason.Huang2@infineon.com> From: Darren Li Some certification-oriented test setups use very short PMKSA lifetime (for example, dot11RSNAConfigPMKLifetime=1) to effectively disable PMKSA caching behavior. In this mode, PMKSA entry removal can trigger local deauthentication in the supplicant PMKSA free path, which may cause repeated disconnect/reconnect cycles and interfere with the intended test behavior. Add a per-network configuration parameter, suppress_deauth_no_pmksa, to allow suppressing deauthentication when PMKSA entries are removed. Behavior by configuration: - suppress_deauth_no_pmksa=0 (default): keep existing behavior - suppress_deauth_no_pmksa=1: suppress deauthentication on PMKSA removal This keeps default behavior unchanged while providing explicit control for test scenarios that require short PMKSA lifetime operation. Signed-off-by: Darren Li Signed-off-by: Jason Huang --- src/rsn_supp/wpa.c | 5 ++++- src/rsn_supp/wpa.h | 1 + src/rsn_supp/wpa_i.h | 1 + wpa_supplicant/config.c | 1 + wpa_supplicant/config_file.c | 1 + wpa_supplicant/config_ssid.h | 6 ++++++ wpa_supplicant/wpas_glue.c | 2 ++ 7 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c index 2c5ed11c8..b42514db2 100644 --- a/src/rsn_supp/wpa.c +++ b/src/rsn_supp/wpa.c @@ -4393,7 +4393,9 @@ static void wpa_sm_pmksa_free_cb(struct rsn_pmksa_cache_entry *entry, if (deauth) { sm->pmk_len = 0; os_memset(sm->pmk, 0, sizeof(sm->pmk)); - wpa_sm_deauthenticate(sm, WLAN_REASON_UNSPECIFIED); + if (!sm->suppress_deauth_no_pmksa) { + wpa_sm_deauthenticate(sm, WLAN_REASON_UNSPECIFIED); + } } } @@ -4808,6 +4810,7 @@ void wpa_sm_set_config(struct wpa_sm *sm, struct rsn_supp_config *config) } #endif /* CONFIG_FILS */ sm->beacon_prot = config->beacon_prot; + sm->suppress_deauth_no_pmksa = config->suppress_deauth_no_pmksa; } else { sm->network_ctx = NULL; sm->allowed_pairwise_cipher = 0; diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h index d42a7c102..e61197051 100644 --- a/src/rsn_supp/wpa.h +++ b/src/rsn_supp/wpa.h @@ -172,6 +172,7 @@ struct rsn_supp_config { const u8 *fils_cache_id; int beacon_prot; bool force_kdk_derivation; + int suppress_deauth_no_pmksa; }; struct wpa_sm_link { diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h index fc99d2041..68e546350 100644 --- a/src/rsn_supp/wpa_i.h +++ b/src/rsn_supp/wpa_i.h @@ -257,6 +257,7 @@ struct wpa_sm { int last_kck_eapol_key_ver; u8 last_kck_aa[ETH_ALEN]; int last_eapol_key_ver; + int suppress_deauth_no_pmksa; }; diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index e99036366..54e1df6f7 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -2959,6 +2959,7 @@ static const struct parse_data ssid_fields[] = { #ifdef CONFIG_PASN { FUNC(pasn_groups) }, #endif /* CONFIG_PASN */ + { INT_RANGE(suppress_deauth_no_pmksa, 0, 1) }, }; #undef OFFSET diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 81d92b7dd..c7a7e8cdf 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -981,6 +981,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid, INT(beacon_prot); INT(transition_disable); INT(sae_pk); + INT(suppress_deauth_no_pmksa); #ifdef CONFIG_HT_OVERRIDES INT_DEF(disable_ht, DEFAULT_DISABLE_HT); INT_DEF(disable_ht40, DEFAULT_DISABLE_HT40); diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h index 7c60d4e38..b796b2a49 100644 --- a/wpa_supplicant/config_ssid.h +++ b/wpa_supplicant/config_ssid.h @@ -1398,6 +1398,12 @@ struct wpa_ssid { */ int *pasn_groups; #endif /* CONFIG_PASN */ + /** + * suppress_deauth_no_pmksa - Whether deauth when PMKSA is empty + * 0 = To deauthenticate if there is no PMKSA entry (default) + * 1 = To suppress deauthenticate if there is no PMKSA entry + */ + int suppress_deauth_no_pmksa; }; #endif /* CONFIG_SSID_H */ diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c index e57114503..d986b9423 100644 --- a/wpa_supplicant/wpas_glue.c +++ b/wpa_supplicant/wpas_glue.c @@ -1611,6 +1611,8 @@ void wpa_supplicant_rsn_supp_set_config(struct wpa_supplicant *wpa_s, conf.force_kdk_derivation = wpa_s->conf->force_kdk_derivation; #endif /* CONFIG_TESTING_OPTIONS */ #endif /* CONFIG_PASN */ + conf.beacon_prot = ssid->beacon_prot; + conf.suppress_deauth_no_pmksa = ssid->suppress_deauth_no_pmksa; } wpa_sm_set_config(wpa_s->wpa, ssid ? &conf : NULL); } -- 2.25.1 From Jason.Huang2 at infineon.com Thu May 14 19:54:21 2026 From: Jason.Huang2 at infineon.com (Jason Huang) Date: Fri, 15 May 2026 10:54:21 +0800 Subject: [PATCH] DPP: Accept plain-text SSID in configurator parameter parsing Message-ID: <20260515025421.690794-1-Jason.Huang2@infineon.com> From: "Shankar Amar (CSTIPL CSS ICW SW WFS 1)" dpp_set_configurator() parses conf parameters through dpp_configuration_parse_helper(). The ssid= value in this path is currently treated as hex-only by dividing the length by two and calling hexstr2bin(). This breaks flows where ssid is provided as plain text, for example via DPP configurator commands in integration environments that pass string SSID directly. In that case parsing fails and the command aborts with: DPP: Failed to set configurator parameters As a result, DPP_CONFIGURATOR_SIGN and DPP_AUTH_INIT can fail before the authentication/provisioning exchange starts. Fix this by parsing ssid= as plain bytes in this helper path: - remove hex-only conversion for ssid - keep the existing length bound check - copy ssid bytes directly into conf->ssid Signed-off-by: Shankar Amar Signed-off-by: Jason Huang --- src/common/dpp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/dpp.c b/src/common/dpp.c index 7601b0d72..fd96ad74c 100644 --- a/src/common/dpp.c +++ b/src/common/dpp.c @@ -1214,10 +1214,11 @@ static int dpp_configuration_parse_helper(struct dpp_authentication *auth, pos += 6; end = os_strchr(pos, ' '); conf->ssid_len = end ? (size_t) (end - pos) : os_strlen(pos); - conf->ssid_len /= 2; - if (conf->ssid_len > sizeof(conf->ssid) || - hexstr2bin(pos, conf->ssid, conf->ssid_len) < 0) + /* Remove check for ssid in hex as we are supplying + * string format in dpp_auth_init */ + if (conf->ssid_len > sizeof(conf->ssid)) goto fail; + os_memcpy(conf->ssid, pos, conf->ssid_len); } else { #ifdef CONFIG_TESTING_OPTIONS /* use a default SSID for legacy testing reasons */ -- 2.25.1 From Jason.Huang2 at infineon.com Thu May 14 20:09:01 2026 From: Jason.Huang2 at infineon.com (Jason Huang) Date: Fri, 15 May 2026 11:09:01 +0800 Subject: [PATCH] AP: Reject WPA-PSK AKM when PMF is required Message-ID: <20260515030901.743078-1-Jason.Huang2@infineon.com> From: Rakshith P PMF required mode (ieee80211w=2) must not be combined with WPA-PSK AKM. That configuration is internally inconsistent and should be rejected during configuration validation instead of being accepted at startup. Add a config-time check to fail when PMF is required and the selected AKM set includes WPA-PSK. Use a bitmask-based test so this also catches mixed AKM sets (for example, WPA-PSK + SAE), not only one specific AKM combination. This makes hostapd fail fast with a clear error for invalid security policy selection and prevents deployment of unsupported PMF-required PSK setups. Signed-off-by: Rakshith P Signed-off-by: Jason Huang --- src/ap/ap_config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 36a4dad65..0a7785cce 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -1536,6 +1536,13 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss, WPA_CIPHER_GCMP_256 | WPA_CIPHER_GCMP))) bss->spp_amsdu = false; + if (full_config && (bss->ieee80211w == 2) && + (bss->wpa_key_mgmt & WPA_KEY_MGMT_PSK)) { + wpa_printf(MSG_ERROR, + "Cannot set ieee80211w=2 along with the selected wpa_key_mgmt"); + return -1; + } + return 0; } -- 2.25.1 From gubertoli at gmail.com Mon May 18 11:22:13 2026 From: gubertoli at gmail.com (Gustavo Bertoli) Date: Mon, 18 May 2026 20:22:13 +0200 Subject: [PATCH] DPP: Accept plain-text SSID in configurator parameter parsing In-Reply-To: <20260515025421.690794-1-Jason.Huang2@infineon.com> References: <20260515025421.690794-1-Jason.Huang2@infineon.com> Message-ID: Hi, Some points once it may affect my deployment... > This breaks flows where ssid is provided as plain text, for example via > DPP configurator commands in integration environments that pass string README-DPP mentions SSID-hexdump. Would this change be more extensive? > + /* Remove check for ssid in hex as we are supplying > + * string format in dpp_auth_init */ > + if (conf->ssid_len > sizeof(conf->ssid)) > goto fail; > + os_memcpy(conf->ssid, pos, conf->ssid_len); Does test_dpp.py still pass with these changes? From j at w1.fi Tue May 19 09:06:48 2026 From: j at w1.fi (Jouni Malinen) Date: Tue, 19 May 2026 19:06:48 +0300 Subject: [PATCH v12 1/4] hostapd: Support 80/160/320 MHz widths in hostapd_is_usable_chans In-Reply-To: <20260408055640.3298054-2-allen.ye@mediatek.com> References: <20260408055640.3298054-1-allen.ye@mediatek.com> <20260408055640.3298054-2-allen.ye@mediatek.com> Message-ID: On Wed, Apr 08, 2026 at 01:56:37PM +0800, Allen Ye wrote: > Update hostapd_is_usable_chans utility routine logic to take into > account 80MHz, 160MHz and 320MHz channel widths. > This is a preliminary patch to introduce AFC support. > diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c > @@ -1105,11 +1106,6 @@ static int hostapd_is_usable_chans(struct hostapd_iface *iface) > - err = hostapd_is_usable_chan(iface, pri_chan->freq, 1); > - if (err <= 0) { > - wpa_printf(MSG_ERROR, "Primary frequency not allowed"); > - return err; > - } Why does this remove this call to hostapd_is_usable_chan() with primary=1 completely? That feels wrong and at minimum, needs to be clearly justified in the commit message. All the new checks use primary=0 in the calls so it looks like this patch would completely eliminate the check with chan_pri_allowed(). > @@ -1117,38 +1113,111 @@ static int hostapd_is_usable_chans(struct hostapd_iface *iface) > - err = hostapd_is_usable_chan(iface, iface->freq + > - iface->conf->secondary_channel * 20, 0); > - if (err > 0) { > if (iface->conf->secondary_channel == 1 && > - (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) > + (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) { > + iface->conf->secondary_channel = -1; > return 1; > + } > + > if (iface->conf->secondary_channel == -1 && > - (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) > + (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) { > + iface->conf->secondary_channel = 1; > return 1; > - } Why do these swap the HOSTAPD_CHAN_WIDTH_40P and HOSTAPD_CHAN_WIDTH_40M checks? It is unfortunately difficult to follow the changes proposed into this function and this feels far from being obviously correct. Would it be possible to split this into smaller changes and describe the needed change in each individual commit message in a manner that would make it much easier to understand what is being changed here and why? -- Jouni Malinen PGP id EFC895FA From j at w1.fi Wed May 20 01:36:12 2026 From: j at w1.fi (Jouni Malinen) Date: Wed, 20 May 2026 11:36:12 +0300 Subject: [PATCH] AP: Reject WPA-PSK AKM when PMF is required In-Reply-To: <20260515030901.743078-1-Jason.Huang2@infineon.com> References: <20260515030901.743078-1-Jason.Huang2@infineon.com> Message-ID: On Fri, May 15, 2026 at 11:09:01AM +0800, Jason Huang wrote: > PMF required mode (ieee80211w=2) must not be combined with WPA-PSK AKM. Why? That combination is what the PMF program was initially launched with and I see no reason to suddenly start disallowing it. > That configuration is internally inconsistent and should be rejected during > configuration validation instead of being accepted at startup. What do you mean with being "internally inconsistent"? > Add a config-time check to fail when PMF is required and the selected AKM > set includes WPA-PSK. Use a bitmask-based test so this also catches mixed > AKM sets (for example, WPA-PSK + SAE), not only one specific AKM > combination. > > This makes hostapd fail fast with a clear error for invalid security policy > selection and prevents deployment of unsupported PMF-required PSK setups. This would disallow configurations that are valid and as such, I don't think this is going to be an acceptable change. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Wed May 20 02:04:11 2026 From: j at w1.fi (Jouni Malinen) Date: Wed, 20 May 2026 12:04:11 +0300 Subject: [PATCH 0/2] wpa_supplicant: Fix VHT/HE status indications In-Reply-To: <20260512132650.18420-1-chung-hsien.hsu@infineon.com> References: <20260512132650.18420-1-chung-hsien.hsu@infineon.com> Message-ID: On Tue, May 12, 2026 at 09:26:48PM +0800, Chung-Hsien Hsu wrote: > This series updates the legacy STATUS indications for VHT/HE > connections. > > Patch 1 fixes ieee80211ac=1 so that it is reported only for actual > VHT/IEEE 802.11ac associations instead of being set based only on the > presence of a VHT Capabilities element in the Association Response. > > Patch 2 adds a corresponding ieee80211ax=1 STATUS indication for actual > HE/IEEE 802.11ax associations, while excluding EHT associations. Why would this be done? That feels misleading and an unexpected change to existing users of that information. An HE STA is also a VHT STA in the 5 GHz band and an EHT STA is also an HE STA as far as the IEEE 802.11 standard is concerned. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Wed May 20 02:20:31 2026 From: j at w1.fi (Jouni Malinen) Date: Wed, 20 May 2026 12:20:31 +0300 Subject: [PATCH 0/1] Fix spelling: "send successfully" to "sent successfully" In-Reply-To: <20260512010509.2190609-1-me@prestonhunt.com> References: <20260512010509.2190609-1-me@prestonhunt.com> Message-ID: On Mon, May 11, 2026 at 06:04:34PM -0700, Preston Hunt wrote: > This is a very minor issue, but the spelling is incorrect in several > messages. > > Preston Hunt (1): > nl80211: fix spelling "send successfully" to "sent successfully" Thanks, applied. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Wed May 20 02:20:52 2026 From: j at w1.fi (Jouni Malinen) Date: Wed, 20 May 2026 12:20:52 +0300 Subject: [PATCH 0/2] RSN: Validate GTK KDE lengths before msg 4/4 In-Reply-To: <20260512131558.18020-1-chung-hsien.hsu@infineon.com> References: <20260512131558.18020-1-chung-hsien.hsu@infineon.com> Message-ID: On Tue, May 12, 2026 at 09:15:56PM +0800, Chung-Hsien Hsu wrote: > Validate GTK KDE lengths in EAPOL-Key message 3/4 before transmitting > message 4/4. > > The GTK KDE length is already checked when processing the GTK for > installation. However, that validation is reached only after message 4/4 > has been transmitted. This allows a malformed message 3/4 with an > invalid GTK KDE length to be acknowledged even though the supplicant > later rejects the GTK and fails the handshake. > > This series splits the early validation into non-MLO and MLO changes. > > Chung-Hsien Hsu (2): > RSN: Reject invalid GTK KDE length in msg 3/4 > RSN: Reject invalid MLO GTK KDE length in msg 3/4 Thanks, applied with some cleanup. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Wed May 20 02:21:10 2026 From: j at w1.fi (Jouni Malinen) Date: Wed, 20 May 2026 12:21:10 +0300 Subject: [PATCH] NAN: Do not clear NAN NDL state on failure In-Reply-To: <20260513065909.12048-2-andrei.otcheretianski@intel.com> References: <20260513065909.12048-2-andrei.otcheretianski@intel.com> Message-ID: On Wed, May 13, 2026 at 09:59:00AM +0300, Andrei Otcheretianski wrote: > When an NDP response is rejected by the subscriber a NAF with > rejection status needs to be sent to the publisher. Since > in the NDL processing of the NDP rejection, the peer NMI > station is removed and with it the resource allocated to it, > e.g., peer schedule etc., the NAF would be sent on the > general resources of NAN Device and the transmission > can be delayed (or even dropped). > > To fix this, when a NAF is expected to be sent to the peer, > do not remove the peer NMI station (it would be removed on > Tx status handling). This way, the NAF would be sent using > the resources allocated to the station. Thanks, applied. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Wed May 20 02:21:26 2026 From: j at w1.fi (Jouni Malinen) Date: Wed, 20 May 2026 12:21:26 +0300 Subject: [PATCH] wpa_supplicant: Store absolute path to P2P config file In-Reply-To: <20260513065909.12048-4-andrei.otcheretianski@intel.com> References: <20260513065909.12048-4-andrei.otcheretianski@intel.com> Message-ID: On Wed, May 13, 2026 at 09:59:02AM +0300, Andrei Otcheretianski wrote: > When wpa_supplicant is daemonized, relative paths are not valid anymore. > If the P2P device is toggled (disabled/enabled) when wpa_supplicant runs > with -B option, the configuration isn't found and P2P device interface > can't be re-added. Thanks, applied. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Wed May 20 02:21:40 2026 From: j at w1.fi (Jouni Malinen) Date: Wed, 20 May 2026 12:21:40 +0300 Subject: [PATCH] NAN: Fix setting PBM in NAN discovery event In-Reply-To: <20260514083418.34498-1-andrei.otcheretianski@intel.com> References: <20260514083418.34498-1-andrei.otcheretianski@intel.com> Message-ID: On Thu, May 14, 2026 at 11:34:17AM +0300, Andrei Otcheretianski wrote: > The pairing and bootstrapping value was set before the NAN discovery > object was memset. Fix this by moving the memset() call earlier. Thanks, applied. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Wed May 20 02:43:29 2026 From: j at w1.fi (Jouni Malinen) Date: Wed, 20 May 2026 12:43:29 +0300 Subject: [PATCH 1/3] NAN: Add support for tracking the status of transmit requests In-Reply-To: <20260513065909.12048-1-andrei.otcheretianski@intel.com> References: <20260513065909.12048-1-andrei.otcheretianski@intel.com> Message-ID: On Wed, May 13, 2026 at 09:58:59AM +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. I applied these three patches now to make progress with the pending NAN patches, but I'm wondering whether it would make more sense to modify the interface to higher layers to generate the cookie values within wpa_supplicant instead of getting them from the higher layers. Isn't the current design depending on the higher layers managing a pool of unique cookie values? wpa_supplicant could do that and the control interface command would simple get a parameter in for requesting TX status and the response to that control interface command would return the assigned cookie when that TX status was requested. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Wed May 20 07:27:43 2026 From: j at w1.fi (Jouni Malinen) Date: Wed, 20 May 2026 17:27:43 +0300 Subject: [PATCH 1/5] NAN: Refactor NAN module cleanup functions In-Reply-To: <20260513065909.12048-3-andrei.otcheretianski@intel.com> References: <20260513065909.12048-3-andrei.otcheretianski@intel.com> Message-ID: Thanks, all five patches applied. -- Jouni Malinen PGP id EFC895FA From benjamin at sipsolutions.net Wed May 20 07:31:16 2026 From: benjamin at sipsolutions.net (Benjamin Berg) Date: Wed, 20 May 2026 16:31:16 +0200 Subject: [PATCH] scan: disable MAC randomization during provisioning Message-ID: <20260520163115.fba1bfb6f8b5.I1fd493f1fa574f06c6f76fe3b347ae6a2090f928@changeid> From: Benjamin Berg If the GO has already been started and we are just trying to find its beacon, then we should not randomize the MAC address. Doing so would conflict with the GO possibly having a MAC filter installed and ignoring the probe requests (wpa_supplicant does this). So, do not enable address randomization if in P2P provisioning. Also move the wpa_state check into wpa_setup_mac_addr_rand_params so that all the checks are in the same location. Note that other P2P scans bypass the main wpa_supplicant scanning facilities and avoid MAC randomization that way. Signed-off-by: Benjamin Berg --- wpa_supplicant/scan.c | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c index b705058565..e5edeac8ce 100644 --- a/wpa_supplicant/scan.c +++ b/wpa_supplicant/scan.c @@ -81,11 +81,20 @@ static int wpas_wps_in_use(struct wpa_supplicant *wpa_s, #endif /* CONFIG_WPS */ -static int wpa_setup_mac_addr_rand_params(struct wpa_driver_scan_params *params, +static int wpa_setup_mac_addr_rand_params(struct wpa_supplicant *wpa_s, + struct wpa_driver_scan_params *params, const u8 *mac_addr) { u8 *tmp; + /* If we are connected, there is no point in randomization */ + if (wpa_s && wpa_s->wpa_state > WPA_SCANNING) + return 0; + + /* The P2P GO might ignore our probe requests otherwise */ + if (wpa_s && wpa_s->p2p_in_provisioning) + return 0; + if (params->mac_addr) { params->mac_addr_mask = NULL; os_free(params->mac_addr); @@ -198,9 +207,9 @@ static void wpas_trigger_scan_cb(struct wpa_radio_work *work, int deinit) return; } - if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) && - wpa_s->wpa_state <= WPA_SCANNING) - wpa_setup_mac_addr_rand_params(params, wpa_s->mac_addr_scan); + if (wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) + wpa_setup_mac_addr_rand_params(wpa_s, params, + wpa_s->mac_addr_scan); if (wpas_update_random_addr_disassoc(wpa_s) < 0) { wpa_msg(wpa_s, MSG_INFO, @@ -1459,9 +1468,9 @@ ssid_list_set: } #endif /* CONFIG_P2P */ - if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) && - wpa_s->wpa_state <= WPA_SCANNING) - wpa_setup_mac_addr_rand_params(¶ms, wpa_s->mac_addr_scan); + if (wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) + wpa_setup_mac_addr_rand_params(wpa_s, ¶ms, + wpa_s->mac_addr_scan); if (!is_zero_ether_addr(wpa_s->next_scan_bssid)) { struct wpa_bss *bss; @@ -1935,9 +1944,8 @@ scan: wpa_setband_scan_freqs(wpa_s, scan_params); - if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCHED_SCAN) && - wpa_s->wpa_state <= WPA_SCANNING) - wpa_setup_mac_addr_rand_params(¶ms, + if (wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCHED_SCAN) + wpa_setup_mac_addr_rand_params(wpa_s, ¶ms, wpa_s->mac_addr_sched_scan); wpa_scan_set_relative_rssi_params(wpa_s, scan_params); @@ -3853,7 +3861,7 @@ wpa_scan_clone_params(const struct wpa_driver_scan_params *src) } if (src->mac_addr_rand && - wpa_setup_mac_addr_rand_params(params, src->mac_addr)) + wpa_setup_mac_addr_rand_params(NULL, params, src->mac_addr)) goto failed; if (src->bssid) { @@ -4043,9 +4051,9 @@ int wpas_start_pno(struct wpa_supplicant *wpa_s) params.freqs = wpa_s->manual_sched_scan_freqs; } - if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_PNO) && - wpa_s->wpa_state <= WPA_SCANNING) - wpa_setup_mac_addr_rand_params(¶ms, wpa_s->mac_addr_pno); + if (wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_PNO) + wpa_setup_mac_addr_rand_params(wpa_s, ¶ms, + wpa_s->mac_addr_pno); wpa_scan_set_relative_rssi_params(wpa_s, ¶ms); -- 2.54.0 From chung-hsien.hsu at infineon.com Wed May 20 10:28:42 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Thu, 21 May 2026 01:28:42 +0800 Subject: [PATCH 0/2] wpa_supplicant: Fix VHT/HE status indications In-Reply-To: References: <20260512132650.18420-1-chung-hsien.hsu@infineon.com> Message-ID: <20260520172842.GA7331@ISCN5CG4472QHC.infineon.com> On Wed, May 20, 2026 at 12:04:11PM +0300, Jouni Malinen wrote: > Why would this be done? That feels misleading and an unexpected change > to existing users of that information. An HE STA is also a VHT STA in > the 5 GHz band and an EHT STA is also an HE STA as far as the IEEE > 802.11 standard is concerned. Thanks for pointing this out. I agree that the previous change would be misleading and could break existing users that interpret ieee80211ac=1 as VHT being part of the association rather than as an exclusive Wi-Fi generation indication. I'll respin this so that wifi_generation remains the exclusive generation indication, while ieee80211ac/ieee80211ax are non-exclusive STATUS indications based on connection_vht/connection_he. Chung-Hsien From Jason.Huang2 at infineon.com Wed May 20 20:58:42 2026 From: Jason.Huang2 at infineon.com (HungTsung Huang) Date: Thu, 21 May 2026 11:58:42 +0800 Subject: [PATCH] DPP: Accept plain-text SSID in configurator parameter parsing In-Reply-To: References: <20260515025421.690794-1-Jason.Huang2@infineon.com> Message-ID: <20260521035842.GA2907593@ISCN5CG5251XQT.infineon.com> On Mon, May 18, 2026 at 08:15:44PM +0200, Gustavo Bertoli wrote: > This breaks flows where ssid is provided as plain text, for example > via DPP configurator commands in integration environments that pass > string I agree the current approach is too broad and risks breaking existing users. I plan to revise as follows: - Introduce a new parameter, ssid_str=, for plain-text SSID. - Parse ssid_str= in a separate branch from ssid=. - Reject ambiguous input when both ssid= and ssid_str= are present in the same command. > README-DPP mentions SSID-hexdump. Would this change be more extensive? yes, will update user-facing documentation/help: - Clarify that ssid= remains hex (backward-compatible). - Document ssid_str= as opt-in plain-text convenience. - Update CLI/help strings and README examples accordingly. > Does test_dpp.py still pass with these changes? Existing test_dpp.py behavior should remain unchanged with above approach. Thanks for the review comments. From j at w1.fi Thu May 21 00:53:42 2026 From: j at w1.fi (Jouni Malinen) Date: Thu, 21 May 2026 10:53:42 +0300 Subject: [PATCH v3 14/46] PR: Replace format_and_bw attribute with preamble/BW bitmaps In-Reply-To: <20260513100010.1947710-15-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> <20260513100010.1947710-15-kavita.kavita@oss.qualcomm.com> Message-ID: On Wed, May 13, 2026 at 03:29:38PM +0530, Kavita Kavita wrote: > The PD ranging capability used a single format-and-bandwidth > attribute in wpa_driver_capa to represent the best supported > combination. This only considered the highest supported bandwidth > and assumed all lower bandwidths were also supported, which is > not always the case. The channel population logic inherited the > same assumption. > > Replace the attribute with separate preamble and bandwidth > bitmaps so each supported combination is explicitly represented. > Parse the corresponding nl80211 attributes to populate these > bitmaps and remove the now redundant attribute from driver.h. > Update the channel population logic to check each operating > class bandwidth directly against the bitmap rather than > inferring support from the highest supported value. What is that discussion about parsing nl80211 attributes about? This patch does not include any changes to src/drivers/driver_nl80211*.c. > diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h > @@ -98,6 +98,7 @@ enum edca_format_and_bw_value { > EDCA_FORMAT_AND_BW_VHT160_SINGLE_LO = 16, > + EDCA_FORMAT_AND_BW_MAX, > /** > @@ -114,6 +115,7 @@ enum ntb_format_and_bw_value { > NTB_FORMAT_AND_BW_HE160_SINGLE_LO = 5, > + NTB_FORMAT_AND_BW_MAX, How exactly are these values expected to be used? Aren't those only for implementation specific purposes to indicate no matching value was found? However, the implementation seems to be encoding these in pr_get_edca_capabilities() into an actual capability field.. Is that expected? It was a bit difficult to understand the changes since these *_FORMAT_AND_BW_MAX values were actually not checked against anywhere, i.e., they were only returned when there was no match. > diff --git a/src/drivers/driver.h b/src/drivers/driver.h > > +/** > + * Channel width index values used in PD ranging capability bitmaps > + * (pd_bandwidths). The bit position in the bitmap corresponds to the > + * channel width; these values match the nl80211_chan_width enum so > + * that drivers can use them directly. > + */ > +#define WPA_PR_CHAN_WIDTH_20 1 > +#define WPA_PR_CHAN_WIDTH_40 2 > +#define WPA_PR_CHAN_WIDTH_80 3 > +#define WPA_PR_CHAN_WIDTH_80P80 4 > +#define WPA_PR_CHAN_WIDTH_160 5 > + > +/** > + * Preamble index values used in PD ranging capability bitmaps > + * (pd_preambles). The bit position in the bitmap corresponds to the > + * preamble type; these values match the nl80211_preamble enum so > + * that drivers can use them directly. > + */ > +#define WPA_PR_PREAMBLE_HT 1 > +#define WPA_PR_PREAMBLE_VHT 2 > +#define WPA_PR_PREAMBLE_HE 4 It is not a good idea to try to match values of nl80211.h enums in this manner in a context that is supposed to be independent of the driver interface. In other words, the references to nl80211 needs to be removed here and driver_nl80211*.c should use mapping functions between the wpa_supplicant specific values and the values in nl80211.h. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Thu May 21 01:12:13 2026 From: j at w1.fi (Jouni Malinen) Date: Thu, 21 May 2026 11:12:13 +0300 Subject: [PATCH v3 18/46] nl80211: Add Proximity Detection (PD) wdev support In-Reply-To: <20260513100010.1947710-19-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> <20260513100010.1947710-19-kavita.kavita@oss.qualcomm.com> Message-ID: On Wed, May 13, 2026 at 03:29:42PM +0530, Kavita Kavita wrote: > diff --git a/src/drivers/driver.h b/src/drivers/driver.h > +#ifdef CONFIG_PR > + /** > + * pd_start - Create and activate a PD (Proximity Detection) wdev > + * @priv: Private driver interface data > + * @addr: Requested MAC address for the PD wdev > + * @pd_addr: Buffer (ETH_ALEN) to receive the actual assigned MAC > + * Returns: 0 on success, -1 on failure > + * > + * Creates a PD virtual interface in the driver and activates it via > + * NL80211_CMD_START_PD. The PD wdev is associated with the parent > + * BSS; all PD operations (key install, TX, peer measurement) are > + * routed through it by matching the PD MAC address. > + */ > + int (*pd_start)(void *priv, const u8 *addr, u8 *pd_addr); > + > + /** > + * pd_stop - Stop and destroy the PD wdev > + * @priv: Private driver interface data > + * > + * Sends NL80211_CMD_STOP_PD, deletes the PD wdev via > + * NL80211_CMD_DEL_INTERFACE, and frees all associated driver > + * resources. Safe to call when no PD wdev is active (no-op). > + */ > + void (*pd_stop)(void *priv); These are specific to nl80211 while driver.h is supposed to be generic interface. Please reword all this to not use nl80211-specific terms (wdev or anything with nl80211 in the name). -- Jouni Malinen PGP id EFC895FA From j at w1.fi Thu May 21 01:32:41 2026 From: j at w1.fi (Jouni Malinen) Date: Thu, 21 May 2026 11:32:41 +0300 Subject: [PATCH v3 00/46] PR: Add nl80211 support and ranging for Proximity Detection In-Reply-To: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: On Wed, May 13, 2026 at 03:29:24PM +0530, Kavita Kavita wrote: > wpa_supplicant already had PASN authentication support for > Proximity Ranging (PR). This series adds the missing nl80211 > driver support and ranging to complete the PD (Proximity > Detection) flow. I applied patches 1-13 and 16-17. I'll continue reviewing patches 21, 23-24, 26-29, 32-33, 39, 41, 44, 46. The I dropped other patches based on open questions and dependencies to the patches with open questions. -- Jouni Malinen PGP id EFC895FA From kavita.kavita at oss.qualcomm.com Thu May 21 03:49:39 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Thu, 21 May 2026 16:19:39 +0530 Subject: [PATCH v3 18/46] nl80211: Add Proximity Detection (PD) wdev support In-Reply-To: References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> <20260513100010.1947710-19-kavita.kavita@oss.qualcomm.com> Message-ID: On 5/21/2026 1:42 PM, Jouni Malinen wrote: > On Wed, May 13, 2026 at 03:29:42PM +0530, Kavita Kavita wrote: >> diff --git a/src/drivers/driver.h b/src/drivers/driver.h >> +#ifdef CONFIG_PR >> + /** >> + * pd_start - Create and activate a PD (Proximity Detection) wdev >> + * @priv: Private driver interface data >> + * @addr: Requested MAC address for the PD wdev >> + * @pd_addr: Buffer (ETH_ALEN) to receive the actual assigned MAC >> + * Returns: 0 on success, -1 on failure >> + * >> + * Creates a PD virtual interface in the driver and activates it via >> + * NL80211_CMD_START_PD. The PD wdev is associated with the parent >> + * BSS; all PD operations (key install, TX, peer measurement) are >> + * routed through it by matching the PD MAC address. >> + */ >> + int (*pd_start)(void *priv, const u8 *addr, u8 *pd_addr); >> + >> + /** >> + * pd_stop - Stop and destroy the PD wdev >> + * @priv: Private driver interface data >> + * >> + * Sends NL80211_CMD_STOP_PD, deletes the PD wdev via >> + * NL80211_CMD_DEL_INTERFACE, and frees all associated driver >> + * resources. Safe to call when no PD wdev is active (no-op). >> + */ >> + void (*pd_stop)(void *priv); > > These are specific to nl80211 while driver.h is supposed to be generic > interface. Please reword all this to not use nl80211-specific terms > (wdev or anything with nl80211 in the name). Sure Jouni, I will fix it in the new version. Thank you. > From j at w1.fi Thu May 21 08:16:26 2026 From: j at w1.fi (Jouni Malinen) Date: Thu, 21 May 2026 18:16:26 +0300 Subject: [PATCH v3 28/46] PR: Add extended ranging parameters to PASN peer structure In-Reply-To: <20260513100010.1947710-29-kavita.kavita@oss.qualcomm.com> References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> <20260513100010.1947710-29-kavita.kavita@oss.qualcomm.com> Message-ID: On Wed, May 13, 2026 at 03:29:52PM +0530, Kavita Kavita wrote: > diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h > + /** > + * @ingress_threshold: Ingress range threshold in millimeters. > + * Only valid when NL80211_PMSR_PEER_ATTR_PD_REQUEST is set. What is that attribute and why is it mentioned here? That is not defined in nl80211.h and this file is supposed to be independent of a specific driver interface and as such, it should not be mentioned here at all. -- Jouni Malinen PGP id EFC895FA From j at w1.fi Thu May 21 08:36:22 2026 From: j at w1.fi (Jouni Malinen) Date: Thu, 21 May 2026 18:36:22 +0300 Subject: [PATCH v3 00/46] PR: Add nl80211 support and ranging for Proximity Detection In-Reply-To: References: <20260513100010.1947710-1-kavita.kavita@oss.qualcomm.com> Message-ID: On Thu, May 21, 2026 at 11:32:41AM +0300, Jouni Malinen wrote: > On Wed, May 13, 2026 at 03:29:24PM +0530, Kavita Kavita wrote: > > wpa_supplicant already had PASN authentication support for > > Proximity Ranging (PR). This series adds the missing nl80211 > > driver support and ranging to complete the PD (Proximity > > Detection) flow. > > I applied patches 1-13 and 16-17. I'll continue reviewing patches 21, > 23-24, 26-29, 32-33, 39, 41, 44, 46. The I dropped other patches based > on open questions and dependencies to the patches with open questions. I applied patches 21, 23-24, 26-27, 29, 32, 39, 41, 44, 46. I have no patches remaining in my queue, i.e., other patches had either open questions or dependencies to the patches with open questions. -- Jouni Malinen PGP id EFC895FA From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:32 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:32 +0530 Subject: [PATCH v4 00/20] PR: Add nl80211 support and ranging for Proximity Detection Message-ID: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> wpa_supplicant already had PASN authentication support for Proximity Ranging (PR). This series adds the missing nl80211 driver support and ranging to complete the PD (Proximity Detection) flow. It introduces a dedicated PD virtual interface (NL80211_IFTYPE_PD) with its own MAC address, updates nl80211_copy.h with the proposed kernel API additions for PD, improves driver capability parsing with dedicated PR-specific flags, extends the set_key path to carry LTF key seeds for secure ranging, and wires up peer measurement start and result handling through the nl80211 driver. The existing PR_PASN_START control interface command is extended with the full set of EDCA, NTB, and proximity threshold parameters and rerouted through wpas_pr_pasn_trigger() to drive the complete PASN-then-ranging flow in a single step. Support is also added for Out-of-Band peer discovery where USD is handled by an external application, and per-peer PMK and password configuration via PR_PASN_START for authenticated PASN without prior NAN USD discovery. Kernel Changes are merged into wireless-next tip. Link: https://patchwork.kernel.org/project/linux-wireless/list/?series=1083204&state=%2A&archive=both --- Changes in v4: - Addressed reviewer comments. --- Changes in v3: - The earlier supplicant patchset (v2) was aligned with a previous kernel patchset series. Since that kernel series has now been merged, the supplicant has been updated again (v3) to align with the current wireless-next. - Fixed several bugs identified during internal testing. Introduced new patches in this series. - Added support for Out-of-Band (OOB) peer discovery: when USD service discovery is handled by an external application and PASN is triggered separately via PR_PASN_START, a minimal peer entry is created automatically to allow PASN to proceed without prior NAN USD discovery. --- Changes in v2: - Added the correct NL attributes. - Refactored dependent code paths to reflect the updated NL attribute handling. --- Kavita Kavita (4): PR: Gate EDCA ranging support on ASAP capability PR: Restore dev_addr to station MAC after PD wdev stop PR: Track peer discovery type and handle OOB peers in PASN initiation PR: Allow PMK and password configuration in PR_PASN_START Peddolla Harshavardhan Reddy (11): PR: Replace format_and_bw attribute with preamble/BW bitmaps PR: Add dedicated PD wdev for PASN with custom MAC address PR: Add extended ranging parameters to PASN peer structure nl80211: Add peer measurement support for proximity ranging PR: Use session time as total ROC budget for PASN responder PR: Extend PR_PASN_START ctrl iface with full ranging parameters wpa_supplicant: Add FTM peer measurement result handling PR: Handle peer measurement complete event PR: Add ranging session timeout and integrate socket cleanup PR: Add API and ctrl iface command to abort ranging session PR: use USD source address for DIRA tag computation Veerendranath Jakkam (5): nl80211: Add Proximity Detection (PD) support nl80211: Route MLME TX via PD wdev based on source address nl80211: Factor out global event BSS lookup and add PD wdev event routing nl80211: Route key operations to PD wdev based on own_addr nl80211: Add dedicated PR ranging socket and stop op src/common/proximity_ranging.c | 97 +++- src/common/proximity_ranging.h | 107 +++- src/common/wpa_ctrl.h | 6 + src/drivers/driver.h | 148 ++++- src/drivers/driver_common.c | 2 + src/drivers/driver_nl80211.c | 582 ++++++++++++++++++- src/drivers/driver_nl80211.h | 7 + src/drivers/driver_nl80211_capa.c | 43 +- src/drivers/driver_nl80211_event.c | 377 ++++++++++-- wpa_supplicant/ctrl_iface.c | 137 ++++- wpa_supplicant/driver_i.h | 44 ++ wpa_supplicant/events.c | 14 + wpa_supplicant/nan_supplicant.c | 8 +- wpa_supplicant/notify.c | 35 ++ wpa_supplicant/notify.h | 4 + wpa_supplicant/pr_supplicant.c | 890 ++++++++++++++++++++++++++--- wpa_supplicant/pr_supplicant.h | 32 +- wpa_supplicant/wpa_supplicant_i.h | 10 + 18 files changed, 2328 insertions(+), 215 deletions(-) base-commit: 52a22e129a22061bede0503de0c1e3141383932a -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:33 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:33 +0530 Subject: [PATCH v4 01/20] PR: Replace format_and_bw attribute with preamble/BW bitmaps In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-2-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy The PD ranging capability used a single format-and-bandwidth attribute in wpa_driver_capa to represent the best supported combination. This only considered the highest supported bandwidth and assumed all lower bandwidths were also supported, which is not always the case. The channel population logic inherited the same assumption. Replace the attribute with separate preamble and bandwidth bitmaps so each supported combination is explicitly represented. Parse the corresponding nl80211 attributes to populate these bitmaps and remove the now redundant attribute from driver.h. Update the channel population logic to check each operating class bandwidth directly against the bitmap rather than inferring support from the highest supported value. Fixes: f45cc220e4bb ("PR: Update PR device configs and capabilities from driver") Fixes: cfe7a215db0b ("PR: Determine channels that are supported to perform ranging") Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.h | 8 ++ src/drivers/driver.h | 15 ++- src/drivers/driver_nl80211_capa.c | 40 +++++- wpa_supplicant/pr_supplicant.c | 206 ++++++++++++++++-------------- 4 files changed, 169 insertions(+), 100 deletions(-) diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index f1d6acd3d..b50a3451d 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -98,6 +98,7 @@ enum edca_format_and_bw_value { EDCA_FORMAT_AND_BW_VHT80P80 = 14, EDCA_FORMAT_AND_BW_VHT160_DUAL_LO = 15, EDCA_FORMAT_AND_BW_VHT160_SINGLE_LO = 16, + EDCA_FORMAT_AND_BW_INVALID, }; /** @@ -114,6 +115,7 @@ enum ntb_format_and_bw_value { NTB_FORMAT_AND_BW_HE80P80 = 3, NTB_FORMAT_AND_BW_HE160_DUAL_LO = 4, NTB_FORMAT_AND_BW_HE160_SINGLE_LO = 5, + NTB_FORMAT_AND_BW_INVALID, }; struct pr_capabilities { @@ -382,6 +384,7 @@ struct pr_config { bool edca_rsta_support; + /* Best single format_bw value derived from pd_format_bw_bitmap */ u8 edca_format_and_bw; u32 edca_min_ranging_interval; @@ -424,6 +427,11 @@ struct pr_config { u8 max_tx_sts_gt_80; + /* PD ranging preamble and bandwidth bitmaps (shared by EDCA and NTB) */ + u32 pd_preamble_bitmap; + u32 pd_format_bw_bitmap; + + /* Best single format_bw value derived from pd_format_bw_bitmap */ u8 ntb_format_and_bw; u32 ntb_min_ranging_interval; diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 22d010a3e..4ef575b00 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2266,6 +2266,19 @@ struct nan_capa { bool he_valid; }; +/* Bit index values for pd_bandwidths bitmap */ +#define WPA_PR_CHAN_WIDTH_20 0 +#define WPA_PR_CHAN_WIDTH_40 1 +#define WPA_PR_CHAN_WIDTH_80 2 +#define WPA_PR_CHAN_WIDTH_80P80 3 +#define WPA_PR_CHAN_WIDTH_160 4 + +/* Bit index values for pd_preambles bitmap */ +#define WPA_PR_PREAMBLE_HT 0 +#define WPA_PR_PREAMBLE_VHT 1 +#define WPA_PR_PREAMBLE_HE 2 + + /** * struct wpa_driver_capa - Driver capability information */ @@ -2677,13 +2690,11 @@ struct wpa_driver_capa { size_t max_probe_req_ie_len; /* EDCA based ranging capabilities */ - u8 edca_format_and_bw; u8 max_tx_antenna; u8 max_rx_antenna; u32 edca_min_ranging_interval; /* NTB based ranging capabilities */ - u8 ntb_format_and_bw; u8 max_tx_ltf_repetations; u8 max_rx_ltf_repetations; u8 max_tx_ltf_total; diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c index 269c60dfa..7b8a7bdb5 100644 --- a/src/drivers/driver_nl80211_capa.c +++ b/src/drivers/driver_nl80211_capa.c @@ -1139,6 +1139,38 @@ out: #ifdef CONFIG_PR +static u32 nl80211_to_pd_bw_bitmap(u32 nl_bitmap) +{ + u32 bandwidths = 0; + + if (nl_bitmap & BIT(NL80211_CHAN_WIDTH_20)) + bandwidths |= BIT(WPA_PR_CHAN_WIDTH_20); + if (nl_bitmap & BIT(NL80211_CHAN_WIDTH_40)) + bandwidths |= BIT(WPA_PR_CHAN_WIDTH_40); + if (nl_bitmap & BIT(NL80211_CHAN_WIDTH_80)) + bandwidths |= BIT(WPA_PR_CHAN_WIDTH_80); + if (nl_bitmap & BIT(NL80211_CHAN_WIDTH_80P80)) + bandwidths |= BIT(WPA_PR_CHAN_WIDTH_80P80); + if (nl_bitmap & BIT(NL80211_CHAN_WIDTH_160)) + bandwidths |= BIT(WPA_PR_CHAN_WIDTH_160); + return bandwidths; +} + + +static u32 nl80211_to_pd_preamble_bitmap(u32 nl_bitmap) +{ + u32 preambles = 0; + + if (nl_bitmap & BIT(NL80211_PREAMBLE_HT)) + preambles |= BIT(WPA_PR_PREAMBLE_HT); + if (nl_bitmap & BIT(NL80211_PREAMBLE_VHT)) + preambles |= BIT(WPA_PR_PREAMBLE_VHT); + if (nl_bitmap & BIT(NL80211_PREAMBLE_HE)) + preambles |= BIT(WPA_PR_PREAMBLE_HE); + return preambles; +} + + static void pmsr_type_ftm_handler(struct wpa_driver_nl80211_data *drv, struct nlattr *capa) { @@ -1244,11 +1276,11 @@ static void pmsr_type_ftm_handler(struct wpa_driver_nl80211_data *drv, drv->capa.support_6ghz = !!tb[NL80211_PMSR_FTM_CAPA_ATTR_6GHZ_SUPPORT]; if (tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES]) - drv->capa.pd_preambles = - nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES]); + drv->capa.pd_preambles = nl80211_to_pd_preamble_bitmap( + nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES])); if (tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS]) - drv->capa.pd_bandwidths = - nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS]); + drv->capa.pd_bandwidths = nl80211_to_pd_bw_bitmap( + nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS])); } diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 0741fb146..c224ccd03 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -27,115 +27,116 @@ static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx); #endif /* CONFIG_PASN */ -static int wpas_pr_edca_get_bw(enum edca_format_and_bw_value format_and_bw) -{ - switch (format_and_bw) { - case EDCA_FORMAT_AND_BW_VHT20: - return 20; - case EDCA_FORMAT_AND_BW_HT40: - case EDCA_FORMAT_AND_BW_VHT40: - return 40; - case EDCA_FORMAT_AND_BW_VHT80: - return 80; - case EDCA_FORMAT_AND_BW_VHT80P80: - case EDCA_FORMAT_AND_BW_VHT160_DUAL_LO: - case EDCA_FORMAT_AND_BW_VHT160_SINGLE_LO: - return 160; - default: - return 0; - } -} - - -static int wpas_pr_ntb_get_bw(enum ntb_format_and_bw_value format_and_bw) -{ - switch (format_and_bw) { - case NTB_FORMAT_AND_BW_HE20: - return 20; - case NTB_FORMAT_AND_BW_HE40: - return 40; - case NTB_FORMAT_AND_BW_HE80: - return 80; - case NTB_FORMAT_AND_BW_HE80P80: - case NTB_FORMAT_AND_BW_HE160_DUAL_LO: - case NTB_FORMAT_AND_BW_HE160_SINGLE_LO: - return 160; - default: - return 0; - } +static u8 wpas_pr_best_edca_format_bw(u32 bw_bitmap, u32 preamble_bitmap) +{ + /* Prefer highest bandwidth first */ + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_160)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT))) + return EDCA_FORMAT_AND_BW_VHT160_DUAL_LO; + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80P80)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT))) + return EDCA_FORMAT_AND_BW_VHT80P80; + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT))) + return EDCA_FORMAT_AND_BW_VHT80; + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_40)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT))) + return EDCA_FORMAT_AND_BW_VHT40; + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_40)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_HT))) + return EDCA_FORMAT_AND_BW_HT40; + if ((bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_20)) && + (preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT))) + return EDCA_FORMAT_AND_BW_VHT20; + return EDCA_FORMAT_AND_BW_INVALID; +} + + +static u8 wpas_pr_best_ntb_format_bw(u32 bw_bitmap, u32 preamble_bitmap) +{ + if (!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_HE))) + return NTB_FORMAT_AND_BW_INVALID; + + if (bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_160)) + return NTB_FORMAT_AND_BW_HE160_SINGLE_LO; + if (bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80P80)) + return NTB_FORMAT_AND_BW_HE80P80; + if (bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80)) + return NTB_FORMAT_AND_BW_HE80; + if (bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_40)) + return NTB_FORMAT_AND_BW_HE40; + if (bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_20)) + return NTB_FORMAT_AND_BW_HE20; + return NTB_FORMAT_AND_BW_INVALID; } static bool -wpas_pr_edca_is_valid_op_class(enum edca_format_and_bw_value format_and_bw, +wpas_pr_edca_is_valid_op_class(u32 bw_bitmap, u32 preamble_bitmap, const struct oper_class_map *op_class_map) { - int bw = 0, op_class_bw = 0; - if (!op_class_map) return false; - op_class_bw = oper_class_bw_to_int(op_class_map); - bw = wpas_pr_edca_get_bw(format_and_bw); - - if (!op_class_bw || !bw) + switch (op_class_map->bw) { + case BW20: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_20)) && + !!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT)); + case BW40PLUS: + case BW40MINUS: + case BW40: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_40)) && + !!(preamble_bitmap & (BIT(WPA_PR_PREAMBLE_VHT) | + BIT(WPA_PR_PREAMBLE_HT))); + case BW80: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80)) && + !!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT)); + case BW80P80: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80P80)) && + !!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT)); + case BW160: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_160)) && + !!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_VHT)); + default: return false; - - if (format_and_bw <= EDCA_FORMAT_AND_BW_VHT80 && - format_and_bw >= EDCA_FORMAT_AND_BW_VHT20 && - op_class_bw <= bw) - return true; - - if (format_and_bw == EDCA_FORMAT_AND_BW_VHT80P80 && - (op_class_bw < bw || op_class_map->bw == BW80P80)) - return true; - - if ((format_and_bw == EDCA_FORMAT_AND_BW_VHT160_DUAL_LO || - format_and_bw == EDCA_FORMAT_AND_BW_VHT160_SINGLE_LO) && - (op_class_bw < bw || op_class_map->bw == BW160)) - return true; - - return false; + } } static bool -wpas_pr_ntb_is_valid_op_class(enum ntb_format_and_bw_value format_and_bw, +wpas_pr_ntb_is_valid_op_class(u32 bw_bitmap, u32 preamble_bitmap, const struct oper_class_map *op_class_map) { - int bw = 0, op_class_bw = 0; - if (!op_class_map) return false; - op_class_bw = oper_class_bw_to_int(op_class_map); - bw = wpas_pr_ntb_get_bw(format_and_bw); - - if (!op_class_bw || !bw) + /* NTB ranging requires HE preamble */ + if (!(preamble_bitmap & BIT(WPA_PR_PREAMBLE_HE))) return false; - if (format_and_bw <= NTB_FORMAT_AND_BW_HE80 && - format_and_bw >= NTB_FORMAT_AND_BW_HE20 && - op_class_bw <= bw) - return true; - - if (format_and_bw == NTB_FORMAT_AND_BW_HE80P80 && - (op_class_bw < bw || op_class_map->bw == BW80P80)) - return true; - - if ((format_and_bw == NTB_FORMAT_AND_BW_HE160_DUAL_LO || - format_and_bw == NTB_FORMAT_AND_BW_HE160_SINGLE_LO) && - (op_class_bw < bw || op_class_map->bw == BW160)) - return true; - - return false; + switch (op_class_map->bw) { + case BW20: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_20)); + case BW40PLUS: + case BW40MINUS: + case BW40: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_40)); + case BW80: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80)); + case BW80P80: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_80P80)); + case BW160: + return !!(bw_bitmap & BIT(WPA_PR_CHAN_WIDTH_160)); + default: + return false; + } } static void wpas_pr_setup_edca_channels(struct wpa_supplicant *wpa_s, struct pr_channels *chan, - enum edca_format_and_bw_value format_and_bw) + u32 bw_bitmap, u32 preamble_bitmap) { struct hostapd_hw_modes *mode; int cla = 0, i; @@ -148,7 +149,8 @@ wpas_pr_setup_edca_channels(struct wpa_supplicant *wpa_s, mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, o->mode, is_6ghz_op_class(o->op_class)); if (!mode || is_6ghz_op_class(o->op_class) || - !wpas_pr_edca_is_valid_op_class(format_and_bw, o)) + !wpas_pr_edca_is_valid_op_class(bw_bitmap, preamble_bitmap, + o)) continue; for (ch = o->min_chan; ch <= o->max_chan; ch += o->inc) { @@ -194,7 +196,7 @@ wpas_pr_setup_edca_channels(struct wpa_supplicant *wpa_s, static void wpas_pr_setup_ntb_channels(struct wpa_supplicant *wpa_s, struct pr_channels *chan, - enum ntb_format_and_bw_value format_and_bw, + u32 bw_bitmap, u32 preamble_bitmap, bool allow_6ghz) { int cla = 0, i; @@ -208,7 +210,8 @@ wpas_pr_setup_ntb_channels(struct wpa_supplicant *wpa_s, mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, o->mode, is_6ghz_op_class(o->op_class)); if (!mode || (!allow_6ghz && is_6ghz_op_class(o->op_class)) || - !wpas_pr_ntb_is_valid_op_class(format_and_bw, o)) + !wpas_pr_ntb_is_valid_op_class(bw_bitmap, preamble_bitmap, + o)) continue; for (ch = o->min_chan; ch <= o->max_chan; ch += o->inc) { @@ -397,18 +400,32 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, (int) (PR_PASN_DH19_UNAUTH | PR_PASN_DH19_AUTH); pr.preferred_ranging_role = wpa_s->conf->pr_preferred_role; - pr.edca_ista_support = capa->ista.support_edca; - pr.edca_rsta_support = capa->rsta.support_edca; - pr.edca_format_and_bw = capa->edca_format_and_bw; + pr.edca_format_and_bw = + wpas_pr_best_edca_format_bw(capa->pd_bandwidths, + capa->pd_preambles); + pr.edca_ista_support = capa->ista.support_edca && + (pr.edca_format_and_bw != + EDCA_FORMAT_AND_BW_INVALID); + pr.edca_rsta_support = capa->rsta.support_edca && + (pr.edca_format_and_bw != + EDCA_FORMAT_AND_BW_INVALID); + pr.pd_format_bw_bitmap = capa->pd_bandwidths; + pr.pd_preamble_bitmap = capa->pd_preambles; pr.max_rx_antenna = capa->max_rx_antenna; pr.max_tx_antenna = capa->max_tx_antenna; wpas_pr_setup_edca_channels(wpa_s, &pr.edca_channels, - pr.edca_format_and_bw); - - pr.ntb_ista_support = capa->ista.support_ntb; - pr.ntb_rsta_support = capa->rsta.support_ntb; - pr.ntb_format_and_bw = capa->ntb_format_and_bw; + capa->pd_bandwidths, + capa->pd_preambles); + pr.ntb_format_and_bw = + wpas_pr_best_ntb_format_bw(capa->pd_bandwidths, + capa->pd_preambles); + pr.ntb_ista_support = capa->ista.support_ntb && + (pr.ntb_format_and_bw != + NTB_FORMAT_AND_BW_INVALID); + pr.ntb_rsta_support = capa->rsta.support_ntb && + (pr.ntb_format_and_bw != + NTB_FORMAT_AND_BW_INVALID); pr.max_tx_ltf_repetations = capa->max_tx_ltf_repetations; pr.max_rx_ltf_repetations = capa->max_rx_ltf_repetations; pr.max_tx_ltf_total = capa->max_tx_ltf_total; @@ -438,7 +455,8 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, pr.secure_he_ltf = wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SEC_LTF_STA; wpas_pr_setup_ntb_channels(wpa_s, &pr.ntb_channels, - pr.ntb_format_and_bw, + capa->pd_bandwidths, + capa->pd_preambles, pr.support_6ghz); if (wpa_s->conf->country[0] && wpa_s->conf->country[1]) { -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:34 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:34 +0530 Subject: [PATCH v4 02/20] PR: Gate EDCA ranging support on ASAP capability In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-3-kavita.kavita@oss.qualcomm.com> Parse NL80211_PMSR_FTM_CAPA_ATTR_ASAP from the driver and store it in capa->asap_support. Gate both pr.edca_ista_support and pr.edca_rsta_support on ASAP support since ASAP is mandatory for EDCA-based ranging. This ensures EDCA ranging requests are only sent when the driver advertises ASAP support, preventing invalid ranging attempts. Signed-off-by: Kavita Kavita --- src/drivers/driver.h | 1 + src/drivers/driver_nl80211_capa.c | 3 +++ wpa_supplicant/pr_supplicant.c | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 4ef575b00..0e7b26a03 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2710,6 +2710,7 @@ struct wpa_driver_capa { u8 max_ftms_per_burst; bool concurrent_ista_rsta; bool support_6ghz; + bool asap_support; u32 pd_preambles; u32 pd_bandwidths; diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c index 7b8a7bdb5..c791973b1 100644 --- a/src/drivers/driver_nl80211_capa.c +++ b/src/drivers/driver_nl80211_capa.c @@ -1275,6 +1275,9 @@ static void pmsr_type_ftm_handler(struct wpa_driver_nl80211_data *drv, /* Parse additional ranging capabilities */ drv->capa.support_6ghz = !!tb[NL80211_PMSR_FTM_CAPA_ATTR_6GHZ_SUPPORT]; + drv->capa.asap_support = + !!tb[NL80211_PMSR_FTM_CAPA_ATTR_ASAP]; + if (tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES]) drv->capa.pd_preambles = nl80211_to_pd_preamble_bitmap( nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES])); diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index c224ccd03..494538e60 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -404,9 +404,11 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, wpas_pr_best_edca_format_bw(capa->pd_bandwidths, capa->pd_preambles); pr.edca_ista_support = capa->ista.support_edca && + capa->asap_support && (pr.edca_format_and_bw != EDCA_FORMAT_AND_BW_INVALID); pr.edca_rsta_support = capa->rsta.support_edca && + capa->asap_support && (pr.edca_format_and_bw != EDCA_FORMAT_AND_BW_INVALID); pr.pd_format_bw_bitmap = capa->pd_bandwidths; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:35 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:35 +0530 Subject: [PATCH v4 03/20] nl80211: Add Proximity Detection (PD) support In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-4-kavita.kavita@oss.qualcomm.com> From: Veerendranath Jakkam Introduce driver and nl80211 support for a Proximity Detection (PD) virtual interface, enabled under CONFIG_PR. Two new driver ops are added: - pd_start(): create interface for Proximity Detection and return the assigned MAC address - pd_stop(): stop interface for Proximity Detection On the nl80211 side, nl80211_pd_start() allocates a dedicated i802_bss (drv->pd_bss) outside the regular BSS list, creates the PD wdev via nl80211_create_iface(), and activates it. nl80211_pd_stop() tears it down in the correct order: deactivate first, then destroy BSS resources, then delete the wdev. The driver deinit path calls nl80211_pd_stop() if a PD wdev is still active. The wdev_info struct and its netlink callback are generalized and renamed to nl80211_wdev_handler_info to capture both wdev_id and MAC address, replacing the previous nl80211_wdev_handler. This handler is shared between PD wdev creation and the existing non-netdev interface path in wpa_driver_nl80211_if_add(). wpa_supplicant is updated with wpa_drv_pd_start() and wpa_drv_pd_stop() wrappers that call into the driver if CONFIG_PR is enabled. Signed-off-by: Veerendranath Jakkam --- src/drivers/driver.h | 19 ++++ src/drivers/driver_nl80211.c | 173 +++++++++++++++++++++++++++++------ src/drivers/driver_nl80211.h | 3 + wpa_supplicant/driver_i.h | 20 ++++ 4 files changed, 188 insertions(+), 27 deletions(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 0e7b26a03..e7ce48c80 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -5903,6 +5903,25 @@ struct wpa_driver_ops { const struct wpabuf *ulw, struct nan_peer_schedule_config *sched); #endif /* CONFIG_NAN */ + +#ifdef CONFIG_PR + /** + * pd_start - Create interface for Proximity Detection + * @priv: Private driver interface data + * @addr: Requested MAC address + * @pd_addr: MAC address of the interface + * Returns: 0 on success, -1 on failure + */ + int (*pd_start)(void *priv, const u8 *addr, u8 *pd_addr); + + /** + * pd_stop - Stop interface for Proximity Detection + * @priv: Private driver interface data + */ + void (*pd_stop)(void *priv); + +#endif /* CONFIG_PR */ + }; /** diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index d87b39969..45086428e 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -199,6 +199,9 @@ static int nl80211_put_mesh_config(struct nl_msg *msg, #endif /* CONFIG_MESH */ static int i802_sta_disassoc(void *priv, const u8 *own_addr, const u8 *addr, u16 reason, int link_id); +#ifdef CONFIG_PR +static void nl80211_pd_stop(void *priv); +#endif /* CONFIG_PR */ /* Converts nl80211_chan_width to a common format */ @@ -3253,6 +3256,31 @@ static int nl80211_set_p2pdev(struct i802_bss *bss, int start) } +struct wdev_info { + u64 wdev_id; + int wdev_id_set; + u8 macaddr[ETH_ALEN]; +}; + +static int nl80211_wdev_handler_info(struct nl_msg *msg, void *arg) +{ + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct wdev_info *wi = arg; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + if (tb[NL80211_ATTR_WDEV]) { + wi->wdev_id = nla_get_u64(tb[NL80211_ATTR_WDEV]); + wi->wdev_id_set = 1; + } + if (tb[NL80211_ATTR_MAC]) + os_memcpy(wi->macaddr, nla_data(tb[NL80211_ATTR_MAC]), + ETH_ALEN); + return NL_SKIP; +} + + static int nl80211_set_pr_dev(struct i802_bss *bss, bool start) { struct nl_msg *msg; @@ -3270,6 +3298,115 @@ static int nl80211_set_pr_dev(struct i802_bss *bss, bool start) } +#ifdef CONFIG_PR +/** + * nl80211_pd_start - Create interface for Proximity Detection + */ +static int nl80211_pd_start(void *priv, const u8 *addr, u8 *pd_addr) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + struct i802_bss *pd_bss; + struct wdev_info info; + int ret; + + if (drv->pd_bss) { + wpa_printf(MSG_DEBUG, "nl80211: PD wdev already active"); + if (pd_addr) + os_memcpy(pd_addr, drv->pd_bss->addr, ETH_ALEN); + return 0; + } + + pd_bss = os_zalloc(sizeof(*pd_bss)); + if (!pd_bss) + return -1; + + pd_bss->drv = drv; + pd_bss->ctx = bss->ctx; /* parent wpa_s */ + pd_bss->flink = &pd_bss->links[0]; + os_strlcpy(pd_bss->ifname, "pd-wdev", sizeof(pd_bss->ifname)); + + os_memset(&info, 0, sizeof(info)); + ret = nl80211_create_iface(drv, "pd-wdev", NL80211_IFTYPE_PD, addr, + 0, nl80211_wdev_handler_info, &info, 0); + if (ret || !info.wdev_id_set) { + wpa_printf(MSG_ERROR, + "nl80211: Failed to create PD wdev (ret=%d)", ret); + os_free(pd_bss); + return -1; + } + + pd_bss->wdev_id = info.wdev_id; + pd_bss->wdev_id_set = 1; + if (!is_zero_ether_addr(info.macaddr)) + os_memcpy(pd_bss->addr, info.macaddr, ETH_ALEN); + else if (addr) + os_memcpy(pd_bss->addr, addr, ETH_ALEN); + os_memcpy(pd_bss->flink->addr, pd_bss->addr, ETH_ALEN); + + /* Activate the PD device */ + ret = nl80211_set_pr_dev(pd_bss, 1); + if (ret) { + wpa_printf(MSG_ERROR, + "nl80211: Failed to start PD device (ret=%d)", ret); + goto failed; + } + + if (nl80211_init_bss(pd_bss)) { + wpa_printf(MSG_ERROR, + "nl80211: Failed to init PD BSS"); + goto failed_stop; + } + + if (nl80211_mgmt_subscribe_non_ap(pd_bss)) + wpa_printf(MSG_DEBUG, + "nl80211: Failed to register frame processing for PD interface - ignore for now"); + + drv->pd_bss = pd_bss; + + if (pd_addr) + os_memcpy(pd_addr, pd_bss->addr, ETH_ALEN); + + wpa_printf(MSG_DEBUG, + "nl80211: PD wdev created wdev_id=0x%llx addr=" MACSTR, + (unsigned long long) pd_bss->wdev_id, + MAC2STR(pd_bss->addr)); + return 0; + +failed_stop: + nl80211_set_pr_dev(pd_bss, 0); +failed: + nl80211_del_non_netdev(pd_bss); + os_free(pd_bss); + return -1; +} + + +/** + * nl80211_pd_stop - Stop interface for Proximity Detection + */ +static void nl80211_pd_stop(void *priv) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + + if (!drv->pd_bss) + return; + + wpa_printf(MSG_DEBUG, + "nl80211: Stopping PD wdev wdev_id=0x%llx addr=" MACSTR, + (unsigned long long) drv->pd_bss->wdev_id, + MAC2STR(drv->pd_bss->addr)); + + nl80211_set_pr_dev(drv->pd_bss, 0); + nl80211_destroy_bss(drv->pd_bss); + nl80211_del_non_netdev(drv->pd_bss); + os_free(drv->pd_bss); + drv->pd_bss = NULL; +} +#endif /* CONFIG_PR */ + + #ifdef CONFIG_NAN static void nl80211_nan_stop(struct i802_bss *bss) { @@ -3634,6 +3771,10 @@ static void wpa_driver_nl80211_deinit(struct i802_bss *bss) eloop_unregister_read_sock(drv->eapol_tx_sock); if (drv->eapol_tx_sock >= 0) close(drv->eapol_tx_sock); +#ifdef CONFIG_PR + if (drv->pd_bss) + nl80211_pd_stop(bss); +#endif /* CONFIG_PR */ if (bss->nl_preq) wpa_driver_nl80211_probe_req_report(bss, 0); @@ -9552,32 +9693,6 @@ static int nl80211_vif_addr(struct wpa_driver_nl80211_data *drv, u8 *new_addr) } -struct wdev_info { - u64 wdev_id; - int wdev_id_set; - u8 macaddr[ETH_ALEN]; -}; - -static int nl80211_wdev_handler(struct nl_msg *msg, void *arg) -{ - struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); - struct nlattr *tb[NL80211_ATTR_MAX + 1]; - struct wdev_info *wi = arg; - - nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), - genlmsg_attrlen(gnlh, 0), NULL); - if (tb[NL80211_ATTR_WDEV]) { - wi->wdev_id = nla_get_u64(tb[NL80211_ATTR_WDEV]); - wi->wdev_id_set = 1; - } - - if (tb[NL80211_ATTR_MAC]) - os_memcpy(wi->macaddr, nla_data(tb[NL80211_ATTR_MAC]), - ETH_ALEN); - - return NL_SKIP; -} - static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type, const char *ifname, const u8 *addr, @@ -9600,7 +9715,7 @@ static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type, os_memset(&nonnetdev_info, 0, sizeof(nonnetdev_info)); ifidx = nl80211_create_iface(drv, ifname, nlmode, addr, - 0, nl80211_wdev_handler, + 0, nl80211_wdev_handler_info, &nonnetdev_info, use_existing); if (!nonnetdev_info.wdev_id_set || ifidx != 0) { wpa_printf(MSG_ERROR, @@ -16253,4 +16368,8 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { .nan_config_schedule = wpa_driver_nl80211_nan_config_schedule, .nan_config_peer_schedule = wpa_driver_nl80211_nan_config_peer_schedule, #endif /* CONFIG_NAN */ +#ifdef CONFIG_PR + .pd_start = nl80211_pd_start, + .pd_stop = nl80211_pd_stop, +#endif /* CONFIG_PR */ }; diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h index 0f03da642..d1133a1fe 100644 --- a/src/drivers/driver_nl80211.h +++ b/src/drivers/driver_nl80211.h @@ -287,6 +287,9 @@ struct wpa_driver_nl80211_data { #ifdef CONFIG_NAN unsigned int nan_started:1; #endif /* CONFIG_NAN */ +#ifdef CONFIG_PR + struct i802_bss *pd_bss; /* PD wdev; not in the BSS list */ +#endif /* CONFIG_PR */ }; struct nl80211_err_info { diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index 034827488..c19c834b5 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -738,6 +738,26 @@ static inline int wpa_drv_wowlan(struct wpa_supplicant *wpa_s, return wpa_s->driver->set_wowlan(wpa_s->drv_priv, triggers); } +#ifdef CONFIG_PR + +static inline int +wpa_drv_pd_start(struct wpa_supplicant *wpa_s, const u8 *addr, u8 *pd_addr) +{ + if (!wpa_s->driver->pd_start) + return -1; + return wpa_s->driver->pd_start(wpa_s->drv_priv, addr, pd_addr); +} + +static inline void +wpa_drv_pd_stop(struct wpa_supplicant *wpa_s) +{ + if (!wpa_s->driver->pd_stop) + return; + wpa_s->driver->pd_stop(wpa_s->drv_priv); +} + +#endif /* CONFIG_PR */ + static inline int wpa_drv_vendor_cmd(struct wpa_supplicant *wpa_s, int vendor_id, int subcmd, const u8 *data, size_t data_len, -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:36 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:36 +0530 Subject: [PATCH v4 04/20] nl80211: Route MLME TX via PD wdev based on source address In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-5-kavita.kavita@oss.qualcomm.com> From: Veerendranath Jakkam When sending MLME frames, check if the source address matches the active PD wdev MAC address and if so, route the transmission via the PD BSS with its associated link reference. This ensures management frames are sent on the correct wdev when a PD interface is active. Signed-off-by: Veerendranath Jakkam --- src/drivers/driver_nl80211.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 45086428e..53e24262b 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -5012,6 +5012,17 @@ static int wpa_driver_nl80211_send_mlme(struct i802_bss *bss, const u8 *data, noack, freq, no_cck, offchanok, wait_time, no_encrypt, fc, fc2str(fc), drv->nlmode); +#ifdef CONFIG_PR + /* Route MLME TX via PD wdev if source address matches */ + if (drv->pd_bss && ether_addr_equal(mgmt->sa, drv->pd_bss->addr)) { + bss = drv->pd_bss; + wpa_printf(MSG_DEBUG, + "nl80211: send_mlme - route via PD wdev sa=" MACSTR, + MAC2STR(mgmt->sa)); + link = bss->flink; + } +#endif /* CONFIG_PR */ + if ((is_sta_interface(drv->nlmode) || drv->nlmode == NL80211_IFTYPE_P2P_DEVICE) && WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT && -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:37 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:37 +0530 Subject: [PATCH v4 05/20] nl80211: Factor out global event BSS lookup and add PD wdev event routing In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-6-kavita.kavita@oss.qualcomm.com> From: Veerendranath Jakkam Introduce nl80211_get_event_bss() to centralize BSS selection for process_global_event(). The helper performs an exact ifindex match first, then checks if the wdev_id matches the active PD wdev so that events arriving on the PD wdev are delivered to pd_bss which is not part of the regular BSS list, and finally falls back to the existing bridge/wiphy/wdev matching rules. The wiphy_idx fallback applies only when wdev_id is not set so that wdev-specific events match exclusively by exact wdev_id. wdev_id is set during creation for wdev-only interfaces (P2P Device, NAN, PD). For netdev interfaces it is not set via if_add_wdevid, so fetch it from the kernel via GET_INTERFACE and store it in the BSS. This ensures events that carry NL80211_ATTR_WDEV without NL80211_ATTR_IFINDEX are routed by exact wdev_id match. Signed-off-by: Veerendranath Jakkam --- src/drivers/driver_nl80211.c | 31 ++++++++++ src/drivers/driver_nl80211_event.c | 94 ++++++++++++++++-------------- 2 files changed, 81 insertions(+), 44 deletions(-) diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 53e24262b..a8c4c61a3 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -891,6 +891,8 @@ struct wiphy_idx_data { enum nl80211_iftype nlmode; u8 *macaddr; u8 use_4addr; + u64 wdev_id; + int wdev_id_set; }; @@ -916,6 +918,11 @@ static int netdev_info_handler(struct nl_msg *msg, void *arg) if (tb[NL80211_ATTR_4ADDR]) info->use_4addr = nla_get_u8(tb[NL80211_ATTR_4ADDR]); + if (tb[NL80211_ATTR_WDEV]) { + info->wdev_id = nla_get_u64(tb[NL80211_ATTR_WDEV]); + info->wdev_id_set = 1; + } + return NL_SKIP; } @@ -3599,6 +3606,30 @@ wpa_driver_nl80211_finish_drv_init(struct i802_bss *bss, const u8 *set_addr, bss->wdev_id = drv->global->if_add_wdevid; bss->wdev_id_set = drv->global->if_add_wdevid_set; + /* + * wdev_id is set for wdev-only interfaces during creation. For netdev + * interfaces it is not set, so fetch it via GET_INTERFACE to allow + * event routing by exact wdev_id match. + */ + if (!bss->wdev_id_set) { + struct nl_msg *msg; + struct wiphy_idx_data info; + + os_memset(&info, 0, sizeof(info)); + msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_GET_INTERFACE); + if (msg && + send_and_recv_resp(drv, msg, netdev_info_handler, + &info) == 0 && + info.wdev_id_set) { + bss->wdev_id = info.wdev_id; + bss->wdev_id_set = 1; + wpa_printf(MSG_DEBUG, + "nl80211: %s wdev_id=0x%llx", + bss->ifname, + (unsigned long long) bss->wdev_id); + } + } + bss->if_dynamic = drv->ifindex == drv->global->if_add_ifindex; bss->if_dynamic = bss->if_dynamic || drv->global->if_add_wdevid_set; if (first) diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index f1a71bf96..b51ba9449 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -4856,13 +4856,51 @@ static bool nl80211_drv_in_list(struct nl80211_global *global, } +static struct i802_bss * +nl80211_get_event_bss(struct wpa_driver_nl80211_data *drv, int ifidx, + int wiphy_idx_rx, int wiphy_idx_set, + u64 wdev_id, int wdev_id_set) +{ + struct i802_bss *bss; + int wiphy_idx = -1; + + if (ifidx != -1) { + for (bss = drv->first_bss; bss; bss = bss->next) { + if (ifidx == bss->ifindex) + return bss; + } + } + +#ifdef CONFIG_PR + if (wdev_id_set && drv->pd_bss && drv->pd_bss->wdev_id_set && + wdev_id == drv->pd_bss->wdev_id) + return drv->pd_bss; +#endif /* CONFIG_PR */ + + for (bss = drv->first_bss; bss; bss = bss->next) { + if (wiphy_idx_set) + wiphy_idx = nl80211_get_wiphy_index(bss); + if ((ifidx == -1 && !wiphy_idx_set && !wdev_id_set) || + (bss->br_ifindex > 0 && + nl80211_has_ifidx(drv, bss->br_ifindex, ifidx)) || + (!wdev_id_set && wiphy_idx_set && + wiphy_idx == wiphy_idx_rx) || + (wdev_id_set && bss->wdev_id_set && + wdev_id == bss->wdev_id)) + return bss; + } + + return NULL; +} + + int process_global_event(struct nl_msg *msg, void *arg) { struct nl80211_global *global = arg; struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); struct nlattr *tb[NL80211_ATTR_MAX + 1]; struct wpa_driver_nl80211_data *drv, *tmp; - int ifidx = -1, wiphy_idx = -1, wiphy_idx_rx = -1; + int ifidx = -1, wiphy_idx_rx = -1; struct i802_bss *bss; u64 wdev_id = 0; int wdev_id_set = 0; @@ -4915,50 +4953,18 @@ int process_global_event(struct nl_msg *msg, void *arg) struct wpa_driver_nl80211_data, list) { unsigned int unique_drv_id = drv->unique_drv_id; - /* First pass: Check for exact ifindex match for events directed - * to a specific interface to avoid incorrect selection based on - * matching rules for bridged interfaces. */ - if (ifidx != -1) { - for (bss = drv->first_bss; bss; bss = bss->next) { - if (ifidx == bss->ifindex) { - do_process_drv_event(bss, gnlh->cmd, - tb); - return NL_SKIP; - } - } - } + bss = nl80211_get_event_bss(drv, ifidx, wiphy_idx_rx, + wiphy_idx_set, wdev_id, + wdev_id_set); + if (!bss) + continue; - /* Second pass: Check all other conditions including bridge */ - for (bss = drv->first_bss; bss; bss = bss->next) { - if (wiphy_idx_set) - wiphy_idx = nl80211_get_wiphy_index(bss); - if ((ifidx == -1 && !wiphy_idx_set && !wdev_id_set) || - (bss->br_ifindex > 0 && - nl80211_has_ifidx(drv, bss->br_ifindex, ifidx)) || - (wiphy_idx_set && wiphy_idx == wiphy_idx_rx) || - (wdev_id_set && bss->wdev_id_set && - wdev_id == bss->wdev_id)) { - processed = true; - do_process_drv_event(bss, gnlh->cmd, tb); - /* There are two types of events that may need - * to be delivered to multiple interfaces: - * 1. Events for a wiphy, as it can have - * multiple interfaces. - * 2. "Global" events, like - * NL80211_CMD_REG_CHANGE. - * - * Terminate early only if the event is directed - * to a specific interface or wdev. */ - if (ifidx != -1 || wdev_id_set) - return NL_SKIP; - /* The driver instance could have been removed, - * e.g., due to NL80211_CMD_RADAR_DETECT event, - * so need to stop the loop if that has - * happened. */ - if (!nl80211_drv_in_list(global, unique_drv_id)) - break; - } - } + processed = true; + do_process_drv_event(bss, gnlh->cmd, tb); + if (ifidx != -1 || wdev_id_set) + return NL_SKIP; + if (!nl80211_drv_in_list(global, unique_drv_id)) + break; } if (processed) -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:38 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:38 +0530 Subject: [PATCH v4 06/20] nl80211: Route key operations to PD wdev based on own_addr In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-7-kavita.kavita@oss.qualcomm.com> From: Veerendranath Jakkam When installing or removing keys for PD-only wdevs, the key operation must be sent using the PD wdev_id instead of the main interface ifindex. Add own_addr field to struct wpa_driver_set_key_params to identify the virtual interface for the key operation. In the nl80211 driver, if own_addr matches the active PD wdev MAC address, route the operation to the PD BSS with ifindex = 0. Update NEW_KEY, DEL_KEY, and SET_KEY message construction to use nl80211_cmd_msg() (wdev-based) instead of nl80211_ifindex_msg() when operating on the PD wdev. Populate own_addr in wpas_pr_pasn_set_keys() and wpas_pr_pasn_clear_keys() so key install and removal are correctly directed to the PD wdev. Signed-off-by: Veerendranath Jakkam --- src/drivers/driver.h | 6 ++++++ src/drivers/driver_nl80211.c | 21 ++++++++++++++++++--- wpa_supplicant/pr_supplicant.c | 2 ++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index e7ce48c80..10c899ead 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -1978,6 +1978,12 @@ struct wpa_driver_set_key_params { * ifname - Interface name (for multi-SSID/VLAN support) */ const char *ifname; + /** + * own_addr - Own MAC address identifying the virtual interface for + * the key operation, or %NULL to use the default interface (ifname) + */ + const u8 *own_addr; + /** * alg - Encryption algorithm * diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index a8c4c61a3..824125be1 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -4133,6 +4133,7 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, struct nl_msg *key_msg; int ret; int skip_set_key = 1; + int is_pd_bss = 0; const char *ifname = params->ifname; enum wpa_alg alg = params->alg; const u8 *addr = params->addr; @@ -4151,6 +4152,20 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, return 0; ifindex = if_nametoindex(ifname); + +#ifdef CONFIG_PR + /* Route key operation to PD wdev if own_addr matches */ + if (drv->pd_bss && params->own_addr && + ether_addr_equal(params->own_addr, drv->pd_bss->addr)) { + bss = drv->pd_bss; + is_pd_bss = 1; + wpa_printf(MSG_DEBUG, + "nl80211: set_key: routing to PD wdev " MACSTR, + MAC2STR(bss->addr)); + ifindex = 0; /* PD wdev has no ifindex */ + } +#endif /* CONFIG_PR */ + wpa_printf(MSG_DEBUG, "%s: ifindex=%d (%s) alg=%d addr=%p key_idx=%d " "set_tx=%d seq_len=%lu key_len=%lu key_flag=0x%x link_id=%d", __func__, ifindex, ifname, alg, addr, key_idx, set_tx, @@ -4197,7 +4212,7 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, KEY_FLAG_PAIRWISE_RX_TX_MODIFY) { wpa_printf(MSG_DEBUG, "nl80211: SET_KEY (pairwise RX/TX modify)"); - if (!nl80211_is_netdev_iftype(drv->nlmode)) + if (!nl80211_is_netdev_iftype(drv->nlmode) || is_pd_bss) msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_SET_KEY); else msg = nl80211_ifindex_msg(drv, ifindex, 0, @@ -4211,7 +4226,7 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, goto fail2; } else if (alg == WPA_ALG_NONE) { wpa_printf(MSG_DEBUG, "nl80211: DEL_KEY"); - if (!nl80211_is_netdev_iftype(drv->nlmode)) + if (!nl80211_is_netdev_iftype(drv->nlmode) || is_pd_bss) msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_DEL_KEY); else msg = nl80211_ifindex_msg(drv, ifindex, 0, @@ -4227,7 +4242,7 @@ static int wpa_driver_nl80211_set_key(struct i802_bss *bss, goto fail2; } wpa_printf(MSG_DEBUG, "nl80211: NEW_KEY"); - if (!nl80211_is_netdev_iftype(drv->nlmode)) + if (!nl80211_is_netdev_iftype(drv->nlmode) || is_pd_bss) msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_NEW_KEY); else msg = nl80211_ifindex_msg(drv, ifindex, 0, diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 494538e60..98da0a432 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -317,6 +317,7 @@ static int wpas_pr_pasn_set_keys(void *ctx, const u8 *own_addr, os_memset(¶ms, 0, sizeof(params)); params.ifname = wpa_s->ifname; + params.own_addr = own_addr; params.alg = wpa_cipher_to_alg(cipher); params.addr = peer_addr; params.key_idx = 0; @@ -351,6 +352,7 @@ static void wpas_pr_pasn_clear_keys(void *ctx, const u8 *own_addr, os_memset(¶ms, 0, sizeof(params)); params.ifname = wpa_s->ifname; + params.own_addr = own_addr; params.alg = WPA_ALG_NONE; params.addr = peer_addr; params.key_idx = 0; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:39 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:39 +0530 Subject: [PATCH v4 07/20] PR: Add dedicated PD wdev for PASN with custom MAC address In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-8-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy When PR PASN is triggered with a custom source address, create a dedicated PD virtual interface (WPA_IF_PD) carrying that MAC before the PASN exchange begins. For the initiator role, the PD wdev is created via wpas_pr_start_pd() before queuing the radio work. For the responder role, the ROC started on the existing interface receives the incoming Auth1 frame, and the PD wdev is created lazily on first M1 reception when a custom MAC was requested. When no custom MAC is provided both roles fall back to the existing interface. wpas_pr_pd_stop() tears down the PD wdev on completion or failure. A pd_addr field tracks the active PD wdev address and is used to route set_key and TX status operations to the correct interface. pr_responder_src_addr stores the requested MAC so it is available when M1 arrives. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/pr_supplicant.c | 125 +++++++++++++++++++++++++++++- wpa_supplicant/pr_supplicant.h | 5 ++ wpa_supplicant/wpa_supplicant_i.h | 10 +++ 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 98da0a432..8641eb3a5 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -532,6 +532,22 @@ void wpas_pr_deinit(struct wpa_supplicant *wpa_s) } +void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s) +{ + if (is_zero_ether_addr(wpa_s->pd_addr)) { + wpa_printf(MSG_DEBUG, "PR: pd_stop: no active PD wdev"); + return; + } + + wpa_printf(MSG_DEBUG, "PR: Stopping PD wdev addr=" MACSTR, + MAC2STR(wpa_s->pd_addr)); + + wpa_drv_pd_stop(wpa_s); + os_memset(wpa_s->pd_addr, 0, ETH_ALEN); + wpa_printf(MSG_DEBUG, "PR: PD wdev stopped"); +} + + void wpas_pr_update_dev_addr(struct wpa_supplicant *wpa_s) { pr_set_dev_addr(wpa_s->global->pr, wpa_s->own_addr); @@ -564,6 +580,41 @@ void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, #ifdef CONFIG_PASN +static int wpas_pr_start_pd(struct wpa_supplicant *wpa_s, const u8 *src_addr) +{ + u8 pd_addr[ETH_ALEN]; + + if (!src_addr || is_zero_ether_addr(src_addr)) { + wpa_printf(MSG_ERROR, "PR: Invalid MAC address for PD wdev"); + return -1; + } + + if (!is_zero_ether_addr(wpa_s->pd_addr)) { + wpa_printf(MSG_ERROR, + "PR: PD wdev already active addr=" MACSTR, + MAC2STR(wpa_s->pd_addr)); + return -1; + } + + wpa_printf(MSG_DEBUG, + "PR: Creating PD wdev with MAC " MACSTR, + MAC2STR(src_addr)); + + os_memset(pd_addr, 0, ETH_ALEN); + if (wpa_drv_pd_start(wpa_s, src_addr, pd_addr) < 0) { + wpa_printf(MSG_ERROR, "PR: Failed to create PD wdev"); + return -1; + } + + os_memcpy(wpa_s->pd_addr, pd_addr, ETH_ALEN); + pr_set_dev_addr(wpa_s->global->pr, pd_addr); + + wpa_printf(MSG_DEBUG, + "PR: PD wdev created addr=" MACSTR, MAC2STR(pd_addr)); + return 0; +} + + struct wpa_pr_pasn_auth_work { u8 peer_addr[ETH_ALEN]; u8 auth_mode; @@ -630,6 +681,7 @@ static void wpas_pr_pasn_roc_total_timeout(void *eloop_ctx, void *timeout_ctx) wpa_printf(MSG_DEBUG, "PR PASN: Total ROC budget expired, stopping responder listen"); wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); if (wpa_s->pr_roc_work) { wpa_drv_cancel_remain_on_channel(wpa_s); @@ -669,6 +721,7 @@ static void wpas_pr_pasn_roc_start_cb(struct wpa_radio_work *work, int deinit) eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s, NULL); wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); os_free(rwork); work->ctx = NULL; return; @@ -693,6 +746,7 @@ static void wpas_pr_pasn_roc_start_cb(struct wpa_radio_work *work, int deinit) eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s, NULL); wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); os_free(rwork); work->ctx = NULL; radio_work_done(work); @@ -730,6 +784,7 @@ static void wpas_pr_schedule_responder_roc(struct wpa_supplicant *wpa_s, fail: wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s, NULL); } @@ -769,6 +824,16 @@ static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx) wpas_pr_pasn_cancel_auth_work(wpa_s); wpa_s->pr_pasn_auth_work = NULL; } + + /* + * Stop the PD wdev only after radio_work_done() has + * fully returned. Calling wpas_pr_pd_stop() from inside + * the radio-work deinit callback would trigger a re-entrant + * radio_remove_works() ? radio_work_free() on the same work item, + * causing a use-after-free / SIGSEGV. + */ + wpas_pr_pd_stop(wpa_s); + wpa_printf(MSG_DEBUG, "PR: PASN timed out"); } @@ -811,6 +876,8 @@ fail: wpas_pr_pasn_free_auth_work(awork); work->ctx = NULL; radio_work_done(work); + /* Stop PD wdev after radio_work_done() to avoid use-after-free */ + wpas_pr_pd_stop(wpa_s); } @@ -824,14 +891,31 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, if (pasn_role == PR_ROLE_PASN_RESPONDER) { struct wpa_pr_pasn_roc_work *rwork; + bool has_src_addr = src_addr && !is_zero_ether_addr(src_addr); + + wpa_printf(MSG_DEBUG, + "PR PASN: Scheduling ROC at freq %d for responder role%s", + freq, has_src_addr ? " with custom MAC" : ""); rwork = os_zalloc(sizeof(*rwork)); if (!rwork) return -1; rwork->freq = freq; + if (has_src_addr) + os_memcpy(rwork->src_addr, src_addr, ETH_ALEN); + /* else rwork->src_addr stays all-zeros (no MAC filter on ROC) */ + /* + * Store state so wpas_pr_pasn_auth_rx can create the PD + * interface when M1 arrives. When no custom MAC is given the + * PD wdev is skipped and the existing interface is used. + */ wpa_s->pr_responder_mode = true; + if (has_src_addr) + os_memcpy(wpa_s->pr_responder_src_addr, src_addr, + ETH_ALEN); + /* else pr_responder_src_addr stays all-zeros */ if (!radio_add_work(wpa_s, freq, "pr-pasn-roc", 0, wpas_pr_pasn_roc_start_cb, rwork)) { @@ -839,6 +923,7 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, "PR PASN: Failed to schedule ROC for responder"); os_free(rwork); wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); return -1; } @@ -853,12 +938,26 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, return 0; } + /* + * PASN initiator role: create the PD wdev if src_addr is provided, + * then queue the radio work to send M1. + */ + if (src_addr && !is_zero_ether_addr(src_addr)) { + if (wpas_pr_start_pd(wpa_s, src_addr) < 0) { + wpa_printf(MSG_ERROR, + "PR PASN: Failed to create PD wdev"); + return -1; + } + } + wpas_pr_pasn_cancel_auth_work(wpa_s); wpa_s->pr_pasn_auth_work = NULL; awork = os_zalloc(sizeof(*awork)); - if (!awork) + if (!awork) { + wpas_pr_pd_stop(wpa_s); return -1; + } awork->freq = freq; os_memcpy(awork->peer_addr, peer_addr, ETH_ALEN); @@ -870,6 +969,7 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, if (!radio_add_work(wpa_s, freq, "pr-pasn-start-auth", 1, wpas_pr_pasn_auth_start_cb, awork)) { wpas_pr_pasn_free_auth_work(awork); + wpas_pr_pd_stop(wpa_s); return -1; } @@ -957,7 +1057,8 @@ int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data, { struct pr_data *pr = wpa_s->global->pr; - if (!wpa_s->pr_pasn_auth_work) + if (!wpa_s->pr_pasn_auth_work && + is_zero_ether_addr(wpa_s->pd_addr)) return -1; return pr_pasn_auth_tx_status(pr, data, data_len, acked); @@ -985,6 +1086,25 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s, auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction); if (auth_transaction == WLAN_AUTH_TR_SEQ_PASN_AUTH1) { + if (!is_zero_ether_addr(wpa_s->pr_responder_src_addr)) { + wpa_printf(MSG_DEBUG, + "PR PASN: M1 received in responder " + "mode, creating PD wdev"); + + if (wpas_pr_start_pd(wpa_s, + wpa_s->pr_responder_src_addr) < 0) { + wpa_printf(MSG_ERROR, + "PR PASN: Failed to create " + "PD wdev for responder"); + return -1; + } + } else { + wpa_printf(MSG_DEBUG, + "PR PASN: M1 received in responder " + "mode, no custom MAC - using " + "existing interface"); + } + /* * Cancel the total-budget timer first so it does not * fire after we have already handed off to the PASN @@ -1011,6 +1131,7 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s, /* Clear responder mode */ wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); wpa_printf(MSG_DEBUG, "PR PASN: M1 processed, proceeding with PASN"); diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index 432b5ad22..e81166ea0 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -17,6 +17,7 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s, const struct wpa_driver_capa *capa); void wpas_pr_flush(struct wpa_supplicant *wpa_s); void wpas_pr_deinit(struct wpa_supplicant *wpa_s); +void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s); void wpas_pr_update_dev_addr(struct wpa_supplicant *wpa_s); void wpas_pr_clear_dev_iks(struct wpa_supplicant *wpa_s); void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, @@ -58,6 +59,10 @@ static inline void wpas_pr_deinit(struct wpa_supplicant *wpa_s) { } +static inline void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s) +{ +} + static inline void wpas_pr_update_dev_addr(struct wpa_supplicant *wpa_s) { } diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 7dcc0db4e..02c2c9458 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -1759,6 +1759,8 @@ struct wpa_supplicant { u8 pmkid_anonce[NONCE_LEN]; bool pmkid_anonce_set; #endif /* CONFIG_PMKSA_PRIVACY */ + u8 pd_addr[ETH_ALEN]; + /** * pr_responder_mode - Waiting for PASN M1 as responder * @@ -1767,6 +1769,14 @@ struct wpa_supplicant { * is created on M1 reception. */ bool pr_responder_mode; + + /** + * pr_responder_src_addr - Source MAC address used for responder ROC + * + * Stored when responder mode is activated so that the dedicated PR + * interface can be created with the same address when M1 arrives. + */ + u8 pr_responder_src_addr[ETH_ALEN]; }; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:40 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:40 +0530 Subject: [PATCH v4 08/20] PR: Add extended ranging parameters to PASN peer structure In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-9-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Extend pr_pasn_ranging_params with EDCA, NTB, continuous ranging, and PD-specific parameters: - EDCA: burst_period, num_bursts_exp, ftms_per_burst, ftmr_retries, burst_duration - NTB: min_time_between_measurements, max_time_between_measurements, availability_window, nominal_time - PD-specific: lmr_feedback, ingress_threshold, egress_threshold, pr_suppress_results, continuous_ranging_session_time, forced_pr_freq Location: request_lci, request_civicloc Add wpas_pr_validate_ranging_request() to validate the request against device capabilities and parameter constraints before triggering PASN. Pass forced_pr_freq from params to wpas_pr_initiate_pasn_auth(). Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.h | 72 +++++++++++++++ wpa_supplicant/pr_supplicant.c | 159 ++++++++++++++++++++++++++++++++- 2 files changed, 230 insertions(+), 1 deletion(-) diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index b50a3451d..404035972 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -275,6 +275,78 @@ struct pr_pasn_ranging_params { int freq; u8 src_addr[ETH_ALEN]; enum pr_pasn_role pasn_role; + + /** + * EDCA based ranging specific parameters + * + * @burst_period: Burst period in units of 100 ms + * @num_bursts_exp: Number of bursts exponent + * @ftms_per_burst: Number of FTM frames per burst + * @ftmr_retries: Number of retries for FTM Request frame + * @burst_duration: Burst duration as defined in IEEE 2024 std + * Table 9-322?Burst Duration subfield encoding. + */ + u16 burst_period; + u8 num_bursts_exp; + u8 ftms_per_burst; + u8 ftmr_retries; + u8 burst_duration; + + /** + * NTB ranging specific parameters + * + * @min_time_between_measurements: Minimum time between two consecutive + * range measurements in units of 100 micro seconds. + * @max_time_between_measurements: Maximum time between two consecutive + * range measurements in units of 10 milli seconds, to avoid FTM + * negotiation. + * @availability_window: Duration of the Availability Window (AW) in + * units of 1 millisecond (0-255 ms). + * @nominal_time: Nominal duration between adjacent Availability Windows + * in units of milliseconds. + */ + u32 min_time_between_measurements; + u32 max_time_between_measurements; + u8 availability_window; + u32 nominal_time; + + /** + * @request_lci: Whether to request LCI + * @request_civicloc: Whether to request civic location + */ + bool request_lci; + bool request_civicloc; + + /** + * @lmr_feedback: Negotiate LMR feedback for NTB ranging. + * Only valid when NTB ranging type is set. Required for RSTA + * to report measurement results back to the initiator. + */ + bool lmr_feedback; + + /** + * @ingress_threshold: Ingress range threshold in millimeters. + * The kernel reports a measurement result when the device + * moves INTO this range. + */ + u64 ingress_threshold; + + /** + * @egress_threshold: Egress range threshold in millimeters. + * The kernel reports a measurement result when the device + * moves OUT OF this range. + */ + u64 egress_threshold; + + /** + * @pr_suppress_results: Suppress ranging results for PD requests. + * Cannot be used with range_report or lmr_feedback. + */ + bool pr_suppress_results; + + u32 continuous_ranging_session_time; + + int forced_pr_freq; }; struct pr_dev_ik { diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 8641eb3a5..1b8a01998 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -979,6 +979,122 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, } +/** + * wpas_pr_validate_ranging_request - Validate PR ranging request parameters + */ +static int wpas_pr_validate_ranging_request(struct wpa_supplicant *wpa_s, + struct pr_pasn_ranging_params *pr_pasn_params) +{ + struct pr_data *pr = wpa_s->global->pr; + struct pr_config *cfg; + bool is_edca, is_ntb, is_ista; + + if (!pr || !pr->cfg) { + wpa_printf(MSG_DEBUG, "PR: PR not initialized"); + return -1; + } + + cfg = pr->cfg; + + /* Peer address must not be all-zeros */ + if (is_zero_ether_addr(pr_pasn_params->peer_addr)) { + wpa_printf(MSG_DEBUG, "PR: Invalid peer address (all zeros)"); + return -1; + } + + /* Frequency must be set */ + if (!pr_pasn_params->freq) { + wpa_printf(MSG_DEBUG, "PR: Invalid frequency (zero)"); + return -1; + } + + /* ranging_type must have at least one valid bit and no unknown bits */ + if (!pr_pasn_params->ranging_type || + (pr_pasn_params->ranging_type & + ~(PR_EDCA_BASED_RANGING | PR_NTB_SECURE_LTF_BASED_RANGING | + PR_NTB_OPEN_BASED_RANGING))) { + wpa_printf(MSG_DEBUG, + "PR: Invalid ranging_type=0x%x", + pr_pasn_params->ranging_type); + return -1; + } + + /* ranging_role must have at least one valid bit and no unknown bits */ + if (!pr_pasn_params->ranging_role || + (pr_pasn_params->ranging_role & + ~(PR_ISTA_SUPPORT | PR_RSTA_SUPPORT))) { + wpa_printf(MSG_DEBUG, + "PR: Invalid ranging_role=0x%x", + pr_pasn_params->ranging_role); + return -1; + } + + is_edca = !!(pr_pasn_params->ranging_type & PR_EDCA_BASED_RANGING); + is_ntb = !!(pr_pasn_params->ranging_type & + (PR_NTB_SECURE_LTF_BASED_RANGING | + PR_NTB_OPEN_BASED_RANGING)); + is_ista = !!(pr_pasn_params->ranging_role & PR_ISTA_SUPPORT); + + /* Validate ranging type against device capabilities */ + if (is_edca) { + if (is_ista && !cfg->edca_ista_support) { + wpa_printf(MSG_DEBUG, + "PR: EDCA ISTA ranging not supported by device"); + return -1; + } + if (!is_ista && !cfg->edca_rsta_support) { + wpa_printf(MSG_DEBUG, + "PR: EDCA RSTA ranging not supported by device"); + return -1; + } + } + + if (is_ntb) { + if (is_ista && !cfg->ntb_ista_support) { + wpa_printf(MSG_DEBUG, + "PR: NTB ISTA ranging not supported by device"); + return -1; + } + if (!is_ista && !cfg->ntb_rsta_support) { + wpa_printf(MSG_DEBUG, + "PR: NTB RSTA ranging not supported by device"); + return -1; + } + + /* Secure LTF requires explicit device support */ + if ((pr_pasn_params->ranging_type & + PR_NTB_SECURE_LTF_BASED_RANGING) && + !cfg->secure_he_ltf) { + wpa_printf(MSG_DEBUG, + "PR: Secure HE-LTF NTB ranging not supported by device"); + return -1; + } + + /* min must not exceed max time between measurements */ + if (pr_pasn_params->min_time_between_measurements && + pr_pasn_params->max_time_between_measurements && + pr_pasn_params->min_time_between_measurements > + pr_pasn_params->max_time_between_measurements * 100) { + wpa_printf(MSG_DEBUG, + "PR: min_time_between_measurements=%u > max=%u (units: 100us vs 10ms)", + pr_pasn_params->min_time_between_measurements, + pr_pasn_params->max_time_between_measurements); + return -1; + } + } + + /* lmr_feedback is only valid for NTB ranging */ + if (pr_pasn_params->lmr_feedback && !is_ntb) { + wpa_printf(MSG_DEBUG, + "PR: lmr_feedback is only valid for NTB ranging"); + return -1; + } + + wpa_printf(MSG_DEBUG, "PR: Ranging request validation successful"); + return 0; +} + + /** * wpas_pr_pasn_trigger - Entry point to trigger PASN authentication for PR */ @@ -1004,6 +1120,13 @@ void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, return; } + /* Validate request before proceeding */ + if (wpas_pr_validate_ranging_request(wpa_s, pr_pasn_params) < 0) { + wpa_printf(MSG_DEBUG, "PR PASN: Request validation failed"); + pr_pasn_params->pr_pasn_status = PASN_STATUS_FAILURE; + return; + } + if (pr_pasn_params->action == PR_PASN_AND_RANGING) { wpa_printf(MSG_DEBUG, "PR PASN: Triggering PASN authentication for " MACSTR @@ -1026,12 +1149,46 @@ void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, os_memcpy(pr->pr_pasn_params, pr_pasn_params, sizeof(*pr->pr_pasn_params)); + /* Log EDCA parameters if applicable */ + if (pr_pasn_params->ranging_type & PR_EDCA_BASED_RANGING) { + wpa_printf(MSG_DEBUG, + "PR PASN: EDCA params - burst_period=%u num_bursts_exp=%u " + "ftms_per_burst=%u ftmr_retries=%u burst_duration=%u", + pr_pasn_params->burst_period, + pr_pasn_params->num_bursts_exp, + pr_pasn_params->ftms_per_burst, + pr_pasn_params->ftmr_retries, + pr_pasn_params->burst_duration); + } + + /* Log NTB parameters if applicable */ + if (pr_pasn_params->ranging_type & (PR_NTB_SECURE_LTF_BASED_RANGING | + PR_NTB_OPEN_BASED_RANGING)) { + wpa_printf(MSG_DEBUG, + "PR PASN: NTB params - min_time=%u max_time=%u " + "aw=%u nominal_time=%u", + pr_pasn_params->min_time_between_measurements, + pr_pasn_params->max_time_between_measurements, + pr_pasn_params->availability_window, + pr_pasn_params->nominal_time); + } + + /* Log location request parameters */ + if (pr_pasn_params->request_lci || + pr_pasn_params->request_civicloc) { + wpa_printf(MSG_DEBUG, + "PR PASN: Location requests - LCI=%d CivicLoc=%d", + pr_pasn_params->request_lci, + pr_pasn_params->request_civicloc); + } + /* Initiate PASN authentication for the peer */ if (wpas_pr_initiate_pasn_auth(wpa_s, pr_pasn_params->peer_addr, pr_pasn_params->freq, pr_pasn_params->auth_mode, pr_pasn_params->ranging_role, - pr_pasn_params->ranging_type, 0, + pr_pasn_params->ranging_type, + pr_pasn_params->forced_pr_freq, pr_pasn_params->src_addr, pr_pasn_params->pasn_role)) { wpa_printf(MSG_DEBUG, -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:41 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:41 +0530 Subject: [PATCH v4 09/20] nl80211: Add peer measurement support for proximity ranging In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-10-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add nl80211_start_peer_measurement() to build and send NL80211_CMD_PEER_MEASUREMENT_START for FTM-based proximity ranging after successful PASN authentication. The function constructs nested nl80211 attributes for channel configuration, preamble selection, and EDCA/NTB-specific FTM parameters, and routes the command via the PD wdev when src_addr matches the active PD wdev. Add wpas_pr_trigger_ranging() as the entry point called from the wpas_pr_ranging_params() callback after PASN completes. It derives center frequencies and channel width from the operating class via wpas_pr_op_class_to_chan_params() and calls the driver op. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.h | 7 + src/drivers/driver.h | 20 +++ src/drivers/driver_nl80211.c | 302 +++++++++++++++++++++++++++++++++ wpa_supplicant/driver_i.h | 16 ++ wpa_supplicant/pr_supplicant.c | 186 ++++++++++++++++++++ 5 files changed, 531 insertions(+) diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 404035972..7ba3b4dbe 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -273,6 +273,7 @@ struct pr_pasn_ranging_params { u8 pr_pasn_status; u8 auth_mode; int freq; + u32 ranging_timeout; u8 src_addr[ETH_ALEN]; enum pr_pasn_role pasn_role; @@ -347,6 +348,12 @@ struct pr_pasn_ranging_params { u32 continuous_ranging_session_time; int forced_pr_freq; + u8 ranging_op_class; + u16 channel_width; /* channel width in MHz (20/40/80/160/320) */ + u8 format_bw; + u32 center_freq1; + u32 center_freq2; + u64 cookie; }; struct pr_dev_ik { diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 10c899ead..879d0e349 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -30,6 +30,7 @@ struct nan_subscribe_params; struct nan_publish_params; +struct pr_pasn_ranging_params; #define HOSTAPD_CHAN_DISABLED 0x00000001 #define HOSTAPD_CHAN_NO_IR 0x00000002 @@ -5836,6 +5837,25 @@ struct wpa_driver_ops { struct hostapd_multi_hw_info * (*get_multi_hw_info)(void *priv, unsigned int *num_multi_hws); +#ifdef CONFIG_PR + /** + * start_peer_measurement - Start peer measurement (FTM ranging) + * @priv: Private driver interface data + * @peer_addr: Peer MAC address + * @freq: Operating frequency in MHz + * @channel: Operating channel number + * @bw: Channel bandwidth in MHz + * @params: Ranging parameters (EDCA/NTB specific) + * Returns: 0 on success, -1 on failure + * + * This function triggers peer measurement (FTM ranging) after + * successful PASN authentication for proximity ranging. + */ + int (*start_peer_measurement)(void *priv, const u8 *peer_addr, + int freq, u8 channel, int bw, + struct pr_pasn_ranging_params *params); +#endif /* CONFIG_PR */ + #ifdef CONFIG_NAN /** * nan_start - Start NAN operation diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 824125be1..ef31ce50a 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -41,6 +41,7 @@ #include "radiotap_iter.h" #include "rfkill.h" #include "driver_nl80211.h" +#include "common/proximity_ranging.h" #ifndef NETLINK_CAP_ACK @@ -9284,6 +9285,306 @@ static int i802_sta_disassoc(void *priv, const u8 *own_addr, const u8 *addr, 0, NULL, 0, 0, link_id); } +#ifdef CONFIG_PR + +static u8 get_pr_preamble(u8 ranging_type, u8 format_bw) +{ + /* Determine preamble based on ranging type and format_bw */ + if (ranging_type & PR_EDCA_BASED_RANGING) { + /* EDCA format_bw maps to different preambles */ + switch (format_bw) { + case EDCA_FORMAT_AND_BW_HT40: + return NL80211_PREAMBLE_HT; + case EDCA_FORMAT_AND_BW_VHT20: + case EDCA_FORMAT_AND_BW_VHT40: + case EDCA_FORMAT_AND_BW_VHT80: + case EDCA_FORMAT_AND_BW_VHT80P80: + case EDCA_FORMAT_AND_BW_VHT160_DUAL_LO: + case EDCA_FORMAT_AND_BW_VHT160_SINGLE_LO: + return NL80211_PREAMBLE_VHT; + default: + return NL80211_PREAMBLE_VHT; + } + } else if (ranging_type & (PR_NTB_SECURE_LTF_BASED_RANGING | PR_NTB_OPEN_BASED_RANGING)) { + /* NTB format_bw all use HE preamble */ + return NL80211_PREAMBLE_HE; + } + + /* Default fallback */ + return NL80211_PREAMBLE_VHT; +} + + +/** + * nl80211_start_peer_measurement - Send NL80211_CMD_PEER_MEASUREMENT_START + */ +static int nl80211_start_peer_measurement(void *priv, const u8 *peer_addr, + int freq, u8 channel, int bw, + struct pr_pasn_ranging_params *params) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + struct nl_msg *msg; + struct nlattr *pmsr_attr, *peers_attr, *peer_attr, *chan_attr, *req_attr, *ftm_attr; + struct nlattr *data_attr; + int ret = -1, center_freq1 = 0, center_freq2 = 0; + struct nl80211_ack_ext_arg ack_arg; + enum nl80211_chan_width width; + u32 preamble; + u64 cookie; + + if (!peer_addr || !params) { + wpa_printf(MSG_ERROR, "nl80211: Invalid parameters for peer measurement"); + return -1; + } + + /* Route via PD wdev if src_addr matches */ + if (drv->pd_bss && !is_zero_ether_addr(params->src_addr) && + ether_addr_equal(params->src_addr, drv->pd_bss->addr)) { + wpa_printf(MSG_DEBUG, + "nl80211: Peer measurement routed via PD wdev addr=" MACSTR, + MAC2STR(drv->pd_bss->addr)); + bss = drv->pd_bss; + } + + wpa_printf(MSG_DEBUG, + "nl80211: Start peer measurement for " MACSTR " freq=%d ch=%u bw=%d", + MAC2STR(peer_addr), freq, channel, bw); + + /* Use center frequencies from params (calculated in wpas_pr_trigger_ranging) */ + center_freq1 = params->center_freq1; + center_freq2 = params->center_freq2; + + /* channel_width is in MHz */ + switch (params->channel_width) { + case 20: + width = NL80211_CHAN_WIDTH_20; + break; + case 40: + width = NL80211_CHAN_WIDTH_40; + break; + case 80: + width = NL80211_CHAN_WIDTH_80; + break; + case 160: + width = NL80211_CHAN_WIDTH_160; + break; + case 320: + width = NL80211_CHAN_WIDTH_320; + break; + default: + wpa_printf(MSG_ERROR, "nl80211: Unsupported channel width %u MHz", + params->channel_width); + return -1; + } + + wpa_printf(MSG_DEBUG, + "nl80211: Using center_freq1=%u, center_freq2=%u, width=%d", + center_freq1, center_freq2, width); + /* + * PD interfaces are non-netdev (ifindex=0); use nl80211_cmd_msg() + * which emits NL80211_ATTR_WDEV instead of NL80211_ATTR_IFINDEX. + */ + msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_PEER_MEASUREMENT_START); + if (!msg) + return -1; + + /* Add timeout if specified */ + if (params->ranging_timeout > 0) { + if (nla_put_u32(msg, NL80211_ATTR_TIMEOUT, params->ranging_timeout)) + goto fail; + wpa_printf(MSG_DEBUG, "nl80211: Added timeout %u ms", params->ranging_timeout); + } + + /* Add peer measurements attribute */ + pmsr_attr = nla_nest_start(msg, NL80211_ATTR_PEER_MEASUREMENTS); + if (!pmsr_attr) + goto fail; + + /* Add peers array */ + peers_attr = nla_nest_start(msg, NL80211_PMSR_ATTR_PEERS); + if (!peers_attr) + goto fail; + + /* Add single peer */ + peer_attr = nla_nest_start(msg, 0); + if (!peer_attr) + goto fail; + + /* Peer MAC address */ + if (nla_put(msg, NL80211_PMSR_PEER_ATTR_ADDR, ETH_ALEN, peer_addr)) + goto fail; + + /* Add proximity detection request type */ + if (nla_put_u32(msg, NL80211_PMSR_PEER_ATTR_REQ_TYPE, + NL80211_PMSR_FTM_REQ_TYPE_PD)) + goto fail; + + /* Channel information */ + chan_attr = nla_nest_start(msg, NL80211_PMSR_PEER_ATTR_CHAN); + if (!chan_attr) + goto fail; + + if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, freq) || + nla_put_u32(msg, NL80211_ATTR_CHANNEL_WIDTH, width) || + nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1, center_freq1)) + goto fail; + + if (center_freq2 && nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ2, center_freq2)) + goto fail; + + nla_nest_end(msg, chan_attr); + + + /* Request attributes */ + req_attr = nla_nest_start(msg, NL80211_PMSR_PEER_ATTR_REQ); + if (!req_attr) + goto fail; + + /* Add DATA attribute as required by NL80211 specification */ + data_attr = nla_nest_start(msg, NL80211_PMSR_REQ_ATTR_DATA); + + if (!data_attr) + goto fail; + + /* FTM request */ + ftm_attr = nla_nest_start(msg, NL80211_PMSR_TYPE_FTM); + if (!ftm_attr) + goto fail; + + preamble = get_pr_preamble(params->ranging_type, params->format_bw); + if (nla_put_u32(msg, NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE, preamble)) + goto fail; + + /* Map ranging parameters to NL80211 attributes based on protocol type */ + if (params->ranging_type & PR_EDCA_BASED_RANGING) { + wpa_printf(MSG_DEBUG, "nl80211: Adding EDCA ranging parameters"); + + /* ASAP and BURST_PERIOD are always set for EDCA */ + if (nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_ASAP) || + nla_put_u16(msg, NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD, + params->burst_period)) + goto fail; + + /* Other EDCA parameters are only set for ISTA */ + if (params->ranging_role == PR_ISTA_SUPPORT) { + if (nla_put_u8(msg, NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP, + params->num_bursts_exp) || + nla_put_u8(msg, NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST, + params->ftms_per_burst) || + nla_put_u8(msg, NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES, + params->ftmr_retries) || + nla_put_u8(msg, NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION, + params->burst_duration)) + goto fail; + } + } + + if (params->ranging_type & (PR_NTB_SECURE_LTF_BASED_RANGING | PR_NTB_OPEN_BASED_RANGING)) { + wpa_printf(MSG_DEBUG, "nl80211: Adding NTB ranging parameters"); + + /* Set non-trigger based flag */ + if (nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED)) + goto fail; + + if (nla_put_u32(msg, NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME, + params->nominal_time)) + goto fail; + + /* ISTA-specific NTB parameters */ + if (params->ranging_role == PR_ISTA_SUPPORT && + (nla_put_u32(msg, NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS, + params->min_time_between_measurements) || + nla_put_u32(msg, NL80211_PMSR_FTM_REQ_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS, + params->max_time_between_measurements) || + nla_put_u32(msg, NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION, + params->availability_window) || + nla_put_u8(msg, NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST, + params->ftms_per_burst))) + goto fail; + } + + /* Location requests */ + if (params->request_lci && + nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI)) + goto fail; + + if (params->request_civicloc && + nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC)) + goto fail; + + /* RSTA mode if responder role */ + if (params->ranging_role == PR_RSTA_SUPPORT && + nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_RSTA)) + goto fail; + + /* + * LMR feedback: set for RSTA role in NTB ranging so that + * the RSTA reports its measurement results back to the ISTA. + * Only valid when NON_TRIGGER_BASED is set. + */ + if (params->ranging_role == PR_RSTA_SUPPORT && + (params->ranging_type & (PR_NTB_SECURE_LTF_BASED_RANGING | + PR_NTB_OPEN_BASED_RANGING)) && + nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK)) + goto fail; + + /* + * PD ingress/egress thresholds: only valid when + * NL80211_PMSR_PEER_ATTR_REQ_TYPE is NL80211_PMSR_FTM_REQ_TYPE_PD + * (always the case here). Thresholds are in millimeters. + */ + if (params->ingress_threshold && + nla_put_u64(msg, NL80211_PMSR_FTM_REQ_ATTR_INGRESS, + params->ingress_threshold)) + goto fail; + + if (params->egress_threshold && + nla_put_u64(msg, NL80211_PMSR_FTM_REQ_ATTR_EGRESS, + params->egress_threshold)) + goto fail; + + /* + * PD suppress results: suppress ranging results for PD requests. + */ + if (params->pr_suppress_results && + nla_put_flag(msg, NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS)) + goto fail; + + nla_nest_end(msg, ftm_attr); + nla_nest_end(msg, data_attr); + nla_nest_end(msg, req_attr); + nla_nest_end(msg, peer_attr); + nla_nest_end(msg, peers_attr); + nla_nest_end(msg, pmsr_attr); + + cookie = 0; + os_memset(&ack_arg, 0, sizeof(struct nl80211_ack_ext_arg)); + ack_arg.ext_data = &cookie; + ret = send_and_recv(drv, drv->global->nl, msg, NULL, NULL, + ack_handler_cookie, &ack_arg, NULL); + + if (ret < 0) { + wpa_printf(MSG_ERROR, + "nl80211: Peer measurement start failed: ret=%d (%s)", + ret, strerror(-ret)); + } else { + wpa_printf(MSG_DEBUG, + "nl80211: Peer measurement started successfully addr=" MACSTR + " cookie=%llu", MAC2STR(peer_addr), + (unsigned long long) cookie); + params->cookie = cookie; + } + + return ret; + +fail: + wpa_printf(MSG_ERROR, "nl80211: Failed to build peer measurement message"); + nlmsg_free(msg); + return -1; +} + +#endif /* CONFIG_PR */ static void dump_ifidx(struct wpa_driver_nl80211_data *drv) { @@ -16428,5 +16729,6 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { #ifdef CONFIG_PR .pd_start = nl80211_pd_start, .pd_stop = nl80211_pd_stop, + .start_peer_measurement = nl80211_start_peer_measurement, #endif /* CONFIG_PR */ }; diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index c19c834b5..c111224a6 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -758,6 +758,22 @@ wpa_drv_pd_stop(struct wpa_supplicant *wpa_s) #endif /* CONFIG_PR */ +#ifdef CONFIG_PR + +static inline int +wpa_drv_start_peer_measurement(struct wpa_supplicant *wpa_s, const u8 *peer, + int freq, u8 channel, int bw, + struct pr_pasn_ranging_params *params) +{ + if (!wpa_s->driver->start_peer_measurement) + return -1; + return wpa_s->driver->start_peer_measurement(wpa_s->drv_priv, peer, + freq, channel, bw, + params); +} + +#endif /* CONFIG_PR */ + static inline int wpa_drv_vendor_cmd(struct wpa_supplicant *wpa_s, int vendor_id, int subcmd, const u8 *data, size_t data_len, diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 1b8a01998..8a7f89a82 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -11,6 +11,7 @@ #include "utils/common.h" #include "utils/eloop.h" #include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" #include "common/proximity_ranging.h" #include "p2p/p2p.h" #include "wpa_supplicant_i.h" @@ -133,6 +134,94 @@ wpas_pr_ntb_is_valid_op_class(u32 bw_bitmap, u32 preamble_bitmap, } +/** + * wpas_pr_op_class_to_chan_params - Derive center freq and width from op_class + */ +static int wpas_pr_op_class_to_chan_params(u8 op_class, u8 op_channel, + u32 *center_freq1, + u32 *center_freq2, + u16 *channel_width) +{ + int control_freq, bw, offset = 0; + int half_bw, block_pos; + + if (!center_freq1 || !center_freq2 || !channel_width) + return -1; + + *center_freq1 = 0; + *center_freq2 = 0; + *channel_width = 0; + + /* 80+80 MHz: center_freq2 is unknown from primary channel alone */ + if (op_class == 130 || op_class == 135) { + wpa_printf(MSG_DEBUG, + "PR: op_class %u (80+80 MHz) not supported, " + "center_freq2 cannot be determined", op_class); + return -1; + } + + control_freq = ieee80211_chan_to_freq(NULL, op_class, op_channel); + if (control_freq < 0) { + wpa_printf(MSG_DEBUG, "PR: Invalid op_class=%u channel=%u", + op_class, op_channel); + return -1; + } + + bw = op_class_to_bandwidth(op_class); + + /* + * 2 GHz 40 MHz: channels are 5 MHz apart so the generic offset + * formula does not apply. Handle upper (op_class 83) and lower + * (op_class 84) secondary channel directions explicitly. + */ + if (op_class == 83) { + *center_freq1 = control_freq + 10; + *channel_width = 40; + return 0; + } + if (op_class == 84) { + *center_freq1 = control_freq - 10; + *channel_width = 40; + return 0; + } + + if (bw == 20) { + *center_freq1 = control_freq; + *channel_width = 20; + return 0; + } + + /* + * For 5 GHz and 6 GHz wider bandwidths, compute the primary channel's + * position (in 20 MHz steps) within its band segment, then derive + * center_freq1 using the formula: + * center_freq1 = control_freq + (bw/2 - 10) - (offset & (bw/20 - 1)) * 20 + */ + if (control_freq >= 5955) + offset = (control_freq - 5955) / 20; + else if (control_freq >= 5745) + offset = (control_freq - 5745) / 20; + else if (control_freq >= 5180) + offset = (control_freq - 5180) / 20; + + /* Distance from the primary channel to the center of the BW block */ + half_bw = bw / 2 - 10; + + /* Position of the primary channel within its BW block (in 20 MHz steps) */ + block_pos = offset & (bw / 20 - 1); + + /* Center = primary channel + half-BW offset - position within block */ + *center_freq1 = control_freq + half_bw - block_pos * 20; + *channel_width = bw; + + wpa_printf(MSG_DEBUG, + "PR: op_class=%u ch=%u -> freq=%d cf1=%u bw=%d", + op_class, op_channel, control_freq, *center_freq1, bw); + + return 0; +} + + static void wpas_pr_setup_edca_channels(struct wpa_supplicant *wpa_s, struct pr_channels *chan, @@ -283,6 +372,99 @@ static void wpas_pr_pasn_result(void *ctx, u8 role, u8 protocol_type, } +/** + * wpas_pr_trigger_ranging - Trigger FTM ranging after successful PASN auth + */ +static int wpas_pr_trigger_ranging(struct wpa_supplicant *wpa_s, + const u8 *peer_addr, int freq, u8 op_class, + u8 op_channel, u8 format_bw, + u8 protocol_type) +{ + struct pr_data *pr = wpa_s->global->pr; + struct pr_pasn_ranging_params *params; + u16 channel_width = 0; + u32 center_freq1 = 0, center_freq2 = 0; + int ret; + + if (!pr || !pr->pr_pasn_params) { + wpa_printf(MSG_DEBUG, + "PR: No ranging params available for " MACSTR, + MAC2STR(peer_addr)); + return -1; + } + + params = pr->pr_pasn_params; + + wpa_printf(MSG_DEBUG, + "PR: Triggering ranging for " MACSTR " freq=%d ch=%u", + MAC2STR(peer_addr), freq, op_channel); + + /* Derive center frequencies and channel width (MHz) from op_class */ + ret = wpas_pr_op_class_to_chan_params(op_class, op_channel, + ¢er_freq1, ¢er_freq2, + &channel_width); + if (ret < 0) { + wpa_printf(MSG_ERROR, + "PR: Failed to derive channel params for " + "op_class=%u ch=%u", op_class, op_channel); + goto fail; + } + + /* Populate ranging params */ + params->ranging_op_class = op_class; + params->channel_width = channel_width; + params->format_bw = format_bw; + params->center_freq1 = center_freq1; + params->center_freq2 = center_freq2; + + /* Validate format_bw is within enum limits before setting preamble */ + if (protocol_type & PR_EDCA_BASED_RANGING) { + if (format_bw < EDCA_FORMAT_AND_BW_VHT20 || + format_bw > EDCA_FORMAT_AND_BW_VHT160_SINGLE_LO) { + wpa_printf(MSG_ERROR, + "PR: Invalid EDCA format_bw %u (valid range: %u-%u)", + format_bw, EDCA_FORMAT_AND_BW_VHT20, + EDCA_FORMAT_AND_BW_VHT160_SINGLE_LO); + goto fail; + } + } else if (protocol_type & (PR_NTB_SECURE_LTF_BASED_RANGING | + PR_NTB_OPEN_BASED_RANGING)) { + if (format_bw > NTB_FORMAT_AND_BW_HE160_SINGLE_LO) { + wpa_printf(MSG_ERROR, + "PR: Invalid NTB format_bw %u (valid range: 0-%u)", + format_bw, + NTB_FORMAT_AND_BW_HE160_SINGLE_LO); + goto fail; + } + } else { + wpa_printf(MSG_ERROR, "PR: Unknown protocol_type %u", + protocol_type); + goto fail; + } + + wpa_printf(MSG_DEBUG, + "PR: Ranging params - op_class=%u, ch_width=%u, format_bw=%u, cf1=%u, cf2=%u", + params->ranging_op_class, params->channel_width, + params->format_bw, params->center_freq1, params->center_freq2); + + /* Call driver operation to start peer measurement */ + if (wpa_drv_start_peer_measurement(wpa_s, peer_addr, freq, op_channel, + channel_width, params) < 0) { + wpa_printf(MSG_ERROR, "PR: Failed to start peer measurement"); + goto fail; + } + + wpa_printf(MSG_DEBUG, "PR: Successfully triggered ranging measurement"); + + return 0; + +fail: + os_free(pr->pr_pasn_params); + pr->pr_pasn_params = NULL; + return -1; +} + + static void wpas_pr_ranging_params(void *ctx, const u8 *dev_addr, const u8 *peer_addr, u8 ranging_role, u8 protocol_type, u8 op_class, u8 op_channel, @@ -299,6 +481,10 @@ static void wpas_pr_ranging_params(void *ctx, const u8 *dev_addr, wpas_notify_pr_ranging_params(wpa_s, dev_addr, peer_addr, ranging_role, protocol_type, freq, op_channel, bw, format_bw); + + /* Trigger ranging measurement after successful PASN authentication */ + wpas_pr_trigger_ranging(wpa_s, peer_addr, freq, op_class, op_channel, + format_bw, protocol_type); } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:42 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:42 +0530 Subject: [PATCH v4 10/20] PR: Use session time as total ROC budget for PASN responder In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-11-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy When continuous_ranging_session_time is configured, use it as the total ROC budget for the PASN responder instead of the fixed PR_PASN_RESPONDER_ROC_DURATION default. This ensures the responder stops listening once the intended session duration expires. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/pr_supplicant.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 8a7f89a82..6b110ff18 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -1077,6 +1077,8 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, if (pasn_role == PR_ROLE_PASN_RESPONDER) { struct wpa_pr_pasn_roc_work *rwork; + struct pr_data *pr = wpa_s->global->pr; + unsigned int roc_time_ms = PR_PASN_RESPONDER_ROC_DURATION; bool has_src_addr = src_addr && !is_zero_ether_addr(src_addr); wpa_printf(MSG_DEBUG, @@ -1116,9 +1118,16 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, /* * Register the total-budget timer. When it fires it clears * pr_responder_mode so the cancel callback stops restarting - * chunks. + * chunks. Defaults to PR_PASN_RESPONDER_ROC_DURATION; overridden + * by continuous_ranging_session_time when non-zero. */ - eloop_register_timeout(0, PR_PASN_RESPONDER_ROC_DURATION * 1000, + if (pr && pr->pr_pasn_params && + pr->pr_pasn_params->continuous_ranging_session_time > 0) + roc_time_ms = + pr->pr_pasn_params->continuous_ranging_session_time; + + eloop_register_timeout(roc_time_ms / 1000, + (roc_time_ms % 1000) * 1000, wpas_pr_pasn_roc_total_timeout, wpa_s, NULL); return 0; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:43 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:43 +0530 Subject: [PATCH v4 11/20] PR: Extend PR_PASN_START ctrl iface with full ranging parameters In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-12-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Refactor wpas_ctrl_iface_pr_pasn_start() to build a pr_pasn_ranging_params struct and route through wpas_pr_pasn_trigger() which performs capability validation before initiating PASN. Extend the command to accept EDCA burst parameters (burst_duration, num_bursts_exp, ftms_per_burst, ftmr_retries, burst_duration), NTB availability window settings (min/max_time_between_meas, availability_window, nominal_time), proximity thresholds (ingress_threshold, egress_threshold), lmr_feedback, pd_suppress_results, and continuous_session_time for session lifetime control. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/ctrl_iface.c | 100 ++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 5dcb4d31b..80da807da 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -11560,64 +11560,108 @@ static int wpas_ctrl_iface_pr_pasn_start(struct wpa_supplicant *wpa_s, char *cmd) { char *token, *context = NULL; - u8 addr[ETH_ALEN]; - int freq = 0, forced_pr_freq = 0; - u8 ranging_type = 0, role = 0, auth_mode = 0; + struct pr_pasn_ranging_params params; bool got_addr = false; - u8 src_addr[ETH_ALEN], *p_src_addr = NULL; - enum pr_pasn_role pasn_role = PR_ROLE_PASN_INITIATOR; + int interval_time = 0; + + os_memset(¶ms, 0, sizeof(params)); + params.action = PR_PASN_AND_RANGING; while ((token = str_token(cmd, " ", &context))) { if (os_strncmp(token, "addr=", 5) == 0) { - if (hwaddr_aton(token + 5, addr)) + if (hwaddr_aton(token + 5, params.peer_addr)) return -1; got_addr = true; } else if (os_strcmp(token, "role=ISTA") == 0) { - role |= PR_ISTA_SUPPORT; + params.ranging_role |= PR_ISTA_SUPPORT; } else if (os_strcmp(token, "role=RSTA") == 0) { - role |= PR_RSTA_SUPPORT; + params.ranging_role |= PR_RSTA_SUPPORT; } else if (os_strcmp(token, "ranging_type=EDCA") == 0) { - ranging_type |= PR_EDCA_BASED_RANGING; + params.ranging_type |= PR_EDCA_BASED_RANGING; } else if (os_strcmp(token, "ranging_type=NTB-OPEN-PHY") == 0) { - ranging_type |= PR_NTB_OPEN_BASED_RANGING; + params.ranging_type |= PR_NTB_OPEN_BASED_RANGING; } else if (os_strcmp(token, "ranging_type=NTB-SEC-PHY") == 0) { - ranging_type |= PR_NTB_SECURE_LTF_BASED_RANGING; + params.ranging_type |= PR_NTB_SECURE_LTF_BASED_RANGING; } else if (os_strncmp(token, "freq=", 5) == 0) { - freq = atoi(token + 5); + params.freq = atoi(token + 5); } else if (os_strncmp(token, "auth=", 5) == 0) { - auth_mode = atoi(token + 5); + params.auth_mode = atoi(token + 5); } else if (os_strncmp(token, "forced_pr_freq=", 15) == 0) { - forced_pr_freq = atoi(token + 15); + params.forced_pr_freq = atoi(token + 15); + } else if (os_strncmp(token, "num_bursts_exp=", 15) == 0) { + params.num_bursts_exp = atoi(token + 15); + } else if (os_strncmp(token, "ftmr_retries=", 13) == 0) { + params.ftmr_retries = atoi(token + 13); + } else if (os_strncmp(token, "burst_duration=", 15) == 0) { + params.burst_duration = atoi(token + 15); + } else if (os_strncmp(token, "ftms_per_burst=", 15) == 0) { + params.ftms_per_burst = atoi(token + 15); + } else if (os_strncmp(token, "interval_time=", 14) == 0) { + interval_time = atoi(token + 14); + } else if (os_strncmp(token, "min_time_between_meas=", 22) == 0) { + params.min_time_between_measurements = atoi(token + 22); + } else if (os_strncmp(token, "max_time_between_meas=", 22) == 0) { + params.max_time_between_measurements = atoi(token + 22); + } else if (os_strncmp(token, "availability_window=", 20) == 0) { + params.availability_window = atoi(token + 20); + } else if (os_strcmp(token, "request_lci=1") == 0) { + params.request_lci = true; + } else if (os_strcmp(token, "request_civicloc=1") == 0) { + params.request_civicloc = true; + } else if (os_strncmp(token, "continuous_session_time=", 24) == 0) { + params.continuous_ranging_session_time = atoi(token + 24); } else if (os_strncmp(token, "src_addr=", 9) == 0) { - if (hwaddr_aton(token + 9, src_addr)) + if (hwaddr_aton(token + 9, params.src_addr)) return -1; - p_src_addr = src_addr; } else if (os_strcmp(token, "pasn_role=INITIATOR") == 0) { - pasn_role = PR_ROLE_PASN_INITIATOR; + params.pasn_role = PR_ROLE_PASN_INITIATOR; } else if (os_strcmp(token, "pasn_role=RESPONDER") == 0) { - pasn_role = PR_ROLE_PASN_RESPONDER; + params.pasn_role = PR_ROLE_PASN_RESPONDER; + } else if (os_strcmp(token, "lmr_feedback=1") == 0) { + params.lmr_feedback = true; + } else if (os_strncmp(token, "ingress_threshold=", 18) == 0) { + params.ingress_threshold = atoll(token + 18); + } else if (os_strncmp(token, "egress_threshold=", 17) == 0) { + params.egress_threshold = atoll(token + 17); + } else if (os_strcmp(token, "pd_suppress_results=1") == 0) { + params.pr_suppress_results = true; } else { wpa_printf(MSG_DEBUG, - "CTRL: PASN invalid parameter: '%s'", + "CTRL: PR_PASN_START invalid parameter: '%s'", token); return -1; } } - if (!got_addr || ranging_type == 0 || role == 0 || freq == 0) { + /* + * Map interval_time to the appropriate protocol-specific field: + * - EDCA: burst_period + * - NTB: nominal_time (nominal time between measurements) + */ + if (interval_time > 0) { + if (params.ranging_type & PR_EDCA_BASED_RANGING) + params.burst_period = interval_time; + else if (params.ranging_type & (PR_NTB_OPEN_BASED_RANGING | + PR_NTB_SECURE_LTF_BASED_RANGING)) + params.nominal_time = interval_time; + } + + if (!got_addr || params.ranging_type == 0 || params.ranging_role == 0 || + params.freq == 0) { wpa_printf(MSG_DEBUG, - "CTRL: Proximity Ranging PASN missing parameter"); + "CTRL: PR_PASN_START missing parameter"); return -1; } wpa_printf(MSG_DEBUG, - "CTRL: PR PASN params: ranging type=0x%x, role=0x%x, pasn_role=%d, auth_mode=%d, forced pr freq=%d, addr=" - MACSTR " src_addr=" MACSTR, - ranging_type, role, pasn_role, auth_mode, forced_pr_freq, - MAC2STR(addr), MAC2STR(p_src_addr ? p_src_addr : addr)); - return wpas_pr_initiate_pasn_auth(wpa_s, addr, freq, auth_mode, role, - ranging_type, forced_pr_freq, - p_src_addr, pasn_role); + "CTRL: PR_PASN_START params: ranging type=0x%x, role=0x%x," + " auth_mode=%d, freq=%d, addr=" MACSTR " src_addr=" MACSTR " pasn_role=%d", + params.ranging_type, params.ranging_role, params.auth_mode, + params.freq, MAC2STR(params.peer_addr), + MAC2STR(params.src_addr), params.pasn_role); + + wpas_pr_pasn_trigger(wpa_s, ¶ms); + return 0; } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:44 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:44 +0530 Subject: [PATCH v4 12/20] nl80211: Add dedicated PR ranging socket and stop op In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-13-kavita.kavita@oss.qualcomm.com> From: Veerendranath Jakkam Create a dedicated netlink socket (nl_pr) in nl80211_global for PR peer measurements. The socket is created when start_peer_measurement() is called, used to send NL80211_CMD_PEER_MEASUREMENT_START, and registered with eloop to receive RESULT and COMPLETE events. Add stop_peer_measurement() driver op implemented by nl80211_stop_peer_measurement() which destroys the nl_pr socket and unregisters it from eloop. Also add cleanup in nl80211_global_deinit() for the case where stop was never called. Signed-off-by: Veerendranath Jakkam --- src/drivers/driver.h | 9 +++++++ src/drivers/driver_nl80211.c | 46 +++++++++++++++++++++++++++++++++++- src/drivers/driver_nl80211.h | 4 ++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 879d0e349..e8088d7c5 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -5854,6 +5854,15 @@ struct wpa_driver_ops { int (*start_peer_measurement)(void *priv, const u8 *peer_addr, int freq, u8 channel, int bw, struct pr_pasn_ranging_params *params); + + /** + * stop_peer_measurement - Stop peer measurement and destroy ranging socket + * @priv: Private driver interface data + * + * Unregisters the ranging socket from eloop and frees all associated + * resources. Safe to call when no ranging session is active (no-op). + */ + void (*stop_peer_measurement)(void *priv); #endif /* CONFIG_PR */ #ifdef CONFIG_NAN diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index ef31ce50a..d227a32b3 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -9338,6 +9338,20 @@ static int nl80211_start_peer_measurement(void *priv, const u8 *peer_addr, return -1; } + /* Create dedicated ranging socket if not already created */ + if (drv->global->nl_pr) { + wpa_printf(MSG_ERROR, + "nl80211: PR ranging socket already in use"); + return -1; + } + + drv->global->nl_pr = nl_create_handle(drv->global->nl_cb, "pr"); + if (!drv->global->nl_pr) { + wpa_printf(MSG_ERROR, + "nl80211: Failed to create PR ranging socket"); + return -1; + } + /* Route via PD wdev if src_addr matches */ if (drv->pd_bss && !is_zero_ether_addr(params->src_addr) && ether_addr_equal(params->src_addr, drv->pd_bss->addr)) { @@ -9561,19 +9575,29 @@ static int nl80211_start_peer_measurement(void *priv, const u8 *peer_addr, cookie = 0; os_memset(&ack_arg, 0, sizeof(struct nl80211_ack_ext_arg)); ack_arg.ext_data = &cookie; - ret = send_and_recv(drv, drv->global->nl, msg, NULL, NULL, + ret = send_and_recv(drv, drv->global->nl_pr, msg, NULL, NULL, ack_handler_cookie, &ack_arg, NULL); if (ret < 0) { wpa_printf(MSG_ERROR, "nl80211: Peer measurement start failed: ret=%d (%s)", ret, strerror(-ret)); + nl_destroy_handles(&drv->global->nl_pr); } else { wpa_printf(MSG_DEBUG, "nl80211: Peer measurement started successfully addr=" MACSTR " cookie=%llu", MAC2STR(peer_addr), (unsigned long long) cookie); params->cookie = cookie; + /* + * Register the PR socket with eloop so that + * NL80211_CMD_PEER_MEASUREMENT_RESULT and + * NL80211_CMD_PEER_MEASUREMENT_COMPLETE events are delivered + * to the existing nl80211 event handler. + */ + nl80211_register_eloop_read(&drv->global->nl_pr, + wpa_driver_nl80211_event_receive, + drv->global->nl_cb, 1); } return ret; @@ -9581,9 +9605,23 @@ static int nl80211_start_peer_measurement(void *priv, const u8 *peer_addr, fail: wpa_printf(MSG_ERROR, "nl80211: Failed to build peer measurement message"); nlmsg_free(msg); + nl_destroy_handles(&drv->global->nl_pr); return -1; } + +static void nl80211_stop_peer_measurement(void *priv) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + + if (!drv->global->nl_pr) + return; + + wpa_printf(MSG_DEBUG, "nl80211: Stopping PR ranging socket"); + nl80211_destroy_eloop_handle(&drv->global->nl_pr, 1); +} + #endif /* CONFIG_PR */ static void dump_ifidx(struct wpa_driver_nl80211_data *drv) @@ -11362,6 +11400,11 @@ static void nl80211_global_deinit(void *priv) nl_cb_put(global->nl_cb); +#ifdef CONFIG_PR + if (global->nl_pr) + nl80211_destroy_eloop_handle(&global->nl_pr, 1); +#endif /* CONFIG_PR */ + if (global->ioctl_sock >= 0) close(global->ioctl_sock); @@ -16730,5 +16773,6 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { .pd_start = nl80211_pd_start, .pd_stop = nl80211_pd_stop, .start_peer_measurement = nl80211_start_peer_measurement, + .stop_peer_measurement = nl80211_stop_peer_measurement, #endif /* CONFIG_PR */ }; diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h index d1133a1fe..9c871ce18 100644 --- a/src/drivers/driver_nl80211.h +++ b/src/drivers/driver_nl80211.h @@ -52,6 +52,10 @@ struct nl80211_global { /* Dedicated socket for NAN interface creation and events */ struct nl_sock *nl_nan; #endif /* CONFIG_NAN */ +#ifdef CONFIG_PR + /* Dedicated socket for PR peer measurement commands and events */ + struct nl_sock *nl_pr; +#endif /* CONFIG_PR */ }; struct nl80211_wiphy_data { -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:45 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:45 +0530 Subject: [PATCH v4 13/20] wpa_supplicant: Add FTM peer measurement result handling In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-14-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add support for receiving and processing FTM ranging measurement results from the kernel via NL80211_CMD_PEER_MEASUREMENT_RESULT. Add EVENT_PEER_MEASUREMENT_RESULT event and peer_measurement_result structure containing RTT, distance, RSSI, burst parameters, LCI, civic location, and NTB-specific fields. nl80211_peer_measurement_result_event() parses the netlink message and fires the event. wpas_pr_measurement_result() validates the cookie against the pending session and forwards all results including failures to the upper layer via PR-PEER-MEASUREMENT ctrl event. On final result, ranging_final_received is set to block any further results for the session. Session cleanup is handled separately on the COMPLETE event. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.c | 1 + src/common/proximity_ranging.h | 3 + src/common/wpa_ctrl.h | 3 + src/drivers/driver.h | 63 +++++++ src/drivers/driver_common.c | 1 + src/drivers/driver_nl80211_event.c | 257 +++++++++++++++++++++++++++++ wpa_supplicant/events.c | 7 + wpa_supplicant/notify.c | 28 ++++ wpa_supplicant/notify.h | 2 + wpa_supplicant/pr_supplicant.c | 47 ++++++ wpa_supplicant/pr_supplicant.h | 8 + 11 files changed, 420 insertions(+) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index 6b40628a8..ba56ab519 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -151,6 +151,7 @@ void pr_deinit(struct pr_data *pr) #ifdef CONFIG_PASN os_free(pr->pr_pasn_params); pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; pasn_initiator_pmksa_cache_deinit(pr->initiator_pmksa); pasn_responder_pmksa_cache_deinit(pr->responder_pmksa); diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 7ba3b4dbe..327b6af06 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -601,6 +601,9 @@ struct pr_data { /* PR PASN request tracking - similar to pasn_params in wpa_supplicant */ struct pr_pasn_ranging_params *pr_pasn_params; + + /* Set when final measurement result received; blocks further results */ + bool ranging_final_received; }; /* PR Device Identity Resolution Attribute parameters */ diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index cb509fc1c..e136a61bf 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -509,6 +509,9 @@ extern "C" { /* Proximity Ranging parameters to use in ranging */ #define PR_RANGING_PARAMS "PR-RANGING-PARAMS " +/* Proximity Ranging measurement result */ +#define PR_EVENT_PEER_MEASUREMENT "PR-PEER-MEASUREMENT " + /* BSS command information masks */ #define WPA_BSS_MASK_ALL 0xFFFDFFFF diff --git a/src/drivers/driver.h b/src/drivers/driver.h index e8088d7c5..55d71a7e4 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -6650,6 +6650,13 @@ enum wpa_event_type { * activities). */ EVENT_NAN_CHAN_EVACUATION, + + /** + * EVENT_PEER_MEASUREMENT_RESULT - Ranging measurement result + * + * This event is used to indicate a ranging measurement result. + */ + EVENT_PEER_MEASUREMENT_RESULT, }; @@ -7701,6 +7708,62 @@ union wpa_event_data { struct nan_chan_evacuation_info { int freq; } nan_chan_evacuation_info; + + /** + * struct peer_measurement_result - Ranging measurement result + */ + struct peer_measurement_result { + u8 addr[ETH_ALEN]; + u32 status; + u64 host_time; + u64 ap_tsf; + u8 final; + u64 cookie; + + /** + * struct peer_measurement_ftm_result - + * FTM-specific measurement results + */ + struct peer_measurement_ftm_result { + u8 fail; + u32 fail_reason; + u32 burst_index; + u32 num_ftmr_attempts; + u32 num_ftmr_successes; + u32 busy_retry_time; + u32 num_bursts_exp; + u32 burst_duration; + u32 ftms_per_burst; + s32 rssi_avg; + s32 rssi_spread; + s64 rtt_avg; + u64 rtt_variance; + u64 rtt_spread; + s64 dist_avg; + u64 dist_variance; + u64 dist_spread; + const u8 *lci; + size_t lci_len; + const u8 *civicloc; + size_t civicloc_len; + /* Additional FTM parameters */ + u32 burst_period; + u32 tx_ltf_repetition_count; + u32 rx_ltf_repetition_count; + u32 max_time_between_measurements; + u32 min_time_between_measurements; + u32 num_tx_spatial_streams; + u32 num_rx_spatial_streams; + u32 nominal_time; + u8 availability_window; + u32 band_width; + u32 preamble; + u8 is_delayed_lmr; + /* Flag indicating if FTM data is present */ + u8 has_data; + } ftm; + + } peer_measurement_result; }; /** diff --git a/src/drivers/driver_common.c b/src/drivers/driver_common.c index f04f737c0..170821423 100644 --- a/src/drivers/driver_common.c +++ b/src/drivers/driver_common.c @@ -108,6 +108,7 @@ const char * event_to_string(enum wpa_event_type event) E2S(NAN_SCHED_UPDATE_DONE); E2S(NAN_ULW_UPDATE); E2S(NAN_CHAN_EVACUATION); + E2S(PEER_MEASUREMENT_RESULT); } return "UNKNOWN"; diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index b51ba9449..8023ec6e2 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -4258,6 +4258,258 @@ static void nl80211_assoc_comeback(struct wpa_driver_nl80211_data *drv, MAC2STR((u8 *) nla_data(mac)), nla_get_u32(timeout)); } +#ifdef CONFIG_PR + +static void +nl80211_parse_peer_ftm_result(struct peer_measurement_ftm_result *ftm, + struct nlattr *ftm_data) +{ + struct nlattr *ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MAX + 1]; + + if (nla_parse_nested(ftm_tb, NL80211_PMSR_FTM_RESP_ATTR_MAX, + ftm_data, NULL)) + return; + + ftm->has_data = 1; + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON]) { + ftm->fail = 1; + ftm->fail_reason = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON]); + wpa_printf(MSG_DEBUG, + "nl80211: Ranging failed with reason %u", + ftm->fail_reason); + return; + } + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_INDEX]) + ftm->burst_index = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_INDEX]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_ATTEMPTS]) + ftm->num_ftmr_attempts = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_ATTEMPTS]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_SUCCESSES]) + ftm->num_ftmr_successes = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_SUCCESSES]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME]) + ftm->busy_retry_time = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_BURSTS_EXP]) + ftm->num_bursts_exp = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_BURSTS_EXP]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_DURATION]) + ftm->burst_duration = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_DURATION]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FTMS_PER_BURST]) + ftm->ftms_per_burst = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_FTMS_PER_BURST]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_AVG]) + ftm->rssi_avg = + nla_get_s32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_AVG]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_SPREAD]) + ftm->rssi_spread = + nla_get_s32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RSSI_SPREAD]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_AVG]) + ftm->rtt_avg = + nla_get_s64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_AVG]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_VARIANCE]) + ftm->rtt_variance = + nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_VARIANCE]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_SPREAD]) + ftm->rtt_spread = + nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RTT_SPREAD]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_AVG]) + ftm->dist_avg = + nla_get_s64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_AVG]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_VARIANCE]) + ftm->dist_variance = + nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_VARIANCE]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_SPREAD]) + ftm->dist_spread = + nla_get_u64(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_DIST_SPREAD]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI]) { + ftm->lci = os_memdup( + nla_data(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI]), + nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI])); + ftm->lci_len = nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_LCI]); + } + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]) { + ftm->civicloc = os_memdup( + nla_data(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]), + nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC])); + ftm->civicloc_len = + nla_len(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC]); + } + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD]) + ftm->burst_period = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_TX_LTF_REPETITION_COUNT]) + ftm->tx_ltf_repetition_count = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_TX_LTF_REPETITION_COUNT]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RX_LTF_REPETITION_COUNT]) + ftm->rx_ltf_repetition_count = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_RX_LTF_REPETITION_COUNT]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS]) + ftm->max_time_between_measurements = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS]) + ftm->min_time_between_measurements = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_TX_SPATIAL_STREAMS]) + ftm->num_tx_spatial_streams = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_TX_SPATIAL_STREAMS]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_RX_SPATIAL_STREAMS]) + ftm->num_rx_spatial_streams = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NUM_RX_SPATIAL_STREAMS]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NOMINAL_TIME]) + ftm->nominal_time = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_NOMINAL_TIME]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_AVAILABILITY_WINDOW]) + ftm->availability_window = + nla_get_u8(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_AVAILABILITY_WINDOW]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CHANNEL_WIDTH]) + ftm->band_width = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_CHANNEL_WIDTH]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_PREAMBLE]) + ftm->preamble = + nla_get_u32(ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_PREAMBLE]); + + if (ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR]) + ftm->is_delayed_lmr = + !!ftm_tb[NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR]; +} + + +static void nl80211_peer_measurement_result_event(struct i802_bss *bss, + struct nlattr **tb) +{ + union wpa_event_data data; + struct nlattr *pmsr[NL80211_PMSR_ATTR_MAX + 1]; + struct nlattr *peer; + u64 cookie = 0; + int rem; + struct nla_policy pmsr_policy[NL80211_PMSR_ATTR_MAX + 1] = { + [NL80211_PMSR_ATTR_PEERS] = { .type = NLA_NESTED }, + }; + struct nla_policy peer_policy[NL80211_PMSR_PEER_ATTR_MAX + 1] = { + [NL80211_PMSR_PEER_ATTR_ADDR] = { + .minlen = ETH_ALEN, + .maxlen = ETH_ALEN, + }, + [NL80211_PMSR_PEER_ATTR_RESP] = { .type = NLA_NESTED }, + }; + struct nla_policy resp_policy[NL80211_PMSR_RESP_ATTR_MAX + 1] = { + [NL80211_PMSR_RESP_ATTR_DATA] = { .type = NLA_NESTED }, + [NL80211_PMSR_RESP_ATTR_STATUS] = { .type = NLA_U32 }, + [NL80211_PMSR_RESP_ATTR_HOST_TIME] = { .type = NLA_U64 }, + [NL80211_PMSR_RESP_ATTR_AP_TSF] = { .type = NLA_U64 }, + [NL80211_PMSR_RESP_ATTR_FINAL] = { .type = NLA_FLAG }, + }; + + os_memset(&data, 0, sizeof(data)); + if (tb[NL80211_ATTR_COOKIE]) { + cookie = nla_get_u64(tb[NL80211_ATTR_COOKIE]); + wpa_printf(MSG_DEBUG, + "nl80211: PR: Peer measurement cookie: %llu", + (unsigned long long) cookie); + } + + if (!tb[NL80211_ATTR_PEER_MEASUREMENTS] || + nla_parse_nested(pmsr, NL80211_PMSR_ATTR_MAX, + tb[NL80211_ATTR_PEER_MEASUREMENTS], pmsr_policy)) + return; + + if (!pmsr[NL80211_PMSR_ATTR_PEERS]) + return; + + nla_for_each_nested(peer, pmsr[NL80211_PMSR_ATTR_PEERS], rem) { + struct nlattr *peer_tb[NL80211_PMSR_PEER_ATTR_MAX + 1]; + struct nlattr *resp_tb[NL80211_PMSR_RESP_ATTR_MAX + 1]; + + if (nla_parse_nested(peer_tb, NL80211_PMSR_PEER_ATTR_MAX, + peer, peer_policy)) + continue; + + if (!peer_tb[NL80211_PMSR_PEER_ATTR_ADDR] || + !peer_tb[NL80211_PMSR_PEER_ATTR_RESP]) + continue; + + os_memset(&data.peer_measurement_result, 0, + sizeof(data.peer_measurement_result)); + data.peer_measurement_result.cookie = cookie; + + os_memcpy(data.peer_measurement_result.addr, + nla_data(peer_tb[NL80211_PMSR_PEER_ATTR_ADDR]), + ETH_ALEN); + + if (nla_parse_nested(resp_tb, NL80211_PMSR_RESP_ATTR_MAX, + peer_tb[NL80211_PMSR_PEER_ATTR_RESP], + resp_policy)) + continue; + + if (resp_tb[NL80211_PMSR_RESP_ATTR_STATUS]) + data.peer_measurement_result.status = + nla_get_u32(resp_tb[NL80211_PMSR_RESP_ATTR_STATUS]); + + if (resp_tb[NL80211_PMSR_RESP_ATTR_HOST_TIME]) + data.peer_measurement_result.host_time = + nla_get_u64(resp_tb[NL80211_PMSR_RESP_ATTR_HOST_TIME]); + + if (resp_tb[NL80211_PMSR_RESP_ATTR_AP_TSF]) + data.peer_measurement_result.ap_tsf = + nla_get_u64(resp_tb[NL80211_PMSR_RESP_ATTR_AP_TSF]); + + if (resp_tb[NL80211_PMSR_RESP_ATTR_FINAL]) + data.peer_measurement_result.final = 1; + + if (resp_tb[NL80211_PMSR_RESP_ATTR_DATA]) { + struct nlattr *data_type_tb[NL80211_PMSR_TYPE_MAX + 1]; + + if (nla_parse_nested(data_type_tb, NL80211_PMSR_TYPE_MAX, + resp_tb[NL80211_PMSR_RESP_ATTR_DATA], + NULL)) + continue; + + if (data_type_tb[NL80211_PMSR_TYPE_FTM]) + nl80211_parse_peer_ftm_result(&data.peer_measurement_result.ftm, + data_type_tb[NL80211_PMSR_TYPE_FTM]); + } + + wpa_supplicant_event(bss->ctx, EVENT_PEER_MEASUREMENT_RESULT, &data); + /* Free deep-copied LCI/civic location data */ + os_free((void *) data.peer_measurement_result.ftm.lci); + os_free((void *) data.peer_measurement_result.ftm.civicloc); + } +} + +#endif /* CONFIG_PR */ #ifdef CONFIG_IEEE80211AX @@ -4833,6 +5085,11 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd, case NL80211_CMD_INCUMBENT_SIGNAL_DETECT: nl80211_incumbt_sig_intf_event(bss, tb); break; +#ifdef CONFIG_PR + case NL80211_CMD_PEER_MEASUREMENT_RESULT: + nl80211_peer_measurement_result_event(bss, tb); + break; +#endif /* CONFIG_PR */ default: wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Ignored unknown event " "(cmd=%d)", cmd); diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index baae94b73..64808c19f 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -7672,6 +7672,13 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, if (data) wpas_setup_link_reconfig(wpa_s, &data->reconfig_info); break; + case EVENT_PEER_MEASUREMENT_RESULT: +#ifdef CONFIG_PR + if (data) + wpas_pr_measurement_result(wpa_s, + &data->peer_measurement_result); +#endif /* CONFIG_PR */ + break; #ifdef CONFIG_NAN case EVENT_NAN_CLUSTER_JOIN: wpas_nan_cluster_join(wpa_s, data->nan_cluster_join_info.bssid, diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c index 234f76f5e..876b2c151 100644 --- a/wpa_supplicant/notify.c +++ b/wpa_supplicant/notify.c @@ -1576,4 +1576,32 @@ void wpas_notify_pr_ranging_params(struct wpa_supplicant *wpa_s, protocol_type, freq, channel, bw, format_bw); } + +void wpas_notify_pr_measurement_result(struct wpa_supplicant *wpa_s, + const struct peer_measurement_result *result) +{ + char rtt[32] = ""; + char dist[32] = ""; + + if (result->ftm.fail) { + wpa_msg_global(wpa_s, MSG_INFO, PR_EVENT_PEER_MEASUREMENT + "addr=" MACSTR " status=%u burst_index=%u fail=1 fail_reason=%u", + MAC2STR(result->addr), result->status, + result->ftm.burst_index, result->ftm.fail_reason); + return; + } + + if (result->ftm.rtt_avg || result->ftm.has_data) + os_snprintf(rtt, sizeof(rtt), " rtt_avg=%lld", + (long long) result->ftm.rtt_avg); + if (result->ftm.dist_avg || result->ftm.has_data) + os_snprintf(dist, sizeof(dist), " dist_avg=%lld", + (long long) result->ftm.dist_avg); + + wpa_msg_global(wpa_s, MSG_INFO, PR_EVENT_PEER_MEASUREMENT + "addr=" MACSTR " status=%u burst_index=%u%s%s", + MAC2STR(result->addr), result->status, + result->ftm.burst_index, rtt, dist); +} + #endif /* CONFIG_PR */ diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h index a9fdfdecd..f2014f3cb 100644 --- a/wpa_supplicant/notify.h +++ b/wpa_supplicant/notify.h @@ -218,6 +218,8 @@ void wpas_notify_pr_ranging_params(struct wpa_supplicant *wpa_s, const u8 *dev_addr, const u8 *peer_addr, u8 role, u8 protocol, int freq, int channel, int bw, int format_bw); +void wpas_notify_pr_measurement_result(struct wpa_supplicant *wpa_s, + const struct peer_measurement_result *result); void wpas_notify_nan_bootstrap_request(struct wpa_supplicant *wpa_s, const u8 *peer_addr, u16 pbm, int handle, u8 requestor_instance_id); diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 6b110ff18..5085df977 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -461,6 +461,7 @@ static int wpas_pr_trigger_ranging(struct wpa_supplicant *wpa_s, fail: os_free(pr->pr_pasn_params); pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; return -1; } @@ -764,6 +765,51 @@ void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, } +void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, + struct peer_measurement_result *result) +{ + struct pr_data *pr = wpa_s->global->pr; + + if (!result) { + wpa_printf(MSG_ERROR, "PR: Invalid measurement result"); + return; + } + + /* Drop results after final has been received */ + if (pr && pr->ranging_final_received) { + wpa_printf(MSG_DEBUG, + "PR: Ignoring result after final for " MACSTR, + MAC2STR(result->addr)); + return; + } + + /* Validate cookie if we have a pending ranging request */ + if (pr && pr->pr_pasn_params && result->cookie != 0) { + if (pr->pr_pasn_params->cookie != result->cookie) { + wpa_printf(MSG_WARNING, + "PR: Cookie mismatch - expected %llu, got %llu. Ignoring result.", + (unsigned long long) pr->pr_pasn_params->cookie, + (unsigned long long) result->cookie); + return; + } + } + + /* Forward result to upper layer ? includes failures and final */ + if (result->ftm.has_data || result->ftm.fail) + wpas_notify_pr_measurement_result(wpa_s, result); + + /* After final result, mark session done ? no more results accepted */ + if (result->final) { + wpa_printf(MSG_DEBUG, + "PR: Final result received for " MACSTR + " ? no further results will be processed", + MAC2STR(result->addr)); + if (pr) + pr->ranging_final_received = true; + } +} + + #ifdef CONFIG_PASN static int wpas_pr_start_pd(struct wpa_supplicant *wpa_s, const u8 *src_addr) @@ -1393,6 +1439,7 @@ void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, pr_pasn_params->pr_pasn_status = PASN_STATUS_FAILURE; os_free(pr->pr_pasn_params); pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; return; } } else { diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index e81166ea0..991a8dd5e 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -41,6 +41,8 @@ void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, unsigned int freq); void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, struct pr_pasn_ranging_params *pr_pasn_params); +void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, + struct peer_measurement_result *result); #else /* CONFIG_PR */ @@ -119,6 +121,12 @@ wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, { } +static inline void +wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, + struct peer_measurement_result *result) +{ +} + #endif /* CONFIG_PR */ #endif /* PR_SUPPLICANT_H */ -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:46 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:46 +0530 Subject: [PATCH v4 14/20] PR: Handle peer measurement complete event In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-15-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add support for NL80211_CMD_PEER_MEASUREMENT_COMPLETE which signals the entire ranging session has ended. Add EVENT_PEER_MEASUREMENT_COMPLETE event and peer_measurement_complete struct. wpas_pr_measurement_complete() validates the cookie, emits PR-RANGING-COMPLETE ctrl event, frees pr_pasn_params, resets ranging_final_received, and stops the PD wdev. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/wpa_ctrl.h | 3 +++ src/drivers/driver.h | 15 +++++++++++++ src/drivers/driver_common.c | 1 + src/drivers/driver_nl80211_event.c | 26 +++++++++++++++++++++ wpa_supplicant/events.c | 7 ++++++ wpa_supplicant/notify.c | 7 ++++++ wpa_supplicant/notify.h | 2 ++ wpa_supplicant/pr_supplicant.c | 36 ++++++++++++++++++++++++++++++ wpa_supplicant/pr_supplicant.h | 8 +++++++ 9 files changed, 105 insertions(+) diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index e136a61bf..69f710185 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -512,6 +512,9 @@ extern "C" { /* Proximity Ranging measurement result */ #define PR_EVENT_PEER_MEASUREMENT "PR-PEER-MEASUREMENT " +/* Proximity Ranging measurement session complete */ +#define PR_EVENT_RANGING_COMPLETE "PR-RANGING-COMPLETE " + /* BSS command information masks */ #define WPA_BSS_MASK_ALL 0xFFFDFFFF diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 55d71a7e4..c95d3767e 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -6657,6 +6657,14 @@ enum wpa_event_type { * This event is used to indicate a ranging measurement result. */ EVENT_PEER_MEASUREMENT_RESULT, + + /** + * EVENT_PEER_MEASUREMENT_COMPLETE - Ranging session fully done + * + * This event is used to notify that the entire peer + * measurement session identified by the cookie has ended. + */ + EVENT_PEER_MEASUREMENT_COMPLETE, }; @@ -7764,6 +7772,13 @@ union wpa_event_data { } ftm; } peer_measurement_result; + + /** + * struct peer_measurement_complete - Ranging session complete + */ + struct peer_measurement_complete { + u64 cookie; + } peer_measurement_complete; }; /** diff --git a/src/drivers/driver_common.c b/src/drivers/driver_common.c index 170821423..812305cae 100644 --- a/src/drivers/driver_common.c +++ b/src/drivers/driver_common.c @@ -109,6 +109,7 @@ const char * event_to_string(enum wpa_event_type event) E2S(NAN_ULW_UPDATE); E2S(NAN_CHAN_EVACUATION); E2S(PEER_MEASUREMENT_RESULT); + E2S(PEER_MEASUREMENT_COMPLETE); } return "UNKNOWN"; diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index 8023ec6e2..55076e16f 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -4407,6 +4407,29 @@ nl80211_parse_peer_ftm_result(struct peer_measurement_ftm_result *ftm, } +static void nl80211_peer_measurement_complete_event(struct i802_bss *bss, + struct nlattr **tb) +{ + union wpa_event_data data; + u64 cookie = 0; + + os_memset(&data, 0, sizeof(data)); + + if (tb[NL80211_ATTR_COOKIE]) { + cookie = nla_get_u64(tb[NL80211_ATTR_COOKIE]); + wpa_printf(MSG_DEBUG, + "nl80211: PR: Peer measurement complete cookie: %llu", + (unsigned long long) cookie); + } else { + wpa_printf(MSG_DEBUG, + "nl80211: PR: Peer measurement complete (no cookie)"); + } + + data.peer_measurement_complete.cookie = cookie; + wpa_supplicant_event(bss->ctx, EVENT_PEER_MEASUREMENT_COMPLETE, &data); +} + + static void nl80211_peer_measurement_result_event(struct i802_bss *bss, struct nlattr **tb) { @@ -5089,6 +5112,9 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd, case NL80211_CMD_PEER_MEASUREMENT_RESULT: nl80211_peer_measurement_result_event(bss, tb); break; + case NL80211_CMD_PEER_MEASUREMENT_COMPLETE: + nl80211_peer_measurement_complete_event(bss, tb); + break; #endif /* CONFIG_PR */ default: wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Ignored unknown event " diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 64808c19f..44ee15744 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -7677,6 +7677,13 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, if (data) wpas_pr_measurement_result(wpa_s, &data->peer_measurement_result); +#endif /* CONFIG_PR */ + break; + case EVENT_PEER_MEASUREMENT_COMPLETE: +#ifdef CONFIG_PR + if (data) + wpas_pr_measurement_complete(wpa_s, + &data->peer_measurement_complete); #endif /* CONFIG_PR */ break; #ifdef CONFIG_NAN diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c index 876b2c151..f7d0fef3d 100644 --- a/wpa_supplicant/notify.c +++ b/wpa_supplicant/notify.c @@ -1604,4 +1604,11 @@ void wpas_notify_pr_measurement_result(struct wpa_supplicant *wpa_s, result->ftm.burst_index, rtt, dist); } + +void wpas_notify_pr_ranging_complete(struct wpa_supplicant *wpa_s, u64 cookie) +{ + wpa_msg_global(wpa_s, MSG_INFO, PR_EVENT_RANGING_COMPLETE + "cookie=%llu", (unsigned long long) cookie); +} + #endif /* CONFIG_PR */ diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h index f2014f3cb..c16b5e163 100644 --- a/wpa_supplicant/notify.h +++ b/wpa_supplicant/notify.h @@ -220,6 +220,8 @@ void wpas_notify_pr_ranging_params(struct wpa_supplicant *wpa_s, int bw, int format_bw); void wpas_notify_pr_measurement_result(struct wpa_supplicant *wpa_s, const struct peer_measurement_result *result); +void wpas_notify_pr_ranging_complete(struct wpa_supplicant *wpa_s, + u64 cookie); void wpas_notify_nan_bootstrap_request(struct wpa_supplicant *wpa_s, const u8 *peer_addr, u16 pbm, int handle, u8 requestor_instance_id); diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 5085df977..b9171362d 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -765,6 +765,42 @@ void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, } +void wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, + struct peer_measurement_complete *complete) +{ + struct pr_data *pr = wpa_s->global->pr; + + if (!complete) { + wpa_printf(MSG_ERROR, + "PR: Invalid measurement complete event"); + return; + } + + wpa_printf(MSG_DEBUG, + "PR: Peer measurement complete cookie=%llu", + (unsigned long long) complete->cookie); + + /* Validate cookie if we have a pending ranging request */ + if (pr && pr->pr_pasn_params && complete->cookie != 0) { + if (pr->pr_pasn_params->cookie != complete->cookie) { + wpa_printf(MSG_WARNING, + "PR: Complete cookie mismatch - expected %llu, got %llu. Ignoring.", + (unsigned long long) pr->pr_pasn_params->cookie, + (unsigned long long) complete->cookie); + return; + } + } + + wpas_notify_pr_ranging_complete(wpa_s, complete->cookie); + if (pr) { + os_free(pr->pr_pasn_params); + pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; + } + wpas_pr_pd_stop(wpa_s); +} + + void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, struct peer_measurement_result *result) { diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index 991a8dd5e..962cd446c 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -41,6 +41,8 @@ void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, unsigned int freq); void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, struct pr_pasn_ranging_params *pr_pasn_params); +void wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, + struct peer_measurement_complete *complete); void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, struct peer_measurement_result *result); @@ -121,6 +123,12 @@ wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, { } +static inline void +wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, + struct peer_measurement_complete *complete) +{ +} + static inline void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, struct peer_measurement_result *result) -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:47 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:47 +0530 Subject: [PATCH v4 15/20] PR: Add ranging session timeout and integrate socket cleanup In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-16-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add wpas_pr_ranging_session_timeout() to automatically clean up when a continuous ranging session expires. When continuous_ranging_session_time is set, a timer is started after ranging begins; on expiry it stops the peer measurement, frees pr_pasn_params, and stops the PD wdev. Cancel the PASN timeout when ranging starts to avoid premature cleanup while ranging is in progress. Cancel the ranging timeout on measurement completion. On final measurement result, only ranging_final_received is set to block further results. Actual cleanup is deferred to the COMPLETE event or session timeout to avoid premature teardown. Integrate wpa_drv_stop_peer_measurement() into wpas_pr_pd_stop() so the nl_pr socket is always destroyed when the PD wdev is stopped, ensuring proper cleanup on all termination paths. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/driver_i.h | 8 +++++ wpa_supplicant/pr_supplicant.c | 55 ++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index c111224a6..7ff9f454c 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -772,6 +772,14 @@ wpa_drv_start_peer_measurement(struct wpa_supplicant *wpa_s, const u8 *peer, params); } +static inline void +wpa_drv_stop_peer_measurement(struct wpa_supplicant *wpa_s) +{ + if (!wpa_s->driver->stop_peer_measurement) + return; + wpa_s->driver->stop_peer_measurement(wpa_s->drv_priv); +} + #endif /* CONFIG_PR */ static inline int wpa_drv_vendor_cmd(struct wpa_supplicant *wpa_s, diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index b9171362d..8873e8798 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -372,6 +372,28 @@ static void wpas_pr_pasn_result(void *ctx, u8 role, u8 protocol_type, } +static void wpas_pr_ranging_session_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + struct pr_data *pr = wpa_s->global->pr; + + wpa_printf(MSG_DEBUG, + "PR: Ranging session timeout - cleaning up"); + + /* Stop peer measurement and cleanup ranging socket */ + wpa_drv_stop_peer_measurement(wpa_s); + + /* Free ranging params */ + if (pr && pr->pr_pasn_params) { + os_free(pr->pr_pasn_params); + pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; + } + + wpas_pr_pd_stop(wpa_s); +} + + /** * wpas_pr_trigger_ranging - Trigger FTM ranging after successful PASN auth */ @@ -456,6 +478,20 @@ static int wpas_pr_trigger_ranging(struct wpa_supplicant *wpa_s, wpa_printf(MSG_DEBUG, "PR: Successfully triggered ranging measurement"); + /* Start session timeout timer if continuous ranging session time is set */ + if (params->continuous_ranging_session_time > 0) { + unsigned int timeout_sec = params->continuous_ranging_session_time / 1000; + unsigned int timeout_usec = (params->continuous_ranging_session_time % 1000) * 1000; + + wpa_printf(MSG_DEBUG, + "PR: Starting ranging session timeout timer for %u ms", + params->continuous_ranging_session_time); + + eloop_cancel_timeout(wpas_pr_ranging_session_timeout, wpa_s, NULL); + eloop_register_timeout(timeout_sec, timeout_usec, + wpas_pr_ranging_session_timeout, wpa_s, NULL); + } + return 0; fail: @@ -483,9 +519,20 @@ static void wpas_pr_ranging_params(void *ctx, const u8 *dev_addr, protocol_type, freq, op_channel, bw, format_bw); + /* + * PASN succeeded - cancel the PASN timeout so it does not fire and + * prematurely stop the PD wdev while ranging is in progress. + * Cleanup happens on COMPLETE event or session timeout. + */ + eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL); + /* Trigger ranging measurement after successful PASN authentication */ - wpas_pr_trigger_ranging(wpa_s, peer_addr, freq, op_class, op_channel, - format_bw, protocol_type); + if (wpas_pr_trigger_ranging(wpa_s, peer_addr, freq, op_class, op_channel, + format_bw, protocol_type) < 0) { + wpa_printf(MSG_DEBUG, + "PR: Failed to trigger ranging, stopping PD wdev"); + wpas_pr_pd_stop(wpa_s); + } } @@ -721,6 +768,10 @@ void wpas_pr_deinit(struct wpa_supplicant *wpa_s) void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s) { + /* Cancel ranging session timeout and stop peer measurement */ + eloop_cancel_timeout(wpas_pr_ranging_session_timeout, wpa_s, NULL); + wpa_drv_stop_peer_measurement(wpa_s); + if (is_zero_ether_addr(wpa_s->pd_addr)) { wpa_printf(MSG_DEBUG, "PR: pd_stop: no active PD wdev"); return; -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:50 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:50 +0530 Subject: [PATCH v4 18/20] PR: Track peer discovery type and handle OOB peers in PASN initiation In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-19-kavita.kavita@oss.qualcomm.com> Add discovery_type to struct pr_device to distinguish peers discovered via NAN USD from peers added out-of-band. Mark peers as NAN-discovered when found via USD and as OOB when added without prior NAN discovery. When PR_PASN_START is issued for a peer that is not in the discovery list, create a minimal OOB peer entry so that PASN can proceed without requiring prior NAN USD discovery. This is done at PR_PASN_START time for both initiator and responder roles. For OOB peers, skip capability comparison and PASN parameter validation since their capabilities are not known. Per spec, all PR devices must support unauthenticated DH group 19, so cipher and group selection defaults to the mandatory baseline. Signed-off-by: Kavita Kavita --- src/common/proximity_ranging.c | 53 +++++++++++++++++++++++++++++++--- src/common/proximity_ranging.h | 6 ++++ wpa_supplicant/pr_supplicant.c | 6 +++- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index ba56ab519..6636f539f 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -1149,6 +1149,7 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len, os_get_reltime(&dev->last_seen); dev->listen_freq = freq; + dev->discovery_type = PR_DISCOVERY_TYPE_USD; pr_process_ranging_capabilities(msg.pr_capability, msg.pr_capability_len, &dev->pr_caps); @@ -1171,11 +1172,45 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len, } +/** + * pr_ensure_oob_peer - Add a minimal OOB peer entry if not already present + */ +int pr_ensure_oob_peer(struct pr_data *pr, const u8 *addr, int freq) +{ + struct pr_device *dev; + + if (!pr || !addr) + return -1; + + dev = pr_get_device(pr, addr); + if (dev) + return 0; + + dev = pr_create_device(pr, addr); + if (!dev) { + wpa_printf(MSG_DEBUG, "PR: Failed to create OOB peer " MACSTR, + MAC2STR(addr)); + return -1; + } + + dev->discovery_type = PR_DISCOVERY_TYPE_OOB; + if (freq) + dev->listen_freq = freq; + + wpa_printf(MSG_DEBUG, "PR: OOB peer " MACSTR " created at PASN_START", + MAC2STR(addr)); + return 0; +} + + #ifdef CONFIG_PASN static bool pr_eq_ranging_capa_params(const struct pr_device *dev, const struct pr_capabilities *caps) { + if (dev->discovery_type == PR_DISCOVERY_TYPE_OOB) + return true; + return dev->pr_caps.edca_support == caps->edca_support && dev->pr_caps.ntb_support == caps->ntb_support && dev->pr_caps.pasn_type == caps->pasn_type && @@ -1188,6 +1223,9 @@ static bool pr_eq_ranging_capa_params(const struct pr_device *dev, static bool pr_eq_edca_params(const struct pr_device *dev, const struct edca_capabilities *edca_caps) { + if (dev->discovery_type == PR_DISCOVERY_TYPE_OOB) + return true; + return dev->edca_caps.ista_support == edca_caps->ista_support && dev->edca_caps.rsta_support == edca_caps->rsta_support && dev->edca_caps.edca_hw_caps == edca_caps->edca_hw_caps && @@ -1198,6 +1236,9 @@ static bool pr_eq_edca_params(const struct pr_device *dev, static bool pr_eq_ntb_params(const struct pr_device *dev, const struct ntb_capabilities *ntb_caps) { + if (dev->discovery_type == PR_DISCOVERY_TYPE_OOB) + return true; + return dev->ntb_caps.ista_support == ntb_caps->ista_support && dev->ntb_caps.rsta_support == ntb_caps->rsta_support && dev->ntb_caps.ntb_hw_caps == ntb_caps->ntb_hw_caps && @@ -1726,9 +1767,12 @@ static int pr_pasn_initialize(struct pr_data *pr, struct pr_device *dev, /* As specified in Proximity Ranging Implementation Considerations for * P2P Operation D1.8, unauthenticated mode PASN with DH group 19 - * should be supported by all P2P proximity ranging devices. */ - if (!(pr->cfg->pasn_type & BIT(0)) || - !(dev->pr_caps.pasn_type & BIT(0))) { + * should be supported by all P2P proximity ranging devices. Skip + * this check for OOB peers whose capabilities are not known. + */ + if (dev->discovery_type != PR_DISCOVERY_TYPE_OOB && + (!(pr->cfg->pasn_type & BIT(0)) || + !(dev->pr_caps.pasn_type & BIT(0)))) { wpa_printf(MSG_DEBUG, "PR PASN: Unauthenticated DH group 19 NOT supported, PASN type of self 0x%x, peer 0x%x", pr->cfg->pasn_type, dev->pr_caps.pasn_type); @@ -1936,7 +1980,8 @@ int pr_initiate_pasn_auth(struct pr_data *pr, const u8 *addr, int freq, return -1; } - if (pr_validate_pasn_request(pr, dev, auth_mode, ranging_role, + if (dev->discovery_type != PR_DISCOVERY_TYPE_OOB && + pr_validate_pasn_request(pr, dev, auth_mode, ranging_role, ranging_type) < 0) { wpa_printf(MSG_INFO, "PR PASN: Invalid parameters to initiate authentication"); diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 327b6af06..8c0507199 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -258,6 +258,10 @@ enum pr_attr_id { #define PR_PASN_AUTH_MODE_SAE 1 #define PR_PASN_AUTH_MODE_PMK 2 +/* Peer discovery type */ +#define PR_DISCOVERY_TYPE_USD 0 +#define PR_DISCOVERY_TYPE_OOB 1 + /** * struct pr_pasn_ranging_params - Peer parameters to be used in PASN to trigger * ranging in case PR PASN is successful. @@ -414,6 +418,7 @@ struct pr_device { u8 protocol_type; u8 final_op_class; u8 final_op_channel; + u8 discovery_type; }; @@ -642,6 +647,7 @@ void pr_add_dev_ik(struct pr_data *pr, const u8 *dik, const char *password, struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr); void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len, const u8 *peer_addr, unsigned int freq); +int pr_ensure_oob_peer(struct pr_data *pr, const u8 *addr, int freq); int pr_initiate_pasn_auth(struct pr_data *pr, const u8 *addr, int freq, u8 auth_mode, u8 ranging_role, u8 ranging_type, int forced_pr_freq); diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 6529e6a0c..038f120aa 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -1264,10 +1264,14 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s, enum pr_pasn_role pasn_role) { struct wpa_pr_pasn_auth_work *awork; + struct pr_data *pr = wpa_s->global->pr; + + /* Add OOB peer if not already in the discovery list */ + if (pr && pr_ensure_oob_peer(pr, peer_addr, freq) < 0) + return -1; if (pasn_role == PR_ROLE_PASN_RESPONDER) { struct wpa_pr_pasn_roc_work *rwork; - struct pr_data *pr = wpa_s->global->pr; unsigned int roc_time_ms = PR_PASN_RESPONDER_ROC_DURATION; bool has_src_addr = src_addr && !is_zero_ether_addr(src_addr); -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:51 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:51 +0530 Subject: [PATCH v4 19/20] PR: Allow PMK and password configuration in PR_PASN_START In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-20-kavita.kavita@oss.qualcomm.com> Add optional pmk and password parameters to the PR_PASN_START ctrl_iface command to allow per-peer credentials to be configured directly at session initiation time. When provided, the credentials are stored in the peer's pr_device entry and used for the PASN exchange. This works for both USD-discovered peers (overriding DIRA-resolved credentials) and OOB peers (where DIRA resolution never occurs). The credentials persist for future sessions with the same peer. The peer device entry is ensured to exist before applying credentials so that PR_PASN_START with pmk or password works even when no prior NAN USD discovery has taken place. Passing pmk or password with auth=0 (unauthenticated PASN) is rejected since credentials are not applicable in that mode. Signed-off-by: Peddolla Harshavardhan Reddy Signed-off-by: Kavita Kavita --- src/common/proximity_ranging.c | 32 +++++++++++++++++++++++++++++++ src/common/proximity_ranging.h | 9 +++++++++ wpa_supplicant/ctrl_iface.c | 35 ++++++++++++++++++++++++++++++++++ wpa_supplicant/pr_supplicant.c | 13 +++++++++++++ 4 files changed, 89 insertions(+) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index 6636f539f..a9fc9c0c1 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -240,6 +240,38 @@ void pr_add_dev_ik(struct pr_data *pr, const u8 *dik, const char *password, } +void pr_set_peer_credentials(struct pr_data *pr, const u8 *addr, + const u8 *pmk, size_t pmk_len, + const char *password) +{ + struct pr_device *dev; + + if (!pr || !addr) + return; + + dev = pr_get_device(pr, addr); + if (!dev) { + wpa_printf(MSG_DEBUG, "PR: set_peer_credentials: " MACSTR + " not found", MAC2STR(addr)); + return; + } + + if (pmk && pmk_len) { + os_memcpy(dev->pmk, pmk, pmk_len); + dev->pmk_len = pmk_len; + dev->pmk_valid = true; + wpa_printf(MSG_DEBUG, "PR: PMK set for " MACSTR, MAC2STR(addr)); + } + + if (password) { + os_strlcpy(dev->password, password, sizeof(dev->password)); + dev->password_valid = true; + wpa_printf(MSG_DEBUG, "PR: password set for " MACSTR, + MAC2STR(addr)); + } +} + + static struct wpabuf * pr_encaps_elem(const struct wpabuf *subelems, u32 ie_type) { diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 8c0507199..e06c327a1 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -358,6 +358,12 @@ struct pr_pasn_ranging_params { u32 center_freq1; u32 center_freq2; u64 cookie; + + /* Per-session credentials ? if set, used instead of stored ones */ + char password[100]; + bool password_valid; + u8 pmk[PMK_LEN_MAX]; + size_t pmk_len; }; struct pr_dev_ik { @@ -644,6 +650,9 @@ void pr_set_dev_addr(struct pr_data *pr, const u8 *addr); void pr_clear_dev_iks(struct pr_data *pr); void pr_add_dev_ik(struct pr_data *pr, const u8 *dik, const char *password, const u8 *pmk, size_t pmk_len, bool own); +void pr_set_peer_credentials(struct pr_data *pr, const u8 *addr, + const u8 *pmk, size_t pmk_len, + const char *password); struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr); void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len, const u8 *peer_addr, unsigned int freq); diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 8cf87ced7..adba94901 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -11625,6 +11625,33 @@ static int wpas_ctrl_iface_pr_pasn_start(struct wpa_supplicant *wpa_s, params.egress_threshold = atoll(token + 17); } else if (os_strcmp(token, "pd_suppress_results=1") == 0) { params.pr_suppress_results = true; + } else if (os_strncmp(token, "password=", 9) == 0) { + size_t pwd_len = os_strlen(token + 9); + + if (pwd_len == 0 || pwd_len >= sizeof(params.password)) { + wpa_printf(MSG_DEBUG, + "CTRL: PR_PASN_START invalid password length %zu", + pwd_len); + return -1; + } + os_strlcpy(params.password, token + 9, + sizeof(params.password)); + params.password_valid = true; + } else if (os_strncmp(token, "pmk=", 4) == 0) { + size_t pmk_len = os_strlen(token + 4) / 2; + + if (pmk_len != 32 && pmk_len != 48 && pmk_len != 64) { + wpa_printf(MSG_DEBUG, + "CTRL: PR_PASN_START invalid PMK length %zu", + pmk_len); + return -1; + } + if (hexstr2bin(token + 4, params.pmk, pmk_len)) { + wpa_printf(MSG_DEBUG, + "CTRL: PR_PASN_START invalid PMK"); + return -1; + } + params.pmk_len = pmk_len; } else { wpa_printf(MSG_DEBUG, "CTRL: PR_PASN_START invalid parameter: '%s'", @@ -11653,6 +11680,14 @@ static int wpas_ctrl_iface_pr_pasn_start(struct wpa_supplicant *wpa_s, return -1; } + /* pmk and password are only valid for authenticated modes */ + if (params.auth_mode == PR_PASN_AUTH_MODE_PASN && + (params.pmk_len > 0 || params.password_valid)) { + wpa_printf(MSG_DEBUG, + "CTRL: PR_PASN_START pmk/password not applicable for unauthenticated mode"); + return -1; + } + wpa_printf(MSG_DEBUG, "CTRL: PR_PASN_START params: ranging type=0x%x, role=0x%x," " auth_mode=%d, freq=%d, addr=" MACSTR " src_addr=" MACSTR " pasn_role=%d", diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 038f120aa..ed265fb09 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -1538,6 +1538,19 @@ void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, os_memcpy(pr->pr_pasn_params, pr_pasn_params, sizeof(*pr->pr_pasn_params)); + /* Ensure peer device entry exists before setting credentials */ + if (pr_pasn_params->pmk_len > 0 || pr_pasn_params->password_valid) { + pr_ensure_oob_peer(pr, pr_pasn_params->peer_addr, + pr_pasn_params->freq); + pr_set_peer_credentials(pr, + pr_pasn_params->peer_addr, + pr_pasn_params->pmk_len > 0 ? + pr_pasn_params->pmk : NULL, + pr_pasn_params->pmk_len, + pr_pasn_params->password_valid ? + pr_pasn_params->password : NULL); + } + /* Log EDCA parameters if applicable */ if (pr_pasn_params->ranging_type & PR_EDCA_BASED_RANGING) { wpa_printf(MSG_DEBUG, -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:49 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:49 +0530 Subject: [PATCH v4 17/20] PR: Restore dev_addr to station MAC after PD wdev stop In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-18-kavita.kavita@oss.qualcomm.com> When a PD wdev is created with a custom MAC, pr_set_dev_addr() updates pr->cfg->dev_addr to the PD MAC address. pr_pasn_auth_rx() validates incoming PASN frames against pr->cfg->dev_addr, so without restoring it, subsequent ranging sessions using the station MAC would have their PASN frames rejected. Call pr_set_dev_addr() with wpa_s->own_addr in wpas_pr_pd_stop() so that pr->cfg->dev_addr reflects the station MAC once the PD wdev is torn down. Signed-off-by: Kavita Kavita --- wpa_supplicant/pr_supplicant.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 23ba5a7cd..6529e6a0c 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -771,6 +771,8 @@ void wpas_pr_deinit(struct wpa_supplicant *wpa_s) void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s) { + struct pr_data *pr = wpa_s->global->pr; + /* Cancel ranging session timeout and stop peer measurement */ eloop_cancel_timeout(wpas_pr_ranging_session_timeout, wpa_s, NULL); wpa_drv_stop_peer_measurement(wpa_s); @@ -785,7 +787,13 @@ void wpas_pr_pd_stop(struct wpa_supplicant *wpa_s) wpa_drv_pd_stop(wpa_s); os_memset(wpa_s->pd_addr, 0, ETH_ALEN); - wpa_printf(MSG_DEBUG, "PR: PD wdev stopped"); + + /* Restore dev_addr to station MAC now that PD wdev is gone */ + if (pr) + pr_set_dev_addr(pr, wpa_s->own_addr); + + wpa_printf(MSG_DEBUG, "PR: PD wdev stopped, dev_addr restored to " MACSTR, + MAC2STR(wpa_s->own_addr)); } -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:48 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:48 +0530 Subject: [PATCH v4 16/20] PR: Add API and ctrl iface command to abort ranging session In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-17-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Add wpas_pr_abort_ranging() and PR_RANGING_ABORT ctrl_iface command to allow early termination of an ongoing ranging session. The function cancels any active PASN work and ROC (for the responder path where no PD wdev exists yet), then calls wpas_pr_pd_stop() to clean up all ranging resources including the nl_pr socket, session timeout, and PD wdev. Signed-off-by: Peddolla Harshavardhan Reddy --- wpa_supplicant/ctrl_iface.c | 2 ++ wpa_supplicant/pr_supplicant.c | 49 ++++++++++++++++++++++++++++++++++ wpa_supplicant/pr_supplicant.h | 5 ++++ 3 files changed, 56 insertions(+) diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 80da807da..8cf87ced7 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -14519,6 +14519,8 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, reply_len = -1; } else if (os_strcmp(buf, "PR_CLEAR_DIK_CONTEXT") == 0) { wpas_pr_clear_dev_iks(wpa_s); + } else if (os_strcmp(buf, "PR_RANGING_ABORT") == 0) { + wpas_pr_abort_ranging(wpa_s); #endif /* CONFIG_PR */ #ifdef CONFIG_TESTING_OPTIONS } else if (os_strncmp(buf, "PASN_DRIVER ", 12) == 0) { diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index 8873e8798..23ba5a7cd 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -22,6 +22,9 @@ #ifdef CONFIG_PASN static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx); +static void wpas_pr_pasn_cancel_auth_work(struct wpa_supplicant *wpa_s); +static void wpas_pr_pasn_roc_work_done(struct wpa_supplicant *wpa_s); +static void wpas_pr_pasn_roc_total_timeout(void *eloop_ctx, void *timeout_ctx); /* Total listen window (ms) for the PASN responder ROC */ #define PR_PASN_RESPONDER_ROC_DURATION 5000 @@ -816,6 +819,52 @@ void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, } +void wpas_pr_abort_ranging(struct wpa_supplicant *wpa_s) +{ + struct pr_data *pr = wpa_s->global->pr; + + if (!pr) { + wpa_printf(MSG_DEBUG, "PR: abort_ranging: PR not initialized"); + return; + } + + /* Check if there's an active ranging session */ + if (is_zero_ether_addr(wpa_s->pd_addr) && !pr->pr_pasn_params) { + wpa_printf(MSG_DEBUG, + "PR: abort_ranging: no active ranging session"); + return; + } + + wpa_printf(MSG_DEBUG, "PR: Aborting ranging session"); + + /* + * Cancel PASN and ROC in case PD wdev is not yet created + * (PASN still in progress or responder ROC active). + */ + wpas_pr_pasn_cancel_auth_work(wpa_s); + wpa_s->pr_pasn_auth_work = NULL; + if (wpa_s->pr_responder_mode) { + eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s, NULL); + wpa_drv_cancel_remain_on_channel(wpa_s); + wpa_s->off_channel_freq = 0; + wpa_s->roc_waiting_drv_freq = 0; + wpas_pr_pasn_roc_work_done(wpa_s); + wpa_s->pr_responder_mode = false; + os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN); + } + + /* Stop PD wdev and cleanup all ranging resources */ + wpas_pr_pd_stop(wpa_s); + + /* Free ranging params so a new session can be started */ + if (pr->pr_pasn_params) { + os_free(pr->pr_pasn_params); + pr->pr_pasn_params = NULL; + pr->ranging_final_received = false; + } +} + + void wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, struct peer_measurement_complete *complete) { diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index 962cd446c..b83782e51 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -41,6 +41,7 @@ void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s, unsigned int freq); void wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, struct pr_pasn_ranging_params *pr_pasn_params); +void wpas_pr_abort_ranging(struct wpa_supplicant *wpa_s); void wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, struct peer_measurement_complete *complete); void wpas_pr_measurement_result(struct wpa_supplicant *wpa_s, @@ -123,6 +124,10 @@ wpas_pr_pasn_trigger(struct wpa_supplicant *wpa_s, { } +static inline void wpas_pr_abort_ranging(struct wpa_supplicant *wpa_s) +{ +} + static inline void wpas_pr_measurement_complete(struct wpa_supplicant *wpa_s, struct peer_measurement_complete *complete) -- 2.34.1 From kavita.kavita at oss.qualcomm.com Fri May 22 18:23:52 2026 From: kavita.kavita at oss.qualcomm.com (Kavita Kavita) Date: Sat, 23 May 2026 06:53:52 +0530 Subject: [PATCH v4 20/20] PR: use USD source address for DIRA tag computation In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: <20260523012352.1509230-21-kavita.kavita@oss.qualcomm.com> From: Peddolla Harshavardhan Reddy Currently when a USD publish or subscribe session is started with a forced (random) PR MAC address, pr_prepare_usd_elems() calls pr_derive_dira() which uses pr->cfg->dev_addr (the regular interface MAC) to compute the DIRA tag. The USD SDF is transmitted with the forced address as the source, so the receiving peer uses that address in pr_validate_dira(). The two addresses differ, causing the HMAC to never match and DIRA verification to always fail. To address this issue, add a src_addr parameter to pr_derive_dira() and pr_prepare_usd_elems() so the caller can supply the address that will appear as the SDF source on air. Thread this through wpas_pr_usd_elems() and use params->forced_addr (falling back to wpa_s->own_addr when no forced address is set) at the two call sites in wpas_nan_publish() and wpas_nan_subscribe(). The PASN path in pr_prepare_pasn_pr_elem() is unchanged and continues to pass pr->cfg->dev_addr, which is already set to the PD wdev address before any PASN DIRA computation occurs. Signed-off-by: Peddolla Harshavardhan Reddy --- src/common/proximity_ranging.c | 11 ++++++----- src/common/proximity_ranging.h | 2 +- wpa_supplicant/nan_supplicant.c | 8 ++++++-- wpa_supplicant/pr_supplicant.c | 5 +++-- wpa_supplicant/pr_supplicant.h | 6 ++++-- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c index a9fc9c0c1..44289d523 100644 --- a/src/common/proximity_ranging.c +++ b/src/common/proximity_ranging.c @@ -388,7 +388,8 @@ static void pr_get_ntb_capabilities(struct pr_data *pr, } -static int pr_derive_dira(struct pr_data *pr, struct pr_dira *dira) +static int pr_derive_dira(struct pr_data *pr, const u8 *src_addr, + struct pr_dira *dira) { u8 nonce[DEVICE_IDENTITY_NONCE_LEN]; u8 tag[DEVICE_MAX_HASH_LEN]; @@ -417,7 +418,7 @@ static int pr_derive_dira(struct pr_data *pr, struct pr_dira *dira) * Nonce)) */ os_memcpy(data, "DIR", DIR_STR_LEN); - os_memcpy(&data[DIR_STR_LEN], pr->cfg->dev_addr, ETH_ALEN); + os_memcpy(&data[DIR_STR_LEN], src_addr, ETH_ALEN); os_memcpy(&data[DIR_STR_LEN + ETH_ALEN], nonce, DEVICE_IDENTITY_NONCE_LEN); @@ -746,7 +747,7 @@ static void pr_buf_add_dira(struct wpabuf *buf, const struct pr_dira *dira) } -struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr) +struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const u8 *src_addr) { u32 ie_type; struct wpabuf *buf, *buf2; @@ -774,7 +775,7 @@ struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr) pr_buf_add_ntb_capa_info(buf, &ntb_caps); } - if (!pr_derive_dira(pr, &dira)) + if (!pr_derive_dira(pr, src_addr, &dira)) pr_buf_add_dira(buf, &dira); ie_type = (OUI_WFA << 8) | PR_OUI_TYPE; @@ -1685,7 +1686,7 @@ static int pr_prepare_pasn_pr_elem(struct pr_data *pr, struct wpabuf *extra_ies, pr_buf_add_operation_mode(buf, &op_mode); /* PR Device Identity Resolution attribute */ - if (!pr_derive_dira(pr, &dira)) + if (!pr_derive_dira(pr, pr->cfg->dev_addr, &dira)) pr_buf_add_dira(buf, &dira); ie_type = (OUI_WFA << 8) | PR_OUI_TYPE; diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index e06c327a1..c7a60c0cb 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -653,7 +653,7 @@ void pr_add_dev_ik(struct pr_data *pr, const u8 *dik, const char *password, void pr_set_peer_credentials(struct pr_data *pr, const u8 *addr, const u8 *pmk, size_t pmk_len, const char *password); -struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr); +struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const u8 *src_addr); void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len, const u8 *peer_addr, unsigned int freq); int pr_ensure_oob_peer(struct pr_data *pr, const u8 *addr, int freq); diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c index e17fbfda6..3f491ca27 100644 --- a/wpa_supplicant/nan_supplicant.c +++ b/wpa_supplicant/nan_supplicant.c @@ -4525,7 +4525,9 @@ int wpas_nan_publish(struct wpa_supplicant *wpa_s, const char *service_name, elems = wpas_p2p_usd_elems(wpa_s, service_name); addr = wpa_s->global->p2p_dev_addr; } else if (params->proximity_ranging) { - elems = wpas_pr_usd_elems(wpa_s); + const u8 *src = params->forced_addr ? + params->forced_addr : wpa_s->own_addr; + elems = wpas_pr_usd_elems(wpa_s, src); } if (params->forced_addr) { @@ -4685,7 +4687,9 @@ int wpas_nan_subscribe(struct wpa_supplicant *wpa_s, elems = wpas_p2p_usd_elems(wpa_s, service_name); addr = wpa_s->global->p2p_dev_addr; } else if (params->proximity_ranging) { - elems = wpas_pr_usd_elems(wpa_s); + const u8 *src = params->forced_addr ? + params->forced_addr : wpa_s->own_addr; + elems = wpas_pr_usd_elems(wpa_s, src); } if (params->forced_addr) { diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c index ed265fb09..1e6f03d53 100644 --- a/wpa_supplicant/pr_supplicant.c +++ b/wpa_supplicant/pr_supplicant.c @@ -600,12 +600,13 @@ static void wpas_pr_pasn_clear_keys(void *ctx, const u8 *own_addr, } -struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s) +struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s, + const u8 *src_addr) { if (!wpa_s->global->pr) return NULL; - return pr_prepare_usd_elems(wpa_s->global->pr); + return pr_prepare_usd_elems(wpa_s->global->pr, src_addr); } diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h index b83782e51..8141cba57 100644 --- a/wpa_supplicant/pr_supplicant.h +++ b/wpa_supplicant/pr_supplicant.h @@ -23,7 +23,8 @@ void wpas_pr_clear_dev_iks(struct wpa_supplicant *wpa_s); void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik, const char *password, const u8 *pmk, size_t pmk_len, bool own); -struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s); +struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s, + const u8 *src_addr); void wpas_pr_process_usd_elems(struct wpa_supplicant *wpa_s, const u8 *buf, u16 buf_len, const u8 *peer_addr, unsigned int freq); @@ -82,7 +83,8 @@ static inline void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, { } -static inline struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s) +static inline struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s, + const u8 *src_addr) { return NULL; } -- 2.34.1 From peter at pcc.me.uk Sat May 23 22:49:27 2026 From: peter at pcc.me.uk (Peter Collingbourne) Date: Sat, 23 May 2026 22:49:27 -0700 Subject: [RESEND PATCH] hostapd: Add MU-BEAMFORMEE case for vht_capab Message-ID: <20260524054927.105358-1-peter@pcc.me.uk> The config parser for vht_capab was missing a case for MU-BEAMFORMEE, add it. Signed-off-by: Peter Collingbourne --- hostapd/config_file.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hostapd/config_file.c b/hostapd/config_file.c index d34b4aa83..f60241ee7 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -1201,6 +1201,8 @@ static int hostapd_config_vht_capab(struct hostapd_config *conf, conf->vht_capab |= (3 << VHT_CAP_SOUNDING_DIMENSION_OFFSET); if (os_strstr(capab, "[MU-BEAMFORMER]")) conf->vht_capab |= VHT_CAP_MU_BEAMFORMER_CAPABLE; + if (os_strstr(capab, "[MU-BEAMFORMEE]")) + conf->vht_capab |= VHT_CAP_MU_BEAMFORMEE_CAPABLE; if (os_strstr(capab, "[VHT-TXOP-PS]")) conf->vht_capab |= VHT_CAP_VHT_TXOP_PS; if (os_strstr(capab, "[HTC-VHT]")) -- 2.54.0 From chung-hsien.hsu at infineon.com Sun May 24 08:09:32 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Sun, 24 May 2026 23:09:32 +0800 Subject: [PATCH v2 1/2] wpa_supplicant: Derive ieee80211ac status from connection_vht In-Reply-To: <20260524150933.3551-1-chung-hsien.hsu@infineon.com> References: <20260524150933.3551-1-chung-hsien.hsu@infineon.com> Message-ID: <20260524150933.3551-2-chung-hsien.hsu@infineon.com> The legacy ieee80211ac=1 STATUS indication is currently set based on whether the Association Response frame includes a VHT Capabilities element. That parses only one side of the association exchange and duplicates information that is already derived by wpas_parse_connection_info(). Use the parsed connection_vht state for the legacy ieee80211ac STATUS indication. Keep this indication non-exclusive so that HE/EHT associations on bands where VHT is part of the association can continue to report ieee80211ac=1. The exclusive generation indication remains the wifi_generation STATUS field. Signed-off-by: Chung-Hsien Hsu Signed-off-by: Yu-Chien Huang --- wpa_supplicant/events.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 0684ada719bd..11857b3e02f9 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -3533,6 +3533,7 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s, struct wpabuf *req_mlbuf, *resp_mlbuf; wpa_s->connection_set = 0; + wpa_s->ieee80211ac = 0; if (!req_ies || !resp_ies || ieee802_11_parse_elems(req_ies, req_ies_len, &req_elems, 0) == @@ -3554,6 +3555,7 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s, resp_elems.he_capabilities; wpa_s->connection_eht = req_elems.eht_capabilities && resp_elems.eht_capabilities; + wpa_s->ieee80211ac = wpa_s->connection_vht; if (req_elems.rrm_enabled) wpa_s->rrm.rrm_used = 1; @@ -3750,11 +3752,6 @@ static int wpa_supplicant_event_associnfo(struct wpa_supplicant *wpa_s, #endif /* CONFIG_WNM */ interworking_process_assoc_resp(wpa_s, data->assoc_info.resp_ies, data->assoc_info.resp_ies_len); - if ((wpa_s->hw_capab & BIT(CAPAB_VHT)) && - get_ie(data->assoc_info.resp_ies, - data->assoc_info.resp_ies_len, WLAN_EID_VHT_CAP)) - wpa_s->ieee80211ac = 1; - multi_ap_process_assoc_resp(wpa_s, data->assoc_info.resp_ies, data->assoc_info.resp_ies_len); } -- 2.25.1 From chung-hsien.hsu at infineon.com Sun May 24 08:09:33 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Sun, 24 May 2026 23:09:33 +0800 Subject: [PATCH v2 2/2] wpa_supplicant: Add ieee80211ax status indication In-Reply-To: <20260524150933.3551-1-chung-hsien.hsu@infineon.com> References: <20260524150933.3551-1-chung-hsien.hsu@infineon.com> Message-ID: <20260524150933.3551-3-chung-hsien.hsu@infineon.com> The STATUS output has a legacy ieee80211ac=1 indication for VHT, but there is no corresponding indication for HE/IEEE 802.11ax. Add ieee80211ax=1 to STATUS output based on the parsed connection_he state. Keep the indication non-exclusive so that EHT associations that include HE can continue to report ieee80211ax=1. The exclusive generation indication remains the wifi_generation STATUS field. Signed-off-by: Chung-Hsien Hsu Signed-off-by: Adesh Kumar Singh --- wpa_supplicant/ctrl_iface.c | 9 +++++++++ wpa_supplicant/events.c | 9 +++++++++ wpa_supplicant/wpa_supplicant_i.h | 3 +++ 3 files changed, 21 insertions(+) diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 14b4cd9d2e4d..83a892a648c9 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -2600,6 +2600,15 @@ static int wpa_supplicant_ctrl_iface_status(struct wpa_supplicant *wpa_s, } #endif /* CONFIG_WPS */ +#ifdef CONFIG_IEEE80211AX + if (wpa_s->ieee80211ax) { + ret = os_snprintf(pos, end - pos, "ieee80211ax=1\n"); + if (os_snprintf_error(end - pos, ret)) + return pos - buf; + pos += ret; + } +#endif /* CONFIG_IEEE80211AX */ + if (wpa_s->ieee80211ac) { ret = os_snprintf(pos, end - pos, "ieee80211ac=1\n"); if (os_snprintf_error(end - pos, ret)) diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 11857b3e02f9..f992c8d458cc 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -439,6 +439,9 @@ void wpa_supplicant_mark_disassoc(struct wpa_supplicant *wpa_s) os_memset(wpa_s->last_tk, 0, sizeof(wpa_s->last_tk)); #endif /* CONFIG_TESTING_OPTIONS */ wpa_s->ieee80211ac = 0; +#ifdef CONFIG_IEEE80211AX + wpa_s->ieee80211ax = 0; +#endif /* CONFIG_IEEE80211AX */ if (wpa_s->enabled_4addr_mode && wpa_drv_set_4addr_mode(wpa_s, 0) == 0) wpa_s->enabled_4addr_mode = 0; @@ -3534,6 +3537,9 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s, wpa_s->connection_set = 0; wpa_s->ieee80211ac = 0; +#ifdef CONFIG_IEEE80211AX + wpa_s->ieee80211ax = 0; +#endif /* CONFIG_IEEE80211AX */ if (!req_ies || !resp_ies || ieee802_11_parse_elems(req_ies, req_ies_len, &req_elems, 0) == @@ -3556,6 +3562,9 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s, wpa_s->connection_eht = req_elems.eht_capabilities && resp_elems.eht_capabilities; wpa_s->ieee80211ac = wpa_s->connection_vht; +#ifdef CONFIG_IEEE80211AX + wpa_s->ieee80211ax = wpa_s->connection_he; +#endif /* CONFIG_IEEE80211AX */ if (req_elems.rrm_enabled) wpa_s->rrm.rrm_used = 1; diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 828795b1967d..eba6d6c62f6d 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -1635,6 +1635,9 @@ struct wpa_supplicant { #ifdef CONFIG_FILS unsigned int disable_fils:1; #endif /* CONFIG_FILS */ +#ifdef CONFIG_IEEE80211AX + unsigned int ieee80211ax:1; +#endif /* CONFIG_IEEE80211AX */ unsigned int ieee80211ac:1; unsigned int enabled_4addr_mode:1; unsigned int multi_bss_support:1; -- 2.25.1 From chung-hsien.hsu at infineon.com Sun May 24 08:09:31 2026 From: chung-hsien.hsu at infineon.com (Chung-Hsien Hsu) Date: Sun, 24 May 2026 23:09:31 +0800 Subject: [PATCH v2 0/2] wpa_supplicant: Update VHT/HE STATUS indications Message-ID: <20260524150933.3551-1-chung-hsien.hsu@infineon.com> This series updates the legacy STATUS indications for VHT/HE associations. Patch 1 derives the existing ieee80211ac=1 STATUS indication from the already parsed connection_vht state instead of separately looking only at the Association Response VHT Capabilities element. Patch 2 adds a corresponding ieee80211ax=1 STATUS indication based on the already parsed connection_he state. Unlike v1, these indications are kept non-exclusive. wifi_generation is the exclusive generation indication, while ieee80211ac/ieee80211ax indicate whether the corresponding VHT/HE capability is part of the association. v2: - Do not clear ieee80211ac for HE/EHT associations. - Do not clear ieee80211ax for EHT associations. - Keep ieee80211ac/ieee80211ax non-exclusive and use wifi_generation as the exclusive generation indication. Chung-Hsien Hsu (2): wpa_supplicant: Derive ieee80211ac status from connection_vht wpa_supplicant: Add ieee80211ax status indication wpa_supplicant/ctrl_iface.c | 9 +++++++++ wpa_supplicant/events.c | 16 +++++++++++----- wpa_supplicant/wpa_supplicant_i.h | 3 +++ 3 files changed, 23 insertions(+), 5 deletions(-) -- 2.25.1 From ilan.peer at intel.com Mon May 25 07:02:59 2026 From: ilan.peer at intel.com (Peer, Ilan) Date: Mon, 25 May 2026 14:02:59 +0000 Subject: [PATCH 1/3] NAN: Add support for tracking the status of transmit requests In-Reply-To: References: <20260513065909.12048-1-andrei.otcheretianski@intel.com> Message-ID: Hi, > -----Original Message----- > From: Jouni Malinen > Sent: Wednesday, May 20, 2026 12:43 PM > To: Otcheretianski, Andrei > Cc: hostap at lists.infradead.org; maheshkkv at google.com; > vamsin at qti.qualcomm.com; Peer, Ilan > Subject: Re: [PATCH 1/3] NAN: Add support for tracking the status of transmit > requests > > On Wed, May 13, 2026 at 09:58:59AM +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. > > I applied these three patches now to make progress with the pending NAN > patches, but I'm wondering whether it would make more sense to modify the > interface to higher layers to generate the cookie values within wpa_supplicant > instead of getting them from the higher layers. Isn't the current design > depending on the higher layers managing a pool of unique cookie values? > wpa_supplicant could do that and the control interface command would simple > get a parameter in for requesting TX status and the response to that control > interface command would return the assigned cookie when that TX status was > requested. I've considered this option while working on this change, but decided to have this configured by the higher layer, as I this cookie value is not useful/needed for the wpa_supplicant internal logic (it is not aware of the frame data etc.), and I expect the higher layer to have some tracking of the cookies one way or another. Regards, Ilan. From j at w1.fi Mon May 25 07:14:47 2026 From: j at w1.fi (Jouni Malinen) Date: Mon, 25 May 2026 17:14:47 +0300 Subject: [RESEND PATCH] hostapd: Add MU-BEAMFORMEE case for vht_capab In-Reply-To: <20260524054927.105358-1-peter@pcc.me.uk> References: <20260524054927.105358-1-peter@pcc.me.uk> Message-ID: On Sat, May 23, 2026 at 10:49:27PM -0700, Peter Collingbourne wrote: > The config parser for vht_capab was missing a case for MU-BEAMFORMEE, > add it. It is not included by design. This is AP mode configuration and the capability bit for MU beamformee is always 0 for the AP since this functionality is for non-AP STAs. In other words, this proposed configuration could result in setting an invalid value in VHT capabilities. -- Jouni Malinen PGP id EFC895FA From benjamin at sipsolutions.net Tue May 26 04:27:00 2026 From: benjamin at sipsolutions.net (Benjamin Berg) Date: Tue, 26 May 2026 13:27:00 +0200 Subject: [PATCH v2] scan: disable MAC randomization during P2P provisioning Message-ID: <20260526132659.c98747d17789.I1fd493f1fa574f06c6f76fe3b347ae6a2090f928@changeid> From: Benjamin Berg If the P2P GO has already been started and we are just trying to find its beacon, then we should not randomize the MAC address. Doing so would conflict with the P2P GO possibly having a MAC filter installed and ignoring the probe requests (wpa_supplicant does this). So, do not enable address randomization if in P2P provisioning. Also move the wpa_state check into wpa_setup_mac_addr_rand_params so that all the checks are in the same location. Note that other P2P scans bypass the main wpa_supplicant scanning facilities and avoid MAC randomization that way. Signed-off-by: Benjamin Berg --- v2: - Adjust commit message - Add CONFIG_P2P guard --- wpa_supplicant/scan.c | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c index b705058565..ca804c76aa 100644 --- a/wpa_supplicant/scan.c +++ b/wpa_supplicant/scan.c @@ -81,11 +81,22 @@ static int wpas_wps_in_use(struct wpa_supplicant *wpa_s, #endif /* CONFIG_WPS */ -static int wpa_setup_mac_addr_rand_params(struct wpa_driver_scan_params *params, +static int wpa_setup_mac_addr_rand_params(struct wpa_supplicant *wpa_s, + struct wpa_driver_scan_params *params, const u8 *mac_addr) { u8 *tmp; + /* If we are connected, there is no point in randomization */ + if (wpa_s && wpa_s->wpa_state > WPA_SCANNING) + return 0; + +#ifdef CONFIG_P2P + /* The P2P GO might ignore our probe requests otherwise */ + if (wpa_s && wpa_s->p2p_in_provisioning) + return 0; +#endif + if (params->mac_addr) { params->mac_addr_mask = NULL; os_free(params->mac_addr); @@ -198,9 +209,9 @@ static void wpas_trigger_scan_cb(struct wpa_radio_work *work, int deinit) return; } - if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) && - wpa_s->wpa_state <= WPA_SCANNING) - wpa_setup_mac_addr_rand_params(params, wpa_s->mac_addr_scan); + if (wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) + wpa_setup_mac_addr_rand_params(wpa_s, params, + wpa_s->mac_addr_scan); if (wpas_update_random_addr_disassoc(wpa_s) < 0) { wpa_msg(wpa_s, MSG_INFO, @@ -1459,9 +1470,9 @@ ssid_list_set: } #endif /* CONFIG_P2P */ - if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) && - wpa_s->wpa_state <= WPA_SCANNING) - wpa_setup_mac_addr_rand_params(¶ms, wpa_s->mac_addr_scan); + if (wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) + wpa_setup_mac_addr_rand_params(wpa_s, ¶ms, + wpa_s->mac_addr_scan); if (!is_zero_ether_addr(wpa_s->next_scan_bssid)) { struct wpa_bss *bss; @@ -1935,9 +1946,8 @@ scan: wpa_setband_scan_freqs(wpa_s, scan_params); - if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCHED_SCAN) && - wpa_s->wpa_state <= WPA_SCANNING) - wpa_setup_mac_addr_rand_params(¶ms, + if (wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCHED_SCAN) + wpa_setup_mac_addr_rand_params(wpa_s, ¶ms, wpa_s->mac_addr_sched_scan); wpa_scan_set_relative_rssi_params(wpa_s, scan_params); @@ -3853,7 +3863,7 @@ wpa_scan_clone_params(const struct wpa_driver_scan_params *src) } if (src->mac_addr_rand && - wpa_setup_mac_addr_rand_params(params, src->mac_addr)) + wpa_setup_mac_addr_rand_params(NULL, params, src->mac_addr)) goto failed; if (src->bssid) { @@ -4043,9 +4053,9 @@ int wpas_start_pno(struct wpa_supplicant *wpa_s) params.freqs = wpa_s->manual_sched_scan_freqs; } - if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_PNO) && - wpa_s->wpa_state <= WPA_SCANNING) - wpa_setup_mac_addr_rand_params(¶ms, wpa_s->mac_addr_pno); + if (wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_PNO) + wpa_setup_mac_addr_rand_params(wpa_s, ¶ms, + wpa_s->mac_addr_pno); wpa_scan_set_relative_rssi_params(wpa_s, ¶ms); -- 2.54.0 From j at w1.fi Wed May 27 06:52:35 2026 From: j at w1.fi (Jouni Malinen) Date: Wed, 27 May 2026 16:52:35 +0300 Subject: [PATCH v4 00/20] PR: Add nl80211 support and ranging for Proximity Detection In-Reply-To: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> References: <20260523012352.1509230-1-kavita.kavita@oss.qualcomm.com> Message-ID: On Sat, May 23, 2026 at 06:53:32AM +0530, Kavita Kavita wrote: > wpa_supplicant already had PASN authentication support for > Proximity Ranging (PR). This series adds the missing nl80211 > driver support and ranging to complete the PD (Proximity > Detection) flow. > > It introduces a dedicated PD virtual interface (NL80211_IFTYPE_PD) > with its own MAC address, updates nl80211_copy.h with the > proposed kernel API additions for PD, improves driver capability > parsing with dedicated PR-specific flags, extends the set_key > path to carry LTF key seeds for secure ranging, and wires up > peer measurement start and result handling through the nl80211 > driver. The existing PR_PASN_START control interface command is > extended with the full set of EDCA, NTB, and proximity threshold > parameters and rerouted through wpas_pr_pasn_trigger() to drive > the complete PASN-then-ranging flow in a single step. > Support is also added for Out-of-Band peer discovery where USD is > handled by an external application, and per-peer PMK and password > configuration via PR_PASN_START for authenticated PASN without > prior NAN USD discovery. Thanks, applied with cleanup. -- Jouni Malinen PGP id EFC895FA From lachlan.hodges at morsemicro.com Wed May 27 23:38:49 2026 From: lachlan.hodges at morsemicro.com (Lachlan Hodges) Date: Thu, 28 May 2026 16:38:49 +1000 Subject: [RFC 0/8] Introduce S1G Support Message-ID: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> Hi Jouni, Firstly, this has been rebased and tested on 4706c1d6d93e ("SME: Fix compilation warnings about unused functions") with wireless-next 1a1f055318d8 ("Merge tag 'wireless-next-2026-05-21' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next"). -- Over the last ~1.5 years we have been improving / fixing the S1G support within mac80211/cfg80211 to the point where you can bring up a native S1G AP/STA. We also have a driver currently under review [1]. This first submission is quite large, even when scoping to the absolute minimum (i.e basic AP and STA, no channel switching, DPP etc.). Conceptually there are two parts to this series - freq_offset and wider S1G support. The freq_offset subset passes the hwsim tests indepedently of the wider patchset. Below is a high level overview of the notable changes made by this series. -- 1) freq_offset ============== The biggest deviation from existing 802.11 standards when it comes to 802.11ah or S1G is the fact that the spectrum is < 1 GHz. Within cfg80211, this is denoted via a frequency component in MHz, and a frequency offset component in KHz. This is potentially most contentious part of this entire series as it requires modifications to core functions that work on various channel calculations. Take hw_mode_get_channel() as an example, to compare channels we now must apply the freq_offset component: MHZ_TO_KHZ(ch->freq) + ch->freq_offset == MHZ_TO_KHZ(freq) + freq_offset) For non-S1G the frequency offset component is always 0, but this method has been chosen as it is similar to how the similar channel calculation functions were refactored to internally use these components, while still limiting the external facing APIs from changing too much. Another example is converting internal channel <-> frequency calculators to use KHz: int ieee80211_chan_to_freq(const char *country, u8 op_class, u8 chan) { return KHZ_TO_MHZ(ieee80211_chan_to_freq_khz(country, op_class, chan); } Where any specific S1G locations can then call ieee80211_chan_to_freq_khz() directly. Again, we are emulating how this is done within cfg80211. Additionally, a freq_offset component is then introduced to the parameter list of key functions to do with setting and/or retrieving channels. 2) Regdom Support ================= Currently, the upstream regdom supports only a limited number of countries, this being US and AU. However, this patchset will _only_ be supporting the US. The reason is that, in order to support the 2024 AU channelisation schemes, additional kernel work is required. This will begin once the driver is accepted, in part, to reduce the initial scope of these patchsets (both within the kernel/driver and hostapd). As a result, only specific US 2024 op_classes are introduced, and an attempt to configure with an unsupported op_class will result in an error. Obviously, as support is extended - so will support in hostapd since country support is far more granular for S1G. 3) S1G Implementation ===================== Since S1G is based on the VHT phy, much of the implementation follows that of VHT, besides some S1G specific parts of course. 4) hw_mode=ah ============= With the new band introduced comes a new hwmode to represent 900 MHz PHY. For existing drivers that use hw_mode=any there is no affect as none of those drivers expose S1G channels. However, hwsim does. For now, we exclude all S1G channels when hw_mode=any. This enables mac80211_hwsim to function properly and emulates real world behaviour since there (as of now, maybe in the future....) no multi-band radios that include both a S1G and non-S1G radio. 5) hwsim tests ============== A basic test suite is also included that tests various channel permutations as well as the NO_PRIMARY flag to ensure edgeband primaries with the NO_PRIMARY flag cannot be used, which is typical of real hardware. See [2] for more information on this. In order for the S1G hwsim tests to function, 2 patches [3] must be applied onto wireless-next. The actual hwsim output in terms of radiotap headers and whatnot is not really accurate at all for S1G, however this is on our TODO list among other things like proper rate reporting and so on. However, as mentioned previously, the goal is to keep the initial scope of this work as small as possible and build it up from there :). 6) Sample Config ================ A sample hostapd configuration for 80211ah is as follows: ``` interface=wlan0 country_code=US driver=nl80211 ssid=wifi_halow channel=43 ieee80211ah=1 op_class=71 beacon_int=100 dtim_period=10 s1g_oper_centr_freq_idx=44 s1g_primary_2mhz=1 hw_mode=ah wpa=2 wpa_key_mgmt=SAE rsn_pairwise=CCMP sae_password=12345678 ieee80211w=2 ``` The supplicant side is no different from regular Wi-Fi: ``` update_config=1 country=US pmf=2 network={ ssid="wifi_halow" sae_password="12345678" key_mgmt=SAE } ``` lachlan [1] https://lore.kernel.org/linux-wireless/20260430045615.334669-1-lachlan.hodges at morsemicro.com/ [2] https://lore.kernel.org/linux-wireless/20250918051913.500781-2-lachlan.hodges at morsemicro.com/ [3] https://lore.kernel.org/linux-wireless/20260527033828.183821-1-lachlan.hodges at morsemicro.com/T/#t Lachlan Hodges (8): AP/STA: introduce support for freq_offset nl80211: introduce freq_offset support tests: expose scan_freq_offset config: enable configuration of S1G AP: add S1G support STA: add S1G support nl80211: add S1G support tests: add S1G channel tests hostapd/Makefile | 5 + hostapd/config_file.c | 10 + hostapd/defconfig | 3 + hostapd/hostapd.conf | 26 ++- src/ap/Makefile | 1 + src/ap/ap_config.c | 34 +++ src/ap/ap_config.h | 23 ++ src/ap/ap_drv_ops.c | 28 ++- src/ap/ap_drv_ops.h | 7 +- src/ap/beacon.c | 98 ++++++++- src/ap/ctrl_iface_ap.c | 8 +- src/ap/dfs.c | 12 +- src/ap/drv_callbacks.c | 6 +- src/ap/hostapd.c | 42 ++-- src/ap/hostapd.h | 1 + src/ap/hw_features.c | 75 +++++-- src/ap/hw_features.h | 2 +- src/ap/ieee802_11.c | 60 +++++- src/ap/ieee802_11.h | 10 + src/ap/ieee802_11_s1g.c | 196 +++++++++++++++++ src/ap/ieee802_11_shared.c | 14 ++ src/ap/interference.c | 3 +- src/ap/sta_info.c | 5 +- src/ap/sta_info.h | 4 +- src/common/defs.h | 10 + src/common/hw_features_common.c | 71 ++++++- src/common/hw_features_common.h | 19 +- src/common/ieee802_11_common.c | 183 +++++++++++----- src/common/ieee802_11_common.h | 7 +- src/common/ieee802_11_defs.h | 55 +++++ src/common/privsep_commands.h | 1 + src/common/proximity_ranging.h | 3 +- src/drivers/driver.h | 67 +++++- src/drivers/driver_nl80211.c | 157 ++++++++++---- src/drivers/driver_nl80211.h | 3 + src/drivers/driver_nl80211_capa.c | 66 +++++- src/drivers/driver_nl80211_event.c | 36 +++- src/drivers/driver_nl80211_scan.c | 34 ++- src/drivers/driver_privsep.c | 7 +- tests/hwsim/example-hostapd.config | 1 + tests/hwsim/example-wpa_supplicant.config | 1 + tests/hwsim/test_s1g.py | 245 ++++++++++++++++++++++ tests/hwsim/wpasupplicant.py | 3 +- wlantest/bss.c | 2 +- wpa_supplicant/Makefile | 6 + wpa_supplicant/ap.c | 4 +- wpa_supplicant/bss.c | 5 +- wpa_supplicant/bss.h | 2 + wpa_supplicant/config.c | 1 + wpa_supplicant/config_ssid.h | 12 ++ wpa_supplicant/ctrl_iface.c | 1 + wpa_supplicant/defconfig | 3 + wpa_supplicant/driver_i.h | 9 +- wpa_supplicant/events.c | 21 +- wpa_supplicant/ibss_rsn.c | 2 +- wpa_supplicant/mesh.c | 5 +- wpa_supplicant/nan_supplicant.c | 2 +- wpa_supplicant/p2p_supplicant.c | 4 +- wpa_supplicant/pasn_supplicant.c | 4 +- wpa_supplicant/scan.c | 10 +- wpa_supplicant/sme.c | 20 +- wpa_supplicant/wpa_priv.c | 1 + wpa_supplicant/wpa_supplicant.c | 26 ++- wpa_supplicant/wpa_supplicant_i.h | 2 + 64 files changed, 1518 insertions(+), 266 deletions(-) create mode 100644 src/ap/ieee802_11_s1g.c create mode 100644 tests/hwsim/test_s1g.py -- 2.43.0 From lachlan.hodges at morsemicro.com Wed May 27 23:38:51 2026 From: lachlan.hodges at morsemicro.com (Lachlan Hodges) Date: Thu, 28 May 2026 16:38:51 +1000 Subject: [RFC 2/8] nl80211: introduce freq_offset support In-Reply-To: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> References: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> Message-ID: <20260528063857.950556-3-lachlan.hodges@morsemicro.com> Introduce the ability to handle S1G channels by introducing the `freq_offset` parameter to the nl80211 driver to handle S1G channels. Signed-off-by: Lachlan Hodges --- src/drivers/driver_nl80211.c | 106 ++++++++++++++++++++--------- src/drivers/driver_nl80211.h | 3 + src/drivers/driver_nl80211_capa.c | 30 +++++--- src/drivers/driver_nl80211_event.c | 26 ++++--- src/drivers/driver_nl80211_scan.c | 34 ++++++--- 5 files changed, 141 insertions(+), 58 deletions(-) diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index f79558911feb..df82c1b670e3 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -169,7 +169,8 @@ wpa_driver_nl80211_finish_drv_init(struct i802_bss *bss, const char *driver_params, enum wpa_p2p_mode p2p_mode); static int nl80211_send_frame_cmd(struct i802_bss *bss, - unsigned int freq, unsigned int wait, + unsigned int freq, unsigned int freq_offset, + unsigned int wait, const u8 *buf, size_t buf_len, int save_cookie, int no_cck, int no_ack, int offchanok, const u16 *csa_offs, @@ -1715,6 +1716,7 @@ static void wpa_driver_nl80211_event_rtm_dellink(void *ctx, struct nl80211_get_assoc_freq_arg { struct wpa_driver_nl80211_data *drv; unsigned int assoc_freq; + unsigned int assoc_freq_offset; unsigned int ibss_freq; u8 assoc_bssid[ETH_ALEN]; u8 assoc_ssid[SSID_MAX_LEN]; @@ -1766,8 +1768,12 @@ static int nl80211_get_assoc_freq_handler(struct nl_msg *msg, void *arg) if (!drv->sta_mlo_info.valid_links || drv->sta_mlo_info.assoc_link_id == link_id) { ctx->assoc_freq = freq; - wpa_printf(MSG_DEBUG, "nl80211: Associated on %u MHz", - ctx->assoc_freq); + if (bss[NL80211_BSS_FREQUENCY_OFFSET]) + ctx->assoc_freq_offset = + nla_get_u32(bss[NL80211_BSS_FREQUENCY_OFFSET]); + + wpa_printf(MSG_DEBUG, "nl80211: Associated on %u.%u MHz", + ctx->assoc_freq, ctx->assoc_freq_offset); } } if (status == NL80211_BSS_STATUS_IBSS_JOINED && @@ -1879,10 +1885,13 @@ try_again: if (ret == 0) { unsigned int freq = drv->nlmode == NL80211_IFTYPE_ADHOC ? arg.ibss_freq : arg.assoc_freq; + unsigned int freq_offset = arg.assoc_freq_offset; wpa_printf(MSG_DEBUG, "nl80211: Operating frequency for the " - "associated BSS from scan results: %u MHz", freq); - if (freq) + "associated BSS from scan results: %u MHz offset: %d KHz", freq, freq_offset); + if (freq) { drv->assoc_freq = freq; + drv->assoc_freq_offset = freq_offset; + } if (drv->sta_mlo_info.valid_links) { int i; @@ -4673,11 +4682,17 @@ retry: if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, params->bssid)) goto fail; } - if (params->freq) { - wpa_printf(MSG_DEBUG, " * freq=%d", params->freq); + if(params->freq){ + wpa_printf(MSG_DEBUG, " * freq=%d MHz", params->freq); if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, params->freq)) goto fail; } + if (params->freq_offset) { + wpa_printf(MSG_DEBUG, " * freq_offset=%d KHz", params->freq_offset); + if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ_OFFSET, + params->freq_offset)) + goto fail; + } if (params->ssid) { wpa_printf(MSG_DEBUG, " * SSID=%s", wpa_ssid_txt(params->ssid, params->ssid_len)); @@ -4873,14 +4888,14 @@ u8 nl80211_get_link_id_from_link(struct i802_bss *bss, struct i802_link *link) } -static void nl80211_link_set_freq(struct i802_bss *bss, s8 link_id, int freq) +static void nl80211_link_set_freq(struct i802_bss *bss, s8 link_id, int freq, int freq_offset) { struct i802_link *link = nl80211_get_link(bss, link_id); link->freq = freq; + link->freq_offset = freq_offset; } - static int nl80211_get_link_freq(struct i802_bss *bss, const u8 *addr, bool bss_freq_debug) { @@ -4906,8 +4921,8 @@ static int nl80211_get_link_freq(struct i802_bss *bss, const u8 *addr, static int wpa_driver_nl80211_send_mlme(struct i802_bss *bss, const u8 *data, size_t data_len, int noack, - unsigned int freq, int no_cck, - int offchanok, + unsigned int freq, unsigned int freq_offset, + int no_cck, int offchanok, unsigned int wait_time, const u16 *csa_offs, size_t csa_offs_len, int no_encrypt, @@ -4953,9 +4968,10 @@ static int wpa_driver_nl80211_send_mlme(struct i802_bss *bss, const u8 *data, * of wpa_supplicant. */ if (freq == 0) { - wpa_printf(MSG_DEBUG, "nl80211: Use last_mgmt_freq=%d", - drv->last_mgmt_freq); + wpa_printf(MSG_DEBUG, "nl80211: Use last_mgmt_freq=%d last_mgmt_freq_offset=%d", + drv->last_mgmt_freq, drv->last_mgmt_freq_offset); freq = drv->last_mgmt_freq; + freq_offset = drv->last_mgmt_freq_offset; } wait_time = 0; use_cookie = 0; @@ -4967,9 +4983,13 @@ static int wpa_driver_nl80211_send_mlme(struct i802_bss *bss, const u8 *data, if (drv->device_ap_sme && is_ap_interface(drv->nlmode)) { unsigned int link_freq = nl80211_get_link_freq(bss, mgmt->sa, !freq); + /* S1G is exempt from MLO so will always be default link */ + unsigned int link_freq_offset = bss->flink->freq_offset; - if (!freq) + if (!freq) { freq = link_freq; + freq_offset = link_freq_offset; + } if (freq == link_freq) wait_time = 0; @@ -4986,9 +5006,10 @@ static int wpa_driver_nl80211_send_mlme(struct i802_bss *bss, const u8 *data, (drv->capa.flags & WPA_DRIVER_FLAGS_SAE) && !(drv->capa.flags & WPA_DRIVER_FLAGS_SME)) { freq = nl80211_get_assoc_freq(drv); + freq_offset = drv->assoc_freq_offset; wpa_printf(MSG_DEBUG, - "nl80211: send_mlme - Use assoc_freq=%u for external auth", - freq); + "nl80211: send_mlme - Use assoc_freq=%u assoc_freq_offset=%u for external auth", + freq, freq_offset); } /* Allow off channel for PASN authentication */ @@ -5021,9 +5042,10 @@ static int wpa_driver_nl80211_send_mlme(struct i802_bss *bss, const u8 *data, freq); } if (freq == 0) { - wpa_printf(MSG_DEBUG, "nl80211: send_mlme - Use bss->freq=%u", - link->freq); + wpa_printf(MSG_DEBUG, "nl80211: send_mlme - Use bss->freq=%u bss->freq_offset=%u", + link->freq, link->freq_offset); freq = link->freq; + freq_offset = link->freq_offset; } if ((noack || WLAN_FC_GET_TYPE(fc) != WLAN_FC_TYPE_MGMT || @@ -5041,7 +5063,7 @@ send_frame_cmd: #endif /* CONFIG_TESTING_OPTIONS */ wpa_printf(MSG_DEBUG, "nl80211: send_mlme -> send_frame_cmd"); - res = nl80211_send_frame_cmd(bss, freq, wait_time, data, data_len, + res = nl80211_send_frame_cmd(bss, freq, freq_offset, wait_time, data, data_len, use_cookie, no_cck, noack, offchanok, csa_offs, csa_offs_len, link_id); if (!res) @@ -5636,6 +5658,9 @@ static int nl80211_put_freq_params(struct nl_msg *msg, wpa_printf(MSG_DEBUG, " * freq=%d", freq->freq); if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, freq->freq)) return -ENOBUFS; + wpa_printf(MSG_DEBUG, " * freq_offset=%d", freq->freq_offset); + if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ_OFFSET, freq->freq_offset)) + return -ENOBUFS; wpa_printf(MSG_DEBUG, " * eht_enabled=%d", freq->eht_enabled); wpa_printf(MSG_DEBUG, " * he_enabled=%d", freq->he_enabled); @@ -5799,7 +5824,8 @@ static int wpa_driver_nl80211_set_ap(void *priv, nl80211_link_set_freq(bss, params->mld_ap ? params->mld_link_id : NL80211_DRV_LINK_ID_NA, - params->freq->freq); + params->freq->freq, + params->freq->freq_offset); if (params->mld_ap) { wpa_printf(MSG_DEBUG, "nl80211: link_id=%u", @@ -6167,10 +6193,10 @@ static int nl80211_set_channel(struct i802_bss *bss, int ret; wpa_printf(MSG_DEBUG, - "nl80211: Set freq %d (ht_enabled=%d, vht_enabled=%d, he_enabled=%d, eht_enabled=%d, bandwidth=%d MHz, cf1=%d MHz, cf2=%d MHz)", - freq->freq, freq->ht_enabled, freq->vht_enabled, + "nl80211: Set freq %d offset %d (ht_enabled=%d, vht_enabled=%d, he_enabled=%d, eht_enabled=%d, , bandwidth=%d MHz, cf1=%d MHz, cf1_offset=%d KHz cf2=%d MHz)", + freq->freq, freq->freq_offset, freq->ht_enabled, freq->vht_enabled, freq->he_enabled, freq->eht_enabled, freq->bandwidth, - freq->center_freq1, freq->center_freq2); + freq->center_freq1, freq->center_freq1_offset, freq->center_freq2); msg = nl80211_bss_msg(bss, 0, set_chan ? NL80211_CMD_SET_CHANNEL : NL80211_CMD_SET_WIPHY); @@ -6191,7 +6217,7 @@ static int nl80211_set_channel(struct i802_bss *bss, ret = send_and_recv_cmd(drv, msg); if (ret == 0) { - nl80211_link_set_freq(bss, freq->link_id, freq->freq); + nl80211_link_set_freq(bss, freq->link_id, freq->freq, freq->freq_offset); return 0; } wpa_printf(MSG_DEBUG, "nl80211: Failed to set channel (freq=%d): " @@ -7534,6 +7560,16 @@ static int nl80211_connect_common(struct wpa_driver_nl80211_data *drv, } drv->assoc_freq = params->freq.freq; + + if (params->freq.freq_offset && !params->mld_params.mld_addr) { + wpa_printf(MSG_DEBUG, " * freq_offset=%d", + params->freq.freq_offset); + if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ_OFFSET, + params->freq.freq_offset)) + return -1; + + drv->assoc_freq_offset = params->freq.freq_offset; + } } if (params->freq_hint) { @@ -9141,7 +9177,7 @@ static int i802_sta_deauth(void *priv, const u8 *own_addr, const u8 *addr, return wpa_driver_nl80211_send_mlme(bss, (u8 *) &mgmt, IEEE80211_HDRLEN + sizeof(mgmt.u.deauth), 0, 0, 0, 0, - 0, NULL, 0, 0, link_id); + 0, 0, NULL, 0, 0, link_id); } @@ -9168,7 +9204,7 @@ static int i802_sta_disassoc(void *priv, const u8 *own_addr, const u8 *addr, return wpa_driver_nl80211_send_mlme(bss, (u8 *) &mgmt, IEEE80211_HDRLEN + sizeof(mgmt.u.disassoc), 0, 0, 0, 0, - 0, NULL, 0, 0, link_id); + 0, 0, NULL, 0, 0, link_id); } @@ -9927,7 +9963,8 @@ static int cookie_handler(struct nl_msg *msg, void *arg) static int nl80211_send_frame_cmd(struct i802_bss *bss, - unsigned int freq, unsigned int wait, + unsigned int freq, unsigned int freq_offset, + unsigned int wait, const u8 *buf, size_t buf_len, int save_cookie, int no_cck, int no_ack, int offchanok, const u16 *csa_offs, @@ -9938,15 +9975,16 @@ static int nl80211_send_frame_cmd(struct i802_bss *bss, u64 cookie; int ret = -1; - wpa_printf(MSG_MSGDUMP, "nl80211: CMD_FRAME freq=%u wait=%u no_cck=%d " + wpa_printf(MSG_MSGDUMP, "nl80211: CMD_FRAME freq=%u freq_offset=%u wait=%u no_cck=%d " "no_ack=%d offchanok=%d", - freq, wait, no_cck, no_ack, offchanok); + freq, freq_offset, wait, no_cck, no_ack, offchanok); wpa_hexdump(MSG_MSGDUMP, "CMD_FRAME", buf, buf_len); if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_FRAME)) || ((link_id != NL80211_DRV_LINK_ID_NA) && nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)) || (freq && nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, freq)) || + (freq_offset && nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ_OFFSET, freq_offset)) || (wait && nla_put_u32(msg, NL80211_ATTR_DURATION, wait)) || (offchanok && ((drv->capa.flags & WPA_DRIVER_FLAGS_OFFCHANNEL_TX) || drv->test_use_roc_tx) && @@ -10079,11 +10117,11 @@ static int wpa_driver_nl80211_send_action(struct i802_bss *bss, if (is_ap_interface(drv->nlmode)) ret = wpa_driver_nl80211_send_mlme(bss, buf, 24 + data_len, - 0, freq, no_cck, offchanok, + 0, freq, 0, no_cck, offchanok, wait_time, NULL, 0, 0, link_id); else - ret = nl80211_send_frame_cmd(bss, freq, wait_time, buf, + ret = nl80211_send_frame_cmd(bss, freq, 0, wait_time, buf, 24 + data_len, 1, no_cck, 0, offchanok, NULL, 0, link_id); @@ -11353,7 +11391,7 @@ static void nl80211_send_null_frame(struct i802_bss *bss, const u8 *own_addr, os_memcpy(nulldata.hdr.IEEE80211_SA_FROMDS, own_addr, ETH_ALEN); if (wpa_driver_nl80211_send_mlme(bss, (u8 *) &nulldata, size, 0, 0, 0, - 0, 0, NULL, 0, 0, -1) < 0) + 0, 0, 0, NULL, 0, 0, -1) < 0) wpa_printf(MSG_DEBUG, "nl80211_send_null_frame: Failed to " "send poll frame"); } @@ -11477,7 +11515,7 @@ static int nl80211_start_radar_detection(void *priv, ret = send_and_recv_cmd(drv, msg); if (ret == 0) { - nl80211_link_set_freq(bss, freq->link_id, freq->freq); + nl80211_link_set_freq(bss, freq->link_id, freq->freq, 0); return 0; } @@ -11857,7 +11895,7 @@ static int driver_nl80211_send_mlme(void *priv, const u8 *data, { struct i802_bss *bss = priv; return wpa_driver_nl80211_send_mlme(bss, data, data_len, noack, - freq, 0, 0, wait, csa_offs, + freq, freq_offset, 0, 0, wait, csa_offs, csa_offs_len, no_encrypt, link_id); } diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h index 9c871ce18e63..14c3a542ecae 100644 --- a/src/drivers/driver_nl80211.h +++ b/src/drivers/driver_nl80211.h @@ -75,6 +75,7 @@ struct i802_link { unsigned int beacon_set:1; int freq; + int freq_offset; int bandwidth; u8 addr[ETH_ALEN]; void *ctx; @@ -173,6 +174,7 @@ struct wpa_driver_nl80211_data { enum nl80211_iftype nlmode; enum nl80211_iftype ap_scan_as_station; unsigned int assoc_freq; + unsigned int assoc_freq_offset; unsigned int disabled_11b_rates:1; unsigned int pending_remain_on_chan:1; @@ -236,6 +238,7 @@ struct wpa_driver_nl80211_data { int eapol_tx_link_id; unsigned int last_mgmt_freq; + unsigned int last_mgmt_freq_offset; struct wpa_driver_scan_filter *filter_ssids; size_t num_filter_ssids; diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c index 66ccbbb3719c..e423eeeb4528 100644 --- a/src/drivers/driver_nl80211_capa.c +++ b/src/drivers/driver_nl80211_capa.c @@ -2171,6 +2171,7 @@ static void phy_info_freq(struct hostapd_hw_modes *mode, os_memset(chan, 0, sizeof(*chan)); chan->freq = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]); + chan->freq_offset = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_OFFSET]); chan->flag = 0; chan->allowed_bw = ~0; chan->dfs_cac_ms = 0; @@ -3152,14 +3153,27 @@ static void nl80211_dump_chan_list(struct wpa_driver_nl80211_data *drv, drv->uses_6ghz = true; if (chan->freq >= 900 && chan->freq < 1000) drv->uses_s1g = true; - res = os_snprintf(pos, end - pos, " %d%s%s%s", - chan->freq, - (chan->flag & HOSTAPD_CHAN_DISABLED) ? - "[DISABLED]" : "", - (chan->flag & HOSTAPD_CHAN_NO_IR) ? - "[NO_IR]" : "", - (chan->flag & HOSTAPD_CHAN_RADAR) ? - "[RADAR]" : ""); + + if (drv->uses_s1g) { + /* For S1G, print frequency in KHz */ + res = os_snprintf(pos, end - pos, " %d%s%s", + MHZ_TO_KHZ(chan->freq) + chan->freq_offset, + (chan->flag & HOSTAPD_CHAN_DISABLED) ? + "[DISABLED]" : "", + (chan->flag & HOSTAPD_CHAN_NO_IR) ? + "[NO_IR]" : ""); + } else { + /* For normal operation, print frequency in MHz */ + res = os_snprintf(pos, end - pos, " %d%s%s%s", + chan->freq, + (chan->flag & HOSTAPD_CHAN_DISABLED) ? + "[DISABLED]" : "", + (chan->flag & HOSTAPD_CHAN_NO_IR) ? + "[NO_IR]" : "", + (chan->flag & HOSTAPD_CHAN_RADAR) ? + "[RADAR]" : ""); + } + if (os_snprintf_error(end - pos, res)) break; pos += res; diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index 1097c08b4e90..28e8a7123af6 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -1114,7 +1114,9 @@ static void mlme_event_connect(struct wpa_driver_nl80211_data *drv, } event.assoc_info.freq = nl80211_get_assoc_freq(drv); + event.assoc_info.freq_offset = drv->assoc_freq_offset; drv->first_bss->flink->freq = drv->assoc_freq; + drv->first_bss->flink->freq_offset = drv->assoc_freq_offset; if ((!ssid || ssid[1] == 0 || ssid[1] > 32) && (ssid_len = nl80211_get_assoc_ssid(drv, drv->ssid)) > 0) { @@ -1424,8 +1426,8 @@ static void mlme_timeout_event(struct wpa_driver_nl80211_data *drv, static void mlme_event_mgmt(struct i802_bss *bss, - struct nlattr *freq, struct nlattr *sig, - const u8 *frame, size_t len, + struct nlattr *freq, struct nlattr *freq_offset, + struct nlattr *sig, const u8 *frame, size_t len, int link_id) { struct wpa_driver_nl80211_data *drv = bss->drv; @@ -1434,6 +1436,7 @@ static void mlme_event_mgmt(struct i802_bss *bss, u16 fc, stype; int ssi_signal = 0; int rx_freq = 0; + int rx_freq_offset = 0; wpa_printf(MSG_MSGDUMP, "nl80211: Frame event"); mgmt = (const struct ieee80211_mgmt *) frame; @@ -1451,13 +1454,15 @@ static void mlme_event_mgmt(struct i802_bss *bss, os_memset(&event, 0, sizeof(event)); if (freq) { event.rx_mgmt.freq = nla_get_u32(freq); + event.rx_mgmt.freq_offset = nla_get_u32(freq_offset); rx_freq = drv->last_mgmt_freq = event.rx_mgmt.freq; + rx_freq_offset = drv->last_mgmt_freq_offset = event.rx_mgmt.freq_offset; } wpa_printf(MSG_DEBUG, "nl80211: RX frame da=" MACSTR " sa=" MACSTR " bssid=" MACSTR - " freq=%d ssi_signal=%d fc=0x%x seq_ctrl=0x%x stype=%u (%s) len=%u", + " freq=%d freq_offset=%d ssi_signal=%d fc=0x%x seq_ctrl=0x%x stype=%u (%s) len=%u", MAC2STR(mgmt->da), MAC2STR(mgmt->sa), MAC2STR(mgmt->bssid), - rx_freq, ssi_signal, fc, + rx_freq, rx_freq_offset, ssi_signal, fc, le_to_host16(mgmt->seq_ctrl), stype, fc2str(fc), (unsigned int) len); event.rx_mgmt.frame = frame; @@ -1842,7 +1847,8 @@ nl80211_get_link_id_by_freq(struct i802_bss *bss, unsigned int freq) static void mlme_event(struct i802_bss *bss, enum nl80211_commands cmd, struct nlattr *frame, struct nlattr *addr, struct nlattr *timed_out, - struct nlattr *freq, struct nlattr *ack, + struct nlattr *freq, struct nlattr *freq_offset, + struct nlattr *ack, struct nlattr *cookie, struct nlattr *sig, struct nlattr *wmm, struct nlattr *req_ie, struct nlattr *link) @@ -1959,7 +1965,7 @@ static void mlme_event(struct i802_bss *bss, nla_data(frame), nla_len(frame)); break; case NL80211_CMD_FRAME: - mlme_event_mgmt(bss, freq, sig, nla_data(frame), + mlme_event_mgmt(bss, freq, freq_offset, sig, nla_data(frame), nla_len(frame), link_id); break; case NL80211_CMD_FRAME_TX_STATUS: @@ -4933,7 +4939,9 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd, case NL80211_CMD_UNPROT_DISASSOCIATE: mlme_event(bss, cmd, tb[NL80211_ATTR_FRAME], tb[NL80211_ATTR_MAC], tb[NL80211_ATTR_TIMED_OUT], - tb[NL80211_ATTR_WIPHY_FREQ], tb[NL80211_ATTR_ACK], + tb[NL80211_ATTR_WIPHY_FREQ], + tb[NL80211_ATTR_WIPHY_FREQ_OFFSET], + tb[NL80211_ATTR_ACK], tb[NL80211_ATTR_COOKIE], tb[NL80211_ATTR_RX_SIGNAL_DBM], tb[NL80211_ATTR_STA_WME], @@ -5326,7 +5334,9 @@ int process_bss_event(struct nl_msg *msg, void *arg) case NL80211_CMD_FRAME_TX_STATUS: mlme_event(bss, gnlh->cmd, tb[NL80211_ATTR_FRAME], tb[NL80211_ATTR_MAC], tb[NL80211_ATTR_TIMED_OUT], - tb[NL80211_ATTR_WIPHY_FREQ], tb[NL80211_ATTR_ACK], + tb[NL80211_ATTR_WIPHY_FREQ], + tb[NL80211_ATTR_WIPHY_FREQ_OFFSET], + tb[NL80211_ATTR_ACK], tb[NL80211_ATTR_COOKIE], tb[NL80211_ATTR_RX_SIGNAL_DBM], tb[NL80211_ATTR_STA_WME], NULL, diff --git a/src/drivers/driver_nl80211_scan.c b/src/drivers/driver_nl80211_scan.c index b44f0a1d5b5a..6328d33dbfbb 100644 --- a/src/drivers/driver_nl80211_scan.c +++ b/src/drivers/driver_nl80211_scan.c @@ -251,16 +251,31 @@ nl80211_scan_common(struct i802_bss *bss, u8 cmd, if (params->freqs) { struct nlattr *freqs; - freqs = nla_nest_start(msg, NL80211_ATTR_SCAN_FREQUENCIES); - if (freqs == NULL) - goto fail; - for (i = 0; params->freqs[i]; i++) { - wpa_printf(MSG_MSGDUMP, "nl80211: Scan frequency %u " - "MHz", params->freqs[i]); - if (nla_put_u32(msg, i + 1, params->freqs[i])) + + i = 0; + if (params->freq_offset) { + freqs = nla_nest_start(msg, NL80211_ATTR_SCAN_FREQ_KHZ); + if (freqs == NULL) goto fail; + for (; params->freqs[i]; i++) { + wpa_printf(MSG_MSGDUMP, "nl80211: Scan frequency %u " + "KHz", MHZ_TO_KHZ(params->freqs[i]) + params->freq_offset); + if (nla_put_u32(msg, i + 1, MHZ_TO_KHZ(params->freqs[i]) + params->freq_offset)) + goto fail; + } + nla_nest_end(msg, freqs); + } else { + freqs = nla_nest_start(msg, NL80211_ATTR_SCAN_FREQUENCIES); + if (freqs == NULL) + goto fail; + for (; params->freqs[i]; i++) { + wpa_printf(MSG_MSGDUMP, "nl80211: Scan frequency %u " + "MHz", params->freqs[i]); + if (nla_put_u32(msg, i + 1, params->freqs[i])) + goto fail; + } + nla_nest_end(msg, freqs); } - nla_nest_end(msg, freqs); } os_free(drv->filter_ssids); @@ -788,6 +803,7 @@ nl80211_parse_bss_info(struct wpa_driver_nl80211_data *drv, [NL80211_BSS_PARENT_TSF] = { .type = NLA_U64 }, [NL80211_BSS_PARENT_BSSID] = { .type = NLA_UNSPEC }, [NL80211_BSS_LAST_SEEN_BOOTTIME] = { .type = NLA_U64 }, + [NL80211_BSS_FREQUENCY_OFFSET] = { .type = NLA_U32 }, }; struct wpa_scan_res *r; const u8 *ie, *beacon_ie; @@ -831,6 +847,8 @@ nl80211_parse_bss_info(struct wpa_driver_nl80211_data *drv, ETH_ALEN); if (bss[NL80211_BSS_FREQUENCY]) r->freq = nla_get_u32(bss[NL80211_BSS_FREQUENCY]); + if (bss[NL80211_BSS_FREQUENCY_OFFSET]) + r->freq_offset = nla_get_u32(bss[NL80211_BSS_FREQUENCY_OFFSET]); if (bss[NL80211_BSS_BEACON_INTERVAL]) r->beacon_int = nla_get_u16(bss[NL80211_BSS_BEACON_INTERVAL]); if (bss[NL80211_BSS_CAPABILITY]) -- 2.43.0 From lachlan.hodges at morsemicro.com Wed May 27 23:38:52 2026 From: lachlan.hodges at morsemicro.com (Lachlan Hodges) Date: Thu, 28 May 2026 16:38:52 +1000 Subject: [RFC 3/8] tests: expose scan_freq_offset In-Reply-To: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> References: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> Message-ID: <20260528063857.950556-4-lachlan.hodges@morsemicro.com> Expose `scan_freq_offset` such that S1G channels can be scanned during tests. Signed-off-by: Lachlan Hodges --- tests/hwsim/wpasupplicant.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/hwsim/wpasupplicant.py b/tests/hwsim/wpasupplicant.py index 635e0235b6c0..9edad575a8f3 100644 --- a/tests/hwsim/wpasupplicant.py +++ b/tests/hwsim/wpasupplicant.py @@ -1149,7 +1149,8 @@ class WpaSupplicant: "sae_password_id_change", "enable_4addr_mode", "pmksa_privacy", - "eap_over_auth_frame"] + "eap_over_auth_frame", + "scan_freq_offset"] for field in not_quoted: if field in kwargs and kwargs[field]: self.set_network(id, field, kwargs[field]) -- 2.43.0 From lachlan.hodges at morsemicro.com Wed May 27 23:38:53 2026 From: lachlan.hodges at morsemicro.com (Lachlan Hodges) Date: Thu, 28 May 2026 16:38:53 +1000 Subject: [RFC 4/8] config: enable configuration of S1G In-Reply-To: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> References: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> Message-ID: <20260528063857.950556-5-lachlan.hodges@morsemicro.com> Introduce the ability to configure an S1G radio. This consists of a new hw_mode=ah for the S1G band, ieee80211ah to denote if S1G is enabled, s1g_oper_centr_freq_idx for the operating center frequency and s1g_primary_2mhz to denote the primary (control) channel width. Signed-off-by: Lachlan Hodges --- hostapd/defconfig | 3 +++ hostapd/hostapd.conf | 26 ++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/hostapd/defconfig b/hostapd/defconfig index 176776677c2b..48dab4697582 100644 --- a/hostapd/defconfig +++ b/hostapd/defconfig @@ -428,3 +428,6 @@ CONFIG_DPP2=y # IEEE P802.11bi/D4.0, 12.16.5 (IEEE 802.1X authentication utilizing # Authentication frames) #CONFIG_IEEE8021X_AUTH=y + +# IEEE 802.11ah (S1G) support +#CONFIG_IEEE80211AH=y diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 20e09a9e329d..7ec47ee8067b 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -147,10 +147,10 @@ ssid=test # g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used # with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this # needs to be set to hw_mode=a. For IEEE 802.11ax (HE) on 6 GHz this needs -# to be set to hw_mode=a. When using ACS (see channel parameter), a -# special value "any" can be used to indicate that any support band can be used. -# This special case is currently supported only with drivers with which -# offloaded ACS is used. +# to be set to hw_mode=a. For IEEE 802.11ah (S1G) this needs to be set to +# hw_mode=ah. When using ACS (see channel parameter), a special value "any" can +# be used to indicate that any support band can be used. This special case is +# currently supported only with drivers with which offloaded ACS is used. # Default: IEEE 802.11b hw_mode=g @@ -1132,6 +1132,24 @@ wmm_ac_vo_acm=0 # is disabled. #disable_mcs15_rx=1 +##### IEEE 802.11ah related configuration ################################ + +# ieee80211ah: Whether IEEE 802.11ah (S1G) is enabled +# 0 = disabled (default) +# 1 = enabled +# Note: hw_mode=ah is used to specify that the 900MHz band is used with ah. +#ieee80211ah=1 + +# S1G operating channel +# Indicates the channel index of the BSS operating channel +#s1g_oper_centr_freq_idx=44 + +# S1G primary 2MHz +# Specifies if a 2MHz primary channel is being used. +# 0 = 1MHz primary +# 1 = 2MHz primary +#s1g_primary_2mhz=1 + ##### IEEE 802.1X-2004 related configuration ################################## # Require IEEE 802.1X authorization -- 2.43.0 From lachlan.hodges at morsemicro.com Wed May 27 23:38:55 2026 From: lachlan.hodges at morsemicro.com (Lachlan Hodges) Date: Thu, 28 May 2026 16:38:55 +1000 Subject: [RFC 6/8] STA: add S1G support In-Reply-To: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> References: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> Message-ID: <20260528063857.950556-7-lachlan.hodges@morsemicro.com> Introduce the ability to bring up an S1G STA. A sample config to connect to the equivalent AP config is as follows: ``` update_config=1 country=US pmf=2 network={ ssid="wifi_halow" sae_password="12345678" key_mgmt=SAE } ``` Signed-off-by: Lachlan Hodges --- src/drivers/driver.h | 23 ++++++++++++++++++++++- wpa_supplicant/Makefile | 6 ++++++ wpa_supplicant/defconfig | 3 +++ wpa_supplicant/events.c | 13 +++++++------ wpa_supplicant/mesh.c | 3 ++- wpa_supplicant/sme.c | 6 ++++++ wpa_supplicant/wpa_supplicant.c | 19 +++++++++++++++++-- 7 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index c382bf90ccf6..22fbc1b4327f 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -63,6 +63,9 @@ enum hostapd_chan_width_attr { HOSTAPD_CHAN_WIDTH_80 = BIT(4), HOSTAPD_CHAN_WIDTH_160 = BIT(5), HOSTAPD_CHAN_WIDTH_320 = BIT(6), + HOSTAPD_CHAN_WIDTH_4 = BIT(9), + HOSTAPD_CHAN_WIDTH_8 = BIT(10), + HOSTAPD_CHAN_WIDTH_16 = BIT(11), }; /* Filter gratuitous ARP */ @@ -332,6 +335,11 @@ struct hostapd_hw_modes { * eht_capab - EHT (IEEE 802.11be) capabilities */ struct eht_capabilities eht_capab[IEEE80211_MODE_NUM]; + + /** + * s1g_capab - S1G (IEEE 802.11ah) capabilities + */ + struct ieee80211_s1g_capabilities s1g_capab; }; @@ -888,6 +896,11 @@ struct hostapd_freq_params { */ int he_enabled; + /** + * s1g_enabled - Whether S1G is enabled + */ + int s1g_enabled; + /** * center_freq1 - Segment 0 center frequency in MHz * @@ -910,7 +923,7 @@ struct hostapd_freq_params { int center_freq2; /** - * bandwidth - Channel bandwidth in MHz (20, 40, 80, 160) + * bandwidth - Channel bandwidth in MHz (1, 2, 4, 8, 16, 20, 40, 80, 160) */ int bandwidth; @@ -942,6 +955,13 @@ struct hostapd_freq_params { * link_id: If >=0 indicates the link of the AP MLD to configure */ int link_id; + + /** + * s1g_primary_2mhz: Determines if the S1G channel is operating as a 2MHz primary. + * + * S1G primary channels can either be 1MHz or 2MHz. + */ + bool s1g_primary_2mhz; }; /** @@ -2856,6 +2876,7 @@ struct hostapd_sta_add_params { const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab; const struct ieee80211_eht_capabilities *eht_capab; size_t eht_capab_len; + const struct ieee80211_s1g_capabilities *s1g_capab; u32 flags; /* bitmask of WPA_STA_* flags */ u32 flags_mask; /* unset bits in flags */ #ifdef CONFIG_ENC_ASSOC diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index 67f337df33da..23f795ff1c35 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_IEEE80211AH +OBJS += ../src/ap/ieee802_11_s1g.o +endif ifdef CONFIG_WNM_AP CFLAGS += -DCONFIG_WNM_AP OBJS += ../src/ap/wnm_ap.o @@ -1092,6 +1095,9 @@ endif ifdef CONFIG_IEEE80211AX CFLAGS += -DCONFIG_IEEE80211AX endif +ifdef CONFIG_IEEE80211AH +CFLAGS += -DCONFIG_IEEE80211AH +endif ifdef NEED_AP_MLME OBJS += ../src/ap/wmm.o diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig index 75832dbba91f..2fa0787379fa 100644 --- a/wpa_supplicant/defconfig +++ b/wpa_supplicant/defconfig @@ -698,3 +698,6 @@ CONFIG_DPP2=y # IEEE P802.11bi/D4.0, 12.16.5 (IEEE 802.1X authentication utilizing # Authentication frames) #CONFIG_IEEE8021X_AUTH=y + +# IEEE 802.11ah (S1G) support +#CONFIG_IEEE80211AH=y diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index f9c51994cae4..8e4991f8733c 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -1455,13 +1455,14 @@ static bool wpa_scan_res_ok(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, } #ifdef CONFIG_SAE - /* When using SAE Password Identifier and when operationg on the 6 GHz - * band, only H2E is allowed. */ + /* When using SAE Password Identifier and when operating on 6 GHz or + * S1G, only H2E is allowed. */ sae_pwe = wpas_get_ssid_sae_pwe(wpa_s, ssid); - if ((sae_pwe == SAE_PWE_HASH_TO_ELEMENT || - wpa_key_mgmt_only_sae_ext_key(ssid->key_mgmt) || - is_6ghz_freq(bss->freq) || ssid->sae_password_id) && - sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK && + if ((is_s1ghz_freq(bss->freq) || + ((sae_pwe == SAE_PWE_HASH_TO_ELEMENT || + wpa_key_mgmt_only_sae_ext_key(ssid->key_mgmt) || + is_6ghz_freq(bss->freq) || ssid->sae_password_id) && + sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK)) && wpa_key_mgmt_only_sae(ssid->key_mgmt) && !(rsnxe_capa & BIT(WLAN_RSNX_CAPAB_SAE_H2E))) { if (debug_print) diff --git a/wpa_supplicant/mesh.c b/wpa_supplicant/mesh.c index 30547b19d108..ae7e66962458 100644 --- a/wpa_supplicant/mesh.c +++ b/wpa_supplicant/mesh.c @@ -206,12 +206,13 @@ static int wpas_mesh_update_freq_params(struct wpa_supplicant *wpa_s) ifmsh->conf->ieee80211ac, ifmsh->conf->ieee80211ax, ifmsh->conf->ieee80211be, + 0, /* No S1G mesh support */ 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)) { + he_capab, NULL, 0, 0, false)) { wpa_printf(MSG_ERROR, "Error updating mesh frequency params"); wpa_supplicant_mesh_deinit(wpa_s, true); return -1; diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index 1dd1f79601b3..204a96bc603c 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -259,6 +259,12 @@ static struct wpabuf * sme_auth_build_sae_commit(struct wpa_supplicant *wpa_s, if (bss && is_6ghz_freq(bss->freq) && sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK) use_pt = 1; + if (bss && is_s1ghz_freq(bss->freq) && + sae_pwe != SAE_PWE_HASH_TO_ELEMENT) { + wpa_dbg(wpa_s, MSG_DEBUG, + "SAE: Force H2E mode for S1G BSS"); + sae_pwe = SAE_PWE_HASH_TO_ELEMENT; + } #ifdef CONFIG_SAE_PK if ((rsnxe_capa & BIT(WLAN_RSNX_CAPAB_SAE_PK)) && ssid->sae_pk != SAE_PK_MODE_DISABLED && diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index be9db6d646fd..932a0e5d3d87 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -2212,6 +2212,12 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, "RSN: Enable SAE hash-to-element mode for 6 GHz BSS"); sae_pwe = SAE_PWE_BOTH; } + if (bss && is_s1ghz_freq(bss->freq) && + sae_pwe != SAE_PWE_HASH_TO_ELEMENT) { + wpa_dbg(wpa_s, MSG_DEBUG, + "RSN: hunt-and-peck not permitted with an S1G BSS"); + sae_pwe = SAE_PWE_HASH_TO_ELEMENT; + } wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_SAE_PWE, sae_pwe); #ifdef CONFIG_SAE_PK wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_SAE_PK, @@ -2229,6 +2235,12 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_MFP, MGMT_FRAME_PROTECTION_REQUIRED); } + if (bss && is_s1ghz_freq(bss->freq) && + wpas_get_ssid_pmf(wpa_s, ssid) != MGMT_FRAME_PROTECTION_REQUIRED) { + wpa_dbg(wpa_s, MSG_DEBUG, "RSN: Force MFPR=1 on S1GHz"); + wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_MFP, + MGMT_FRAME_PROTECTION_REQUIRED); + } #ifdef CONFIG_TESTING_OPTIONS wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_FT_RSNXE_USED, wpa_s->ft_rsnxe_used); @@ -3531,11 +3543,11 @@ skip_80mhz: freq->channel, ssid->enable_edmg, ssid->edmg_channel, freq->ht_enabled, freq->vht_enabled, freq->he_enabled, - freq->eht_enabled, + freq->eht_enabled, 0, /* No S1G mesh */ freq->sec_channel_offset, chwidth, seg0, seg1, vht_caps, &mode->he_capab[ieee80211_mode], - &mode->eht_capab[ieee80211_mode], 0) != 0) + &mode->eht_capab[ieee80211_mode], 0, 0, false) != 0) return false; *freq = vht_freq; @@ -5002,6 +5014,9 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) #ifdef CONFIG_SAE params.sae_pwe = wpas_get_ssid_sae_pwe(wpa_s, ssid); + if (bss && is_s1ghz_freq(bss->freq) && + params.sae_pwe != SAE_PWE_HASH_TO_ELEMENT) + params.sae_pwe = SAE_PWE_HASH_TO_ELEMENT; #endif /* CONFIG_SAE */ ret = wpa_drv_associate(wpa_s, ¶ms); -- 2.43.0 From lachlan.hodges at morsemicro.com Wed May 27 23:38:56 2026 From: lachlan.hodges at morsemicro.com (Lachlan Hodges) Date: Thu, 28 May 2026 16:38:56 +1000 Subject: [RFC 7/8] nl80211: add S1G support In-Reply-To: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> References: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> Message-ID: <20260528063857.950556-8-lachlan.hodges@morsemicro.com> Introduce the ability to configure an S1G interface via nl80211. Signed-off-by: Lachlan Hodges --- src/drivers/driver_nl80211.c | 51 ++++++++++++++++++++++++++++-- src/drivers/driver_nl80211_capa.c | 36 +++++++++++++++++++-- src/drivers/driver_nl80211_event.c | 10 ++++-- 3 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index df82c1b670e3..0cdeb9bd5668 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -5666,6 +5666,7 @@ static int nl80211_put_freq_params(struct nl_msg *msg, wpa_printf(MSG_DEBUG, " * he_enabled=%d", freq->he_enabled); wpa_printf(MSG_DEBUG, " * vht_enabled=%d", freq->vht_enabled); wpa_printf(MSG_DEBUG, " * ht_enabled=%d", freq->ht_enabled); + wpa_printf(MSG_DEBUG, " * s1g_enabled=%d", freq->s1g_enabled); wpa_printf(MSG_DEBUG, " * radar_background=%d", freq->radar_background); @@ -5673,7 +5674,43 @@ static int nl80211_put_freq_params(struct nl_msg *msg, is_24ghz = hw_mode == HOSTAPD_MODE_IEEE80211G || hw_mode == HOSTAPD_MODE_IEEE80211B; - if (freq->vht_enabled || + if (freq->s1g_enabled) { + enum nl80211_chan_width cw; + + wpa_printf(MSG_DEBUG, " * channel_width=%d", freq->bandwidth); + wpa_printf(MSG_DEBUG, " * center_freq1=%d", freq->center_freq1); + wpa_printf(MSG_DEBUG, " * center_freq1_offset=%d", freq->center_freq1_offset); + wpa_printf(MSG_DEBUG, " * s1g_primary_2mhz=%d", freq->s1g_primary_2mhz); + + switch (freq->bandwidth) { + case 1: + cw = NL80211_CHAN_WIDTH_1; + break; + case 2: + cw = NL80211_CHAN_WIDTH_2; + break; + case 4: + cw = NL80211_CHAN_WIDTH_4; + break; + case 8: + cw = NL80211_CHAN_WIDTH_8; + break; + case 16: + cw = NL80211_CHAN_WIDTH_16; + break; + default: + return -EINVAL; + } + + if (nla_put_u32(msg, NL80211_ATTR_CHANNEL_WIDTH, cw)) + return -ENOBUFS; + if (nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1, freq->center_freq1)) + return -ENOBUFS; + if (nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1_OFFSET, freq->center_freq1_offset)) + return -ENOBUFS; + if (freq->s1g_primary_2mhz && nla_put_flag(msg, NL80211_ATTR_S1G_PRIMARY_2MHZ)) + return -ENOBUFS; + } else if (freq->vht_enabled || ((freq->he_enabled || freq->eht_enabled) && !is_24ghz)) { enum nl80211_chan_width cw; @@ -6193,9 +6230,9 @@ static int nl80211_set_channel(struct i802_bss *bss, int ret; wpa_printf(MSG_DEBUG, - "nl80211: Set freq %d offset %d (ht_enabled=%d, vht_enabled=%d, he_enabled=%d, eht_enabled=%d, , bandwidth=%d MHz, cf1=%d MHz, cf1_offset=%d KHz cf2=%d MHz)", + "nl80211: Set freq %d offset %d (ht_enabled=%d, vht_enabled=%d, he_enabled=%d, eht_enabled=%d, s1g_enabled=%d, bandwidth=%d MHz, cf1=%d MHz, cf1_offset=%d KHz cf2=%d MHz)", freq->freq, freq->freq_offset, freq->ht_enabled, freq->vht_enabled, - freq->he_enabled, freq->eht_enabled, freq->bandwidth, + freq->he_enabled, freq->eht_enabled, freq->s1g_enabled, freq->bandwidth, freq->center_freq1, freq->center_freq1_offset, freq->center_freq2); msg = nl80211_bss_msg(bss, 0, set_chan ? NL80211_CMD_SET_CHANNEL : @@ -6381,6 +6418,14 @@ static int wpa_driver_nl80211_sta_add(void *priv, goto fail; } + if (params->s1g_capab) { + wpa_hexdump(MSG_DEBUG, " * s1g_capab", + params->s1g_capab, sizeof(*params->s1g_capab)); + if (nla_put(msg, NL80211_ATTR_S1G_CAPABILITY, + sizeof(*params->s1g_capab), params->s1g_capab)) + goto fail; + } + if (params->ext_capab) { wpa_hexdump(MSG_DEBUG, " * ext_capab", params->ext_capab, params->ext_capab_len); diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c index e423eeeb4528..2b96721c8d49 100644 --- a/src/drivers/driver_nl80211_capa.c +++ b/src/drivers/driver_nl80211_capa.c @@ -2132,6 +2132,24 @@ static void phy_info_vht_capa(struct hostapd_hw_modes *mode, } +static void phy_info_s1g_capa(struct hostapd_hw_modes *mode, + struct nlattr *capability, + struct nlattr *mcs_nss_set) +{ + if (capability && nla_len(capability) >= 10) { + u8 *capa; + capa = nla_data(capability); + os_memcpy(&mode->s1g_capab.capab_info, capa, 10); + } + + if (mcs_nss_set && nla_len(mcs_nss_set) >= 5) { + u8 *mcs_nss; + mcs_nss = nla_data(mcs_nss_set); + os_memcpy(&mode->s1g_capab.supp_mcs_nss, mcs_nss, 5); + } +} + + static int phy_info_edmg_capa(struct hostapd_hw_modes *mode, struct nlattr *bw_config, struct nlattr *channels) @@ -2208,6 +2226,13 @@ static void phy_info_freq(struct hostapd_hw_modes *mode, if (tb_freq[NL80211_FREQUENCY_ATTR_NO_320MHZ]) chan->allowed_bw &= ~HOSTAPD_CHAN_WIDTH_320; + if (tb_freq[NL80211_FREQUENCY_ATTR_NO_4MHZ]) + chan->allowed_bw &= ~HOSTAPD_CHAN_WIDTH_4; + if (tb_freq[NL80211_FREQUENCY_ATTR_8MHZ]) + chan->allowed_bw &= ~HOSTAPD_CHAN_WIDTH_8; + if (tb_freq[NL80211_FREQUENCY_ATTR_16MHZ]) + chan->allowed_bw &= ~HOSTAPD_CHAN_WIDTH_16; + if (tb_freq[NL80211_FREQUENCY_ATTR_DFS_STATE]) { enum nl80211_dfs_state state = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_DFS_STATE]); @@ -2589,6 +2614,8 @@ static int phy_info_band(struct phy_info_arg *phy_info, struct nlattr *nl_band) tb_band[NL80211_BAND_ATTR_HT_MCS_SET]); phy_info_vht_capa(mode, tb_band[NL80211_BAND_ATTR_VHT_CAPA], tb_band[NL80211_BAND_ATTR_VHT_MCS_SET]); + phy_info_s1g_capa(mode, tb_band[NL80211_BAND_ATTR_S1G_CAPA], + tb_band[NL80211_BAND_ATTR_S1G_MCS_NSS_SET]); ret = phy_info_edmg_capa(mode, tb_band[NL80211_BAND_ATTR_EDMG_BW_CONFIG], tb_band[NL80211_BAND_ATTR_EDMG_CHANNELS]); @@ -2658,8 +2685,9 @@ wpa_driver_nl80211_postprocess_modes(struct hostapd_hw_modes *modes, continue; modes[m].is_6ghz = false; - - if (modes[m].channels[0].freq < 2000) { + if (is_s1ghz_freq(modes[m].channels[0].freq)) { + modes[m].mode = HOSTAPD_MODE_IEEE80211AH; + } else if (modes[m].channels[0].freq < 2000) { modes[m].num_channels = 0; continue; } else if (modes[m].channels[0].freq < 4000) { @@ -3124,6 +3152,8 @@ static const char * modestr(enum hostapd_hw_mode mode) return "802.11a"; case HOSTAPD_MODE_IEEE80211AD: return "802.11ad"; + case HOSTAPD_MODE_IEEE80211AH: + return "802.11ah"; default: return "?"; } @@ -3151,7 +3181,7 @@ static void nl80211_dump_chan_list(struct wpa_driver_nl80211_data *drv, if (is_6ghz_freq(chan->freq)) drv->uses_6ghz = true; - if (chan->freq >= 900 && chan->freq < 1000) + if (is_s1ghz_freq(chan->freq)) drv->uses_s1g = true; if (drv->uses_s1g) { diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index 28e8a7123af6..14c6f27fbebf 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -327,9 +327,13 @@ static void mlme_event_assoc(struct wpa_driver_nl80211_data *drv, event.assoc_info.resp_frame = frame; event.assoc_info.resp_frame_len = len; if (len > 24 + sizeof(mgmt->u.assoc_resp)) { - event.assoc_info.resp_ies = (u8 *) mgmt->u.assoc_resp.variable; - event.assoc_info.resp_ies_len = - len - 24 - sizeof(mgmt->u.assoc_resp); + if (is_s1ghz_freq(drv->assoc_freq)) { + event.assoc_info.resp_ies = (u8 *) mgmt->u.s1g_assoc_resp.variable; + event.assoc_info.resp_ies_len = len - 24 - sizeof(mgmt->u.s1g_assoc_resp); + } else { + event.assoc_info.resp_ies = (u8 *) mgmt->u.assoc_resp.variable; + event.assoc_info.resp_ies_len = len - 24 - sizeof(mgmt->u.assoc_resp); + } } if (req_ie) { -- 2.43.0 From lachlan.hodges at morsemicro.com Wed May 27 23:38:57 2026 From: lachlan.hodges at morsemicro.com (Lachlan Hodges) Date: Thu, 28 May 2026 16:38:57 +1000 Subject: [RFC 8/8] tests: add S1G channel tests In-Reply-To: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> References: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> Message-ID: <20260528063857.950556-9-lachlan.hodges@morsemicro.com> Add a small suite of S1G tests which test various channel permutations, the NO_PRIMARY flag and correct AID Response element configuring and parsing. Signed-off-by: Lachlan Hodges --- tests/hwsim/example-hostapd.config | 1 + tests/hwsim/example-wpa_supplicant.config | 1 + tests/hwsim/test_s1g.py | 245 ++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 tests/hwsim/test_s1g.py diff --git a/tests/hwsim/example-hostapd.config b/tests/hwsim/example-hostapd.config index 41611b0f2f16..6a01cd61c38d 100644 --- a/tests/hwsim/example-hostapd.config +++ b/tests/hwsim/example-hostapd.config @@ -128,3 +128,4 @@ CONFIG_PROCESS_COORDINATION=y CONFIG_ENC_ASSOC=y CONFIG_PMKSA_PRIVACY=y CONFIG_IEEE8021X_AUTH=y +CONFIG_IEEE80211AH=y diff --git a/tests/hwsim/example-wpa_supplicant.config b/tests/hwsim/example-wpa_supplicant.config index c5b364757afa..48283fc4dd8f 100644 --- a/tests/hwsim/example-wpa_supplicant.config +++ b/tests/hwsim/example-wpa_supplicant.config @@ -176,3 +176,4 @@ CONFIG_NAN=y CONFIG_ENC_ASSOC=y CONFIG_PMKSA_PRIVACY=y CONFIG_IEEE8021X_AUTH=y +CONFIG_IEEE80211AH=y diff --git a/tests/hwsim/test_s1g.py b/tests/hwsim/test_s1g.py new file mode 100644 index 000000000000..39ebe6efe871 --- /dev/null +++ b/tests/hwsim/test_s1g.py @@ -0,0 +1,245 @@ +# Test cases for IEEE 802.11ah S1G operation +# Copyright (c) 2026 Morse Micro +# +# This software may be distributed under the terms of the BSD license. +# See README for more details. + +import logging +logger = logging.getLogger() +import hostapd +import hwsim_utils +from utils import * + +# 4 MHz op 2 MHz primary lower 1 MHz +def test_s1g_4mhz_op_2mhz_pri_lower_loc(dev, apdev): + """S1G 4 MHz op with 2 MHz primary lower location""" + check_sae_capab(dev[0]) + hapd = None + clear_scan_cache(apdev[0]) + try: + params = hostapd.wpa3_params(ssid="s1g-1mhz", password="s1gpass") + params.update({ + "country_code": "US", + "ieee80211ah": "1", + "channel": "41", + "op_class": "70", + "hw_mode": "ah", + "s1g_oper_centr_freq_idx": "40", + "s1g_primary_2mhz": "1", + }) + hapd = hostapd.add_ap(apdev[0], params) + status = hapd.get_status() + if status.get("ieee80211ah") != "1": + raise Exception("AP did not report IEEE 802.11ah enabled") + dev[0].connect("s1g-1mhz", sae_password="s1gpass", key_mgmt="SAE", + scan_freq="922", scan_freq_offset="500", sae_pwe="1", + ieee80211w="2") + except Exception as e: + if isinstance(e, Exception) and str(e) == "AP startup failed": + raise HwsimSkip("S1G operation not supported in this environment") + raise + finally: + dev[0].request("DISCONNECT") + dev[0].wait_disconnected() + clear_regdom(hapd, dev) + +# 2 MHz op 2 MHz primary upper 1 MHz +def test_s1g_2mhz_op_2mhz_pri_upper_loc(dev, apdev): + """S1G 2 MHz op with 2 MHz primary upper location""" + check_sae_capab(dev[0]) + hapd = None + clear_scan_cache(apdev[0]) + try: + params = hostapd.wpa3_params(ssid="s1g-1mhz", password="s1gpass") + params.update({ + "country_code": "US", + "ieee80211ah": "1", + "channel": "31", + "op_class": "69", + "hw_mode": "ah", + "s1g_oper_centr_freq_idx": "30", + "s1g_primary_2mhz": "1", + }) + hapd = hostapd.add_ap(apdev[0], params) + status = hapd.get_status() + if status.get("ieee80211ah") != "1": + raise Exception("AP did not report IEEE 802.11ah enabled") + dev[0].connect("s1g-1mhz", sae_password="s1gpass", key_mgmt="SAE", + scan_freq="917", scan_freq_offset="500", sae_pwe="1", + ieee80211w="2") + except Exception as e: + if isinstance(e, Exception) and str(e) == "AP startup failed": + raise HwsimSkip("S1G operation not supported in this environment") + raise + finally: + dev[0].request("DISCONNECT") + dev[0].wait_disconnected() + clear_regdom(hapd, dev) + +# 2 MHz op 2 MHz primary lower 1 MHz +def test_s1g_2mhz_op_2mhz_pri_lower_loc(dev, apdev): + """S1G 2 MHz op with 2 MHz primary lower location""" + check_sae_capab(dev[0]) + hapd = None + clear_scan_cache(apdev[0]) + try: + params = hostapd.wpa3_params(ssid="s1g-1mhz", password="s1gpass") + params.update({ + "country_code": "US", + "ieee80211ah": "1", + "channel": "29", + "op_class": "69", + "hw_mode": "ah", + "s1g_oper_centr_freq_idx": "30", + "s1g_primary_2mhz": "1", + }) + hapd = hostapd.add_ap(apdev[0], params) + status = hapd.get_status() + if status.get("ieee80211ah") != "1": + raise Exception("AP did not report IEEE 802.11ah enabled") + dev[0].connect("s1g-1mhz", sae_password="s1gpass", key_mgmt="SAE", + scan_freq="916", scan_freq_offset="500", sae_pwe="1", + ieee80211w="2") + except Exception as e: + if isinstance(e, Exception) and str(e) == "AP startup failed": + raise HwsimSkip("S1G operation not supported in this environment") + raise + finally: + dev[0].request("DISCONNECT") + dev[0].wait_disconnected() + clear_regdom(hapd, dev) + +# 2 MHz 1 MHz primary lower +def test_s1g_2mhz_op_1mhz_pri(dev, apdev): + """S1G 2 MHz op with 1 MHz primary""" + check_sae_capab(dev[0]) + hapd = None + clear_scan_cache(apdev[0]) + try: + params = hostapd.wpa3_params(ssid="s1g-1mhz", password="s1gpass") + params.update({ + "country_code": "US", + "ieee80211ah": "1", + "channel": "5", + "op_class": "69", + "hw_mode": "ah", + "s1g_oper_centr_freq_idx": "6", + }) + hapd = hostapd.add_ap(apdev[0], params) + status = hapd.get_status() + if status.get("ieee80211ah") != "1": + raise Exception("AP did not report IEEE 802.11ah enabled") + dev[0].connect("s1g-1mhz", sae_password="s1gpass", key_mgmt="SAE", + scan_freq="904", scan_freq_offset="500", sae_pwe="1", + ieee80211w="2") + except Exception as e: + if isinstance(e, Exception) and str(e) == "AP startup failed": + raise HwsimSkip("S1G operation not supported in this environment") + raise + finally: + dev[0].request("DISCONNECT") + dev[0].wait_disconnected() + clear_regdom(hapd, dev) + +def test_s1g_1mhz(dev, apdev): + """S1G 1 MHz AP with SAE""" + check_sae_capab(dev[0]) + hapd = None + clear_scan_cache(apdev[0]) + try: + params = hostapd.wpa3_params(ssid="s1g-1mhz", password="s1gpass") + params.update({ + "country_code": "US", + "ieee80211ah": "1", + "channel": "3", + "op_class": "68", + "hw_mode": "ah", + "s1g_oper_centr_freq_idx": "0", + }) + hapd = hostapd.add_ap(apdev[0], params) + status = hapd.get_status() + if status.get("ieee80211ah") != "1": + raise Exception("AP did not report IEEE 802.11ah enabled") + dev[0].connect("s1g-1mhz", sae_password="s1gpass", key_mgmt="SAE", + scan_freq="903", scan_freq_offset="500", sae_pwe="1", + ieee80211w="2") + except Exception as e: + if isinstance(e, Exception) and str(e) == "AP startup failed": + raise HwsimSkip("S1G operation not supported in this environment") + raise + finally: + dev[0].request("DISCONNECT") + dev[0].wait_disconnected() + clear_regdom(hapd, dev) + +def test_s1g_1mhz_no_primary(dev, apdev): + """S1G 1 MHz AP using 1 MHz primary that is marked as NO_PRIMARY""" + check_sae_capab(dev[0]) + hapd = None + clear_scan_cache(apdev[0]) + try: + params = hostapd.wpa3_params(ssid="s1g-1mhz", password="s1gpass") + params.update({ + "country_code": "US", + "ieee80211ah": "1", + "channel": "1", + "op_class": "68", + "hw_mode": "ah", + "s1g_oper_centr_freq_idx": "0", + }) + hapd = hostapd.add_ap(apdev[0], params, wait_enabled=False) + ev = hapd.wait_event(["AP-DISABLED", "AP-ENABLED"], timeout=10) + if not ev: + raise Exception("AP setup failure timed out") + if "AP-ENABLED" in ev: + raise Exception("S1G 1 MHz NO_PRIMARY channel accepted") + except Exception as e: + if isinstance(e, Exception) and str(e) == "AP startup failed": + raise HwsimSkip("S1G operation not supported in this environment") + raise + finally: + clear_regdom(hapd, dev) + +def test_s1g_multi_sta_aid(dev, apdev): + """S1G AID response is correctly built and parsed""" + for i in range(3): + check_sae_capab(dev[i]) + hapd = None + clear_scan_cache(apdev[0]) + try: + params = hostapd.wpa3_params(ssid="s1g-aid", password="s1gpass") + params.update({ + "country_code": "US", + "ieee80211ah": "1", + "channel": "5", + "op_class": "69", + "hw_mode": "ah", + "s1g_oper_centr_freq_idx": "6", + }) + hapd = hostapd.add_ap(apdev[0], params) + status = hapd.get_status() + if status.get("ieee80211ah") != "1": + raise Exception("AP did not report IEEE 802.11ah enabled") + for i in range(3): + dev[i].connect("s1g-aid", sae_password="s1gpass", key_mgmt="SAE", + scan_freq="904", scan_freq_offset="500", + sae_pwe="1", ieee80211w="2") + for i in range(3): + hapd.wait_sta() + aid = [] + for i in range(3): + aid.append(int(hapd.get_sta(dev[i].own_addr())['aid'])) + logger.info("S1G assigned AIDs: " + str(aid)) + if len(set(aid)) != 3: + raise Exception("AP did not assign unique AID to each S1G STA") + if any(a == 0 for a in aid): + raise Exception("AP assigned AID 0 to an S1G STA") + except Exception as e: + if isinstance(e, Exception) and str(e) == "AP startup failed": + raise HwsimSkip("S1G operation not supported in this environment") + raise + finally: + for i in range(3): + dev[i].request("DISCONNECT") + dev[i].wait_disconnected() + clear_regdom(hapd, dev) -- 2.43.0 From lachlan.hodges at morsemicro.com Wed May 27 23:38:50 2026 From: lachlan.hodges at morsemicro.com (Lachlan Hodges) Date: Thu, 28 May 2026 16:38:50 +1000 Subject: [RFC 1/8] AP/STA: introduce support for freq_offset In-Reply-To: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> References: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> Message-ID: <20260528063857.950556-2-lachlan.hodges@morsemicro.com> 80211ah or S1G sits below 1GHz with channel granularity of 500KHz, meaning MHz alone is not enough to correctly represent a channels center frequency. This requires the introduction of a 'freq_offset' parameter that sits alongside the 'freq' parameter. Where `freq_offset` exists as a KHz component to be added to `freq`. This parameter is introduced anywhere we pass `freq` such that an S1G channel can be correctly defined. Additionally, internal channel calculation functions have been converted to KHz - similarly to cfg80211 - whilst the external callers such as ieee80211_chan_to_freq() simply convert the internal KHz result to MHz to reduce API churn. Signed-off-by: Lachlan Hodges --- src/ap/ap_drv_ops.c | 10 ++- src/ap/ap_drv_ops.h | 3 +- src/ap/beacon.c | 1 + src/ap/dfs.c | 4 +- src/ap/drv_callbacks.c | 6 +- src/ap/hostapd.c | 29 ++++--- src/ap/hostapd.h | 1 + src/ap/hw_features.c | 43 +++++----- src/ap/hw_features.h | 2 +- src/ap/interference.c | 2 +- src/common/defs.h | 4 + src/common/hw_features_common.c | 22 +++-- src/common/hw_features_common.h | 13 +-- src/common/ieee802_11_common.c | 136 ++++++++++++++++++------------ src/common/ieee802_11_common.h | 1 + src/common/privsep_commands.h | 1 + src/common/proximity_ranging.h | 3 +- src/drivers/driver.h | 44 +++++++++- src/drivers/driver_nl80211.c | 2 +- src/drivers/driver_privsep.c | 7 +- wpa_supplicant/ap.c | 4 +- wpa_supplicant/bss.c | 5 +- wpa_supplicant/bss.h | 2 + wpa_supplicant/config.c | 1 + wpa_supplicant/config_ssid.h | 12 +++ wpa_supplicant/ctrl_iface.c | 1 + wpa_supplicant/driver_i.h | 9 +- wpa_supplicant/events.c | 8 +- wpa_supplicant/ibss_rsn.c | 2 +- wpa_supplicant/mesh.c | 2 +- wpa_supplicant/nan_supplicant.c | 2 +- wpa_supplicant/p2p_supplicant.c | 4 +- wpa_supplicant/pasn_supplicant.c | 4 +- wpa_supplicant/scan.c | 10 ++- wpa_supplicant/sme.c | 14 +-- wpa_supplicant/wpa_priv.c | 1 + wpa_supplicant/wpa_supplicant.c | 7 +- wpa_supplicant/wpa_supplicant_i.h | 2 + 38 files changed, 280 insertions(+), 144 deletions(-) diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c index 61b823abe138..5862bedff238 100644 --- a/src/ap/ap_drv_ops.c +++ b/src/ap/ap_drv_ops.c @@ -646,7 +646,8 @@ int hostapd_flush(struct hostapd_data *hapd) int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode, - int freq, int channel, int edmg, u8 edmg_channel, + int freq, int freq_offset, int channel, + int edmg, u8 edmg_channel, int ht_enabled, int vht_enabled, int he_enabled, bool eht_enabled, int sec_channel_offset, int oper_chwidth, @@ -655,7 +656,8 @@ 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; - if (hostapd_set_freq_params(&data, mode, freq, channel, edmg, + if (hostapd_set_freq_params(&data, mode, freq, freq_offset, + channel, edmg, edmg_channel, ht_enabled, vht_enabled, he_enabled, eht_enabled, sec_channel_offset, oper_chwidth, @@ -883,7 +885,7 @@ int hostapd_drv_send_mlme(struct hostapd_data *hapd, if (!hapd->driver || !hapd->driver->send_mlme || !hapd->drv_priv) return 0; - return hapd->driver->send_mlme(hapd->drv_priv, msg, len, noack, 0, + return hapd->driver->send_mlme(hapd->drv_priv, msg, len, noack, 0, 0, csa_offs, csa_offs_len, no_encrypt, 0, link_id); } @@ -1094,7 +1096,7 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface, return -1; } - if (hostapd_set_freq_params(&data, mode, freq, channel, 0, 0, + if (hostapd_set_freq_params(&data, mode, freq, 0, channel, 0, 0, ht_enabled, vht_enabled, he_enabled, eht_enabled, sec_channel_offset, diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h index 66913ed0fca7..fa84f709de91 100644 --- a/src/ap/ap_drv_ops.h +++ b/src/ap/ap_drv_ops.h @@ -71,7 +71,8 @@ int hostapd_get_seqnum(const char *ifname, struct hostapd_data *hapd, const u8 *addr, int idx, int link_id, u8 *seq); int hostapd_flush(struct hostapd_data *hapd); int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode, - int freq, int channel, int edmg, u8 edmg_channel, + int freq, int freq_offset, + int channel, int edmg, u8 edmg_channel, int ht_enabled, int vht_enabled, int he_enabled, bool eht_enabled, int sec_channel_offset, int oper_chwidth, int center_segment0, int center_segment1); diff --git a/src/ap/beacon.c b/src/ap/beacon.c index 2521e3ab293e..2610106871a6 100644 --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -2808,6 +2808,7 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd) if (cmode && hostapd_set_freq_params(&freq, iconf->hw_mode, iface->freq, + iface->freq_offset, iconf->channel, iconf->enable_edmg, iconf->edmg_channel, iconf->ieee80211n, iconf->ieee80211ac, iconf->ieee80211ax, diff --git a/src/ap/dfs.c b/src/ap/dfs.c index d72c8ddb4464..2758454ff033 100644 --- a/src/ap/dfs.c +++ b/src/ap/dfs.c @@ -1014,7 +1014,7 @@ static int hostapd_dfs_request_channel_switch(struct hostapd_iface *iface, #endif /* CONFIG_MESH */ err = hostapd_set_freq_params(&csa_settings.freq_params, iface->conf->hw_mode, - freq, channel, + freq, 0, channel, iface->conf->enable_edmg, iface->conf->edmg_channel, iface->conf->ieee80211n, @@ -1138,7 +1138,7 @@ hostapd_is_freq_in_current_hw_info(struct hostapd_iface *iface, int freq) if (!iface->current_mode) return false; - chan = hw_mode_get_channel(iface->current_mode, freq, NULL); + chan = hw_mode_get_channel(iface->current_mode, freq, 0, NULL); /* If channel data is not found for the given frequency, consider it is * out of the current hardware info. */ diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c index 0c1e1c67c105..71e09cacfa56 100644 --- a/src/ap/drv_callbacks.c +++ b/src/ap/drv_callbacks.c @@ -1275,7 +1275,7 @@ void hostapd_event_ch_switch(struct hostapd_data *hapd, int freq, int ht, is_dfs0 = hostapd_is_dfs_required(hapd->iface); hapd->iface->freq = freq; - channel = hostapd_hw_get_channel(hapd, freq); + channel = hostapd_hw_get_channel(hapd, freq, 0); if (!channel) { hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_WARNING, @@ -1528,7 +1528,7 @@ void hostapd_acs_channel_selected(struct hostapd_data *hapd, if (mode->mode == acs_res->hw_mode) { if (hapd->iface->freq > 0 && !hw_get_chan(mode->mode, - hapd->iface->freq, + hapd->iface->freq, 0, hapd->iface->hw_features, hapd->iface->num_hw_features)) continue; @@ -1553,7 +1553,7 @@ void hostapd_acs_channel_selected(struct hostapd_data *hapd, goto out; } pri_chan = hw_get_channel_freq(hapd->iface->current_mode->mode, - acs_res->pri_freq, NULL, + acs_res->pri_freq, 0, NULL, hapd->iface->hw_features, hapd->iface->num_hw_features); if (!pri_chan) { diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 72a0bf503efe..a15e17919b3f 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -1976,7 +1976,7 @@ static int hostapd_no_ir_channel_list_updated(struct hostapd_iface *iface, if (mode->mode == iface->conf->hw_mode) { if (iface->freq > 0 && - !hw_mode_get_channel(mode, iface->freq, NULL)) { + !hw_mode_get_channel(mode, iface->freq, iface->freq_offset, NULL)) { mode = NULL; continue; } @@ -2001,7 +2001,8 @@ static int hostapd_no_ir_channel_list_updated(struct hostapd_iface *iface, struct hostapd_channel_data *chan; chan = hw_get_channel_freq(mode->mode, - iface->freq, NULL, + iface->freq, + iface->freq_offset, NULL, hw_features, num_hw_features); @@ -2036,7 +2037,8 @@ static int hostapd_no_ir_channel_list_updated(struct hostapd_iface *iface, struct hostapd_channel_data *chan; chan = hw_get_channel_freq(mode->mode, - iface->freq, NULL, + iface->freq, + iface->freq_offset, NULL, hw_features, num_hw_features); if (!chan) { @@ -2177,20 +2179,21 @@ static int setup_interface(struct hostapd_iface *iface) static int configured_fixed_chan_to_freq(struct hostapd_iface *iface) { - int freq, i, j; + int freq_khz, i, j; if (!iface->conf->channel) return 0; if (iface->conf->op_class) { - freq = ieee80211_chan_to_freq(NULL, iface->conf->op_class, - iface->conf->channel); - if (freq < 0) { + freq_khz = ieee80211_chan_to_freq_khz(NULL, iface->conf->op_class, + iface->conf->channel); + if (freq_khz < 0) { wpa_printf(MSG_INFO, "Could not convert op_class %u channel %u to operating frequency", iface->conf->op_class, iface->conf->channel); return -1; } - iface->freq = freq; + iface->freq = KHZ_TO_MHZ(freq_khz); + iface->freq_offset = KHZ_TO_S1G_OFFSET(freq_khz); return 0; } @@ -2642,7 +2645,8 @@ static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface, #endif /* CONFIG_MESH */ if (!delay_apply_cfg && - hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq, + hostapd_set_freq(hapd, hapd->iconf->hw_mode, + iface->freq, iface->freq_offset, hapd->iconf->channel, hapd->iconf->enable_edmg, hapd->iconf->edmg_channel, @@ -4511,7 +4515,7 @@ int hostapd_change_config_freq(struct hostapd_data *hapd, if (!params->channel) { /* check if the new channel is supported by hw */ - params->channel = hostapd_hw_get_channel(hapd, params->freq); + params->channel = hostapd_hw_get_channel(hapd, params->freq, 0); } channel = params->channel; @@ -4524,7 +4528,7 @@ int hostapd_change_config_freq(struct hostapd_data *hapd, /* 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), + hostapd_hw_get_freq(hapd, conf->channel), 0, conf->channel, conf->enable_edmg, conf->edmg_channel, conf->ieee80211n, conf->ieee80211ac, conf->ieee80211ax, @@ -4794,7 +4798,8 @@ int hostapd_force_channel_switch(struct hostapd_iface *iface, if (!settings->freq_params.channel) { /* Check if the new channel is supported */ settings->freq_params.channel = hostapd_hw_get_channel( - iface->bss[0], settings->freq_params.freq); + iface->bss[0], settings->freq_params.freq, + settings->freq_params.freq_offset); if (!settings->freq_params.channel) return -1; } diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index fb06a5afd935..362924aeff54 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -646,6 +646,7 @@ struct hostapd_iface { int num_hw_features; struct hostapd_hw_modes *current_mode; int freq; + int freq_offset; bool radar_detected; diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c index be8ed53522a8..6ac7cd9d8837 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -197,10 +197,11 @@ int hostapd_get_hw_features(struct hostapd_iface *iface) continue; wpa_printf(MSG_MSGDUMP, "Allowed channel: mode=%d " - "chan=%d freq=%d MHz max_tx_power=%d dBm%s", + "chan=%d freq=%d MHz freq_offset=%d KHz max_tx_power=%d dBm%s", feature->mode, feature->channels[j].chan, feature->channels[j].freq, + feature->channels[j].freq_offset, feature->channels[j].max_tx_power, dfs ? dfs_info(&feature->channels[j]) : ""); } @@ -333,11 +334,11 @@ static int ieee80211n_allowed_ht40_channel_pair(struct hostapd_iface *iface) if (!iface->current_mode) return 0; - p_chan = hw_get_channel_freq(iface->current_mode->mode, pri_freq, NULL, + p_chan = hw_get_channel_freq(iface->current_mode->mode, pri_freq, 0, NULL, iface->hw_features, iface->num_hw_features); - s_chan = hw_get_channel_freq(iface->current_mode->mode, sec_freq, NULL, + s_chan = hw_get_channel_freq(iface->current_mode->mode, sec_freq, 0, NULL, iface->hw_features, iface->num_hw_features); @@ -372,10 +373,10 @@ static int ieee80211n_check_40mhz_5g(struct hostapd_iface *iface, if (!iface->current_mode) return 0; - pri_chan = hw_get_channel_freq(iface->current_mode->mode, pri_freq, + pri_chan = hw_get_channel_freq(iface->current_mode->mode, pri_freq, 0, NULL, iface->hw_features, iface->num_hw_features); - sec_chan = hw_get_channel_freq(iface->current_mode->mode, sec_freq, + sec_chan = hw_get_channel_freq(iface->current_mode->mode, sec_freq, 0, NULL, iface->hw_features, iface->num_hw_features); @@ -920,15 +921,16 @@ int hostapd_check_he_6ghz_capab(struct hostapd_iface *iface) * -1 = not currently usable due to 6 GHz NO-IR */ static int hostapd_is_usable_chan(struct hostapd_iface *iface, - int frequency, int primary) + int frequency, int freq_offset, int primary) { struct hostapd_channel_data *chan; if (!iface->current_mode) return 0; - chan = hw_get_channel_freq(iface->current_mode->mode, frequency, NULL, - iface->hw_features, iface->num_hw_features); + chan = hw_get_channel_freq(iface->current_mode->mode, frequency, + freq_offset, NULL, iface->hw_features, + iface->num_hw_features); if (!chan) return 0; @@ -965,7 +967,7 @@ static int hostapd_is_usable_edmg(struct hostapd_iface *iface) if (!iface->current_mode) return 0; pri_chan = hw_get_channel_freq(iface->current_mode->mode, - iface->freq, NULL, + iface->freq, 0, NULL, iface->hw_features, iface->num_hw_features); if (!pri_chan) @@ -995,7 +997,7 @@ static int hostapd_is_usable_edmg(struct hostapd_iface *iface) if (num_of_enabled > 4) return 0; - err = hostapd_is_usable_chan(iface, freq, 1); + err = hostapd_is_usable_chan(iface, freq, 0, 1); if (err <= 0) return err; @@ -1097,7 +1099,8 @@ static int hostapd_is_usable_chans(struct hostapd_iface *iface) if (!iface->current_mode) return 0; pri_chan = hw_get_channel_freq(iface->current_mode->mode, - iface->freq, NULL, + iface->freq, + iface->freq_offset, NULL, iface->hw_features, iface->num_hw_features); if (!pri_chan) { @@ -1105,7 +1108,8 @@ static int hostapd_is_usable_chans(struct hostapd_iface *iface) return 0; } - err = hostapd_is_usable_chan(iface, pri_chan->freq, 1); + err = hostapd_is_usable_chan(iface, pri_chan->freq, + pri_chan->freq_offset, 1); if (err <= 0) { wpa_printf(MSG_ERROR, "Primary frequency not allowed"); return err; @@ -1121,7 +1125,7 @@ static int hostapd_is_usable_chans(struct hostapd_iface *iface) return 1; err = hostapd_is_usable_chan(iface, iface->freq + - iface->conf->secondary_channel * 20, 0); + iface->conf->secondary_channel * 20, 0, 0); if (err > 0) { if (iface->conf->secondary_channel == 1 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) @@ -1135,14 +1139,14 @@ static int hostapd_is_usable_chans(struct hostapd_iface *iface) /* Both HT40+ and HT40- are set, pick a valid secondary channel */ secondary_freq = iface->freq + 20; - err2 = hostapd_is_usable_chan(iface, secondary_freq, 0); + err2 = hostapd_is_usable_chan(iface, secondary_freq, 0, 0); if (err2 > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) { iface->conf->secondary_channel = 1; return 1; } secondary_freq = iface->freq - 20; - err2 = hostapd_is_usable_chan(iface, secondary_freq, 0); + err2 = hostapd_is_usable_chan(iface, secondary_freq, 0, 0); if (err2 > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) { iface->conf->secondary_channel = -1; return 1; @@ -1157,7 +1161,7 @@ static bool skip_mode(struct hostapd_iface *iface, { int chan; - if (iface->freq > 0 && !hw_mode_get_channel(mode, iface->freq, &chan)) + if (iface->freq > 0 && !hw_mode_get_channel(mode, iface->freq, iface->freq_offset, &chan)) return true; if (is_6ghz_op_class(iface->conf->op_class) && iface->freq == 0 && @@ -1427,13 +1431,14 @@ int hostapd_hw_get_freq(struct hostapd_data *hapd, int chan) } -int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq) +int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq, int freq_offset) { int i, channel; struct hostapd_hw_modes *mode; if (hapd->iface->current_mode) { - channel = hw_get_chan(hapd->iface->current_mode->mode, freq, + channel = hw_get_chan(hapd->iface->current_mode->mode, + freq, freq_offset, hapd->iface->hw_features, hapd->iface->num_hw_features); if (channel) @@ -1446,7 +1451,7 @@ int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq) return 0; for (i = 0; i < hapd->iface->num_hw_features; i++) { mode = &hapd->iface->hw_features[i]; - channel = hw_get_chan(mode->mode, freq, + channel = hw_get_chan(mode->mode, freq, freq_offset, hapd->iface->hw_features, hapd->iface->num_hw_features); if (channel) diff --git a/src/ap/hw_features.h b/src/ap/hw_features.h index 6f945cc7736b..4d73c3656146 100644 --- a/src/ap/hw_features.h +++ b/src/ap/hw_features.h @@ -20,7 +20,7 @@ int hostapd_acs_completed(struct hostapd_iface *iface, int err); int hostapd_select_hw_mode(struct hostapd_iface *iface); const char * hostapd_hw_mode_txt(int mode); int hostapd_hw_get_freq(struct hostapd_data *hapd, int chan); -int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq); +int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq, int freq_offset); int hostapd_check_ht_capab(struct hostapd_iface *iface); int hostapd_check_edmg_capab(struct hostapd_iface *iface); int hostapd_check_he_6ghz_capab(struct hostapd_iface *iface); diff --git a/src/ap/interference.c b/src/ap/interference.c index c93cdc75f13f..44aa6c0019ef 100644 --- a/src/ap/interference.c +++ b/src/ap/interference.c @@ -552,7 +552,7 @@ int hostapd_incumbt_sig_intf_detected(struct hostapd_iface *iface, int freq, goto exit; } if (hostapd_set_freq( - iface->bss[0], iface->conf->hw_mode, iface->freq, + iface->bss[0], iface->conf->hw_mode, iface->freq, 0, iface->conf->channel, iface->conf->enable_edmg, iface->conf->edmg_channel, iface->conf->ieee80211n, iface->conf->ieee80211ac, diff --git a/src/common/defs.h b/src/common/defs.h index c88766e7512a..ec602d660ace 100644 --- a/src/common/defs.h +++ b/src/common/defs.h @@ -585,4 +585,8 @@ enum wpa_p2p_mode { #define USEC_TO_TU(m) ((m) / USEC_80211_TU) #define TU_TO_USEC(m) ((m) * USEC_80211_TU) +#define MHZ_TO_KHZ(x) ((x) * 1000) +#define KHZ_TO_MHZ(x) ((x) / 1000) +#define KHZ_TO_S1G_OFFSET(x) ((x) % 1000) + #endif /* DEFS_H */ diff --git a/src/common/hw_features_common.c b/src/common/hw_features_common.c index e928738ee568..32471ce36651 100644 --- a/src/common/hw_features_common.c +++ b/src/common/hw_features_common.c @@ -41,14 +41,15 @@ struct hostapd_channel_data * hw_get_channel_chan(struct hostapd_hw_modes *mode, struct hostapd_channel_data * -hw_mode_get_channel(struct hostapd_hw_modes *mode, int freq, int *chan) +hw_mode_get_channel(struct hostapd_hw_modes *mode, int freq, + int freq_offset, int *chan) { int i; for (i = 0; i < mode->num_channels; i++) { struct hostapd_channel_data *ch = &mode->channels[i]; - if (ch->freq == freq) { + if (MHZ_TO_KHZ(ch->freq) + ch->freq_offset == MHZ_TO_KHZ(freq) + freq_offset) { if (chan) *chan = ch->chan; return ch; @@ -60,8 +61,9 @@ hw_mode_get_channel(struct hostapd_hw_modes *mode, int freq, int *chan) struct hostapd_channel_data * -hw_get_channel_freq(enum hostapd_hw_mode mode, int freq, int *chan, - struct hostapd_hw_modes *hw_features, int num_hw_features) +hw_get_channel_freq(enum hostapd_hw_mode mode, int freq, int freq_offset, + int *chan, struct hostapd_hw_modes *hw_features, + int num_hw_features) { struct hostapd_channel_data *chan_data; int i; @@ -78,7 +80,8 @@ hw_get_channel_freq(enum hostapd_hw_mode mode, int freq, int *chan, if (curr_mode->mode != mode) continue; - chan_data = hw_mode_get_channel(curr_mode, freq, chan); + chan_data = hw_mode_get_channel(curr_mode, freq, + freq_offset, chan); if (chan_data) return chan_data; } @@ -97,12 +100,13 @@ int hw_get_freq(struct hostapd_hw_modes *mode, int chan) } -int hw_get_chan(enum hostapd_hw_mode mode, int freq, +int hw_get_chan(enum hostapd_hw_mode mode, int freq, int freq_offset, struct hostapd_hw_modes *hw_features, int num_hw_features) { int chan; - hw_get_channel_freq(mode, freq, &chan, hw_features, num_hw_features); + hw_get_channel_freq(mode, freq, freq_offset, &chan, hw_features, + num_hw_features); return chan; } @@ -477,7 +481,8 @@ 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, + int freq, int freq_offset, + int channel, int enable_edmg, u8 edmg_channel, int ht_enabled, int vht_enabled, int he_enabled, bool eht_enabled, int sec_channel_offset, @@ -498,6 +503,7 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, os_memset(data, 0, sizeof(*data)); data->mode = mode; data->freq = freq; + data->freq_offset = freq_offset; data->channel = channel; data->ht_enabled = ht_enabled; data->vht_enabled = vht_enabled; diff --git a/src/common/hw_features_common.h b/src/common/hw_features_common.h index 80e33adf2d5c..63c0893e2c2b 100644 --- a/src/common/hw_features_common.h +++ b/src/common/hw_features_common.h @@ -15,14 +15,16 @@ struct hostapd_channel_data * hw_get_channel_chan(struct hostapd_hw_modes *mode, int chan, int *freq); struct hostapd_channel_data * -hw_mode_get_channel(struct hostapd_hw_modes *mode, int freq, int *chan); +hw_mode_get_channel(struct hostapd_hw_modes *mode, int freq, + int freq_offset, int *chan); struct hostapd_channel_data * -hw_get_channel_freq(enum hostapd_hw_mode mode, int freq, int *chan, - struct hostapd_hw_modes *hw_features, int num_hw_features); +hw_get_channel_freq(enum hostapd_hw_mode mode, int freq, int freq_offset, + int *chan, struct hostapd_hw_modes *hw_features, + int num_hw_features); int hw_get_freq(struct hostapd_hw_modes *mode, int chan); -int hw_get_chan(enum hostapd_hw_mode mode, int freq, +int hw_get_chan(enum hostapd_hw_mode mode, int freq, int freq_offset, struct hostapd_hw_modes *hw_features, int num_hw_features); int allowed_ht40_channel_pair(enum hostapd_hw_mode mode, @@ -39,7 +41,8 @@ void punct_update_legacy_bw(u16 bitmap, u8 pri_chan, enum oper_chan_width *width, u8 *seg0, u8 *seg1); 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 freq, int freq_offset, + int channel, int edmg, u8 edmg_channel, int ht_enabled, int vht_enabled, int he_enabled, bool eht_enabled, int sec_channel_offset, diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c index 32342f7d991c..a3b9592e23bc 100644 --- a/src/common/ieee802_11_common.c +++ b/src/common/ieee802_11_common.c @@ -1825,7 +1825,7 @@ static int country_match(const char *const cc[], const char *const country) } -static int ieee80211_chan_to_freq_us(u8 op_class, u8 chan) +static int ieee80211_chan_to_freq_khz_us(u8 op_class, u8 chan) { switch (op_class) { case 12: /* channels 1..11 */ @@ -1833,7 +1833,7 @@ static int ieee80211_chan_to_freq_us(u8 op_class, u8 chan) case 33: /* channels 5..11; 40 MHz */ if (chan < 1 || chan > 11) return -1; - return 2407 + 5 * chan; + return MHZ_TO_KHZ(2407 + 5 * chan); case 1: /* channels 36,40,44,48 */ case 2: /* channels 52,56,60,64; dfs */ case 22: /* channels 36,44; 40 MHz */ @@ -1842,12 +1842,12 @@ static int ieee80211_chan_to_freq_us(u8 op_class, u8 chan) case 28: /* channels 56,64; 40 MHz */ if (chan < 36 || chan > 64) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 4: /* channels 100-144 */ case 24: /* channels 100-140; 40 MHz */ if (chan < 100 || chan > 144) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 3: /* channels 149,153,157,161 */ case 25: /* channels 149,157; 40 MHz */ case 26: /* channels 149,157; 40 MHz */ @@ -1855,11 +1855,11 @@ static int ieee80211_chan_to_freq_us(u8 op_class, u8 chan) case 31: /* channels 153,161; 40 MHz */ if (chan < 149 || chan > 161) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 5: /* channels 149,153,157,161,165 */ if (chan < 149 || chan > 165) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 34: /* 60 GHz band, channels 1..8 */ if (chan < 1 || chan > 8) return -1; @@ -1867,22 +1867,22 @@ static int ieee80211_chan_to_freq_us(u8 op_class, u8 chan) case 37: /* 60 GHz band, EDMG CB2, channels 9..15 */ if (chan < 9 || chan > 15) return -1; - return 56160 + 2160 * (chan - 8); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 8)); case 38: /* 60 GHz band, EDMG CB3, channels 17..22 */ if (chan < 17 || chan > 22) return -1; - return 56160 + 2160 * (chan - 16); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 16)); case 39: /* 60 GHz band, EDMG CB4, channels 25..29 */ if (chan < 25 || chan > 29) return -1; - return 56160 + 2160 * (chan - 24); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 24)); default: return -1; } } -static int ieee80211_chan_to_freq_eu(u8 op_class, u8 chan) +static int ieee80211_chan_to_freq_khz_eu(u8 op_class, u8 chan) { switch (op_class) { case 4: /* channels 1..13 */ @@ -1890,7 +1890,7 @@ static int ieee80211_chan_to_freq_eu(u8 op_class, u8 chan) case 12: /* channels 5..13; 40 MHz */ if (chan < 1 || chan > 13) return -1; - return 2407 + 5 * chan; + return MHZ_TO_KHZ(2407 + 5 * chan); case 1: /* channels 36,40,44,48 */ case 2: /* channels 52,56,60,64; dfs */ case 5: /* channels 36,44; 40 MHz */ @@ -1899,41 +1899,41 @@ static int ieee80211_chan_to_freq_eu(u8 op_class, u8 chan) case 9: /* channels 56,64; 40 MHz */ if (chan < 36 || chan > 64) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 3: /* channels 100-140 */ case 7: /* channels 100-132; 40 MHz */ case 10: /* channels 104-136; 40 MHz */ case 16: /* channels 100-140 */ if (chan < 100 || chan > 140) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 17: /* channels 149,153,157,161,165,169 */ if (chan < 149 || chan > 169) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 18: /* 60 GHz band, channels 1..6 */ if (chan < 1 || chan > 6) return -1; - return 56160 + 2160 * chan; + return MHZ_TO_KHZ(56160 + 2160 * chan); case 21: /* 60 GHz band, EDMG CB2, channels 9..11 */ if (chan < 9 || chan > 11) return -1; - return 56160 + 2160 * (chan - 8); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 8)); case 22: /* 60 GHz band, EDMG CB3, channels 17..18 */ if (chan < 17 || chan > 18) return -1; - return 56160 + 2160 * (chan - 16); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 16)); case 23: /* 60 GHz band, EDMG CB4, channels 25 */ if (chan != 25) return -1; - return 56160 + 2160 * (chan - 24); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 24)); default: return -1; } } -static int ieee80211_chan_to_freq_jp(u8 op_class, u8 chan) +static int ieee80211_chan_to_freq_khz_jp(u8 op_class, u8 chan) { /* Table E-3 in IEEE Std 802.11-2020 - Operating classes in Japan */ switch (op_class) { @@ -1942,11 +1942,11 @@ static int ieee80211_chan_to_freq_jp(u8 op_class, u8 chan) case 57: /* channels 5..13; 40 MHz */ if (chan < 1 || chan > 13) return -1; - return 2407 + 5 * chan; + return MHZ_TO_KHZ(2407 + 5 * chan); case 31: /* channel 14 */ if (chan != 14) return -1; - return 2414 + 5 * chan; + return MHZ_TO_KHZ(2414 + 5 * chan); case 1: /* channels 34,38,42,46(old) or 36,40,44,48 */ case 32: /* channels 52,56,60,64 */ case 33: /* channels 52,56,60,64 */ @@ -1958,7 +1958,7 @@ static int ieee80211_chan_to_freq_jp(u8 op_class, u8 chan) case 43: /* channels 56,64; 40 MHz */ if (chan < 34 || chan > 64) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 34: /* channels 100-144 */ case 35: /* reserved */ case 39: /* channels 100-140; 40 MHz */ @@ -1968,30 +1968,30 @@ static int ieee80211_chan_to_freq_jp(u8 op_class, u8 chan) case 58: /* channels 100-144 */ if (chan < 100 || chan > 144) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 59: /* 60 GHz band, channels 1..6 */ if (chan < 1 || chan > 6) return -1; - return 56160 + 2160 * chan; + return MHZ_TO_KHZ(56160 + 2160 * chan); case 62: /* 60 GHz band, EDMG CB2, channels 9..11 */ if (chan < 9 || chan > 11) return -1; - return 56160 + 2160 * (chan - 8); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 8)); case 63: /* 60 GHz band, EDMG CB3, channels 17..18 */ if (chan < 17 || chan > 18) return -1; - return 56160 + 2160 * (chan - 16); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 16)); case 64: /* 60 GHz band, EDMG CB4, channel 25 */ if (chan != 25) return -1; - return 56160 + 2160 * (chan - 24); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 24)); default: return -1; } } -static int ieee80211_chan_to_freq_cn(u8 op_class, u8 chan) +static int ieee80211_chan_to_freq_khz_cn(u8 op_class, u8 chan) { switch (op_class) { case 7: /* channels 1..13 */ @@ -1999,44 +1999,56 @@ static int ieee80211_chan_to_freq_cn(u8 op_class, u8 chan) case 9: /* channels 5..13; 40 MHz */ if (chan < 1 || chan > 13) return -1; - return 2407 + 5 * chan; + return MHZ_TO_KHZ(2407 + 5 * chan); case 1: /* channels 36,40,44,48 */ case 2: /* channels 52,56,60,64; dfs */ case 4: /* channels 36,44; 40 MHz */ case 5: /* channels 52,60; 40 MHz */ if (chan < 36 || chan > 64) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 3: /* channels 149,153,157,161,165 */ case 6: /* channels 149,157; 40 MHz */ if (chan < 149 || chan > 165) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); default: return -1; } } -static int ieee80211_chan_to_freq_global(u8 op_class, u8 chan) +static int ieee80211_chan_to_freq_khz_global(u8 op_class, u8 chan) { /* Table E-4 in IEEE Std 802.11-2020 - Global operating classes */ switch (op_class) { + case 50: + case 51: + case 52: + case 53: + case 68: + case 69: + case 70: + case 71: + case 72: + if (chan < 1 || chan > 51) + return -1; + return 902000 + chan * 500; case 81: /* channels 1..13 */ if (chan < 1 || chan > 13) return -1; - return 2407 + 5 * chan; + return MHZ_TO_KHZ(2407 + 5 * chan); case 82: /* channel 14 */ if (chan != 14) return -1; - return 2414 + 5 * chan; + return MHZ_TO_KHZ(2414 + 5 * chan); case 83: /* channels 1..9; 40 MHz */ case 84: /* channels 5..13; 40 MHz */ if (chan < 1 || chan > 13) return -1; - return 2407 + 5 * chan; + return MHZ_TO_KHZ(2407 + 5 * chan); case 115: /* channels 36,40,44,48; indoor only */ case 116: /* channels 36,44; 40 MHz; indoor only */ case 117: /* channels 40,48; 40 MHz; indoor only */ @@ -2045,32 +2057,32 @@ static int ieee80211_chan_to_freq_global(u8 op_class, u8 chan) case 120: /* channels 56,64; 40 MHz; dfs */ if (chan < 36 || chan > 64) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 121: /* channels 100-144 */ case 122: /* channels 100-140; 40 MHz */ case 123: /* channels 104-144; 40 MHz */ if (chan < 100 || chan > 144) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 124: /* channels 149,153,157,161 */ if (chan < 149 || chan > 161) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 125: /* channels 149,153,157,161,165,169,173,177 */ case 126: /* channels 149,157,165,173; 40 MHz */ case 127: /* channels 153,161,169,177; 40 MHz */ if (chan < 149 || chan > 177) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 128: /* center freqs 42, 58, 106, 122, 138, 155, 171; 80 MHz */ case 130: /* center freqs 42, 58, 106, 122, 138, 155, 171; 80 MHz */ if (chan < 36 || chan > 177) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 129: /* center freqs 50, 114, 163; 160 MHz */ if (chan < 36 || chan > 177) return -1; - return 5000 + 5 * chan; + return MHZ_TO_KHZ(5000 + 5 * chan); case 131: /* UHB channels, 20 MHz: 1, 5, 9.. */ case 132: /* UHB channels, 40 MHz: 3, 11, 19.. */ case 133: /* UHB channels, 80 MHz: 7, 23, 39.. */ @@ -2079,7 +2091,7 @@ static int ieee80211_chan_to_freq_global(u8 op_class, u8 chan) case 137: /* UHB channels, 320 MHz: 31, 63, 95, 127, 159, 191 */ if (chan < 1 || chan > 233) return -1; - return 5950 + chan * 5; + return MHZ_TO_KHZ(5950 + chan * 5); case 136: /* UHB channels, 20 MHz: 2 */ if (chan == 2) return 5935; @@ -2087,60 +2099,76 @@ static int ieee80211_chan_to_freq_global(u8 op_class, u8 chan) case 180: /* 60 GHz band, channels 1..8 */ if (chan < 1 || chan > 8) return -1; - return 56160 + 2160 * chan; + return MHZ_TO_KHZ(56160 + 2160 * chan); case 181: /* 60 GHz band, EDMG CB2, channels 9..15 */ if (chan < 9 || chan > 15) return -1; - return 56160 + 2160 * (chan - 8); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 8)); case 182: /* 60 GHz band, EDMG CB3, channels 17..22 */ if (chan < 17 || chan > 22) return -1; - return 56160 + 2160 * (chan - 16); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 16)); case 183: /* 60 GHz band, EDMG CB4, channel 25..29 */ if (chan < 25 || chan > 29) return -1; - return 56160 + 2160 * (chan - 24); + return MHZ_TO_KHZ(56160 + 2160 * (chan - 24)); default: return -1; } } /** - * ieee80211_chan_to_freq - Convert channel info to frequency + * ieee80211_chan_to_freq_khz - Convert channel info to frequency KHz * @country: Country code, if known; otherwise, global operating class is used * @op_class: Operating class * @chan: Channel number - * Returns: Frequency in MHz or -1 if the specified channel is unknown + * Returns: Frequency in KHz or -1 if the specified channel is unknown */ -int ieee80211_chan_to_freq(const char *country, u8 op_class, u8 chan) +int ieee80211_chan_to_freq_khz(const char *country, u8 op_class, u8 chan) { int freq; if (country_match(us_op_class_cc, country)) { - freq = ieee80211_chan_to_freq_us(op_class, chan); + freq = ieee80211_chan_to_freq_khz_us(op_class, chan); if (freq > 0) return freq; } if (country_match(eu_op_class_cc, country)) { - freq = ieee80211_chan_to_freq_eu(op_class, chan); + freq = ieee80211_chan_to_freq_khz_eu(op_class, chan); if (freq > 0) return freq; } if (country_match(jp_op_class_cc, country)) { - freq = ieee80211_chan_to_freq_jp(op_class, chan); + freq = ieee80211_chan_to_freq_khz_jp(op_class, chan); if (freq > 0) return freq; } if (country_match(cn_op_class_cc, country)) { - freq = ieee80211_chan_to_freq_cn(op_class, chan); + freq = ieee80211_chan_to_freq_khz_cn(op_class, chan); if (freq > 0) return freq; } - return ieee80211_chan_to_freq_global(op_class, chan); + return ieee80211_chan_to_freq_khz_global(op_class, chan); +} + +/** + * ieee80211_chan_to_freq - Convert channel info to frequency + * @country: Country code, if known; otherwise, global operating class is used + * @op_class: Operating class + * @chan: Channel number + * Returns: Frequency in MHz or -1 if the specified channel is unknown + */ +int ieee80211_chan_to_freq(const char *country, u8 op_class, u8 chan) +{ + int freq_khz = ieee80211_chan_to_freq_khz(country, op_class, chan); + if (freq_khz < 0) + return -1; + + return KHZ_TO_MHZ(freq_khz); } diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h index 5d9c840cee76..ed0147812fde 100644 --- a/src/common/ieee802_11_common.h +++ b/src/common/ieee802_11_common.h @@ -264,6 +264,7 @@ int hostapd_config_tx_queue(struct hostapd_tx_queue_params queue[], const char *name, const char *val); enum hostapd_hw_mode ieee80211_freq_to_chan(int freq, u8 *channel); int ieee80211_chan_to_freq(const char *country, u8 op_class, u8 chan); +int ieee80211_chan_to_freq_khz(const char *country, u8 op_class, u8 chan); enum hostapd_hw_mode ieee80211_freq_to_channel_ext(unsigned int freq, int sec_channel, enum oper_chan_width chanwidth, diff --git a/src/common/privsep_commands.h b/src/common/privsep_commands.h index d2c4bbd5e8b6..85e1e89977b2 100644 --- a/src/common/privsep_commands.h +++ b/src/common/privsep_commands.h @@ -63,6 +63,7 @@ struct privsep_cmd_associate { size_t ssid_len; int hwmode; int freq; + int freq_offset; int channel; int pairwise_suite; int group_suite; diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h index 902ced541040..dbcd04a0ae9e 100644 --- a/src/common/proximity_ranging.h +++ b/src/common/proximity_ranging.h @@ -563,7 +563,8 @@ struct pr_config { * Returns: 0 on success, -1 on failure */ int (*pasn_send_mgmt)(void *ctx, const u8 *data, size_t data_len, - int noack, unsigned int freq, unsigned int wait); + int noack, unsigned int freq, unsigned int freq_offset, + unsigned int wait); /** * negotiation_started - Called when PASN negotiation begins diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 6450c66b5d3a..c382bf90ccf6 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -126,6 +126,11 @@ struct hostapd_channel_data { */ int freq; + /** + * freq_offset - Frequency offset from @freq in KHz + */ + int freq_offset; + /** * flag - Channel flags (HOSTAPD_CHAN_*) */ @@ -374,6 +379,7 @@ struct hostapd_multi_hw_info { * @flags: information flags about the BSS/IBSS (WPA_SCAN_*) * @bssid: BSSID * @freq: frequency of the channel in MHz (e.g., 2412 = channel 1) + * @freq_offset: frequency offset of the channel from @freq in KHz * @max_cw: the max channel width of the connection (calculated during scan * result processing) * @beacon_int: beacon interval in TUs (host byte order) @@ -414,6 +420,7 @@ struct wpa_scan_res { unsigned int flags; u8 bssid[ETH_ALEN]; int freq; + int freq_offset; enum chan_width max_cw; u16 beacon_int; u16 caps; @@ -521,6 +528,13 @@ struct wpa_driver_scan_params { */ int *freqs; + /** + * freq_offset - Frequency offset in KHz + * + * Each @freqs value will have this frequency offset applied. + */ + int freq_offset; + /** * filter_ssids - Filter for reporting SSIDs * @@ -759,6 +773,7 @@ struct wpa_driver_scan_params { */ struct wpa_driver_auth_params { int freq; + int freq_offset; const u8 *bssid; const u8 *ssid; size_t ssid_len; @@ -839,6 +854,11 @@ struct hostapd_freq_params { */ int freq; + /** + * freq_offset - Frequency offset in KHz from @freq + */ + int freq_offset; + /** * channel - Channel number */ @@ -875,6 +895,13 @@ struct hostapd_freq_params { */ int center_freq1; + /** + * center_freq1_offset - Segment 0 center frequency offset in KHz + * + * Valid for S1G. + */ + int center_freq1_offset; + /** * center_freq2 - Segment 1 center frequency in MHz * @@ -3891,6 +3918,7 @@ struct wpa_driver_ops { * @noack: Do not wait for this frame to be acked (disable retries) * @freq: Frequency (in MHz) to send the frame on, or 0 to let the * driver decide + * @freq_offset: Frequency offset (in KHz) from @freq to send the frame on * @csa_offs: Array of CSA offsets or %NULL * @csa_offs_len: Number of elements in csa_offs * @no_encrypt: Do not encrypt frame even if appropriate key exists @@ -3900,8 +3928,8 @@ struct wpa_driver_ops { * Returns: 0 on success, -1 on failure */ int (*send_mlme)(void *priv, const u8 *data, size_t data_len, - int noack, unsigned int freq, const u16 *csa_offs, - size_t csa_offs_len, int no_encrypt, + int noack, unsigned int freq, unsigned int freq_offset, + const u16 *csa_offs, size_t csa_offs_len, int no_encrypt, unsigned int wait, int link_id); /** @@ -6822,6 +6850,11 @@ union wpa_event_data { */ unsigned int freq; + /** + * freq_offset - Frequency offset from @freq in KHz + */ + unsigned int freq_offset; + /** * wmm_params - WMM parameters used in this association. */ @@ -7215,6 +7248,11 @@ union wpa_event_data { */ int freq; + /** + * freq_offset - Frequency offset (in KHz) from @freq on which the frame was received + */ + int freq_offset; + /** * ssi_signal - Signal strength in dBm (or 0 if not available) */ @@ -7418,6 +7456,7 @@ union wpa_event_data { /** * struct ch_switch * @freq: Frequency of new channel in MHz + * @freq_offset: Frequency offset from @freq in KHz * @ht_enabled: Whether this is an HT channel * @ch_offset: Secondary channel offset * @ch_width: Channel width @@ -7428,6 +7467,7 @@ union wpa_event_data { */ struct ch_switch { int freq; + int freq_offset; int ht_enabled; int ch_offset; enum chan_width ch_width; diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 033e4eb46e8b..f79558911feb 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -11850,7 +11850,7 @@ static bool nl80211_is_drv_shared(void *priv, int link_id) static int driver_nl80211_send_mlme(void *priv, const u8 *data, size_t data_len, int noack, - unsigned int freq, + unsigned int freq, unsigned int freq_offset, const u16 *csa_offs, size_t csa_offs_len, int no_encrypt, unsigned int wait, int link_id) diff --git a/src/drivers/driver_privsep.c b/src/drivers/driver_privsep.c index d7c6b01a317e..e53421179d01 100644 --- a/src/drivers/driver_privsep.c +++ b/src/drivers/driver_privsep.c @@ -306,10 +306,10 @@ static int wpa_driver_privsep_associate( int res; size_t buflen; - wpa_printf(MSG_DEBUG, "%s: priv=%p freq=%d pairwise_suite=%d " + wpa_printf(MSG_DEBUG, "%s: priv=%p freq=%d freq_offset=%d pairwise_suite=%d " "group_suite=%d key_mgmt_suite=%d auth_alg=%d mode=%d", - __func__, priv, params->freq.freq, params->pairwise_suite, - params->group_suite, params->key_mgmt_suite, + __func__, priv, params->freq.freq, params->freq.freq_offset, + params->pairwise_suite, params->group_suite, params->key_mgmt_suite, params->auth_alg, params->mode); buflen = sizeof(*data) + params->wpa_ie_len; @@ -323,6 +323,7 @@ static int wpa_driver_privsep_associate( data->ssid_len = params->ssid_len; data->hwmode = params->freq.mode; data->freq = params->freq.freq; + data->freq_offset = params->freq.freq_offset; data->channel = params->freq.channel; data->pairwise_suite = params->pairwise_suite; data->group_suite = params->group_suite; diff --git a/wpa_supplicant/ap.c b/wpa_supplicant/ap.c index d0a1a96fd5b4..8a3f86c2e0ff 100644 --- a/wpa_supplicant/ap.c +++ b/wpa_supplicant/ap.c @@ -457,7 +457,8 @@ int wpa_supplicant_conf_ap_ht(struct wpa_supplicant *wpa_s, { if (iface == wpa_s || iface->wpa_state < WPA_AUTHENTICATING || - (int) iface->assoc_freq != ssid->frequency) + MHZ_TO_KHZ(iface->assoc_freq) + iface->assoc_freq_offset != + MHZ_TO_KHZ(ssid->frequency) + ssid->freq_offset) continue; /* @@ -1182,6 +1183,7 @@ int wpa_supplicant_create_ap(struct wpa_supplicant *wpa_s, eapol_sm_notify_config(wpa_s->eapol, NULL, NULL); os_memcpy(wpa_s->bssid, wpa_s->own_addr, ETH_ALEN); wpa_s->assoc_freq = ssid->frequency; + wpa_s->assoc_freq_offset = ssid->freq_offset; wpa_s->ap_iface->conf->enable_edmg = ssid->enable_edmg; wpa_s->ap_iface->conf->edmg_channel = ssid->edmg_channel; diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c index 2cd9cc8c73f5..304486f4b650 100644 --- a/wpa_supplicant/bss.c +++ b/wpa_supplicant/bss.c @@ -384,6 +384,7 @@ static void wpa_bss_copy_res(struct wpa_bss *dst, struct wpa_scan_res *src, dst->flags = src->flags; os_memcpy(dst->bssid, src->bssid, ETH_ALEN); dst->freq = src->freq; + dst->freq_offset = src->freq_offset; dst->max_cw = src->max_cw; dst->beacon_int = src->beacon_int; dst->caps = src->caps; @@ -635,9 +636,9 @@ static struct wpa_bss * wpa_bss_add(struct wpa_supplicant *wpa_s, } wpa_dbg(wpa_s, MSG_DEBUG, "BSS: Add new id %u BSSID " MACSTR - " SSID '%s' freq %d%s", + " SSID '%s' freq %d freq_offset %d%s", bss->id, MAC2STR(bss->bssid), wpa_ssid_txt(ssid, ssid_len), - bss->freq, extra); + bss->freq, bss->freq_offset, extra); wpas_notify_bss_added(wpa_s, bss->bssid, bss->id); return bss; } diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h index d9d0401cef32..5580fc0d32c4 100644 --- a/wpa_supplicant/bss.h +++ b/wpa_supplicant/bss.h @@ -93,6 +93,8 @@ struct wpa_bss { size_t ssid_len; /** Frequency of the channel in MHz (e.g., 2412 = channel 1) */ int freq; + /** Frequency offset from @freq of the channel in KHz */ + int freq_offset; /** The max channel width supported by both the AP and the STA */ enum chan_width max_cw; /** Beacon interval in TUs (host byte order) */ diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index e99036366681..6bb57ae7dc19 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -2959,6 +2959,7 @@ static const struct parse_data ssid_fields[] = { #ifdef CONFIG_PASN { FUNC(pasn_groups) }, #endif /* CONFIG_PASN */ + { INT_RANGE(scan_freq_offset, 0, 999)}, }; #undef OFFSET diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h index 7c60d4e38cab..aa3c1a166819 100644 --- a/wpa_supplicant/config_ssid.h +++ b/wpa_supplicant/config_ssid.h @@ -547,6 +547,11 @@ struct wpa_ssid { */ int frequency; + /** + * freq_offset - Channel frequency offset from @frequency in KHz + */ + int freq_offset; + /** * enable_edmg - Enable EDMG feature in STA/AP mode * @@ -653,6 +658,13 @@ struct wpa_ssid { */ int *scan_freq; + /** + * scan_freq_offset - Frequency offset in KHz + * + * To be applied to each entry in @scan_freq array. + */ + int scan_freq_offset; + /** * bgscan - Background scan and roaming parameters or %NULL if none * diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index ce9bc3277a32..93ceeb223cd9 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -10582,6 +10582,7 @@ static int wpas_ctrl_resend_assoc(struct wpa_supplicant *wpa_s) params.ssid = wpa_s->sme.ssid; params.ssid_len = wpa_s->sme.ssid_len; params.freq.freq = wpa_s->sme.freq; + params.freq.freq_offset = wpa_s->sme.freq_offset; if (wpa_s->last_assoc_req_wpa_ie) { params.wpa_ie = wpabuf_head(wpa_s->last_assoc_req_wpa_ie); params.wpa_ie_len = wpabuf_len(wpa_s->last_assoc_req_wpa_ie); diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index 13ac06bfd8b2..930946ebcd7a 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -334,12 +334,13 @@ static inline int wpa_drv_set_country(struct wpa_supplicant *wpa_s, static inline int wpa_drv_send_mlme(struct wpa_supplicant *wpa_s, const u8 *data, size_t data_len, int noack, - unsigned int freq, unsigned int wait) + unsigned int freq, unsigned int freq_offset, + unsigned int wait) { if (wpa_s->driver->send_mlme) - return wpa_s->driver->send_mlme(wpa_s->drv_priv, - data, data_len, noack, - freq, NULL, 0, 0, wait, -1); + return wpa_s->driver->send_mlme(wpa_s->drv_priv, data, data_len, + noack, freq, freq_offset, NULL, + 0, 0, wait, -1); return -1; } diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 1d203a8004b7..f9c51994cae4 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -1659,11 +1659,11 @@ struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, if (debug_print) { wpa_dbg(wpa_s, MSG_DEBUG, "%d: " MACSTR - " ssid='%s' wpa_ie_len=%u rsn_ie_len=%u caps=0x%x level=%d freq=%d %s%s", + " ssid='%s' wpa_ie_len=%u rsn_ie_len=%u caps=0x%x level=%d freq=%d freq_offset=%d %s%s", i, MAC2STR(bss->bssid), wpa_ssid_txt(bss->ssid, bss->ssid_len), wpa_ie_len, rsn_ie_len, bss->caps, bss->level, - bss->freq, + bss->freq, bss->freq_offset, wpa_bss_get_vendor_ie(bss, WPS_IE_VENDOR_TYPE) ? " wps" : "", (wpa_bss_get_vendor_ie(bss, P2P_IE_VENDOR_TYPE) || @@ -5557,8 +5557,10 @@ static void ft_rx_action(struct wpa_supplicant *wpa_s, const u8 *da, { struct wpa_bss *bss; bss = wpa_bss_get_bssid(wpa_s, target_ap_addr); - if (bss) + if (bss) { wpa_s->sme.freq = bss->freq; + wpa_s->sme.freq_offset = bss->freq_offset; + } wpa_s->sme.auth_alg = WPA_AUTH_ALG_FT; sme_associate(wpa_s, WPAS_MODE_INFRA, target_ap_addr, WLAN_AUTH_FT); diff --git a/wpa_supplicant/ibss_rsn.c b/wpa_supplicant/ibss_rsn.c index 06228d0ef6c9..3d26bc1368a4 100644 --- a/wpa_supplicant/ibss_rsn.c +++ b/wpa_supplicant/ibss_rsn.c @@ -533,7 +533,7 @@ static int ibss_rsn_send_auth(struct ibss_rsn *ibss_rsn, const u8 *da, int seq) wpa_printf(MSG_DEBUG, "RSN: IBSS TX Auth frame (SEQ %d) to " MACSTR, seq, MAC2STR(da)); - return wpa_drv_send_mlme(wpa_s, (u8 *) &auth, auth_length, 0, 0, 0); + return wpa_drv_send_mlme(wpa_s, (u8 *) &auth, auth_length, 0, 0, 0, 0); } diff --git a/wpa_supplicant/mesh.c b/wpa_supplicant/mesh.c index 8f7143f6ea63..30547b19d108 100644 --- a/wpa_supplicant/mesh.c +++ b/wpa_supplicant/mesh.c @@ -198,7 +198,7 @@ static int wpas_mesh_update_freq_params(struct wpa_supplicant *wpa_s) if (hostapd_set_freq_params( ¶ms->freq, ifmsh->conf->hw_mode, - ifmsh->freq, + ifmsh->freq, 0, ifmsh->conf->channel, ifmsh->conf->enable_edmg, ifmsh->conf->edmg_channel, diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c index b842293dfccc..4ad92a7993ab 100644 --- a/wpa_supplicant/nan_supplicant.c +++ b/wpa_supplicant/nan_supplicant.c @@ -1044,7 +1044,7 @@ static int wpas_nan_pasn_send_cb(void *ctx, const u8 *data, size_t data_len) { struct wpa_supplicant *wpa_s = ctx; - return wpa_drv_send_mlme(wpa_s, data, data_len, 0, 0, 0); + return wpa_drv_send_mlme(wpa_s, data, data_len, 0, 0, 0, 0); } diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c index 7ae584a5a9d9..66d396826d56 100644 --- a/wpa_supplicant/p2p_supplicant.c +++ b/wpa_supplicant/p2p_supplicant.c @@ -3349,7 +3349,7 @@ static int wpas_send_probe_resp(void *ctx, const struct wpabuf *buf, { struct wpa_supplicant *wpa_s = ctx; return wpa_drv_send_mlme(wpa_s, wpabuf_head(buf), wpabuf_len(buf), 1, - freq, 0); + freq, 0, 0); } @@ -5739,7 +5739,7 @@ static int wpas_p2p_pasn_send_mgmt(void *ctx, const u8 *data, size_t data_len, { struct wpa_supplicant *wpa_s = ctx; - return wpa_drv_send_mlme(wpa_s, data, data_len, noack, freq, wait); + return wpa_drv_send_mlme(wpa_s, data, data_len, noack, freq, 0, wait); } diff --git a/wpa_supplicant/pasn_supplicant.c b/wpa_supplicant/pasn_supplicant.c index e2862c6fab0c..b3939ed24d04 100644 --- a/wpa_supplicant/pasn_supplicant.c +++ b/wpa_supplicant/pasn_supplicant.c @@ -76,7 +76,7 @@ static int wpas_pasn_send_mlme(void *ctx, const u8 *data, size_t data_len, { struct wpa_supplicant *wpa_s = ctx; - return wpa_drv_send_mlme(wpa_s, data, data_len, noack, freq, wait); + return wpa_drv_send_mlme(wpa_s, data, data_len, noack, freq, 0, wait); } @@ -1637,7 +1637,7 @@ int wpas_pasn_deauthenticate(struct wpa_supplicant *wpa_s, const u8 *own_addr, * without a radio work. */ ret = wpa_drv_send_mlme(wpa_s, wpabuf_head(buf), wpabuf_len(buf), 1, - bss->freq, 0); + bss->freq, 0, 0); wpabuf_free(buf); wpa_printf(MSG_DEBUG, "PASN: deauth: send_mlme ret=%d", ret); diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c index b7050585651f..92c1143700f6 100644 --- a/wpa_supplicant/scan.c +++ b/wpa_supplicant/scan.c @@ -1327,6 +1327,7 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx) os_free(params.freqs); params.freqs = NULL; } + params.freq_offset = tssid->scan_freq_offset; freqs_set = 1; } int_array_sort_unique(params.freqs); @@ -2553,20 +2554,20 @@ static void dump_scan_res(struct wpa_scan_results *scan_res) int noise_valid = !(r->flags & WPA_SCAN_NOISE_INVALID); wpa_printf(MSG_EXCESSIVE, MACSTR - " ssid=%s freq=%d qual=%d noise=%d%s level=%d snr=%d%s flags=0x%x age=%u est=%u", + " ssid=%s freq=%d freq_offset=%d qual=%d noise=%d%s level=%d snr=%d%s flags=0x%x age=%u est=%u", MAC2STR(r->bssid), wpa_ssid_txt(ssid, ssid_len), - r->freq, r->qual, + r->freq, r->freq_offset, r->qual, r->noise, noise_valid ? "" : "~", r->level, r->snr, r->snr >= GREAT_SNR ? "*" : "", r->flags, r->age, r->est_throughput); } else { wpa_printf(MSG_EXCESSIVE, MACSTR - " ssid=%s freq=%d qual=%d noise=%d level=%d flags=0x%x age=%u est=%u", + " ssid=%s freq=%d freq_offset=%d qual=%d noise=%d level=%d flags=0x%x age=%u est=%u", MAC2STR(r->bssid), wpa_ssid_txt(ssid, ssid_len), - r->freq, r->qual, + r->freq, r->freq_offset, r->qual, r->noise, r->level, r->flags, r->age, r->est_throughput); } @@ -3872,6 +3873,7 @@ wpa_scan_clone_params(const struct wpa_driver_scan_params *src) params->p2p_include_6ghz = src->p2p_include_6ghz; params->non_coloc_6ghz = src->non_coloc_6ghz; params->min_probe_req_content = src->min_probe_req_content; + params->freq_offset = src->freq_offset; return params; failed: diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index b0b77fe60335..1dd1f79601b3 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -1257,6 +1257,7 @@ static void sme_send_authentication(struct wpa_supplicant *wpa_s, wpa_s->reassociate = 0; params.freq = bss->freq; + params.freq_offset = bss->freq_offset; params.bssid = bss->bssid; params.ssid = bss->ssid; params.ssid_len = bss->ssid_len; @@ -1267,6 +1268,7 @@ static void sme_send_authentication(struct wpa_supplicant *wpa_s, wpa_s->sme.prev_bssid_set = 0; wpa_s->sme.freq = params.freq; + wpa_s->sme.freq_offset = params.freq_offset; os_memcpy(wpa_s->sme.ssid, params.ssid, params.ssid_len); wpa_s->sme.ssid_len = params.ssid_len; @@ -1892,8 +1894,9 @@ no_fils: wpa_supplicant_cancel_scan(wpa_s); wpa_msg(wpa_s, MSG_INFO, "SME: Trying to authenticate with " MACSTR - " (SSID='%s' freq=%d MHz)", MAC2STR(params.bssid), - wpa_ssid_txt(params.ssid, params.ssid_len), params.freq); + " (SSID='%s' freq=%d freq_offset=%d)", MAC2STR(params.bssid), + wpa_ssid_txt(params.ssid, params.ssid_len), + params.freq, params.freq_offset); eapol_sm_notify_portValid(wpa_s->eapol, false); wpa_clear_keys(wpa_s, bss->bssid); @@ -2153,7 +2156,7 @@ static int sme_external_auth_send_sae_commit(struct wpa_supplicant *wpa_s, wpa_s->sme.ext_ml_auth ? wpa_s->own_addr : NULL); wpa_drv_send_mlme(wpa_s, wpabuf_head(buf), wpabuf_len(buf), 1, - wpa_s->sme.ext_auth_freq, 0); + wpa_s->sme.ext_auth_freq, 0, 0); wpabuf_free(resp); wpabuf_free(buf); @@ -2272,7 +2275,7 @@ static void sme_external_auth_send_sae_confirm(struct wpa_supplicant *wpa_s, wpa_s->own_addr : NULL); wpa_drv_send_mlme(wpa_s, wpabuf_head(buf), wpabuf_len(buf), 1, - wpa_s->sme.ext_auth_freq, 0); + wpa_s->sme.ext_auth_freq, 0, 0); wpabuf_free(resp); wpabuf_free(buf); } @@ -2476,7 +2479,7 @@ static int sme_external_auth_send_802_1x(struct wpa_supplicant *wpa_s, wpa_s->own_addr : NULL); if (wpa_drv_send_mlme(wpa_s, wpabuf_head(buf), wpabuf_len(buf), - 0, 0, 0) < 0) { + 0, 0, 0, 0) < 0) { wpa_printf(MSG_INFO, "IEEE 802.1X: Failed to send Authentication frame"); goto fail; @@ -4509,6 +4512,7 @@ mscs_fail: params.ssid = wpa_s->sme.ssid; params.ssid_len = wpa_s->sme.ssid_len; params.freq.freq = wpa_s->sme.freq; + params.freq.freq_offset = wpa_s->sme.freq_offset; params.bg_scan_period = ssid ? ssid->bg_scan_period : -1; params.wpa_ie = wpa_s->sme.assoc_req_ie_len ? wpa_s->sme.assoc_req_ie : NULL; diff --git a/wpa_supplicant/wpa_priv.c b/wpa_supplicant/wpa_priv.c index 88f3f2a52356..b49da9f299d1 100644 --- a/wpa_supplicant/wpa_priv.c +++ b/wpa_supplicant/wpa_priv.c @@ -325,6 +325,7 @@ static void wpa_priv_cmd_associate(struct wpa_priv_interface *iface, params.ssid_len = assoc->ssid_len; params.freq.mode = assoc->hwmode; params.freq.freq = assoc->freq; + params.freq.freq_offset = assoc->freq_offset; params.freq.channel = assoc->channel; if (assoc->wpa_ie_len) { params.wpa_ie = (u8 *) (assoc + 1); diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 367448eaf70b..be9db6d646fd 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -2953,8 +2953,10 @@ void wpa_supplicant_associate(struct wpa_supplicant *wpa_s, "Driver does not support mesh mode"); return; } - if (bss) + if (bss) { ssid->frequency = bss->freq; + ssid->freq_offset = bss->freq_offset; + } if (wpa_supplicant_join_mesh(wpa_s, ssid) < 0) { wpa_supplicant_set_state(wpa_s, WPA_INACTIVE); wpa_msg(wpa_s, MSG_ERROR, "Could not join mesh"); @@ -3525,6 +3527,7 @@ 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->freq_offset, freq->channel, ssid->enable_edmg, ssid->edmg_channel, freq->ht_enabled, freq->vht_enabled, freq->he_enabled, @@ -3555,6 +3558,7 @@ void ibss_mesh_setup_freq(struct wpa_supplicant *wpa_s, bool is_6ghz, is_24ghz; freq->freq = ssid->frequency; + freq->freq_offset = ssid->freq_offset; if (ssid->mode == WPAS_MODE_IBSS && !ssid->fixed_freq) { struct wpa_bss *bss = ibss_find_existing_bss(wpa_s, ssid); @@ -4737,6 +4741,7 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) wpa_s->key_mgmt == WPA_KEY_MGMT_WPS); params.bssid = bss->bssid; params.freq.freq = bss->freq; + params.freq.freq_offset = bss->freq_offset; } params.bssid_hint = bss->bssid; params.freq_hint = bss->freq; diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index fd327cdb37dc..e446f683a002 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -798,6 +798,7 @@ struct wpa_supplicant { struct wpa_bss *current_bss; int ap_ies_from_associnfo; unsigned int assoc_freq; + unsigned int assoc_freq_offset; u8 ap_mld_addr[ETH_ALEN]; u8 mlo_assoc_link_id; u16 valid_links; /* bitmap of valid MLO link IDs */ @@ -1077,6 +1078,7 @@ struct wpa_supplicant { u8 ssid[SSID_MAX_LEN]; size_t ssid_len; int freq; + int freq_offset; u8 assoc_req_ie[1500]; size_t assoc_req_ie_len; int mfp; -- 2.43.0 From lachlan.hodges at morsemicro.com Wed May 27 23:38:54 2026 From: lachlan.hodges at morsemicro.com (Lachlan Hodges) Date: Thu, 28 May 2026 16:38:54 +1000 Subject: [RFC 5/8] AP: add S1G support In-Reply-To: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> References: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> Message-ID: <20260528063857.950556-6-lachlan.hodges@morsemicro.com> Introduce the ability to bring up an S1G AP. Currently only US 2024 channelisation is supported. A sample config is as follows: ``` interface=wlan0 country_code=US driver=nl80211 ssid=wifi_halow channel=43 ieee80211ah=1 op_class=71 beacon_int=100 dtim_period=10 s1g_oper_centr_freq_idx=44 s1g_primary_2mhz=1 hw_mode=ah wpa=2 wpa_key_mgmt=SAE rsn_pairwise=CCMP sae_password=12345678 ieee80211w=2 ``` Where we are configuring an operating channel of 44, primary 1MHz channel of 43, whilst utilising a 2MHz primary. There is no need to include sae_pwe=1 as H2E will automatically be enabled for S1G. ieee80211w=2 is required, and will error if not set correctly during config validation. Signed-off-by: Lachlan Hodges --- hostapd/Makefile | 5 + hostapd/config_file.c | 10 ++ src/ap/Makefile | 1 + src/ap/ap_config.c | 34 ++++++ src/ap/ap_config.h | 23 ++++ src/ap/ap_drv_ops.c | 18 ++- src/ap/ap_drv_ops.h | 4 +- src/ap/beacon.c | 97 ++++++++++++++-- src/ap/ctrl_iface_ap.c | 8 +- src/ap/dfs.c | 8 +- src/ap/hostapd.c | 13 ++- src/ap/hw_features.c | 32 +++++- src/ap/ieee802_11.c | 60 ++++++++-- src/ap/ieee802_11.h | 10 ++ src/ap/ieee802_11_s1g.c | 196 ++++++++++++++++++++++++++++++++ src/ap/ieee802_11_shared.c | 14 +++ src/ap/interference.c | 1 + src/ap/sta_info.c | 5 +- src/ap/sta_info.h | 4 +- src/common/defs.h | 6 + src/common/hw_features_common.c | 49 +++++++- src/common/hw_features_common.h | 6 +- src/common/ieee802_11_common.c | 55 ++++++++- src/common/ieee802_11_common.h | 6 +- src/common/ieee802_11_defs.h | 55 +++++++++ wlantest/bss.c | 2 +- 26 files changed, 677 insertions(+), 45 deletions(-) create mode 100644 src/ap/ieee802_11_s1g.c diff --git a/hostapd/Makefile b/hostapd/Makefile index b2420e8474be..89f041720ace 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -355,6 +355,11 @@ CFLAGS += -DCONFIG_IEEE80211AX OBJS += ../src/ap/ieee802_11_he.o endif +ifdef CONFIG_IEEE80211AH +CFLAGS += -DCONFIG_IEEE80211AH +OBJS += ../src/ap/ieee802_11_s1g.o +endif + ifdef CONFIG_MBO CFLAGS += -DCONFIG_MBO OBJS += ../src/ap/mbo_ap.o diff --git a/hostapd/config_file.c b/hostapd/config_file.c index d34b4aa83950..790e8dcc1811 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -3177,6 +3177,8 @@ static int hostapd_config_fill(struct hostapd_config *conf, conf->hw_mode = HOSTAPD_MODE_IEEE80211G; else if (os_strcmp(pos, "ad") == 0) conf->hw_mode = HOSTAPD_MODE_IEEE80211AD; + else if (os_strcmp(pos, "ah") == 0) + conf->hw_mode = HOSTAPD_MODE_IEEE80211AH; else if (os_strcmp(pos, "any") == 0) conf->hw_mode = HOSTAPD_MODE_IEEE80211ANY; else { @@ -3752,6 +3754,14 @@ static int hostapd_config_fill(struct hostapd_config *conf, } else if (os_strcmp(buf, "mbssid_max") == 0) { conf->mbssid_max = atoi(pos); #endif /* CONFIG_IEEE80211AX */ +#ifdef CONFIG_IEEE80211AH + } else if (os_strcmp(buf, "ieee80211ah") == 0) { + conf->ieee80211ah = atoi(pos); + } else if (os_strcmp(buf, "s1g_oper_centr_freq_idx") == 0) { + conf->s1g_oper_centr_freq_idx = atoi(pos); + } else if (os_strcmp(buf, "s1g_primary_2mhz") == 0) { + conf->s1g_primary_2mhz = atoi(pos); +#endif /* CONFIG_IEEE80211AH */ } else if (os_strcmp(buf, "max_listen_interval") == 0) { bss->max_listen_interval = atoi(pos); } else if (os_strcmp(buf, "disable_pmksa_caching") == 0) { diff --git a/src/ap/Makefile b/src/ap/Makefile index 22a21d31eda6..790deabc2886 100644 --- a/src/ap/Makefile +++ b/src/ap/Makefile @@ -35,6 +35,7 @@ LIB_OBJS= \ ieee802_11_ht.o \ ieee802_11_shared.o \ ieee802_11_vht.o \ + ieee802_11_s1g.o \ ieee802_1x.o \ interference.o \ neighbor_db.o \ diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 36a4dad65626..7e14f3efd168 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -1199,6 +1199,26 @@ static bool hostapd_sae_pk_password_without_pk(struct hostapd_bss_config *bss) } #endif /* CONFIG_SAE_PK */ +#ifdef CONFIG_IEEE80211AH +bool hostapd_config_check_bss_s1g(struct hostapd_bss_config *bss) +{ + if (bss->ieee80211w != MGMT_FRAME_PROTECTION_REQUIRED) { + wpa_printf(MSG_ERROR, + "Management frame protection is required in S1GHz (ieee80211w=2)"); + return false; + } + +#ifdef CONFIG_SAE + if (wpa_key_mgmt_sae(bss->wpa_key_mgmt) && + bss->sae_pwe != SAE_PWE_HASH_TO_ELEMENT) { + wpa_printf(MSG_INFO, "SAE: Forcing SAE H2E on S1GHz"); + bss->sae_pwe = SAE_PWE_HASH_TO_ELEMENT; + } +#endif /* CONFIG_SAE */ + + return true; +} +#endif /* CONFIG_IEEE80211AH */ bool hostapd_config_check_bss_6g(struct hostapd_bss_config *bss) { @@ -1256,6 +1276,12 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss, !hostapd_config_check_bss_6g(bss)) return -1; +#ifdef CONFIG_IEEE80211AH + if (full_config && conf->hw_mode == HOSTAPD_MODE_IEEE80211AH && + !hostapd_config_check_bss_s1g(bss)) + return -1; +#endif /* CONFIG_IEEE80211AH */ + if (full_config && bss->ieee802_1x && !bss->eap_server && !bss->radius->auth_servers) { wpa_printf(MSG_ERROR, "Invalid IEEE 802.1X configuration (no " @@ -1629,6 +1655,14 @@ int hostapd_config_check(struct hostapd_config *conf, int full_config) return -1; } +#ifdef CONFIG_IEEE80211AH + if (full_config && conf->hw_mode == HOSTAPD_MODE_IEEE80211AH && + !is_supported_s1g_op_class(conf->op_class)) { + wpa_printf(MSG_ERROR, "Unsupported global op_class for S1G operation"); + return -1; + } +#endif /* CONFIG_IEEE80211AH */ + for (i = 0; i < conf->num_bss; i++) { if (hostapd_config_check_bss(conf->bss[i], conf, full_config)) return -1; diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 3fe2206b9133..9e1d9c9e925f 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -1068,6 +1068,7 @@ struct hostapd_config { u8 channel; int enable_edmg; u8 edmg_channel; + bool s1g_primary_2mhz; u8 acs; struct wpa_freq_range_list acs_ch_list; struct wpa_freq_range_list acs_freq_list; @@ -1207,6 +1208,12 @@ struct hostapd_config { bool require_he; #endif /* CONFIG_IEEE80211AX */ + int ieee80211ah; +#ifdef CONFIG_IEEE80211AH + int s1g_oper_centr_freq_idx; + enum oper_chan_width s1g_oper_chwidth; +#endif /* CONFIG_IEEE80211AH */ + /* VHT enable/disable config from CHAN_SWITCH */ #define CH_SWITCH_VHT_ENABLED BIT(0) #define CH_SWITCH_VHT_DISABLED BIT(1) @@ -1274,6 +1281,10 @@ struct hostapd_config { static inline enum oper_chan_width hostapd_get_oper_chwidth(struct hostapd_config *conf) { +#ifdef CONFIG_IEEE80211AH + if (conf->ieee80211ah) + return conf->s1g_oper_chwidth; +#endif #ifdef CONFIG_IEEE80211BE if (conf->ieee80211be) return conf->eht_oper_chwidth; @@ -1289,6 +1300,10 @@ static inline void hostapd_set_oper_chwidth(struct hostapd_config *conf, enum oper_chan_width oper_chwidth) { +#ifdef CONFIG_IEEE80211AH + if (conf->ieee80211ah) + conf->s1g_oper_chwidth = oper_chwidth; +#endif /* CONFIG_IEEE80211AH */ #ifdef CONFIG_IEEE80211BE if (conf->ieee80211be) conf->eht_oper_chwidth = oper_chwidth; @@ -1305,6 +1320,10 @@ hostapd_set_oper_chwidth(struct hostapd_config *conf, static inline u8 hostapd_get_oper_centr_freq_seg0_idx(struct hostapd_config *conf) { +#ifdef CONFIG_IEEE80211AH + if (conf->ieee80211ah) + return conf->s1g_oper_centr_freq_idx; +#endif /* CONFIG_IEEE80211AH */ #ifdef CONFIG_IEEE80211BE if (conf->ieee80211be) return conf->eht_oper_centr_freq_seg0_idx; @@ -1320,6 +1339,10 @@ static inline void hostapd_set_oper_centr_freq_seg0_idx(struct hostapd_config *conf, u8 oper_centr_freq_seg0_idx) { +#ifdef CONFIG_IEEE80211AH + if (conf->ieee80211ah) + conf->s1g_oper_centr_freq_idx = oper_centr_freq_seg0_idx; +#endif /* CONFIG_IEEE80211AH */ #ifdef CONFIG_IEEE80211BE if (conf->ieee80211be) conf->eht_oper_centr_freq_seg0_idx = oper_centr_freq_seg0_idx; diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c index 5862bedff238..a10472155068 100644 --- a/src/ap/ap_drv_ops.c +++ b/src/ap/ap_drv_ops.c @@ -478,6 +478,7 @@ int hostapd_sta_add(struct hostapd_data *hapd, const struct ieee80211_eht_capabilities *eht_capab, size_t eht_capab_len, const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab, + const struct ieee80211_s1g_capabilities *s1g_capab, u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps, int set, const u8 *link_addr, bool mld_link_sta, u16 eml_cap, bool epp_sta) @@ -503,6 +504,7 @@ int hostapd_sta_add(struct hostapd_data *hapd, params.eht_capab = eht_capab; params.eht_capab_len = eht_capab_len; params.he_6ghz_capab = he_6ghz_capab; + params.s1g_capab = s1g_capab; params.vht_opmode_enabled = !!(flags & WLAN_STA_VHT_OPMODE_ENABLED); params.vht_opmode = vht_opmode; params.flags = hostapd_sta_flags_to_drv(flags); @@ -650,6 +652,7 @@ int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode, int edmg, u8 edmg_channel, int ht_enabled, int vht_enabled, int he_enabled, bool eht_enabled, + int s1g_enabled, int sec_channel_offset, int oper_chwidth, int center_segment0, int center_segment1) { @@ -659,7 +662,8 @@ int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode, if (hostapd_set_freq_params(&data, mode, freq, freq_offset, channel, edmg, edmg_channel, ht_enabled, - vht_enabled, he_enabled, eht_enabled, + vht_enabled, he_enabled, + eht_enabled, s1g_enabled, sec_channel_offset, oper_chwidth, center_segment0, center_segment1, cmode ? cmode->vht_capab : 0, @@ -667,7 +671,9 @@ int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode, &cmode->he_capab[IEEE80211_MODE_AP] : NULL, cmode ? &cmode->eht_capab[IEEE80211_MODE_AP] : - NULL, hostapd_get_punct_bitmap(hapd))) + NULL, hostapd_get_punct_bitmap(hapd), + hapd->iconf->op_class, + hapd->iconf->s1g_primary_2mhz)) return -1; if (hapd->driver == NULL) @@ -1098,14 +1104,16 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface, if (hostapd_set_freq_params(&data, mode, freq, 0, channel, 0, 0, ht_enabled, - vht_enabled, he_enabled, eht_enabled, + vht_enabled, he_enabled, + eht_enabled, 0, 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))) { + hostapd_get_punct_bitmap(hapd), + hapd->iconf->op_class, false)) { wpa_printf(MSG_ERROR, "Can't set freq params"); return -1; } @@ -1255,6 +1263,8 @@ int hostapd_drv_do_acs(struct hostapd_data *hapd) for (i = 0; i < hapd->iface->num_hw_features; i++) { mode = &hapd->iface->hw_features[i]; + if (mode->mode == HOSTAPD_MODE_IEEE80211AH) + continue; if (selected_mode != HOSTAPD_MODE_IEEE80211ANY && selected_mode != mode->mode) continue; diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h index fa84f709de91..76e2f875fd50 100644 --- a/src/ap/ap_drv_ops.h +++ b/src/ap/ap_drv_ops.h @@ -48,6 +48,7 @@ int hostapd_sta_add(struct hostapd_data *hapd, const struct ieee80211_eht_capabilities *eht_capab, size_t eht_capab_len, const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab, + const struct ieee80211_s1g_capabilities *s1g_capab, u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps, int set, const u8 *link_addr, bool mld_link_sta, u16 eml_cap, bool epp_sta); @@ -74,7 +75,8 @@ int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode, int freq, int freq_offset, int channel, int edmg, u8 edmg_channel, int ht_enabled, int vht_enabled, int he_enabled, - bool eht_enabled, int sec_channel_offset, int oper_chwidth, + bool eht_enabled, int s1g_enabled, + int sec_channel_offset, int oper_chwidth, int center_segment0, int center_segment1); int hostapd_set_rts(struct hostapd_data *hapd, int rts); int hostapd_set_frag(struct hostapd_data *hapd, int frag); diff --git a/src/ap/beacon.c b/src/ap/beacon.c index 2610106871a6..0fd541868306 100644 --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -809,8 +809,15 @@ static size_t hostapd_probe_resp_elems_len(struct hostapd_data *hapd, } #endif /* CONFIG_IEEE80211BE */ - buflen += hostapd_eid_mbssid_len(hapd_probed, WLAN_FC_STYPE_PROBE_RESP, - NULL, + +#ifdef CONFIG_IEEE80211AH + if (hapd->iconf->ieee80211ah) { + buflen += 2 + sizeof(struct ieee80211_s1g_capabilities) + + 2 + sizeof(struct ieee80211_s1g_operation); + } +#endif /* CONFIG_IEEE80211AH */ + + buflen += hostapd_eid_mbssid_len(hapd_probed, WLAN_FC_STYPE_PROBE_RESP, NULL, params->known_bss, params->known_bss_len, NULL); buflen += hostapd_eid_rnr_len(hapd, WLAN_FC_STYPE_PROBE_RESP, true); @@ -919,6 +926,13 @@ static u8 * hostapd_probe_resp_fill_elems(struct hostapd_data *hapd, } #endif /* CONFIG_IEEE80211AC */ +#ifdef CONFIG_IEEE80211AH + if (hapd->iconf->ieee80211ah) { + pos = hostapd_eid_s1g_capab(hapd, pos); + pos = hostapd_eid_s1g_oper(hapd, pos); + } +#endif /* CONFIG_IEEE80211AH */ + #ifdef CONFIG_IEEE80211AX if (hostapd_is_he_enabled(hapd) && is_6ghz_op_class(hapd->iconf->op_class)) @@ -1504,7 +1518,11 @@ void handle_probe_req(struct hostapd_data *hapd, return; } - if ((!elems.ssid || !elems.supp_rates)) { + /* + * S1G does not support legacy rates. The equivalent information is + * advertised in the S1G Capabilities element. + */ + if ((!elems.ssid || (!elems.supp_rates && !elems.s1g_capabilities))) { wpa_printf(MSG_DEBUG, "STA " MACSTR " sent probe request " "without SSID or supported rates element", MAC2STR(mgmt->sa)); @@ -2234,6 +2252,9 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, struct wpa_driver_ap_params *params) { struct ieee80211_mgmt *head = NULL; +#ifdef CONFIG_IEEE80211AH + struct ieee80211_ext *ext_head = NULL; +#endif /* CONFIG_IEEE80211AH */ u8 *tail = NULL; size_t head_len = 0, tail_len = 0; u8 *resp = NULL; @@ -2299,6 +2320,14 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, } #endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211AH + if (hapd->iconf->ieee80211ah) { + /* +2 for element type + length respectively */ + tail_len += 2 + sizeof(struct ieee80211_s1g_operation) + + 2 + sizeof(struct ieee80211_s1g_capabilities); + } +#endif /* CONFIG_IEEE80211AH */ + if (hapd->iconf->mbssid == ENHANCED_MBSSID_ENABLED && hapd == hostapd_mbssid_get_tx_bss(hapd)) tail_len += 5; /* Multiple BSSID Configuration element */ @@ -2317,12 +2346,25 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, } tailend = tail + tail_len; - head->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, - WLAN_FC_STYPE_BEACON); + if (hapd->iconf->ieee80211ah) { +#ifdef CONFIG_IEEE80211AH + ext_head = (struct ieee80211_ext *)head; + ext_head->duration = host_to_le16(0); + ext_head->frame_control = IEEE80211_FC(WLAN_FC_TYPE_EXT, WLAN_FC_STYPE_S1G_BEACON); + ext_head->frame_control |= WLAN_S1G_FC_BSS_BW(WLAN_S1G_BSS_BW_MIN_1_MAX_16); +#endif /* CONFIG_IEEE80211AH */ + } else { + head->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, WLAN_FC_STYPE_BEACON); + } + head->duration = host_to_le16(0); os_memset(head->da, 0xff, ETH_ALEN); os_memcpy(head->sa, hapd->own_addr, ETH_ALEN); +#ifdef CONFIG_IEEE80211AH + if (hapd->iconf->ieee80211ah) + os_memcpy(ext_head->sa, hapd->own_addr, ETH_ALEN); +#endif /* CONFIG_IEEE80211AH */ os_memcpy(head->bssid, hapd->own_addr, ETH_ALEN); head->u.beacon.beacon_int = host_to_le16(hapd->iconf->beacon_int); @@ -2330,7 +2372,30 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, /* hardware or low-level driver will setup seq_ctrl and timestamp */ capab_info = hostapd_own_capab_info(hapd); head->u.beacon.capab_info = host_to_le16(capab_info); + +#ifdef CONFIG_IEEE80211AH + if (hapd->iconf->ieee80211ah) + pos = &ext_head->u.beacon.variable[0]; + else + pos = &head->u.beacon.variable[0]; +#else pos = &head->u.beacon.variable[0]; +#endif /* CONFIG_IEEE80211AH */ + + /* + * As per IEEE80211-2024 11.1.3.10.1 the first element in a beacon + * transmitted at a TBTT that is not a TSBTT (i.e a long beacon) shall + * contain the Beacon Compatibility element as the first element in a + * beacon. Therefore insert this at the head before the SSID element. + * Other elements can be inserted in the tail as normal. + */ +#ifdef CONFIG_IEEE80211AH + if (hapd->iconf->ieee80211ah) { + pos = hostapd_eid_s1g_beacon_compat(hapd, pos); + tailpos = hostapd_eid_s1g_capab(hapd, tailpos); + tailpos = hostapd_eid_s1g_oper(hapd, tailpos); + } +#endif /* CONFIG_IEEE80211AH */ /* SSID */ *pos++ = WLAN_EID_SSID; @@ -2354,7 +2419,14 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, /* DS Params */ pos = hostapd_eid_ds_params(hapd, pos); +#ifdef CONFIG_IEEE80211AH + if (hapd->iconf->ieee80211ah) + head_len = pos - (u8 *) ext_head; + else + head_len = pos - (u8 *) head; +#else head_len = pos - (u8 *) head; +#endif /* CONFIG_IEEE80211AH */ tailpos = hostapd_eid_country(hapd, tailpos, tailend - tailpos); @@ -2590,7 +2662,14 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, } #endif /* CONFIG_SAE */ + #ifdef CONFIG_IEEE80211AH + if (hapd->iconf->ieee80211ah) + params->head = (u8 *) ext_head; + else + params->head = (u8 *) head; + #else params->head = (u8 *) head; + #endif /* CONFIG_IEEE80211AH */ params->head_len = head_len; params->tail = tail; params->tail_len = tail_len; @@ -2812,7 +2891,7 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd) iconf->channel, iconf->enable_edmg, iconf->edmg_channel, iconf->ieee80211n, iconf->ieee80211ac, iconf->ieee80211ax, - iconf->ieee80211be, + iconf->ieee80211be, iconf->ieee80211ah, iconf->secondary_channel, hostapd_get_oper_chwidth(iconf), hostapd_get_oper_centr_freq_seg0_idx(iconf), @@ -2820,7 +2899,9 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd) cmode->vht_capab, &cmode->he_capab[IEEE80211_MODE_AP], &cmode->eht_capab[IEEE80211_MODE_AP], - hostapd_get_punct_bitmap(hapd)) == 0) { + hostapd_get_punct_bitmap(hapd), + hapd->iconf->op_class, + hapd->iconf->s1g_primary_2mhz) == 0) { freq.link_id = -1; #ifdef CONFIG_IEEE80211BE if (hapd->conf->mld_ap) @@ -2832,6 +2913,8 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd) for (i = 0; i < hapd->iface->num_hw_features; i++) { mode = &hapd->iface->hw_features[i]; + if (mode->mode == HOSTAPD_MODE_IEEE80211AH) + continue; if (iconf->hw_mode != HOSTAPD_MODE_IEEE80211ANY && iconf->hw_mode != mode->mode) continue; diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c index 90a0a49363e3..50bc35665768 100644 --- a/src/ap/ctrl_iface_ap.c +++ b/src/ap/ctrl_iface_ap.c @@ -224,6 +224,8 @@ static const char * hw_mode_str(enum hostapd_hw_mode mode) return "a"; case HOSTAPD_MODE_IEEE80211AD: return "ad"; + case HOSTAPD_MODE_IEEE80211AH: + return "ah"; case HOSTAPD_MODE_IEEE80211ANY: return "any"; case NUM_HOSTAPD_MODES: @@ -885,6 +887,7 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf, "ieee80211ac=%d\n" "ieee80211ax=%d\n" "ieee80211be=%d\n" + "ieee80211ah=%d\n" "beacon_int=%u\n" "dtim_period=%d\n", iface->conf->channel, @@ -896,6 +899,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), + iface->conf->ieee80211ah, iface->conf->beacon_int, hapd->conf->dtim_period); if (os_snprintf_error(buflen - len, ret)) @@ -1173,7 +1177,9 @@ static struct hostapd_hw_modes * get_target_hw_mode(struct hostapd_iface *iface, enum hostapd_hw_mode target_mode; bool is_6ghz = is_6ghz_freq(freq); - if (freq < 4000) + if (freq < 1000) + target_mode = HOSTAPD_MODE_IEEE80211AH; + else if (freq < 4000) target_mode = HOSTAPD_MODE_IEEE80211G; else if (freq > 50000) target_mode = HOSTAPD_MODE_IEEE80211AD; diff --git a/src/ap/dfs.c b/src/ap/dfs.c index 2758454ff033..683485f85c6b 100644 --- a/src/ap/dfs.c +++ b/src/ap/dfs.c @@ -839,7 +839,7 @@ int hostapd_handle_dfs(struct hostapd_iface *iface) int res, n_chans, n_chans1, start_chan_idx, start_chan_idx1; int skip_radar = 0; - if (is_6ghz_freq(iface->freq)) + if (is_6ghz_freq(iface->freq) || is_s1ghz_freq(iface->freq)) return 1; if (!iface->current_mode) { @@ -1021,6 +1021,7 @@ static int hostapd_dfs_request_channel_switch(struct hostapd_iface *iface, iface->conf->ieee80211ac, iface->conf->ieee80211ax, iface->conf->ieee80211be, + 0, /* No DFS for S1G */ secondary_channel, new_vht_oper_chwidth, oper_centr_freq_seg0_idx, @@ -1028,7 +1029,8 @@ static int hostapd_dfs_request_channel_switch(struct hostapd_iface *iface, cmode->vht_capab, &cmode->he_capab[ieee80211_mode], &cmode->eht_capab[ieee80211_mode], - hostapd_get_punct_bitmap(iface->bss[0])); + hostapd_get_punct_bitmap(iface->bss[0]), + iface->conf->op_class, false); if (err) { wpa_printf(MSG_ERROR, @@ -1599,6 +1601,7 @@ int hostapd_is_dfs_required(struct hostapd_iface *iface) if ((!(iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) && !iface->conf->ieee80211h) || !iface->current_mode || + iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AH || iface->current_mode->mode != HOSTAPD_MODE_IEEE80211A) return 0; @@ -1715,6 +1718,7 @@ int hostapd_is_dfs_overlap(struct hostapd_iface *iface, enum chan_width width, int i; if (!iface->conf->ieee80211h || !mode || + mode->mode == HOSTAPD_MODE_IEEE80211AH || mode->mode != HOSTAPD_MODE_IEEE80211A) return 0; diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index a15e17919b3f..6f0b34b55de5 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -133,7 +133,8 @@ static void hostapd_reload_bss(struct hostapd_data *hapd) if (hapd->conf->wmm_enabled < 0) hapd->conf->wmm_enabled = hapd->iconf->ieee80211n | - hapd->iconf->ieee80211ax; + hapd->iconf->ieee80211ax | + hapd->iconf->ieee80211ah; #ifndef CONFIG_NO_RADIUS radius_client_reconfig(hapd->radius, hapd->conf->radius); @@ -1541,7 +1542,8 @@ setup_mld: if (conf->wmm_enabled < 0) conf->wmm_enabled = hapd->iconf->ieee80211n | - hapd->iconf->ieee80211ax; + hapd->iconf->ieee80211ax | + hapd->iconf->ieee80211ah; #ifdef CONFIG_IEEE80211R_AP if (is_zero_ether_addr(conf->r1_key_holder)) @@ -2654,6 +2656,7 @@ static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface, hapd->iconf->ieee80211ac, hapd->iconf->ieee80211ax, hapd->iconf->ieee80211be, + hapd->iconf->ieee80211ah, hapd->iconf->secondary_channel, hostapd_get_oper_chwidth(hapd->iconf), hostapd_get_oper_centr_freq_seg0_idx( @@ -4532,7 +4535,8 @@ int hostapd_change_config_freq(struct hostapd_data *hapd, conf->channel, conf->enable_edmg, conf->edmg_channel, conf->ieee80211n, conf->ieee80211ac, conf->ieee80211ax, - conf->ieee80211be, conf->secondary_channel, + conf->ieee80211be, 0, + conf->secondary_channel, hostapd_get_oper_chwidth(conf), hostapd_get_oper_centr_freq_seg0_idx(conf), hostapd_get_oper_centr_freq_seg1_idx(conf), @@ -4541,7 +4545,8 @@ int hostapd_change_config_freq(struct hostapd_data *hapd, NULL, mode ? &mode->eht_capab[IEEE80211_MODE_AP] : NULL, - hostapd_get_punct_bitmap(hapd))) + hostapd_get_punct_bitmap(hapd), + hapd->iconf->op_class, false)) return -1; switch (params->bandwidth) { diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c index 6ac7cd9d8837..b55c0a62cc75 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -261,6 +261,12 @@ int hostapd_prepare_rates(struct hostapd_data *hapd, int basic_rates_g[] = { 10, 20, 55, 110, 0 }; const int *basic_rates; +#ifdef CONFIG_IEEE80211AH + /* S1G does not support basic rates */ + if (mode->mode == HOSTAPD_MODE_IEEE80211AH) + return 0; +#endif + if (conf->basic_rates) basic_rates = conf->basic_rates; else switch (mode->mode) { @@ -1114,6 +1120,12 @@ static int hostapd_is_usable_chans(struct hostapd_iface *iface) wpa_printf(MSG_ERROR, "Primary frequency not allowed"); return err; } + +#ifdef CONFIG_IEEE80211AH + if (iface->conf->ieee80211ah) + return 1; +#endif + err = hostapd_is_usable_edmg(iface); if (err <= 0) return err; @@ -1181,7 +1193,9 @@ int hostapd_determine_mode(struct hostapd_iface *iface) iface->conf->hw_mode != HOSTAPD_MODE_IEEE80211ANY) return 0; - if (iface->freq < 4000) + if (iface->freq < 1000) + target_mode = HOSTAPD_MODE_IEEE80211AH; + else if (iface->freq < 4000) target_mode = HOSTAPD_MODE_IEEE80211G; else if (iface->freq > 50000) target_mode = HOSTAPD_MODE_IEEE80211AD; @@ -1357,6 +1371,14 @@ int hostapd_select_hw_mode(struct hostapd_iface *iface) iface->conf->ieee80211be = 0; } + if (iface->conf->hw_mode == HOSTAPD_MODE_IEEE80211AH) { + wpa_printf(MSG_INFO, "Disabling HT/VHT/HE/EHT for S1G mode"); + iface->conf->ieee80211n = 0; + iface->conf->ieee80211ac = 0; + iface->conf->ieee80211ax = 0; + iface->conf->ieee80211be = 0; + } + iface->current_mode = NULL; for (i = 0; i < iface->num_hw_features; i++) { struct hostapd_hw_modes *mode = &iface->hw_features[i]; @@ -1419,6 +1441,8 @@ const char * hostapd_hw_mode_txt(int mode) return "IEEE 802.11g"; case HOSTAPD_MODE_IEEE80211AD: return "IEEE 802.11ad"; + case HOSTAPD_MODE_IEEE80211AH: + return "IEEE 802.11ah"; default: return "UNKNOWN"; } @@ -1466,6 +1490,12 @@ int hostapd_hw_skip_mode(struct hostapd_iface *iface, { int i; + if (mode->mode == HOSTAPD_MODE_IEEE80211AH && !iface->current_mode) + return 1; + if (iface->current_mode && + iface->current_mode->mode != HOSTAPD_MODE_IEEE80211AH && + mode->mode == HOSTAPD_MODE_IEEE80211AH) + return 1; if (iface->current_mode) return mode != iface->current_mode; if (mode->mode != HOSTAPD_MODE_IEEE80211B) diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 50f7e2a35f2e..fbb5f83f7e90 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -173,6 +173,12 @@ u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid) u8 buf[100]; size_t len; +#ifdef CONFIG_IEEE80211AH + /* No supp rates for S1G */ + if (hapd->iconf->ieee80211ah) + return eid; +#endif /* CONFIG_IEEE80211AH */ + len = hostapd_supp_rates(hapd, buf); if (len == 0) return eid; @@ -4971,9 +4977,10 @@ static u16 check_multi_ap(struct hostapd_data *hapd, struct sta_info *sta, static u16 copy_supp_rates(struct hostapd_data *hapd, struct sta_info *sta, struct ieee802_11_elems *elems) { - /* Supported rates not used in IEEE 802.11ad/DMG */ - if (hapd->iface->current_mode && - hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) + /* Supported rates not used in IEEE 802.11ad/DMG/ah */ + if ((hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) || + hapd->iconf->ieee80211ah) return WLAN_STATUS_SUCCESS; if (!elems->supp_rates) { @@ -5543,6 +5550,15 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta, } #endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211AH + if (hapd->iconf->ieee80211ah) { + resp = copy_sta_s1g_capab(hapd, sta, IEEE80211_MODE_AP, + elems->s1g_capabilities); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + } +#endif /* CONFIG_IEEE80211AH */ + #ifdef CONFIG_P2P if (elems->p2p && ies && ies_len) { wpabuf_free(sta->p2p_ie); @@ -6471,6 +6487,9 @@ 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; +#ifdef CONFIG_IEEE80211AH + struct ieee80211_s1g_capabilities s1g_cap; +#endif /* CONFIG_IEEE80211AH */ int set = 1; const u8 *mld_link_addr = NULL; bool mld_link_sta = false, epp_sta = false; @@ -6553,6 +6572,12 @@ static int add_associated_sta(struct hostapd_data *hapd, sta->eht_capab_len); #endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211AH + if (sta->flags & WLAN_STA_S1G) + hostapd_get_s1g_capab(hapd, sta->s1g_capab, &s1g_cap, + sizeof(s1g_cap)); +#endif /* CONFIG_IEEE80211AH */ + /* * Add the station with forced WLAN_STA_ASSOC flag. The sta->flags * will be set when the ACK frame for the (Re)Association Response frame @@ -6567,7 +6592,7 @@ 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->he_6ghz_capab, + sta->he_6ghz_capab, sta->s1g_capab, sta->flags | WLAN_STA_ASSOC, sta->qosinfo, sta->vht_opmode, sta->p2p_ie ? 1 : 0, set, mld_link_addr, mld_link_sta, eml_cap, @@ -6602,6 +6627,7 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, struct ieee80211_mgmt *reply; u8 *p; u16 res = WLAN_STATUS_SUCCESS; + le16 aid; buflen = sizeof(struct ieee80211_mgmt) + 1024; #ifdef CONFIG_FILS @@ -6645,15 +6671,23 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, os_memcpy(reply->bssid, hapd->own_addr, ETH_ALEN); send_len = IEEE80211_HDRLEN; - send_len += sizeof(reply->u.assoc_resp); reply->u.assoc_resp.capab_info = host_to_le16(hostapd_own_capab_info(hapd)); reply->u.assoc_resp.status_code = host_to_le16(status_code); - reply->u.assoc_resp.aid = host_to_le16((sta ? sta->aid : 0) | - BIT(14) | BIT(15)); + aid = host_to_le16((sta ? sta->aid : 0) | BIT(14) | BIT(15)); + if (hapd->iconf->ieee80211ah) { + /* S1G AID is carried in the AID Response element */ + send_len += sizeof(reply->u.s1g_assoc_resp); + p = reply->u.s1g_assoc_resp.variable; + } else { + send_len += sizeof(reply->u.assoc_resp); + reply->u.assoc_resp.aid = aid; + p = reply->u.assoc_resp.variable; + } + /* Supported rates */ - p = hostapd_eid_supp_rates(hapd, reply->u.assoc_resp.variable); + p = hostapd_eid_supp_rates(hapd, p); /* Extended supported rates */ p = hostapd_eid_ext_supp_rates(hapd, p); @@ -6747,6 +6781,14 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, } #endif /* CONFIG_IEEE80211AX */ +#ifdef CONFIG_IEEE80211AH + if (hapd->iconf->ieee80211ah) { + p = hostapd_eid_s1g_capab(hapd, p); + p = hostapd_eid_s1g_oper(hapd, p); + p = hostapd_eid_aid_response(hapd, p, aid); + } +#endif /* CONFIG_IEEE80211AH */ + p = hostapd_eid_ext_capab(hapd, p, false); p = hostapd_eid_bss_max_idle_period(hapd, p, sta ? sta->max_idle_period : 0); @@ -7339,7 +7381,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%lx)", 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..38cb181aa850 100644 --- a/src/ap/ieee802_11.h +++ b/src/ap/ieee802_11.h @@ -103,6 +103,10 @@ u8 * hostapd_eid_he_operation(struct hostapd_data *hapd, u8 *eid); u8 * hostapd_eid_he_mu_edca_parameter_set(struct hostapd_data *hapd, u8 *eid); u8 * hostapd_eid_spatial_reuse(struct hostapd_data *hapd, u8 *eid); u8 * hostapd_eid_he_6ghz_band_cap(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_s1g_oper(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_s1g_capab(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_s1g_beacon_compat(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_aid_response(struct hostapd_data *hapd, u8 *eid, le16 aid); int hostapd_ht_operation_update(struct hostapd_iface *iface); void ieee802_11_send_sa_query_req(struct hostapd_data *hapd, @@ -189,6 +193,12 @@ int hostapd_update_time_adv(struct hostapd_data *hapd); void hostapd_client_poll_ok(struct hostapd_data *hapd, const u8 *addr); u8 * hostapd_eid_bss_max_idle_period(struct hostapd_data *hapd, u8 *eid, u16 value); +u16 copy_sta_s1g_capab(struct hostapd_data *hapd, struct sta_info *sta, + enum ieee80211_op_mode opmode, const u8 *s1g_capab); +void hostapd_get_s1g_capab(struct hostapd_data *hapd, + const struct ieee80211_s1g_capabilities *src, + struct ieee80211_s1g_capabilities *dest, + size_t len); int auth_sae_init_committed(struct hostapd_data *hapd, struct sta_info *sta); #ifdef CONFIG_SAE diff --git a/src/ap/ieee802_11_s1g.c b/src/ap/ieee802_11_s1g.c new file mode 100644 index 000000000000..b79de44bd4f2 --- /dev/null +++ b/src/ap/ieee802_11_s1g.c @@ -0,0 +1,196 @@ +/* + * hostapd / IEEE 802.11ah S1G + * Copyright (c) 2022, Morse Micro + * + * 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/ieee802_11_defs.h" +#include "hostapd.h" +#include "ap_config.h" +#include "sta_info.h" +#include "beacon.h" +#include "ieee802_11.h" + +/* 1SS, MCS9. TODO set via config */ +#define S1G_BASIC_MCS_NSS 0xCCC4 + +static u8 hostapd_global_op_class_to_s1g_op_class(u8 global) +{ + switch(global) { + /* US */ + case 68: return 1; + case 69: return 2; + case 70: return 3; + case 71: return 4; + case 72: return 5; + default: return 0; + } +} + +static u8 hostapd_s1g_calc_pri_1mhz_loc(struct hostapd_data *hapd) +{ + u8 low_chan; + u8 pri_1mhz_chan = hapd->iconf->channel; + u8 oper_ch = hapd->iconf->s1g_oper_centr_freq_idx ? + hapd->iconf->s1g_oper_centr_freq_idx : + hapd->iconf->channel; + + if (hapd->iconf->s1g_oper_chwidth == CONF_OPER_CHWIDTH_1MHZ) + return 0; + + switch (hapd->iconf->s1g_oper_chwidth) { + case CONF_OPER_CHWIDTH_4MHZ: + low_chan = oper_ch - 3; + break; + case CONF_OPER_CHWIDTH_8MHZ: + low_chan = oper_ch - 7; + break; + case CONF_OPER_CHWIDTH_16MHZ: + low_chan = oper_ch - 15; + break; + default: + return 0; + } + + return ((pri_1mhz_chan - low_chan) / 2) & 1; +} + +u8 *hostapd_eid_s1g_oper(struct hostapd_data *hapd, u8 *eid) +{ + struct ieee80211_s1g_operation *cap; + u8 *pos = eid; + u8 ch_width = 0; + u8 bss_width; + u8 s1g_pri_loc = hostapd_s1g_calc_pri_1mhz_loc(hapd); + + *pos++ = WLAN_EID_S1G_OPERATION; + *pos++ = sizeof(*cap); + + cap = (struct ieee80211_s1g_operation *) pos; + os_memset(cap, 0, sizeof(*cap)); + + /* IEEE80211-2024 Table 10-39 */ + switch (hapd->iconf->s1g_oper_chwidth) { + case CONF_OPER_CHWIDTH_1MHZ: + bss_width = 0x0; + break; + case CONF_OPER_CHWIDTH_2MHZ: + bss_width = 0x1; + break; + case CONF_OPER_CHWIDTH_4MHZ: + bss_width = 0x3; + break; + case CONF_OPER_CHWIDTH_8MHZ: + bss_width = 0x7; + break; + case CONF_OPER_CHWIDTH_16MHZ: + bss_width = 0xF; + break; + default: + return NULL; + } + + if (!hapd->iconf->s1g_primary_2mhz) + ch_width |= S1G_OPER_IE_CHANWIDTH_PRIM_CH_MASK; + + ch_width |= (bss_width << 1) & S1G_OPER_IE_CHANWIDTH_OPER_CH_MASK; + + if (hapd->iconf->s1g_primary_2mhz && s1g_pri_loc) + ch_width |= S1G_OPER_IE_CHANWIDTH_PRIM_OFFSET; + + cap->ch_width = ch_width; + cap->oper_class = hostapd_global_op_class_to_s1g_op_class(hapd->iconf->op_class); + cap->basic_mcs_nss = host_to_le16(S1G_BASIC_MCS_NSS); + + if (hapd->iconf->s1g_primary_2mhz) { + if (s1g_pri_loc) + cap->primary_ch = hapd->iconf->channel - 1; + else + cap->primary_ch = hapd->iconf->channel + 1; + } else { + cap->primary_ch = hapd->iconf->channel; + } + + cap->oper_ch = hapd->iconf->s1g_oper_centr_freq_idx ? + hapd->iconf->s1g_oper_centr_freq_idx : + cap->primary_ch; + + pos += sizeof(*cap); + return pos; +} + +u8 * hostapd_eid_s1g_capab(struct hostapd_data *hapd, u8 *eid) +{ + struct ieee80211_s1g_capabilities *cap; + u8 *pos = eid; + + *pos++ = WLAN_EID_S1G_CAPABILITIES; + *pos++ = sizeof(*cap); + + cap = (struct ieee80211_s1g_capabilities *) pos; + os_memset(cap, 0, sizeof(*cap)); + + os_memcpy(cap, &hapd->iface->current_mode->s1g_capab, sizeof(*cap)); + + pos += sizeof(*cap); + return pos; +} + + +u8 * hostapd_eid_s1g_beacon_compat(struct hostapd_data *hapd, u8 *eid) +{ + u8 *pos = eid; + u16 beacon_int; + + beacon_int = hapd->iconf->beacon_int; + + *pos++ = WLAN_EID_S1G_BCN_COMPAT; + *pos++ = 8; + *pos++ = WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_PRIVACY;; + *pos++ = 0; + + *pos++ = beacon_int & 0xff; + *pos++ = (beacon_int >> 8) & 0xff; + + /* + * Last 4 octets will be filled by the driver/fw to contain the 4 + * MSB of the TSF timer at time of generation. + */ + *pos++ = 0; + *pos++ = 0; + *pos++ = 0; + *pos++ = 0; + return pos; +} + +u16 copy_sta_s1g_capab(struct hostapd_data *hapd, struct sta_info *sta, + enum ieee80211_op_mode opmode, const u8 *s1g_capab) +{ + sta->s1g_capab = os_memdup(s1g_capab, sizeof(struct ieee80211_s1g_capabilities)); + if (!sta->s1g_capab) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + sta->flags |= WLAN_STA_S1G; + + return WLAN_STATUS_SUCCESS; +} + +void hostapd_get_s1g_capab(struct hostapd_data *hapd, + const struct ieee80211_s1g_capabilities *src, + struct ieee80211_s1g_capabilities *dest, + size_t len) +{ + if (!src || !dest) + return; + + if (len > sizeof(*dest)) + len = sizeof(*dest); + + os_memset(dest, 0, sizeof(*dest)); + os_memcpy(dest, src, len); +} \ No newline at end of file diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c index a6decab8d97b..42b1efe471aa 100644 --- a/src/ap/ieee802_11_shared.c +++ b/src/ap/ieee802_11_shared.c @@ -496,6 +496,20 @@ static void hostapd_ext_capab_byte(struct hostapd_data *hapd, u8 *pos, int idx, } } +u8 * hostapd_eid_aid_response(struct hostapd_data *hapd, u8 *eid, le16 aid) +{ + u8 *pos = eid; + const u8 len = 5; + + *pos++ = WLAN_EID_AID_RESPONSE; + *pos++ = len; + + memset(pos, 0, len); + u8 *end = pos + len; + *pos++ = aid; + + return end; +} u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid, bool mbssid_complete) diff --git a/src/ap/interference.c b/src/ap/interference.c index 44aa6c0019ef..12ab133c32d6 100644 --- a/src/ap/interference.c +++ b/src/ap/interference.c @@ -558,6 +558,7 @@ int hostapd_incumbt_sig_intf_detected(struct hostapd_iface *iface, int freq, iface->conf->ieee80211ac, iface->conf->ieee80211ax, iface->conf->ieee80211be, + iface->conf->ieee80211ah, iface->conf->secondary_channel, hostapd_get_oper_chwidth(iface->conf), hostapd_get_oper_centr_freq_seg0_idx(iface->conf), diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index 2bb7e1235cb0..246996054e44 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->s1g_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%lx timeout_next=%d", hapd->conf->iface, __func__, MAC2STR(sta->addr), sta->flags, sta->timeout_next); if (sta->timeout_next == STA_REMOVE) { @@ -2115,7 +2116,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, 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..10dfdb935892 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -49,6 +49,7 @@ #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_S1G 1ull << 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_* */ + u64 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,7 @@ 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_s1g_capabilities *s1g_capab; int sa_query_count; /* number of pending SA Query requests; * 0 = no SA Query in progress */ diff --git a/src/common/defs.h b/src/common/defs.h index ec602d660ace..0ba6f5ae5a59 100644 --- a/src/common/defs.h +++ b/src/common/defs.h @@ -418,6 +418,7 @@ enum hostapd_hw_mode { HOSTAPD_MODE_IEEE80211G, HOSTAPD_MODE_IEEE80211A, HOSTAPD_MODE_IEEE80211AD, + HOSTAPD_MODE_IEEE80211AH, HOSTAPD_MODE_IEEE80211ANY, NUM_HOSTAPD_MODES }; @@ -510,6 +511,11 @@ enum oper_chan_width { CONF_OPER_CHWIDTH_8640MHZ, CONF_OPER_CHWIDTH_40MHZ_6GHZ, CONF_OPER_CHWIDTH_320MHZ, + CONF_OPER_CHWIDTH_1MHZ, + CONF_OPER_CHWIDTH_2MHZ, + CONF_OPER_CHWIDTH_4MHZ, + CONF_OPER_CHWIDTH_8MHZ, + CONF_OPER_CHWIDTH_16MHZ, }; enum key_flag { diff --git a/src/common/hw_features_common.c b/src/common/hw_features_common.c index 32471ce36651..cd17bb6d5546 100644 --- a/src/common/hw_features_common.c +++ b/src/common/hw_features_common.c @@ -485,13 +485,15 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, int channel, int enable_edmg, u8 edmg_channel, int ht_enabled, int vht_enabled, int he_enabled, - bool eht_enabled, int sec_channel_offset, + bool eht_enabled, int s1g_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) + u16 punct_bitmap, int op_class, + bool s1g_primary_2mhz) { enum oper_chan_width oper_chwidth_legacy; u8 seg0_legacy, seg1_legacy; @@ -509,10 +511,12 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, data->vht_enabled = vht_enabled; data->he_enabled = he_enabled; data->eht_enabled = eht_enabled; + data->s1g_enabled = s1g_enabled; data->sec_channel_offset = sec_channel_offset; data->center_freq1 = freq + sec_channel_offset * 10; data->center_freq2 = 0; data->punct_bitmap = punct_bitmap; + data->s1g_primary_2mhz = s1g_primary_2mhz; if (oper_chwidth == CONF_OPER_CHWIDTH_80MHZ) data->bandwidth = 80; else if (oper_chwidth == CONF_OPER_CHWIDTH_160MHZ || @@ -529,7 +533,40 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, hostapd_encode_edmg_chan(enable_edmg, edmg_channel, channel, &data->edmg); - if (is_6ghz_freq(freq)) { + if (is_s1ghz_freq(freq)) { + if (oper_chwidth == CONF_OPER_CHWIDTH_16MHZ) + data->bandwidth = 16; + else if (oper_chwidth == CONF_OPER_CHWIDTH_8MHZ) + data->bandwidth = 8; + else if (oper_chwidth == CONF_OPER_CHWIDTH_4MHZ) + data->bandwidth = 4; + else if (oper_chwidth == CONF_OPER_CHWIDTH_2MHZ) + data->bandwidth = 2; + else + data->bandwidth = 1; + + if (!center_segment0) { + data->center_freq1 = data->freq; + data->center_freq1_offset = data->freq_offset; + } else { + int freq1 = ieee80211_chan_to_freq_khz(NULL, op_class, center_segment0); + if (freq1 < 0) { + wpa_printf(MSG_ERROR, + "Invalid segment 0 center frequency for S1G"); + return -1; + } + + data->center_freq1 = KHZ_TO_MHZ(freq1); + data->center_freq1_offset = KHZ_TO_S1G_OFFSET(freq1); + } + + data->ht_enabled = 0; + data->vht_enabled = 0; + data->he_enabled = 0; + data->eht_enabled = 0; + data->center_freq2 = 0; + return 0; + } else if (is_6ghz_freq(freq)) { if (!data->he_enabled && !data->eht_enabled) { wpa_printf(MSG_ERROR, "Can't set 6 GHz mode - HE or EHT aren't enabled"); @@ -962,6 +999,12 @@ int chan_bw_allowed(const struct hostapd_channel_data *chan, u32 bw, u32 bw_mask; switch (bw) { + case 4: + return chan->allowed_bw & HOSTAPD_CHAN_WIDTH_4; + case 8: + return chan->allowed_bw & HOSTAPD_CHAN_WIDTH_8; + case 16: + return chan->allowed_bw & HOSTAPD_CHAN_WIDTH_16; case 20: bw_mask = HOSTAPD_CHAN_WIDTH_20; break; diff --git a/src/common/hw_features_common.h b/src/common/hw_features_common.h index 63c0893e2c2b..58138c49fe42 100644 --- a/src/common/hw_features_common.h +++ b/src/common/hw_features_common.h @@ -45,13 +45,15 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, int channel, int edmg, u8 edmg_channel, int ht_enabled, int vht_enabled, int he_enabled, - bool eht_enabled, int sec_channel_offset, + bool eht_enabled, int s1g_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); + u16 punct_bitmap, int op_class, + bool s1g_primary_2mhz); void set_disable_ht40(struct ieee80211_ht_capabilities *htcaps, int disabled); int ieee80211ac_cap_check(u32 hw, u32 conf); diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c index a3b9592e23bc..88dbe0fc83ba 100644 --- a/src/common/ieee802_11_common.c +++ b/src/common/ieee802_11_common.c @@ -702,7 +702,7 @@ static ParseRes __ieee802_11_parse_elems(const u8 *start, size_t len, case WLAN_EID_S1G_CAPABILITIES: if (elen < 15) break; - elems->s1g_capab = pos; + elems->s1g_capabilities = pos; break; case WLAN_EID_FRAGMENT: wpa_printf(MSG_MSGDUMP, @@ -714,6 +714,16 @@ static ParseRes __ieee802_11_parse_elems(const u8 *start, size_t len, len, show_errors)) unknown++; break; + case WLAN_EID_S1G_OPERATION: + if (elen < sizeof(struct ieee80211_s1g_operation)) + break; + elems->s1g_operation = pos; + break; + case WLAN_EID_AID_RESPONSE: + if (elen < sizeof(struct ieee80211_aid_response)) + break; + elems->aid_response = pos; + break; default: unknown++; if (!show_errors) @@ -917,7 +927,10 @@ void ieee802_11_elems_clear_ids(struct ieee802_11_elems *elems, elems->dils_len = 0; break; case WLAN_EID_S1G_CAPABILITIES: - elems->s1g_capab = NULL; + elems->s1g_capabilities = NULL; + break; + case WLAN_EID_S1G_OPERATION: + elems->s1g_operation = NULL; break; } } @@ -1515,6 +1528,9 @@ ieee80211_freq_to_channel_ext(unsigned int freq, int sec_channel, /* TODO: more operating classes */ + if (is_s1ghz_freq(freq)) + return HOSTAPD_MODE_IEEE80211AH; + if (sec_channel > 1 || sec_channel < -1) return NUM_HOSTAPD_MODES; @@ -2022,10 +2038,6 @@ static int ieee80211_chan_to_freq_khz_global(u8 op_class, u8 chan) { /* Table E-4 in IEEE Std 802.11-2020 - Global operating classes */ switch (op_class) { - case 50: - case 51: - case 52: - case 53: case 68: case 69: case 70: @@ -3165,6 +3177,12 @@ bool is_same_band(int freq1, int freq2) } +bool is_s1ghz_freq(int freq) +{ + return freq < 1000; +} + + int ieee802_11_parse_candidate_list(const char *pos, u8 *nei_rep, size_t nei_rep_len) { @@ -3453,6 +3471,16 @@ int op_class_to_bandwidth(u8 op_class) enum oper_chan_width op_class_to_ch_width(u8 op_class) { switch (op_class) { + case 68: /* S1G 2024 US 1MHz */ + return CONF_OPER_CHWIDTH_1MHZ; + case 69: /* S1G 2024 US 2MHz */ + return CONF_OPER_CHWIDTH_2MHZ; + case 70: /* S1G 2024 US 4MHz */ + return CONF_OPER_CHWIDTH_4MHZ; + case 71: /* S1G 2024 US 8MHz */ + return CONF_OPER_CHWIDTH_8MHZ; + case 72: /* S1G 2024 US 16MHz */ + return CONF_OPER_CHWIDTH_16MHZ; case 81: case 82: return CONF_OPER_CHWIDTH_USE_HT; @@ -4311,3 +4339,18 @@ int ieee80211_get_center_freq(int ctrl_freq, u32 bw) return -1; } } + +bool is_supported_s1g_op_class(u8 op_class) +{ + switch (op_class) { + /* IEEE80211-2024 US */ + case 68: + case 69: + case 70: + case 71: + case 72: + return true; + default: + return false; + } +} \ No newline at end of file diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h index ed0147812fde..12a0eb6ca252 100644 --- a/src/common/ieee802_11_common.h +++ b/src/common/ieee802_11_common.h @@ -111,7 +111,7 @@ struct ieee802_11_elems { const u8 *short_ssid_list; const u8 *he_6ghz_band_cap; const u8 *sae_pk; - const u8 *s1g_capab; + const u8 *s1g_capabilities; const u8 *pasn_params; const u8 *eht_capabilities; const u8 *eht_operation; @@ -131,6 +131,8 @@ struct ieee802_11_elems { const u8 *akm_suite_selector; const u8 *supported_groups; const u8 *nan_ie; + const u8 *s1g_operation; + const u8 *aid_response; u8 ssid_len; u8 supp_rates_len; @@ -331,6 +333,8 @@ int get_6ghz_sec_channel(int channel); bool is_same_band(int freq1, int freq2); #define IS_2P4GHZ(n) (n >= 2412 && n <= 2484) #define IS_5GHZ(n) (n > 4000 && n < 5895) +bool is_s1ghz_freq(int freq); +bool is_supported_s1g_op_class(u8 op_class); u8 op_class_idx_to_chan(const struct oper_class_map *op, u8 idx); int op_class_chan_to_idx(const struct oper_class_map *op, u8 chan); diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index febaab8f1be3..992585859dd8 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -89,6 +89,12 @@ #define WLAN_FC_STYPE_DMG_BEACON 0 #define WLAN_FC_STYPE_S1G_BEACON 1 +#define WLAN_S1G_BSS_BW_MIN_1_MAX_16 7 + +#define WLAN_S1G_FC_BSS_BW_SHIFT 11 +#define WLAN_S1G_FC_BSS_BW_MASK (0x7 << WLAN_S1G_FC_BSS_BW_SHIFT) +#define WLAN_S1G_FC_BSS_BW(bw) (((bw) & 0x7) << WLAN_S1G_FC_BSS_BW_SHIFT) + /* Authentication algorithms */ #define WLAN_AUTH_OPEN 0 #define WLAN_AUTH_SHARED_KEY 1 @@ -482,7 +488,10 @@ #define WLAN_EID_DEVICE_LOCATION 204 #define WLAN_EID_WHITE_SPACE_MAP 205 #define WLAN_EID_FTM_PARAMETERS 206 +#define WLAN_EID_AID_REQUEST 210 +#define WLAN_EID_AID_RESPONSE 211 #define WLAN_EID_S1G_BCN_COMPAT 213 +#define WLAN_EID_S1G_SHORT_BCN_INTERVAL 214 #define WLAN_EID_TWT 216 #define WLAN_EID_S1G_CAPABILITIES 217 #define WLAN_EID_VENDOR_SPECIFIC 221 @@ -1009,6 +1018,7 @@ struct ieee80211_hdr { #define IEEE80211_HDRLEN (sizeof(struct ieee80211_hdr)) +/* TODO this needs to be removed */ struct ieee80211_hdr_s1g_beacon { le16 frame_control; le16 duration_id; @@ -1060,6 +1070,11 @@ struct ieee80211_mgmt { /* followed by Supported rates */ u8 variable[]; } STRUCT_PACKED assoc_resp, reassoc_resp; + struct { + le16 capab_info; + le16 status_code; + u8 variable[]; + } STRUCT_PACKED s1g_assoc_resp, s1g_reassoc_resp; struct { le16 capab_info; le16 listen_interval; @@ -1231,6 +1246,18 @@ struct ieee80211_mgmt { } u; } STRUCT_PACKED; +struct ieee80211_ext { + le16 frame_control; + le16 duration; + u8 sa[6]; + union { + struct { + u8 timestamp[4]; + u8 change_seq; + u8 variable[]; + } STRUCT_PACKED beacon; + } u; +} STRUCT_PACKED; #define IEEE80211_MIN_ACTION_LEN(type) \ (offsetof(struct ieee80211_mgmt, u.action.u.type) + \ @@ -2691,6 +2718,34 @@ struct ieee80211_he_mu_edca_parameter_set { /* B7: Reserved if sent by an AP; More Data Ack if sent by a non-AP STA */ #define HE_QOS_INFO_MORE_DATA_ACK ((u8) (BIT(7))) +struct ieee80211_s1g_capabilities { + u8 capab_info[10]; + u8 supp_mcs_nss[5]; +} STRUCT_PACKED; + +struct ieee80211_s1g_operation { + u8 ch_width; + u8 oper_class; + u8 primary_ch; + u8 oper_ch; + le16 basic_mcs_nss; +} STRUCT_PACKED; + +struct ieee80211_aid_response { + u16 aid; + u8 switch_count; + u16 response_interval; +} STRUCT_PACKED; + +#define S1G_OPER_IE_CHANWIDTH_PRIM_CH_MASK (BIT(0)) +#define S1G_OPER_IE_CHANWIDTH_OPER_CH_MASK (BIT(1) | BIT(2) | \ + BIT(3) | BIT(4)) +#define S1G_OPER_IE_CHANWIDTH_PRIM_OFFSET (BIT(5)) +#define S1G_OPER_IE_MAX_MCS_1_NSS_SHIFT (2) +#define S1G_OPER_IE_MAX_MCS_2_NSS_SHIFT (6) +#define S1G_OPER_IE_MAX_MCS_3_NSS_SHIFT (10) +#define S1G_OPER_IE_MAX_MCS_4_NSS_SHIFT (14) + /* * IEEE Std 802.11-2020 and IEEE Std 802.11ax-2021 * 9.4.2.170 Reduced Neighbor Report element diff --git a/wlantest/bss.c b/wlantest/bss.c index 0e94ab1cc240..5879b8b0ba70 100644 --- a/wlantest/bss.c +++ b/wlantest/bss.c @@ -178,7 +178,7 @@ void bss_update(struct wlantest *wt, struct wlantest_bss *bss, * Probe Response frames. Note this assumes short beacons were dropped * due to missing SSID above. */ - if (!elems->rsn_ie && (!elems->s1g_capab || beacon != 1)) { + if (!elems->rsn_ie && (!elems->s1g_capabilities || beacon != 1)) { if (bss->rsnie[0]) { add_note(wt, MSG_INFO, "BSS " MACSTR " - RSN IE removed", MAC2STR(bss->bssid)); -- 2.43.0 From lachlan.hodges at morsemicro.com Thu May 28 04:57:49 2026 From: lachlan.hodges at morsemicro.com (Lachlan Hodges) Date: Thu, 28 May 2026 21:57:49 +1000 Subject: [RFC 0/8] Introduce S1G Support In-Reply-To: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> References: <20260528063857.950556-1-lachlan.hodges@morsemicro.com> Message-ID: > 5) hwsim tests > ============== > > A basic test suite is also included that tests various channel permutations > as well as the NO_PRIMARY flag to ensure edgeband primaries with the > NO_PRIMARY flag cannot be used, which is typical of real hardware. See [2] > for more information on this. > > In order for the S1G hwsim tests to function, 2 patches [3] must be > applied onto wireless-next. The patches were just applied by Johannes, so the tip of wireless-next now has everything needed for the tests to run. lachlan From j at w1.fi Fri May 29 06:31:27 2026 From: j at w1.fi (Jouni Malinen) Date: Fri, 29 May 2026 16:31:27 +0300 Subject: [PATCH v4 00/12] some UHR support In-Reply-To: <20260507172335.546456-14-johannes@sipsolutions.net> References: <20260507172335.546456-14-johannes@sipsolutions.net> Message-ID: On Thu, May 07, 2026 at 07:21:27PM +0200, Johannes Berg wrote: > 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 Thanks, I applied most of these with cleanup and fixes (compilation was broken without CONFIG_IEEE80211BN=y and one of the presence bits was not set to 1 for DBE). Furthermore, I updated this to use IEEE P802.11bn/D1.4 definitions for the elements which broke the test cases against the current snapshot in mac80211, so I did not apply the hwsim test cases now. Those can be added once the mac80211 updates (sent to linux-wireless today) are in the upstream kernel. I left out patch 9/12 (remove signal for MLO signal poll) for now since there is some offline discussion on that functionality and how the current implementation is actually used in deployed devices. -- Jouni Malinen PGP id EFC895FA From senthil.balasubramanian at netgear.com Thu May 28 23:34:02 2026 From: senthil.balasubramanian at netgear.com (Senthil Balasubramanian) Date: Fri, 29 May 2026 12:04:02 +0530 Subject: [PATCH hostap] AP: Send Deauthentication before removing STA on (Re)Assoc Resp ack=0 Message-ID: When the driver reports via TX status that the STA did not acknowledge the (Re)Association Response, handle_assoc_cb() calls hostapd_drv_sta_remove() without sending any over-the-air notification to the STA. This was a reasonable assumption when station and AP power budgets were close to symmetric: if the AP did not see the ACK, the STA most likely did not receive the Response either, so the STA would time out and re-authenticate within a short window. This assumption no longer holds on 6 GHz Low Power Indoor (LPI) deployments. There the STA EIRP is capped 6 dB below the AP EIRP by regulation (FCC and ETSI), and additional asymmetry comes from typical laptop/phone antenna gain being lower than the AP's MIMO antenna gain at the 6 GHz wavelength, and from STA PA back-off due to thermal and battery constraints. The net result is that the AP can reliably reach the STA while the STA's frames (including the L2 ACK following the AP's (Re)Assoc Response) routinely fall below the AP's effective noise floor. In this scenario the STA decodes the (Re)Assoc Response and considers itself associated, while hostapd silently removes the station entry. The two sides remain out of sync until a STA-side timeout (implementation-defined, e.g. beacon-loss or null-frame failure) eventually fires. During that window the STA's unicast traffic is silently dropped at the AP, since there is no association table entry to map it to. Address this by sending a Deauthentication frame (WLAN_REASON_PREV_AUTH_NOT_VALID) before hostapd_drv_sta_remove() in the !ok path of handle_assoc_cb(). The Deauthentication is transmitted at the basic rate with the usual MAC-level retries, which gives it a materially better chance of reaching the STA than the higher-rate (Re)Assoc Response that just failed. If the STA never received the (Re)Assoc Response at all, the Deauthentication is benign because it is a Class 1 frame and does not tear down any association state on the STA side. Reported-by: Sudharsan Narayanan Signed-off-by: Sudharsan Narayanan Signed-off-by: Senthil Balasubramanian --- src/ap/ieee802_11.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 50f7e2a35..af363cd53 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -8494,8 +8494,29 @@ static void handle_assoc_cb(struct hostapd_data *hapd, "did not acknowledge association response"); sta->flags &= ~WLAN_STA_ASSOC_REQ_OK; /* The STA is added only in case of SUCCESS */ - if (status == WLAN_STATUS_SUCCESS) + if (status == WLAN_STATUS_SUCCESS) { + /* + * (Re)Assoc Response TX completed without an L2 ACK + * from the STA. The STA may still have received and + * accepted the frame. This can happen on links with + * asymmetric path budget (e.g., 6 GHz where the STA + * EIRP is capped well below the AP EIRP), where the + * AP can reach the STA but the STA's ACK does not + * reach the AP. Without explicit notification the + * STA would consider itself associated while the AP + * has already removed its entry, leaving the two + * sides out of sync until a STA-side timeout fires. + * Send a Deauthentication frame at the basic rate + * before removing the STA so it can recover quickly. + * If the STA never received the (Re)Assoc Response + * at all, the Deauthentication is benign since it is + * a Class 1 frame that does not tear down any + * association state on the STA side. + */ + hostapd_drv_sta_deauth(hapd, sta->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); hostapd_drv_sta_remove(hapd, sta->addr); + } goto handle_ml; } -- 2.50.1 (Apple Git-155) This e-mail, including attachments, may include confidential and/or proprietary information, and may be used only by the person or entity to which it is addressed. If the reader of this e-mail is not the intended recipient or his or her authorized agent, the reader is hereby notified that any dissemination, distribution or copying of this e-mail is prohibited. If you have received this e-mail in error, please notify the sender by replying to this message and delete this e-mail immediately. From j at w1.fi Fri May 29 06:48:58 2026 From: j at w1.fi (Jouni Malinen) Date: Fri, 29 May 2026 16:48:58 +0300 Subject: [PATCH hostap] AP: Send Deauthentication before removing STA on (Re)Assoc Resp ack=0 In-Reply-To: References: Message-ID: On Fri, May 29, 2026 at 12:04:02PM +0530, Senthil Balasubramanian wrote: > When the driver reports via TX status that the STA did not acknowledge > the (Re)Association Response, handle_assoc_cb() calls > hostapd_drv_sta_remove() without sending any over-the-air notification > to the STA. This was a reasonable assumption when station and AP power > budgets were close to symmetric: if the AP did not see the ACK, the STA > most likely did not receive the Response either, so the STA would time > out and re-authenticate within a short window. I'm not actually sure that calling hostapd_drv_sta_remove() here is the best behavior in all cases.. In particular, if there is already a valid TK configured and the STA would be authorized and ready to use the connection, the STA could be marked associated on reception of a correctly encrypted Data frame. > This assumption no longer holds on 6 GHz Low Power Indoor (LPI) > deployments. There the STA EIRP is capped 6 dB below the AP EIRP by > regulation (FCC and ETSI), and additional asymmetry comes from typical > laptop/phone antenna gain being lower than the AP's MIMO antenna gain > at the 6 GHz wavelength, and from STA PA back-off due to thermal and > battery constraints. The net result is that the AP can reliably reach > the STA while the STA's frames (including the L2 ACK following the AP's > (Re)Assoc Response) routinely fall below the AP's effective noise > floor. > > In this scenario the STA decodes the (Re)Assoc Response and considers > itself associated, while hostapd silently removes the station entry. > The two sides remain out of sync until a STA-side timeout > (implementation-defined, e.g. beacon-loss or null-frame failure) > eventually fires. During that window the STA's unicast traffic is > silently dropped at the AP, since there is no association table entry > to map it to. AP does not silently drop that traffic; it is sending out a Deauthentication or Disassociation frame with reason code indicating Class 3 frame received from not associated STA. That should trigger the STA to try connection again. > Address this by sending a Deauthentication frame > (WLAN_REASON_PREV_AUTH_NOT_VALID) before hostapd_drv_sta_remove() in > the !ok path of handle_assoc_cb(). The Deauthentication is transmitted > at the basic rate with the usual MAC-level retries, which gives it a > materially better chance of reaching the STA than the higher-rate > (Re)Assoc Response that just failed. What makes the Deauthentication frame use basic rates and the (Re)Association Response frame use higher rates? Why would this additional Deauthentication frame be needed if Deauthentication frames are already sent as a response to any received Data frame from that STA when it is not in associated state? > If the STA never received the > (Re)Assoc Response at all, the Deauthentication is benign because it > is a Class 1 frame and does not tear down any association state on > the STA side. What do you mean with this being "benign" and not tearing down any association state? Deauthentication frame is a notification for the exact purpose of tearing down association (and also authentication) state on the receiver.. -- Jouni Malinen PGP id EFC895FA From gubertoli at gmail.com Sat May 30 02:22:08 2026 From: gubertoli at gmail.com (Gustavo Bertoli) Date: Sat, 30 May 2026 11:22:08 +0200 Subject: [PATCH] dpp: Fix association with hidden SSID via directed probing Message-ID: <20260530092208.59471-1-gubertoli@gmail.com> When a station receives a DPP Configuration Object and the AP's SSID is hidden (ignore_broadcast_ssid=1), the auto-provisioned network never associates. The DPP handshake completes successfully and the Config Object is accepted, but wpa_supplicant subsequently triggers a passive scan. Because the AP does not respond to wildcard Probe Requests, no matching network is found and the session sits silently disconnected. Log shows the handshake succeeding without any subsequent SME authentication attempts: wlan1: DPP-CONF-RECEIVED wlan1: DPP-CONFOBJ-AKM sae wlan1: DPP-CONFOBJ-SSID DPP wlan1: DPP-NETWORK-ID 0 wlan1: DPP-TX dst=... type=11 wpas_dpp_add_network() leaves ssid->scan_ssid=0. The DPP enrollee receives the SSID through an authenticated channel (the DPP Config Object) and already knows the exact target SSID. Set scan_ssid=1 for the network block created by wpas_dpp_add_network(). This ensures directed probing is used, allowing the DPP enrollee to successfully discover and associate with a hidden network while remaining functional for broadcast SSIDs. Signed-off-by: Gustavo Bertoli --- wpa_supplicant/dpp_supplicant.c | 1 + 1 file changed, 1 insertion(+) diff --git a/wpa_supplicant/dpp_supplicant.c b/wpa_supplicant/dpp_supplicant.c index a55d0e7ab..3ae40ad64 100644 --- a/wpa_supplicant/dpp_supplicant.c +++ b/wpa_supplicant/dpp_supplicant.c @@ -1445,6 +1445,7 @@ static struct wpa_ssid * wpas_dpp_add_network(struct wpa_supplicant *wpa_s, goto fail; os_memcpy(ssid->ssid, conf->ssid, conf->ssid_len); ssid->ssid_len = conf->ssid_len; + ssid->scan_ssid = 1; #ifdef CONFIG_DPP3 if (conf->akm == DPP_AKM_SAE && conf->password_id[0]) { -- 2.43.0 From gubertoli at gmail.com Sun May 31 11:30:06 2026 From: gubertoli at gmail.com (Gustavo Bertoli) Date: Sun, 31 May 2026 20:30:06 +0200 Subject: [PATCH v2 0/2] dpp: Fix association with hidden SSID via directed probing Message-ID: <20260531183008.72079-1-gubertoli@gmail.com> v2: Add hwsim test case for the hidden SSID scenario. v1: https://lists.infradead.org/pipermail/hostap/2026-May/045191.html When a DPP enrollee receives a configuration object for a hidden SSID network, wpa_supplicant fails to associate because wpas_dpp_add_network() leaves scan_ssid=0. The subsequent passive scan never discovers the AP. This series fixes the issue by setting scan_ssid=1 in the provisioned network block, and adds a hwsim test to cover the scenario. Gustavo Bertoli (2): dpp: Fix association with hidden SSID via directed probing tests: Add hwsim test for DPP provisioning with hidden SSID tests/hwsim/test_dpp.py | 23 +++++++++++++++++++++++ wpa_supplicant/dpp_supplicant.c | 1 + 2 files changed, 24 insertions(+) -- 2.43.0 From gubertoli at gmail.com Sun May 31 11:30:07 2026 From: gubertoli at gmail.com (Gustavo Bertoli) Date: Sun, 31 May 2026 20:30:07 +0200 Subject: [PATCH v2 1/2] dpp: Fix association with hidden SSID via directed probing In-Reply-To: <20260531183008.72079-1-gubertoli@gmail.com> References: <20260531183008.72079-1-gubertoli@gmail.com> Message-ID: <20260531183008.72079-2-gubertoli@gmail.com> When a station receives a DPP Configuration Object and the AP's SSID is hidden (ignore_broadcast_ssid=1), the auto-provisioned network never associates. The DPP handshake completes successfully and the Config Object is accepted, but wpa_supplicant subsequently triggers a passive scan. Because the AP does not respond to wildcard Probe Requests, no matching network is found and the session sits silently disconnected. Log shows the handshake succeeding without any subsequent SME authentication attempts: wlan1: DPP-CONF-RECEIVED wlan1: DPP-CONFOBJ-AKM sae wlan1: DPP-CONFOBJ-SSID DPP wlan1: DPP-NETWORK-ID 0 wlan1: DPP-TX dst=... type=11 wpas_dpp_add_network() leaves ssid->scan_ssid=0. The DPP enrollee receives the SSID through an authenticated channel (the DPP Config Object) and already knows the exact target SSID. Set scan_ssid=1 for the network block created by wpas_dpp_add_network(). This ensures directed probing is used, allowing the DPP enrollee to successfully discover and associate with a hidden network while remaining functional for broadcast SSIDs. Signed-off-by: Gustavo Bertoli --- wpa_supplicant/dpp_supplicant.c | 1 + 1 file changed, 1 insertion(+) diff --git a/wpa_supplicant/dpp_supplicant.c b/wpa_supplicant/dpp_supplicant.c index a55d0e7ab..3ae40ad64 100644 --- a/wpa_supplicant/dpp_supplicant.c +++ b/wpa_supplicant/dpp_supplicant.c @@ -1445,6 +1445,7 @@ static struct wpa_ssid * wpas_dpp_add_network(struct wpa_supplicant *wpa_s, goto fail; os_memcpy(ssid->ssid, conf->ssid, conf->ssid_len); ssid->ssid_len = conf->ssid_len; + ssid->scan_ssid = 1; #ifdef CONFIG_DPP3 if (conf->akm == DPP_AKM_SAE && conf->password_id[0]) { -- 2.43.0 From gubertoli at gmail.com Sun May 31 11:30:08 2026 From: gubertoli at gmail.com (Gustavo Bertoli) Date: Sun, 31 May 2026 20:30:08 +0200 Subject: [PATCH v2 2/2] tests: Add hwsim test for DPP provisioning with hidden SSID In-Reply-To: <20260531183008.72079-1-gubertoli@gmail.com> References: <20260531183008.72079-1-gubertoli@gmail.com> Message-ID: <20260531183008.72079-3-gubertoli@gmail.com> Signed-off-by: Gustavo Bertoli --- tests/hwsim/test_dpp.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/hwsim/test_dpp.py b/tests/hwsim/test_dpp.py index 938b12c08..68b5966d4 100644 --- a/tests/hwsim/test_dpp.py +++ b/tests/hwsim/test_dpp.py @@ -2431,6 +2431,29 @@ def run_dpp_auto_connect_legacy_pmf_required(dev, apdev): raise Exception("DPP network profile not generated") dev[0].wait_connected() +def test_dpp_hidden_ssid(dev, apdev): + """DPP provisioning and connect to hidden SSID""" + check_dpp_capab(dev[0]) + check_dpp_capab(dev[1]) + + params = hostapd.wpa2_params(ssid="dpp-hidden", + passphrase="secret passphrase") + params["ignore_broadcast_ssid"] = "1" + hapd = hostapd.add_ap(apdev[0], params) + + id0 = dev[0].dpp_bootstrap_gen(chan="81/1", mac=True) + uri0 = dev[0].request("DPP_BOOTSTRAP_GET_URI %d" % id0) + dev[0].set("dpp_config_processing", "2") + dev[0].dpp_listen(2412) + dev[1].dpp_auth_init(uri=uri0, conf="sta-psk", ssid="dpp-hidden", + passphrase="secret passphrase") + wait_auth_success(dev[0], dev[1], configurator=dev[1], enrollee=dev[0]) + ev = dev[0].wait_event(["DPP-NETWORK-ID"], timeout=1) + if ev is None: + raise Exception("DPP network profile not generated") + dev[0].wait_connected() + dev[0].set("dpp_config_processing", "0") + def test_dpp_qr_code_auth_responder_configurator(dev, apdev): """DPP QR Code and responder as the configurator""" run_dpp_qr_code_auth_responder_configurator(dev, apdev, "") -- 2.43.0 From johannes at sipsolutions.net Sun May 31 23:02:16 2026 From: johannes at sipsolutions.net (Johannes Berg) Date: Mon, 01 Jun 2026 08:02:16 +0200 Subject: [PATCH v2 1/2] dpp: Fix association with hidden SSID via directed probing In-Reply-To: <20260531183008.72079-2-gubertoli@gmail.com> References: <20260531183008.72079-1-gubertoli@gmail.com> <20260531183008.72079-2-gubertoli@gmail.com> Message-ID: > > diff --git a/wpa_supplicant/dpp_supplicant.c b/wpa_supplicant/dpp_supplicant.c > index a55d0e7ab..3ae40ad64 100644 > --- a/wpa_supplicant/dpp_supplicant.c > +++ b/wpa_supplicant/dpp_supplicant.c > @@ -1445,6 +1445,7 @@ static struct wpa_ssid * wpas_dpp_add_network(struct wpa_supplicant *wpa_s, > goto fail; > os_memcpy(ssid->ssid, conf->ssid, conf->ssid_len); > ssid->ssid_len = conf->ssid_len; > + ssid->scan_ssid = 1; Seems to me this should be set only conditionally when it's really required, since for privacy reasons it's really much preferred to not set it. joahnnes From senthil.balasubramanian at netgear.com Sun May 31 02:45:13 2026 From: senthil.balasubramanian at netgear.com (senthil.balasubramanian at netgear.com) Date: Sun, 31 May 2026 15:15:13 +0530 Subject: [PATCH hostap] AP: Send Deauthentication before removing STA on (Re)Assoc Resp ack=0 In-Reply-To: References: Message-ID: <81b483659e4e980b0f0d48f463d1145a@netgear.com> Thanks for your review, Jouni ? you've caught two over-claims in the commit message that I need to walk back, and raised one architectural alternative worth considering. Responses inline. On Fri, May 29, 2026 at 06:48:58AM -0700, Jouni Malinen wrote: > I'm not actually sure that calling hostapd_drv_sta_remove() here is the > best behavior in all cases.. In particular, if there is already a > valid TK configured and the STA would be authorized and ready to use the > connection, the STA could be marked associated on reception of a > correctly encrypted Data frame. Fair point ? but two things would need to be true for the "promote on encrypted Data" recovery to actually rescue the STA in the case we observed: (i) hostapd would need code that doesn't exist today ? a path that re-promotes a STA to WLAN_STA_ASSOC on successful decrypt of a Data frame after the (Re)Association tracking has given up. In the current code, hostapd_drv_sta_remove() has already been called by the time any subsequent Data frame would arrive, so there is no per-STA crypto context left in the driver and the frame is dropped before any "promote" logic could see it. (ii) the STA->AP uplink would need to deliver that Data frame. In the observed case the uplink is exactly the side that failed ? that is what produced ack=0 in the first place. Every subsequent STA->AP frame shares the same uplink budget (FCC LPI client EIRP capped at AP EIRP - 6 dB on 6 GHz, plus smaller STA antenna at 6 GHz wavelength, plus STA PA back-off for thermal/battery reasons), so the AP very often does not receive an encrypted Data frame from the STA either, and the trigger for any "promote" path never arrives. The AP->STA direction is the one that still works in this RF asymmetry ? that's exactly what made the STA decode the (Re)Assoc Response we then failed to get ACKed. So an AP-initiated Deauthentication has a real chance of reaching the STA; a STA-initiated "prove I'm alive" frame does not. The case we hit is a 5 GHz -> 6 GHz FT-SAE roam, so PTK derivation completed during the FT Authentication exchange and TK was installed on both sides before the (Re)Association Request was even sent ? so the "TK already valid" precondition you mention holds. But the uplink-asymmetry argument above is what makes the encrypted-Data recovery path unhelpful here even if it existed. > AP does not silently drop that traffic; it is sending out a > Deauthentication or Disassociation frame with reason code indicating > Class 3 frame received from not associated STA. That should trigger the > STA to try connection again. Correct ? I should not have written "silently dropped". When the AP does receive a Class 3 frame from a STA without an association entry, it sends Deauthentication and the STA recovers. That mechanism is what makes the existing sta_remove path tolerable in the symmetric-link case. The reason it does not help in the asymmetric-link case is the same as above: the AP never receives the triggering Class 3 frame, because the STA's uplink is the side that's failing. So the Class-3-response Deauthentication is never generated and the desync persists. I'll reword that paragraph to: "On a symmetric link the AP's existing Class-3-frame response would deauthenticate the STA on its first data frame and the STA would recover. On the asymmetric path described above, the AP does not receive that data frame, so the Class-3 response never fires." > What makes the Deauthentication frame use basic rates and the > (Re)Association Response frame use higher rates? My commit log in this place is sloppy and I'll drop it. The honest argument for the patch is simpler: - The Class-3 recovery does not fire on an asymmetric path, because the AP receives no frame from the STA to respond to. - The AP->STA direction still works ? the STA decoded the (Re)Assoc Response just fine; only the L2 ACK failed. - One AP-initiated Deauthentication is much more likely to reach the STA than waiting indefinitely for an STA->AP frame that the AP cannot receive. > Why would this additional Deauthentication frame be needed if > Deauthentication frames are already sent as a response to any received > Data frame from that STA when it is not in associated state? Same answer: in the asymmetric case the AP does not receive the triggering Data frame, so no response Deauth is generated. In the symmetric case this patch is largely a no-op ? the explicit Deauth and the Class-3-response Deauth would both put the STA back to state 1; the second one is redundant but not harmful (except additional airtime for the deauth frame) since the STA is already moving to unauthenticated state. > What do you mean with this being "benign" and not tearing down any > association state? That wording was wrong ? Deauthentication is by definition a teardown of both authentication and association state on the receiver. What I meant was: in the case where the STA never received the (Re)Association Response, the STA is still in its prior state (post-Authentication, not yet associated). The Deauthentication takes that STA back to unauthenticated. So the Deauthentication does not regress anything; it just makes the STA's recovery deterministic instead of waiting on a timeout. I'll reword to: "If the STA never received the (Re)Association Response (or) AP couldn't hear the ACK for Re-Assoc response that it has sent, the Deauthentication takes the STA from its post- Authentication state back to unauthenticated. This is the same end state the STA would have reached anyway after timing out its (Re)Association attempt; the Deauthentication just gets it there faster." Happy to spin v2 with the commit-message fixes if that's acceptable, or take a different approach if you prefer. Thanks again for the review and great to talk to you after Atheros/QCA days. -- Senthil Balasubramanian This e-mail, including attachments, may include confidential and/or proprietary information, and may be used only by the person or entity to which it is addressed. If the reader of this e-mail is not the intended recipient or his or her authorized agent, the reader is hereby notified that any dissemination, distribution or copying of this e-mail is prohibited. If you have received this e-mail in error, please notify the sender by replying to this message and delete this e-mail immediately.