From pmartin-gomez at freebox.fr  Mon Jun  2 03:08:53 2025
From: pmartin-gomez at freebox.fr (Pablo MARTIN-GOMEZ)
Date: Mon, 2 Jun 2025 12:08:53 +0200
Subject: [PATCH] hostapd: send own Neighbor Report only if specific
 Measurement Request
In-Reply-To: <13357b86-bfc1-415f-b7e3-95c3c5f31d9c@david-bauer.net>
References: <3c483f83-ca86-40b3-842b-cbb6f0e5d1ca@freebox.fr>
 <13357b86-bfc1-415f-b7e3-95c3c5f31d9c@david-bauer.net>
Message-ID: <c66a5f13-688d-49ea-a19c-18e2c81a1d43@freebox.fr>

Hi David,
On 29/05/2025 13:00, David Bauer wrote:
> Hi Pablo,
>
> On 5/21/25 18:06, Pablo MARTIN-GOMEZ wrote:
>> Every Neighbor Report that matches the requested SSID is sent in the
>> Neighbor Report Response, including our own report. According to 
>> standard
>> IEEE 802.11-2020 (11.10.10.3), the self report should not be included in
>> the response unless the request includes a LCI Measurement Request or a
>> Location Civic Measurement Request.
>
> I read the standard as a "can not have" not a "should not"
>
>> [...] then the AP shall include a Neighbor Report element for the AP?s
>> own BSSID in the Neighbor Report Response frame [...]
>
> I think the change is correct and should be applied, I'm just a bit
> confused with the description.
>
> If i missed the part where it stated "should not contain", please
> correct me.

To be honest, I find the standard to be unclear on that point. It's 
clear when the self Neighbor Report has to be included but outside of 
those conditions, the standard has no "shall not include" so I sort of 
deduced from the "spirit" of the standard that the self Neighbor Report 
is not expected to be included otherwise .

One could even claim that:

 > NOTE?The Neighbor Report Response frame includes a list of Neighbor 
Report elements one for each neighbor. The
 > criteria for the selection of an AP as a neighbor AP is beyond the 
scope of this specification.

can be interpreted as an implementer is free to consider an AP as its 
own neighbor AP and therefor send a self Neighbor Report by default. So 
I'm not even sure about the "spirit" of the standard.

>
> Best
> David

Best regards,

Pablo



From benjamin at sipsolutions.net  Thu Jun  5 00:05:02 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Thu,  5 Jun 2025 09:05:02 +0200
Subject: [PATCH] nl80211: wait on udev when creating new device
Message-ID: <20250605070502.1119627-1-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

udev/systemd will process new network device. This can result in various
issues as for example the MAC address may be randomized. Add the
appropriate integration to wait for the udev "add" event before
continuing to use the device.

This resolves race conditions when reading the MAC address during
interface creation (or even changing it right afterwards when creating a
P2P device).

Enable this feature by default. Systems that do not use udev need to
explicitly disable it at compile time.

See https://github.com/systemd/systemd/issues/13642

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 src/drivers/driver_nl80211.c | 78 ++++++++++++++++++++++++++++++++++++
 src/drivers/drivers.mak      | 13 ++++++
 2 files changed, 91 insertions(+)

diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 7e876dcf80..67da682101 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -21,6 +21,9 @@
 #include <linux/rtnetlink.h>
 #include <netpacket/packet.h>
 #include <linux/errqueue.h>
+#ifndef CONFIG_DRIVER_NL80211_DISABLE_UDEV
+#include <libudev.h>
+#endif
 
 #include "common.h"
 #include "eloop.h"
@@ -6239,6 +6242,43 @@ static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv,
 	int ifidx;
 	int ret = -ENOBUFS;
 
+#ifndef CONFIG_DRIVER_NL80211_DISABLE_UDEV
+	/*
+	 * systemd/udev insist on processing new interfaces and may
+	 * randomize the MAC address. We need to avoid race conditions between
+	 * hostap reading the MAC address and systemd/udev changing it.
+	 * Setup a monitor and wait for an event for a "wlan" "net" device
+	 * with the expected IFINDEX.
+	 * We are guaranteed to receive an event because we install the monitor
+	 * before creating it.
+	 */
+	struct udev *udev;
+	struct udev_monitor *monitor = NULL;
+
+	udev = udev_new();
+	if (udev) {
+		monitor = udev_monitor_new_from_netlink(udev, "udev");
+		if (!monitor)
+			wpa_printf(MSG_ERROR, "nl80211: Failed to create udev monitor");
+	} else {
+		wpa_printf(MSG_ERROR, "nl80211: Failed to connect to udev");
+	}
+
+	udev_unref(udev);
+	udev = NULL;
+
+	if (monitor) {
+		if (udev_monitor_filter_add_match_subsystem_devtype(monitor,
+								    "net",
+								    "wlan") < 0 ||
+		    udev_monitor_enable_receiving(monitor) < 0) {
+			wpa_printf(MSG_ERROR, "nl80211: Failed to add match for udev events");
+			udev_monitor_unref(monitor);
+			monitor = NULL;
+		}
+	}
+#endif
+
 	wpa_printf(MSG_DEBUG, "nl80211: Create interface iftype %d (%s)",
 		   iftype, nl80211_iftype_str(iftype));
 
@@ -6282,6 +6322,44 @@ static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv,
 	if (ifidx <= 0)
 		return -1;
 
+#ifndef CONFIG_DRIVER_NL80211_DISABLE_UDEV
+	if (monitor) {
+		/* Set blocking mode on the FD */
+		int fd = udev_monitor_get_fd(monitor);
+		int flags = fcntl(fd, F_GETFL);
+
+		fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
+
+		while (1) {
+			struct udev_device *device;
+			const char *dev_ifidx_str;
+			int dev_ifidx;
+
+			device = udev_monitor_receive_device(monitor);
+			if (device == NULL)
+				wpa_printf(MSG_ERROR, "nl80211: Error receiving device from udev");
+
+			dev_ifidx_str = udev_device_get_property_value(device, "IFINDEX");
+			if (!dev_ifidx_str)
+				continue;
+
+			dev_ifidx = atoi(dev_ifidx_str);
+
+			udev_device_unref(device);
+			device = NULL;
+
+			/* We got an (add) event for the device; it is ready */
+			if (dev_ifidx == ifidx) {
+				wpa_printf(MSG_INFO, "nl80211: udev event received, interface is ready");
+				break;
+			}
+		}
+
+		udev_monitor_unref(monitor);
+		monitor = NULL;
+	}
+#endif
+
 	/*
 	 * Some virtual interfaces need to process EAPOL packets and events on
 	 * the parent interface. This is used mainly with hostapd.
diff --git a/src/drivers/drivers.mak b/src/drivers/drivers.mak
index a03d4a0345..e9a280c14e 100644
--- a/src/drivers/drivers.mak
+++ b/src/drivers/drivers.mak
@@ -57,6 +57,11 @@ NEED_LINUX_IOCTL=y
 NEED_RFKILL=y
 NEED_RADIOTAP=y
 NEED_LIBNL=y
+ifdef CONFIG_DRIVER_NL80211_DISABLE_UDEV
+DRV_CFLAGS += -DCONFIG_DRIVER_NL80211_DISABLE_UDEV
+else
+NEED_UDEV=y
+endif
 endif
 
 ifdef CONFIG_DRIVER_BSD
@@ -202,6 +207,14 @@ else
 endif
 endif
 
+# We need to integrate with udev when available, as it may randomize mac
+# addresses on newly created interfaces.
+ifeq ($(NEED_UDEV),y)
+PKG_CONFIG ?= pkg-config
+DRV_LIBS += $(shell $(PKG_CONFIG) --libs libudev)
+DRV_CFLAGS += $(shell $(PKG_CONFIG) --cflags libudev)
+endif
+
 ##### COMMON VARS
 DRV_BOTH_CFLAGS := $(DRV_CFLAGS) $(DRV_WPA_CFLAGS) $(DRV_AP_CFLAGS)
 DRV_WPA_CFLAGS += $(DRV_CFLAGS)
-- 
2.49.0



From benjamin at sipsolutions.net  Thu Jun  5 00:22:11 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Thu,  5 Jun 2025 09:22:11 +0200
Subject: [PATCH v2] nl80211: wait on udev when creating new device
Message-ID: <20250605072211.1123397-1-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

udev/systemd will process new network device. This can result in various
issues as for example the MAC address may be randomized. Add the
appropriate integration to wait for the udev "add" event before
continuing to use the device.

This resolves race conditions when reading the MAC address during
interface creation (or even changing it right afterwards when creating a
P2P device).

Enable this feature by default. Systems that do not use udev need to
explicitly disable it at compile time.

See https://github.com/systemd/systemd/issues/13642

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>

---
v2:
- Break out of the loop on udev receive failure. We should give up as
  something bad has happened.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 src/drivers/driver_nl80211.c | 80 ++++++++++++++++++++++++++++++++++++
 src/drivers/drivers.mak      | 13 ++++++
 2 files changed, 93 insertions(+)

diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 7e876dcf80..67507dbe21 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -21,6 +21,9 @@
 #include <linux/rtnetlink.h>
 #include <netpacket/packet.h>
 #include <linux/errqueue.h>
+#ifndef CONFIG_DRIVER_NL80211_DISABLE_UDEV
+#include <libudev.h>
+#endif
 
 #include "common.h"
 #include "eloop.h"
@@ -6239,6 +6242,43 @@ static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv,
 	int ifidx;
 	int ret = -ENOBUFS;
 
+#ifndef CONFIG_DRIVER_NL80211_DISABLE_UDEV
+	/*
+	 * systemd/udev insist on processing new interfaces and may
+	 * randomize the MAC address. We need to avoid race conditions between
+	 * hostap reading the MAC address and systemd/udev changing it.
+	 * Setup a monitor and wait for an event for a "wlan" "net" device
+	 * with the expected IFINDEX.
+	 * We are guaranteed to receive an event because we install the monitor
+	 * before creating it.
+	 */
+	struct udev *udev;
+	struct udev_monitor *monitor = NULL;
+
+	udev = udev_new();
+	if (udev) {
+		monitor = udev_monitor_new_from_netlink(udev, "udev");
+		if (!monitor)
+			wpa_printf(MSG_ERROR, "nl80211: Failed to create udev monitor");
+	} else {
+		wpa_printf(MSG_ERROR, "nl80211: Failed to connect to udev");
+	}
+
+	udev_unref(udev);
+	udev = NULL;
+
+	if (monitor) {
+		if (udev_monitor_filter_add_match_subsystem_devtype(monitor,
+								    "net",
+								    "wlan") < 0 ||
+		    udev_monitor_enable_receiving(monitor) < 0) {
+			wpa_printf(MSG_ERROR, "nl80211: Failed to add match for udev events");
+			udev_monitor_unref(monitor);
+			monitor = NULL;
+		}
+	}
+#endif
+
 	wpa_printf(MSG_DEBUG, "nl80211: Create interface iftype %d (%s)",
 		   iftype, nl80211_iftype_str(iftype));
 
@@ -6282,6 +6322,46 @@ static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv,
 	if (ifidx <= 0)
 		return -1;
 
+#ifndef CONFIG_DRIVER_NL80211_DISABLE_UDEV
+	if (monitor) {
+		/* Set blocking mode on the FD */
+		int fd = udev_monitor_get_fd(monitor);
+		int flags = fcntl(fd, F_GETFL);
+
+		fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
+
+		while (1) {
+			struct udev_device *device;
+			const char *dev_ifidx_str;
+			int dev_ifidx;
+
+			device = udev_monitor_receive_device(monitor);
+			if (!device) {
+				wpa_printf(MSG_ERROR, "nl80211: Error receiving device from udev");
+				break;
+			}
+
+			dev_ifidx_str = udev_device_get_property_value(device, "IFINDEX");
+			if (!dev_ifidx_str)
+				continue;
+
+			dev_ifidx = atoi(dev_ifidx_str);
+
+			udev_device_unref(device);
+			device = NULL;
+
+			/* We got an (add) event for the device; it is ready */
+			if (dev_ifidx == ifidx) {
+				wpa_printf(MSG_INFO, "nl80211: udev event received, interface is ready");
+				break;
+			}
+		}
+
+		udev_monitor_unref(monitor);
+		monitor = NULL;
+	}
+#endif
+
 	/*
 	 * Some virtual interfaces need to process EAPOL packets and events on
 	 * the parent interface. This is used mainly with hostapd.
diff --git a/src/drivers/drivers.mak b/src/drivers/drivers.mak
index a03d4a0345..e9a280c14e 100644
--- a/src/drivers/drivers.mak
+++ b/src/drivers/drivers.mak
@@ -57,6 +57,11 @@ NEED_LINUX_IOCTL=y
 NEED_RFKILL=y
 NEED_RADIOTAP=y
 NEED_LIBNL=y
+ifdef CONFIG_DRIVER_NL80211_DISABLE_UDEV
+DRV_CFLAGS += -DCONFIG_DRIVER_NL80211_DISABLE_UDEV
+else
+NEED_UDEV=y
+endif
 endif
 
 ifdef CONFIG_DRIVER_BSD
@@ -202,6 +207,14 @@ else
 endif
 endif
 
+# We need to integrate with udev when available, as it may randomize mac
+# addresses on newly created interfaces.
+ifeq ($(NEED_UDEV),y)
+PKG_CONFIG ?= pkg-config
+DRV_LIBS += $(shell $(PKG_CONFIG) --libs libudev)
+DRV_CFLAGS += $(shell $(PKG_CONFIG) --cflags libudev)
+endif
+
 ##### COMMON VARS
 DRV_BOTH_CFLAGS := $(DRV_CFLAGS) $(DRV_WPA_CFLAGS) $(DRV_AP_CFLAGS)
 DRV_WPA_CFLAGS += $(DRV_CFLAGS)
-- 
2.49.0



From muna.sinada at oss.qualcomm.com  Mon Jun  9 11:06:14 2025
From: muna.sinada at oss.qualcomm.com (Muna Sinada)
Date: Mon,  9 Jun 2025 11:06:14 -0700
Subject: [PATCH v4 0/2] Dynamic VLAN: Enable Dynamic VLAN for MLO
Message-ID: <20250609180616.4045177-1-muna.sinada@oss.qualcomm.com>

Enable Dynamic VLAN for Multi-Link Operation (MLO) for APs in hostapd.

When Dynamic VLAN is enabled for AP MLD (Multi-Link Device), VLAN
list will be maintained in the first BSS (Basic Service Set).

For MLO, VLAN interfaces will generate a GTK for each link it has.
These VLAN GTKs will need to be exchanged in 3/4 message in EAPOL
handshake.

Add test case to AP VLAN test cases that brings up an EHT MLD AP with
Dynamic VLAN configurations and successfully create VLANs for the
connected non-AP MLD and a non-ML STA.

v4:
 - dropped "RFC" prefix
 - moved CONFIG_IEEE80211BE compiler flag outside
   hostapd_mld_move_vlan_list() since it is called by a function that
   is inside CONFIG_IEEE80211BE block. When CONFIG_IEEE80211BE is not
   enabled, compiler warning is observed for unused defined function.

v3:
 - rebased to TOT
 - dropped HWSIM patch from series. Will be submitting patch separate
   from series. A bit of rework is required.
 - dependency kernel patches are present in wireless-next:
https://lore.kernel.org/linux-wireless/20250325213125.1509362-1-muna.sinada at oss.qualcomm.com/

v2:
 - incorrectly passed the next bss, causing memory leak. Now passsing
   valid next bss
 - VLAN MLD interface was not properly being deleted due to failing
   check in driver_nl80211_link_remove()
 - add description for wpa_select_vlan_wpa_group()
 - move wpa_select_vlan_wpa_group() into CONFIG_IEEE80211BE block


Muna Sinada (2):
  Dynamic VLAN: Enable Dynamic VLAN for MLO
  wpa_auth: Use VLAN group keys for EAPOL frames and FT reassoc

 src/ap/ap_drv_ops.c          | 11 +++++-
 src/ap/drv_callbacks.c       |  2 +-
 src/ap/hostapd.c             | 27 ++++++++++++++
 src/ap/ieee802_11.c          |  4 +-
 src/ap/sta_info.c            | 71 ++++++++++++++++++++++++++----------
 src/ap/vlan_full.c           | 22 ++++++++++-
 src/ap/wpa_auth.c            | 42 +++++++++++++++++++--
 src/ap/wpa_auth.h            | 10 +++--
 src/ap/wpa_auth_ft.c         | 13 +++++--
 src/ap/wpa_auth_glue.c       | 17 +++++++--
 src/drivers/driver_nl80211.c |  5 +++
 11 files changed, 186 insertions(+), 38 deletions(-)

-- 
2.34.1



From muna.sinada at oss.qualcomm.com  Mon Jun  9 11:06:15 2025
From: muna.sinada at oss.qualcomm.com (Muna Sinada)
Date: Mon,  9 Jun 2025 11:06:15 -0700
Subject: [PATCH v4 1/2] Dynamic VLAN: Enable Dynamic VLAN for MLO
In-Reply-To: <20250609180616.4045177-1-muna.sinada@oss.qualcomm.com>
References: <20250609180616.4045177-1-muna.sinada@oss.qualcomm.com>
Message-ID: <20250609180616.4045177-2-muna.sinada@oss.qualcomm.com>

This commit enables Dynamic VLAN for Multi-Link Operation (MLO)
in hostapd.

When Dynamic VLAN is enabled for AP MLD (Multi-Link Device), VLAN
list will be maintained in the first BSS (Basic Service Set).

Key changes include:
- Utilize MLD address for creating new VLAN interfaces.
- Access the VLAN list from first BSS for the MLD.
- If first bss is removed, move reference to VLAN list to new first
  bss.
- When setting Group Temporal Key (GTK) per link, reference the
  first BSS for the VLAN list.
- For non-AP MLD, copy the vlan_id from the primary link to other
  links.
- Pass first bss when calling functions that iterate through the VLAN
  list such as vlan_add_dynamic() and vlan_remove_dynamic()
- Allow MLD VLAN interface to be removed kernel

Signed-off-by: Muna Sinada <muna.sinada at oss.qualcomm.com>
Signed-off-by: Pradeep Kumar Chitrapu <quic_pradeepc at quicinc.com>

---
v4: moved CONFIG_IEEE80211BE compiler flag outside
    hostapd_mld_move_vlan_list() since it is called by a function that
    is inside CONFIG_IEEE80211BE block. When CONFIG_IEEE80211BE is not
    enabled, compiler warning is observed for unused defined function.

v3: rebase to TOT

v2:
 - incorrectly passed the next bss, causing memory leak. Now passsing
   valid next bss
 - VLAN MLD interface was not properly being deleted due to failing
   check in driver_nl80211_link_remove()

---
 src/ap/ap_drv_ops.c          | 11 +++++-
 src/ap/hostapd.c             | 27 ++++++++++++++
 src/ap/ieee802_11.c          |  1 +
 src/ap/sta_info.c            | 71 ++++++++++++++++++++++++++----------
 src/ap/vlan_full.c           | 22 ++++++++++-
 src/ap/wpa_auth_glue.c       | 11 +++++-
 src/drivers/driver_nl80211.c |  5 +++
 7 files changed, 125 insertions(+), 23 deletions(-)

diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index aeae0af20abc..1a1bd4df3d3c 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -370,7 +370,16 @@ int hostapd_vlan_if_add(struct hostapd_data *hapd, const char *ifname)
 {
 	char force_ifname[IFNAMSIZ];
 	u8 if_addr[ETH_ALEN];
-	return hostapd_if_add(hapd, WPA_IF_AP_VLAN, ifname, hapd->own_addr,
+	const u8 *addr;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		addr = hapd->mld->mld_addr;
+	else
+#endif /* CONFIG_IEEE80211BE */
+		addr = hapd->own_addr;
+
+	return hostapd_if_add(hapd, WPA_IF_AP_VLAN, ifname, addr,
 			      NULL, NULL, force_ifname, if_addr, NULL, 0);
 }
 
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index 0dd2b89bb88b..71a3034809ea 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -616,6 +616,25 @@ void hostapd_free_hapd_data(struct hostapd_data *hapd)
 #endif /* CONFIG_IEEE80211AX */
 }
 
+#ifdef CONFIG_IEEE80211BE
+/* hostapd_mld_move_vlan_list - Move vlan list to new first bss
+ * @old_fbss
+ * @new_fbss
+ *
+ * This function is used to copy reference to vlan list from old first bss to
+ * new first bss when the first bss is removed.
+ */
+static void hostapd_mld_move_vlan_list(struct hostapd_data *old_fbss,
+				       struct hostapd_data *new_fbss)
+{
+	if (!old_fbss || !new_fbss)
+		return;
+
+	new_fbss->conf->vlan = old_fbss->conf->vlan;
+	old_fbss->conf->vlan = NULL;
+
+}
+#endif /* CONFIG_IEEE80211BE */
 
 /* hostapd_bss_link_deinit - Per-BSS ML cleanup (deinitialization)
  * @hapd: Pointer to BSS data
@@ -5129,8 +5148,16 @@ int hostapd_mld_remove_link(struct hostapd_data *hapd)
 	if (dl_list_empty(&mld->links)) {
 		mld->fbss = NULL;
 	} else {
+		struct hostapd_data *last_link_bss;
+
 		next_fbss = dl_list_entry(mld->links.next, struct hostapd_data,
 					  link);
+		last_link_bss = dl_list_last(&mld->links, struct hostapd_data,
+					     link);
+
+		if (next_fbss != last_link_bss)
+			hostapd_mld_move_vlan_list(hapd, next_fbss);
+
 		mld->fbss = next_fbss;
 	}
 
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index bf36d7f9e3cf..bb998b39f5cf 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -4888,6 +4888,7 @@ static int ieee80211_ml_process_link(struct hostapd_data *hapd,
 		   hapd->mld_link_id, sta->aid);
 
 	sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC_REQ_OK;
+	sta->vlan_id = origin_sta->vlan_id;
 
 	/* TODO: What other processing is required? */
 
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index da5693c8a0e9..7c5ff00552b6 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -225,6 +225,9 @@ void clear_wpa_sm_for_each_partner_link(struct hostapd_data *hapd,
 
 void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 {
+#ifndef CONFIG_NO_VLAN
+	struct hostapd_data *vlan_bss = hapd;
+#endif /* CONFIG_NO_VLAN */
 	int set_beacon = 0;
 
 	accounting_sta_stop(hapd, sta);
@@ -364,13 +367,20 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 #endif /* CONFIG_NO_RADIUS */
 
 #ifndef CONFIG_NO_VLAN
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap) {
+		vlan_bss = hostapd_mld_get_first_bss(hapd);
+		if (vlan_bss == NULL)
+			vlan_bss = hapd;
+	}
+#endif
 	/*
 	 * sta->wpa_sm->group needs to be released before so that
 	 * vlan_remove_dynamic() can check that no stations are left on the
 	 * AP_VLAN netdev.
 	 */
 	if (sta->vlan_id)
-		vlan_remove_dynamic(hapd, sta->vlan_id);
+		vlan_remove_dynamic(vlan_bss, sta->vlan_id);
 	if (sta->vlan_id_bound) {
 		/*
 		 * Need to remove the STA entry before potentially removing the
@@ -381,7 +391,7 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 			hostapd_drv_sta_remove(hapd, sta->addr);
 			sta->added_unassoc = 0;
 		}
-		vlan_remove_dynamic(hapd, sta->vlan_id_bound);
+		vlan_remove_dynamic(vlan_bss, sta->vlan_id_bound);
 	}
 #endif /* CONFIG_NO_VLAN */
 
@@ -1207,10 +1217,19 @@ int ap_sta_wps_cancel(struct hostapd_data *hapd,
 static int ap_sta_get_free_vlan_id(struct hostapd_data *hapd)
 {
 	struct hostapd_vlan *vlan;
+	struct hostapd_data *vlan_bss = hapd;
 	int vlan_id = MAX_VLAN_ID + 2;
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap) {
+		vlan_bss = hostapd_mld_get_first_bss(hapd);
+		if (vlan_bss == NULL)
+			vlan_bss = hapd;
+	}
+#endif
+
 retry:
-	for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
+	for (vlan = vlan_bss->conf->vlan; vlan; vlan = vlan->next) {
 		if (vlan->vlan_id == vlan_id) {
 			vlan_id++;
 			goto retry;
@@ -1224,8 +1243,17 @@ int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 		    struct vlan_description *vlan_desc)
 {
 	struct hostapd_vlan *vlan = NULL, *wildcard_vlan = NULL;
+	struct hostapd_data *vlan_bss = hapd;
 	int old_vlan_id, vlan_id = 0, ret = 0;
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap) {
+		vlan_bss = hostapd_mld_get_first_bss(hapd);
+		if (vlan_bss == NULL)
+			vlan_bss = hapd;
+	}
+#endif
+
 	/* Check if there is something to do */
 	if (hapd->conf->ssid.per_sta_vif && !sta->vlan_id) {
 		/* This sta is lacking its own vif */
@@ -1242,7 +1270,7 @@ int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 		/* find a free vlan_id sufficiently big */
 		vlan_id = ap_sta_get_free_vlan_id(hapd);
 		/* Get wildcard VLAN */
-		for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
+		for (vlan = vlan_bss->conf->vlan; vlan; vlan = vlan->next) {
 			if (vlan->vlan_id == VLAN_ID_WILDCARD)
 				break;
 		}
@@ -1256,7 +1284,7 @@ int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 			goto done;
 		}
 	} else if (vlan_desc && vlan_desc->notempty) {
-		for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
+		for (vlan = vlan_bss->conf->vlan; vlan; vlan = vlan->next) {
 			if (!vlan_compare(&vlan->vlan_desc, vlan_desc))
 				break;
 			if (vlan->vlan_id == VLAN_ID_WILDCARD)
@@ -1269,10 +1297,10 @@ int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 			vlan_id = vlan_desc->untagged;
 			if (vlan_desc->tagged[0]) {
 				/* Tagged VLAN configuration */
-				vlan_id = ap_sta_get_free_vlan_id(hapd);
+				vlan_id = ap_sta_get_free_vlan_id(vlan_bss);
 			}
 		} else {
-			hostapd_logger(hapd, sta->addr,
+			hostapd_logger(vlan_bss, sta->addr,
 				       HOSTAPD_MODULE_IEEE80211,
 				       HOSTAPD_LEVEL_DEBUG,
 				       "missing vlan and wildcard for vlan=%d%s",
@@ -1285,9 +1313,9 @@ int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 	}
 
 	if (vlan && vlan->vlan_id == VLAN_ID_WILDCARD) {
-		vlan = vlan_add_dynamic(hapd, vlan, vlan_id, vlan_desc);
+		vlan = vlan_add_dynamic(vlan_bss, vlan, vlan_id, vlan_desc);
 		if (vlan == NULL) {
-			hostapd_logger(hapd, sta->addr,
+			hostapd_logger(vlan_bss, sta->addr,
 				       HOSTAPD_MODULE_IEEE80211,
 				       HOSTAPD_LEVEL_DEBUG,
 				       "could not add dynamic VLAN interface for vlan=%d%s",
@@ -1299,13 +1327,13 @@ int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 			goto done;
 		}
 
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+		hostapd_logger(vlan_bss, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
 			       "added new dynamic VLAN interface '%s'",
 			       vlan->ifname);
 	} else if (vlan && vlan->dynamic_vlan > 0) {
 		vlan->dynamic_vlan++;
-		hostapd_logger(hapd, sta->addr,
+		hostapd_logger(vlan_bss, sta->addr,
 			       HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
 			       "updated existing dynamic VLAN interface '%s'",
@@ -1317,7 +1345,7 @@ done:
 	sta->vlan_desc = vlan ? &vlan->vlan_desc : NULL;
 
 	if (vlan_id != old_vlan_id && old_vlan_id)
-		vlan_remove_dynamic(hapd, old_vlan_id);
+		vlan_remove_dynamic(vlan_bss, old_vlan_id);
 
 	return ret;
 }
@@ -1328,13 +1356,18 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta)
 #ifndef CONFIG_NO_VLAN
 	const char *iface;
 	struct hostapd_vlan *vlan = NULL;
+	struct hostapd_data *vlan_bss = hapd;
 	int ret;
 	int old_vlanid = sta->vlan_id_bound;
 	int mld_link_id = -1;
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap)
+	if (hapd->conf->mld_ap) {
 		mld_link_id = hapd->mld_link_id;
+		vlan_bss = hostapd_mld_get_first_bss(hapd);
+		if (vlan_bss == NULL)
+			vlan_bss = hapd;
+	}
 #endif /* CONFIG_IEEE80211BE */
 
 	if ((sta->flags & WLAN_STA_WDS) && sta->vlan_id == 0) {
@@ -1349,7 +1382,7 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta)
 		iface = hapd->conf->ssid.vlan;
 
 	if (sta->vlan_id > 0) {
-		for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
+		for (vlan = vlan_bss->conf->vlan; vlan; vlan = vlan->next) {
 			if (vlan->vlan_id == sta->vlan_id)
 				break;
 		}
@@ -1367,7 +1400,7 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta)
 
 	if (sta->vlan_id > 0 && !vlan &&
 	    !(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD)) {
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+		hostapd_logger(vlan_bss, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG, "could not find VLAN for "
 			       "binding station to (vlan_id=%d)",
 			       sta->vlan_id);
@@ -1375,7 +1408,7 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta)
 		goto done;
 	} else if (vlan && vlan->dynamic_vlan > 0) {
 		vlan->dynamic_vlan++;
-		hostapd_logger(hapd, sta->addr,
+		hostapd_logger(vlan_bss, sta->addr,
 			       HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
 			       "updated existing dynamic VLAN interface '%s'",
@@ -1386,7 +1419,7 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta)
 	sta->vlan_id_bound = sta->vlan_id;
 
 skip_counting:
-	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+	hostapd_logger(vlan_bss, sta->addr, HOSTAPD_MODULE_IEEE80211,
 		       HOSTAPD_LEVEL_DEBUG, "binding station to interface "
 		       "'%s'", iface);
 
@@ -1396,14 +1429,14 @@ skip_counting:
 	ret = hostapd_drv_set_sta_vlan(iface, hapd, sta->addr, sta->vlan_id,
 				       mld_link_id);
 	if (ret < 0) {
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+		hostapd_logger(vlan_bss, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG, "could not bind the STA "
 			       "entry to vlan_id=%d", sta->vlan_id);
 	}
 
 	/* During 1x reauth, if the vlan id changes, then remove the old id. */
 	if (old_vlanid > 0 && old_vlanid != sta->vlan_id)
-		vlan_remove_dynamic(hapd, old_vlanid);
+		vlan_remove_dynamic(vlan_bss, old_vlanid);
 done:
 
 	return ret;
diff --git a/src/ap/vlan_full.c b/src/ap/vlan_full.c
index 19aa3c649a5d..bd79b35c9c10 100644
--- a/src/ap/vlan_full.c
+++ b/src/ap/vlan_full.c
@@ -462,11 +462,19 @@ void vlan_newlink(const char *ifname, struct hostapd_data *hapd)
 {
 	char br_name[IFNAMSIZ];
 	struct hostapd_vlan *vlan;
+	struct hostapd_data *vlan_bss = hapd;
 	int untagged, *tagged, i, notempty;
 
 	wpa_printf(MSG_DEBUG, "VLAN: vlan_newlink(%s)", ifname);
 
-	for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap) {
+		vlan_bss = hostapd_mld_get_first_bss(hapd);
+		if (vlan_bss == NULL)
+			vlan_bss = hapd;
+	}
+#endif
+	for (vlan = vlan_bss->conf->vlan; vlan; vlan = vlan->next) {
 		if (vlan->configured ||
 		    os_strcmp(ifname, vlan->ifname) != 0)
 			continue;
@@ -563,10 +571,20 @@ static void vlan_put_bridge(const char *br_name, struct hostapd_data *hapd,
 
 void vlan_dellink(const char *ifname, struct hostapd_data *hapd)
 {
-	struct hostapd_vlan *first, *prev, *vlan = hapd->conf->vlan;
+	struct hostapd_vlan *first, *prev, *vlan;
+	struct hostapd_data *vlan_bss = hapd;
 
 	wpa_printf(MSG_DEBUG, "VLAN: vlan_dellink(%s)", ifname);
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap) {
+		vlan_bss = hostapd_mld_get_first_bss(hapd);
+		if (vlan_bss == NULL)
+			vlan_bss = hapd;
+	}
+#endif
+
+	vlan = vlan_bss->conf->vlan;
 	first = prev = vlan;
 
 	while (vlan) {
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index 519948a518c2..ca9e167771da 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -500,11 +500,20 @@ static int hostapd_wpa_auth_set_key(void *ctx, int vlan_id, enum wpa_alg alg,
 				    size_t key_len, enum key_flag key_flag)
 {
 	struct hostapd_data *hapd = ctx;
+	struct hostapd_data *link_bss = hapd;
 	const char *ifname = hapd->conf->iface;
 	int set_tx = !(key_flag & KEY_FLAG_NEXT);
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap) {
+		link_bss = hostapd_mld_get_first_bss(hapd);
+		if (link_bss == NULL)
+			link_bss = hapd;
+	}
+#endif
 	if (vlan_id > 0) {
-		ifname = hostapd_get_vlan_id_ifname(hapd->conf->vlan, vlan_id);
+		ifname = hostapd_get_vlan_id_ifname(link_bss->conf->vlan,
+						    vlan_id);
 		if (!ifname) {
 			if (!(hapd->iface->drv_flags &
 			      WPA_DRIVER_FLAGS_VLAN_OFFLOAD))
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 26213be6fcd0..f2fb09f06a6c 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -11075,6 +11075,11 @@ static int driver_nl80211_link_remove(void *priv, enum wpa_driver_if_type type,
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	int ret;
 
+	if (type == WPA_IF_AP_VLAN) {
+		ret = wpa_driver_nl80211_if_remove(bss, type, ifname);
+		return ret;
+	}
+
 	if (type != WPA_IF_AP_BSS)
 		return -1;
 
-- 
2.34.1



From muna.sinada at oss.qualcomm.com  Mon Jun  9 11:06:16 2025
From: muna.sinada at oss.qualcomm.com (Muna Sinada)
Date: Mon,  9 Jun 2025 11:06:16 -0700
Subject: [PATCH v4 2/2] wpa_auth: Use VLAN group keys for EAPOL frames and FT reassoc
In-Reply-To: <20250609180616.4045177-1-muna.sinada@oss.qualcomm.com>
References: <20250609180616.4045177-1-muna.sinada@oss.qualcomm.com>
Message-ID: <20250609180616.4045177-3-muna.sinada@oss.qualcomm.com>

When MLO Dynamic VLAN is enabled and non-APs in a VLAN group are
exchanging EAPOL messages, the AP is providing the primary
authenticator's GTKs instead of the VLAN's GTKs. This results in STAs
being unable to decrypt the VLAN's multicast frames due to incorrect
keys.

In wpa_auth_ml_get_key_info(), if vlan_id is provided, traverse
through the wpa_group list and select the one that matches the
vlan_id. From the matched wpa_group, the correct GTKs are taken.

Similarly in the case of FT + MLO + Dynamic VLAN, handle selecting VLAN's
wpa_group for the FT protocol Reassociation Response.

Signed-off-by: Muna Sinada <muna.sinada at oss.qualcomm.com>
Signed-off-by: Pradeep Kumar Chitrapu <quic_pradeepc at quicinc.com>

---
v4: no change

v3: rebase to TOT

v2:
 - add description for wpa_select_vlan_wpa_group()
 - move wpa_select_vlan_wpa_group() into CONFIG_IEEE80211BE block

---
 src/ap/drv_callbacks.c |  2 +-
 src/ap/ieee802_11.c    |  3 ++-
 src/ap/wpa_auth.c      | 42 ++++++++++++++++++++++++++++++++++++++----
 src/ap/wpa_auth.h      | 10 +++++++---
 src/ap/wpa_auth_ft.c   | 13 ++++++++++---
 src/ap/wpa_auth_glue.c |  6 +++---
 6 files changed, 61 insertions(+), 15 deletions(-)

diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index b54bb4f9e3c2..6ec82b2908f4 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -788,7 +788,7 @@ skip_wpa_check:
 #ifdef CONFIG_IEEE80211R_AP
 	p = wpa_sm_write_assoc_resp_ies(sta->wpa_sm, buf, sizeof(buf),
 					sta->auth_alg, req_ies, req_ies_len,
-					!elems.rsnxe);
+					!elems.rsnxe, reassoc, sta->vlan_id);
 	if (!p) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to write AssocResp IEs");
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index bb998b39f5cf..6f5a147ef873 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -5214,7 +5214,8 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
 		p = wpa_sm_write_assoc_resp_ies(sta->wpa_sm, p,
 						buf + buflen - p,
 						sta->auth_alg, ies, ies_len,
-						omit_rsnxe);
+						omit_rsnxe, reassoc,
+						sta->vlan_id);
 		if (!p) {
 			wpa_printf(MSG_DEBUG,
 				   "FT: Failed to write AssocResp IEs");
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 3f82bae7bb37..68c77720fe2f 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -4295,7 +4295,7 @@ static u8 * replace_ie(const char *name, const u8 *old_buf, size_t *len, u8 eid,
 void wpa_auth_ml_get_key_info(struct wpa_authenticator *a,
 			      struct wpa_auth_ml_link_key_info *info,
 			      bool mgmt_frame_prot, bool beacon_prot,
-			      bool rekey)
+			      bool rekey, int vlan_id)
 {
 	struct wpa_group *gsm = a->group;
 	u8 rsc[WPA_KEY_RSC_LEN];
@@ -4304,6 +4304,9 @@ void wpa_auth_ml_get_key_info(struct wpa_authenticator *a,
 		   "MLD: Get group key info: link_id=%u, IGTK=%u, BIGTK=%u",
 		   info->link_id, mgmt_frame_prot, beacon_prot);
 
+	if (vlan_id)
+		gsm = wpa_select_vlan_wpa_group(gsm, vlan_id);
+
 	info->gtkidx = gsm->GN & 0x03;
 	info->gtk = gsm->GTK[gsm->GN - 1];
 	info->gtk_len = gsm->GTK_len;
@@ -4345,12 +4348,12 @@ void wpa_auth_ml_get_key_info(struct wpa_authenticator *a,
 
 static void wpa_auth_get_ml_key_info(struct wpa_authenticator *wpa_auth,
 				     struct wpa_auth_ml_key_info *info,
-				     bool rekey)
+				     bool rekey, int vlan_id)
 {
 	if (!wpa_auth->cb->get_ml_key_info)
 		return;
 
-	wpa_auth->cb->get_ml_key_info(wpa_auth->cb_ctx, info, rekey);
+	wpa_auth->cb->get_ml_key_info(wpa_auth->cb_ctx, info, rekey, vlan_id);
 }
 
 
@@ -4435,7 +4438,8 @@ static u8 * wpa_auth_ml_group_kdes(struct wpa_state_machine *sm, u8 *pos,
 	}
 	ml_key_info.n_mld_links = i;
 
-	wpa_auth_get_ml_key_info(sm->wpa_auth, &ml_key_info, rekey);
+	wpa_auth_get_ml_key_info(sm->wpa_auth, &ml_key_info, rekey,
+				 sm->group->vlan_id);
 
 	/* Add MLO GTK KDEs */
 	for (i = 0; i < ml_key_info.n_mld_links; i++) {
@@ -7682,3 +7686,33 @@ bool wpa_auth_sm_known_sta_identification(struct wpa_state_machine *sm,
 
 	return true;
 }
+#ifdef CONFIG_IEEE80211BE
+
+/* wpa_select_vlan_wpa_group - Traverse through the wpa_group list and select
+ * the one that matches the vlan_id.
+ *
+ * @gsm: head of wpa_group list
+ * @vlan_id: vlan_id utilized to search to the group key state machine data that
+ *	     corresponds to that vlan group
+ * Returns: pointer to wpa_group that corresponds to the vlan group on success,
+ *	    pointer to head of wpa_group list that was passed in
+ */
+struct wpa_group * wpa_select_vlan_wpa_group(struct wpa_group *gsm, int vlan_id)
+{
+	struct wpa_group *vlan_gsm = gsm;
+
+	while (vlan_gsm) {
+		if (vlan_gsm->vlan_id == vlan_id)
+			break;
+
+		vlan_gsm = vlan_gsm->next;
+	}
+
+	if (!vlan_gsm) {
+		wpa_printf(MSG_DEBUG, "vlan group not found");
+		vlan_gsm = gsm;
+	}
+
+	return vlan_gsm;
+}
+#endif /* CONFIG_IEEE80211BE */
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index a98a0448721a..c5930698a0e0 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -427,7 +427,7 @@ struct wpa_auth_callbacks {
 #endif /* CONFIG_PASN */
 #ifdef CONFIG_IEEE80211BE
 	int (*get_ml_key_info)(void *ctx, struct wpa_auth_ml_key_info *info,
-			       bool rekey);
+			       bool rekey, int vlan_id);
 	struct wpa_authenticator * (*next_primary_auth)(void *ctx);
 #endif /* CONFIG_IEEE80211BE */
 	int (*get_drv_flags)(void *ctx, u64 *drv_flags, u64 *drv_flags2);
@@ -554,7 +554,7 @@ void wpa_auth_eapol_key_tx_status(struct wpa_authenticator *wpa_auth,
 u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos,
 				 size_t max_len, int auth_alg,
 				 const u8 *req_ies, size_t req_ies_len,
-				 int omit_rsnxe);
+				 int omit_rsnxe, int reassoc, int vlan_id);
 void wpa_ft_process_auth(struct wpa_state_machine *sm,
 			 u16 auth_transaction, const u8 *ies, size_t ies_len,
 			 void (*cb)(void *ctx, const u8 *dst,
@@ -682,7 +682,7 @@ void wpa_auth_set_ml_info(struct wpa_state_machine *sm,
 void wpa_auth_ml_get_key_info(struct wpa_authenticator *a,
 			      struct wpa_auth_ml_link_key_info *info,
 			      bool mgmt_frame_prot, bool beacon_prot,
-			      bool rekey);
+			      bool rekey, int vlan_id);
 
 void wpa_release_link_auth_ref(struct wpa_state_machine *sm, u8 link_id,
 			       bool rejected);
@@ -703,5 +703,9 @@ static inline bool wpa_auth_pmf_enabled(struct wpa_auth_config *conf)
 bool wpa_auth_sm_known_sta_identification(struct wpa_state_machine *sm,
 					  const u8 *timestamp,
 					  const u8 *mic, size_t mic_len);
+#ifdef CONFIG_IEEE80211BE
+struct wpa_group * wpa_select_vlan_wpa_group(struct wpa_group *gsm,
+					     int vlan_id);
+#endif /* CONFIG_IEEE80211BE */
 
 #endif /* WPA_AUTH_H */
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index d5400a9f77d3..912bb7862e61 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -2212,7 +2212,8 @@ static inline int wpa_auth_get_seqnum(struct wpa_authenticator *wpa_auth,
 }
 
 
-static u8 * wpa_ft_gtk_subelem(struct wpa_state_machine *sm, size_t *len)
+static u8 * wpa_ft_gtk_subelem(struct wpa_state_machine *sm, size_t *len,
+			       int reassoc, int vlan_id)
 {
 	u8 *subelem;
 	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
@@ -2224,6 +2225,11 @@ static u8 * wpa_ft_gtk_subelem(struct wpa_state_machine *sm, size_t *len)
 	const u8 *kek;
 	size_t kek_len;
 
+#ifdef CONFIG_IEEE80211BE
+	if (reassoc && vlan_id)
+		gsm = wpa_select_vlan_wpa_group(gsm, vlan_id);
+#endif /* CONFIG_IEEE80211BE */
+
 	if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) {
 		kek = sm->PTK.kek2;
 		kek_len = sm->PTK.kek2_len;
@@ -2545,7 +2551,7 @@ static u8 * wpa_ft_process_ric(struct wpa_state_machine *sm, u8 *pos, u8 *end,
 u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos,
 				 size_t max_len, int auth_alg,
 				 const u8 *req_ies, size_t req_ies_len,
-				 int omit_rsnxe)
+				 int omit_rsnxe, int reassoc, int vlan_id)
 {
 	u8 *end, *mdie, *ftie, *rsnie = NULL, *r0kh_id, *subelem = NULL;
 	u8 *fte_mic, *elem_count;
@@ -2635,7 +2641,8 @@ u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos,
 
 	/* Fast BSS Transition Information */
 	if (auth_alg == WLAN_AUTH_FT) {
-		subelem = wpa_ft_gtk_subelem(sm, &subelem_len);
+		subelem = wpa_ft_gtk_subelem(sm, &subelem_len, reassoc,
+					     vlan_id);
 		if (!subelem) {
 			wpa_printf(MSG_DEBUG,
 				   "FT: Failed to add GTK subelement");
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index ca9e167771da..73a1bf47d78e 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -1590,7 +1590,7 @@ static int hostapd_set_ltf_keyseed(void *ctx, const u8 *peer_addr,
 
 static int hostapd_wpa_auth_get_ml_key_info(void *ctx,
 					    struct wpa_auth_ml_key_info *info,
-					    bool rekey)
+					    bool rekey, int vlan_id)
 {
 	struct hostapd_data *hapd = ctx;
 	unsigned int i;
@@ -1615,7 +1615,7 @@ static int hostapd_wpa_auth_get_ml_key_info(void *ctx,
 						 &info->links[i],
 						 info->mgmt_frame_prot,
 						 info->beacon_prot,
-						 rekey);
+						 rekey, vlan_id);
 			continue;
 		}
 
@@ -1627,7 +1627,7 @@ static int hostapd_wpa_auth_get_ml_key_info(void *ctx,
 						 &info->links[i],
 						 info->mgmt_frame_prot,
 						 info->beacon_prot,
-						 rekey);
+						 rekey, vlan_id);
 			link_bss_found = true;
 			break;
 		}
-- 
2.34.1



From benjamin at sipsolutions.net  Tue Jun 10 08:08:55 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Tue, 10 Jun 2025 17:08:55 +0200
Subject: [PATCH 1/3] ctrl_iface: Report all TBTT info elements from RNR
Message-ID: <20250610150857.1579130-1-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

The existing code would only report the first TBTT entry of the first
RNR element. Add the code to first iterate all RNR elements and then
also iterate all TBTT entries for each AP information block.

Fixes: e3e68668c1f6 ("ctrl_iface: Report RNR and ML in BSS command")
Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 wpa_supplicant/ctrl_iface.c | 149 ++++++++++++++++++++----------------
 1 file changed, 81 insertions(+), 68 deletions(-)

diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index afad3d18b0..4b345ffaa0 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -5095,17 +5095,13 @@ static int print_fils_indication(struct wpa_bss *bss, char *pos, char *end)
 #endif /* CONFIG_FILS */
 
 
-static int print_rnr(struct wpa_bss *bss, char *pos, char *end)
+static int print_rnr(const u8 *ie, char *pos, char *end)
 {
 	char *start = pos;
-	const u8 *ie, *ie_end;
+	const u8 *ie_end;
 	unsigned int n = 0;
 	int ret;
 
-	ie = wpa_bss_get_ie(bss, WLAN_EID_REDUCED_NEIGHBOR_REPORT);
-	if (!ie)
-		return 0;
-
 	ie_end = ie + 2 + ie[1];
 	ie += 2;
 
@@ -5114,93 +5110,103 @@ static int print_rnr(struct wpa_bss *bss, char *pos, char *end)
 			(const struct ieee80211_neighbor_ap_info *) ie;
 		const u8 *tbtt_start;
 		size_t left = ie_end - ie;
+		unsigned int count;
+		unsigned int i;
 
 		if (left < sizeof(struct ieee80211_neighbor_ap_info))
 			return 0;
 
-		left -= sizeof(struct ieee80211_neighbor_ap_info);
-		if (left < info->tbtt_info_len)
-			return 0;
+		count = RNR_TBTT_INFO_COUNT_VAL(info->tbtt_info_hdr) + 1;
 
-		ret = os_snprintf(pos, end - pos,
-				  "ap_info[%u]: tbtt_info: hdr=0x%x, len=%u, op_c=%u, channel=%u, ",
-				  n, *ie, info->tbtt_info_len,
-				  info->op_class, info->channel);
-		if (os_snprintf_error(end - pos, ret))
+		left -= sizeof(struct ieee80211_neighbor_ap_info);
+		if (left < count * info->tbtt_info_len)
 			return 0;
-		pos += ret;
 
 		ie += sizeof(struct ieee80211_neighbor_ap_info);
 		tbtt_start = ie;
-		if (info->tbtt_info_len >= 1) {
-			ret = os_snprintf(pos, end - pos,
-					  "tbtt_offset=%u, ", *ie);
-			if (os_snprintf_error(end - pos, ret))
-				return 0;
 
-			ie++;
-			pos += ret;
-		}
+		for (i = 0; i < count; i++) {
+			ie = tbtt_start + i * info->tbtt_info_len;
 
-		if (info->tbtt_info_len >= 7) {
 			ret = os_snprintf(pos, end - pos,
-					  "bssid=" MACSTR ", ",
-					  MAC2STR(ie));
+					  "ap_info[%u]: tbtt_info[%u]: hdr=0x%x, len=%u, op_c=%u, channel=%u, ",
+					  n, i, info->tbtt_info_hdr,
+					  info->tbtt_info_len,
+					  info->op_class, info->channel);
 			if (os_snprintf_error(end - pos, ret))
 				return 0;
-
-			ie += ETH_ALEN;
 			pos += ret;
-		}
 
-		if (info->tbtt_info_len >= 11) {
-			ret = os_snprintf(pos, end - pos,
-					  "short SSID=0x%x, ",
-					  WPA_GET_LE32(ie));
-			if (os_snprintf_error(end - pos, ret))
-				return 0;
+			if (info->tbtt_info_len >= 1) {
+				ret = os_snprintf(pos, end - pos,
+						  "tbtt_offset=%u, ", *ie);
+				if (os_snprintf_error(end - pos, ret))
+					return 0;
 
-			ie += 4;
-			pos += ret;
-		}
+				ie++;
+				pos += ret;
+			}
 
-		if (info->tbtt_info_len >= 12) {
-			ret = os_snprintf(pos, end - pos,
-					  "bss_params=0x%x, ", *ie);
-			if (os_snprintf_error(end - pos, ret))
-				return 0;
+			if (info->tbtt_info_len >= 7) {
+				ret = os_snprintf(pos, end - pos,
+						  "bssid=" MACSTR ", ",
+						  MAC2STR(ie));
+				if (os_snprintf_error(end - pos, ret))
+					return 0;
 
-			ie++;
-			pos += ret;
-		}
+				ie += ETH_ALEN;
+				pos += ret;
+			}
 
-		if (info->tbtt_info_len >= 13) {
-			ret = os_snprintf(pos, end - pos,
-					  "PSD=0x%x, ", *ie);
-			if (os_snprintf_error(end - pos, ret))
-				return 0;
+			if (info->tbtt_info_len >= 11) {
+				ret = os_snprintf(pos, end - pos,
+						  "short SSID=0x%x, ",
+						  WPA_GET_LE32(ie));
+				if (os_snprintf_error(end - pos, ret))
+					return 0;
 
-			ie++;
-			pos += ret;
-		}
+				ie += 4;
+				pos += ret;
+			}
 
-		if (info->tbtt_info_len >= 16) {
-			ret = os_snprintf(pos, end - pos,
-					  "mld ID=%u, link ID=%u",
-					  *ie, *(ie + 1) & 0xF);
+			if (info->tbtt_info_len >= 12) {
+				ret = os_snprintf(pos, end - pos,
+						  "bss_params=0x%x, ", *ie);
+				if (os_snprintf_error(end - pos, ret))
+					return 0;
+
+				ie++;
+				pos += ret;
+			}
+
+			if (info->tbtt_info_len >= 13) {
+				ret = os_snprintf(pos, end - pos,
+						  "PSD=0x%x, ", *ie);
+				if (os_snprintf_error(end - pos, ret))
+					return 0;
+
+				ie++;
+				pos += ret;
+			}
+
+			if (info->tbtt_info_len >= 16) {
+				ret = os_snprintf(pos, end - pos,
+						  "mld ID=%u, link ID=%u",
+						  *ie, *(ie + 1) & 0xF);
+				if (os_snprintf_error(end - pos, ret))
+					return 0;
+
+				ie += 3;
+				pos += ret;
+			}
+
+			ret = os_snprintf(pos, end - pos, "\n");
 			if (os_snprintf_error(end - pos, ret))
 				return 0;
-
-			ie += 3;
 			pos += ret;
 		}
 
-		ie = tbtt_start + info->tbtt_info_len;
-
-		ret = os_snprintf(pos, end - pos, "\n");
-		if (os_snprintf_error(end - pos, ret))
-			return 0;
-		pos += ret;
+		ie = tbtt_start + count * info->tbtt_info_len;
 
 		n++;
 	}
@@ -5783,8 +5789,15 @@ static int print_bss_info(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
 		pos += ret;
 	}
 
-	if (mask & WPA_BSS_MASK_RNR)
-		pos += print_rnr(bss, pos, end);
+	if (mask & WPA_BSS_MASK_RNR) {
+		size_t ies_len = bss->ie_len ? bss->ie_len : bss->beacon_ie_len;
+		const struct element *elem;
+
+		for_each_element_id(elem, WLAN_EID_REDUCED_NEIGHBOR_REPORT,
+				    wpa_bss_ie_ptr(bss), ies_len) {
+			pos += print_rnr((const u8 *) elem, pos, end);
+		}
+	}
 
 	if (mask & WPA_BSS_MASK_ML)
 		pos += print_ml(bss, pos, end);
-- 
2.49.0



From benjamin at sipsolutions.net  Tue Jun 10 08:08:56 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Tue, 10 Jun 2025 17:08:56 +0200
Subject: [PATCH 2/3] tests: Verify RNR reporting in EHT discovery test
In-Reply-To: <20250610150857.1579130-1-benjamin@sipsolutions.net>
References: <20250610150857.1579130-1-benjamin@sipsolutions.net>
Message-ID: <20250610150857.1579130-2-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

Add a bit more verification of the RNR that is reported by the
supplicant in the EHT discovery test.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 tests/hwsim/test_eht.py | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/tests/hwsim/test_eht.py b/tests/hwsim/test_eht.py
index f254d00b3f..3a28507d51 100644
--- a/tests/hwsim/test_eht.py
+++ b/tests/hwsim/test_eht.py
@@ -330,9 +330,13 @@ def test_eht_mld_discovery(dev, apdev):
         link1_params = {"ssid": ssid,
                         "hw_mode": "g",
                         "channel": "2"}
+        link2_params = {"ssid": ssid,
+                        "hw_mode": "g",
+                        "channel": "3"}
 
         hapd0 = eht_mld_enable_ap(hapd_iface, 0, link0_params)
         hapd1 = eht_mld_enable_ap(hapd_iface, 1, link1_params)
+        hapd2 = eht_mld_enable_ap(hapd_iface, 2, link2_params)
 
         # Only scan link 0
         res = wpas.request("SCAN freq=2412")
@@ -349,15 +353,16 @@ def test_eht_mld_discovery(dev, apdev):
 
         logger.info("Scan done")
 
-        rnr_pattern = re.compile(".*ap_info.*, mld ID=0, link ID=",
+        rnr_pattern = re.compile("^.*ap_info.*, bssid=(.*?),.*mld ID=0, link ID=([0-9]*).*$",
                                  re.MULTILINE)
         ml_pattern = re.compile(".*multi-link:.*, MLD addr=.*", re.MULTILINE)
 
         bss = wpas.request("BSS " + hapd0.own_addr())
         logger.info("BSS 0: " + str(bss))
 
-        if rnr_pattern.search(bss) is None:
-            raise Exception("RNR element not found for first link")
+        rnr_entries = rnr_pattern.findall(bss)
+        if (hapd1.own_addr(), '1') not in rnr_entries or (hapd2.own_addr(), '2') not in rnr_entries:
+            raise Exception("RNR entries for other links not found on first link, found: " + str(rnr_pattern.findall(bss)))
 
         if ml_pattern.search(bss) is None:
             raise Exception("ML element not found for first link")
@@ -379,8 +384,9 @@ def test_eht_mld_discovery(dev, apdev):
         bss = wpas.request("BSS " + hapd1.own_addr())
         logger.info("BSS 1: " + str(bss))
 
-        if rnr_pattern.search(bss) is None:
-            raise Exception("RNR element not found for second link")
+        rnr_entries = rnr_pattern.findall(bss)
+        if (hapd0.own_addr(), '0') not in rnr_entries or (hapd2.own_addr(), '2') not in rnr_entries:
+            raise Exception("RNR entries for other links not found on second link, found: " + str(rnr_entries))
 
         if ml_pattern.search(bss) is None:
             raise Exception("ML element not found for second link")
-- 
2.49.0



From benjamin at sipsolutions.net  Tue Jun 10 08:08:57 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Tue, 10 Jun 2025 17:08:57 +0200
Subject: [PATCH 3/3] tests: try next tshark filter for UnknownFieldsException
In-Reply-To: <20250610150857.1579130-1-benjamin@sipsolutions.net>
References: <20250610150857.1579130-1-benjamin@sipsolutions.net>
Message-ID: <20250610150857.1579130-3-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

The intention in commit cc35ed3e257a ("tests: Permit passing multiple
valid tshark filters") was to permit passing multiple filters in order
to permit compatibility with older tshark versions. However, this did
not work as expected for the UnknownFieldsException.

Fix that by storing it in last_exception as for other errors. This fixes
running some EHT tests with older tshark versions.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 tests/hwsim/tshark.py | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/tests/hwsim/tshark.py b/tests/hwsim/tshark.py
index 73e54b6dfe..89ccb6104e 100644
--- a/tests/hwsim/tshark.py
+++ b/tests/hwsim/tshark.py
@@ -111,8 +111,12 @@ def run_tshark(filename, filters, display=None, wait=True):
                     all_wlan_mgt = False
                     break
             if not all_wlan_mgt:
-                raise
-            return _run_tshark(filename, filter, display, wait)
+                last_exception = e
+                continue
+            try:
+                return _run_tshark(filename, filter, display, wait)
+            except Exception as e:
+                last_exception = e
 
         except AssertionError as e:
             # Catch the error (and try the next provided filter)
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 11 01:47:22 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 11 Jun 2025 10:47:22 +0200
Subject: [PATCH 0/7] Various test fixes
Message-ID: <20250611084729.1640913-1-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

Hi,

This set contains various test fixes that are hopefully all reasonably
straight forward fixes for minor issues.

Benjamin

Benjamin Berg (7):
  tests: use clear_regdom helper in ap_vht tests
  tests: Ensure property change notifications happen in VM
  tests: Add sleep to ensure wlantest processed packets
  tests: improve tshark boolean output support
  tests: generate proper dependencies for all tests
  tests: ensure the RoC is setup when the NAN frame arrives
  tests: permit encryption of DEAUTH frame

 tests/Makefile                 | 54 ++++++++++++++++---------------
 tests/hwsim/test_ap_ciphers.py |  1 +
 tests/hwsim/test_ap_open.py    | 15 +++------
 tests/hwsim/test_ap_vht.py     | 59 ++++++----------------------------
 tests/hwsim/test_ap_vlan.py    | 13 +++-----
 tests/hwsim/test_dbus.py       | 16 +++++++--
 tests/hwsim/utils.py           | 14 ++++++++
 tests/test.mk                  |  6 ++++
 8 files changed, 81 insertions(+), 97 deletions(-)
 create mode 100644 tests/test.mk

-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 11 01:47:23 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 11 Jun 2025 10:47:23 +0200
Subject: [PATCH 1/7] tests: use clear_regdom helper in ap_vht tests
In-Reply-To: <20250611084729.1640913-1-benjamin@sipsolutions.net>
References: <20250611084729.1640913-1-benjamin@sipsolutions.net>
Message-ID: <20250611084729.1640913-2-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

In some places, the code did not wait for the regulatory domain change.
Replace the cleanup code with calls to clear_regdom as that does the
right thing, which should avoid errors in UML with time-travel mode.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 tests/hwsim/test_ap_vht.py | 59 ++++++--------------------------------
 1 file changed, 9 insertions(+), 50 deletions(-)

diff --git a/tests/hwsim/test_ap_vht.py b/tests/hwsim/test_ap_vht.py
index 88ea1dd971..d0bd6468ef 100644
--- a/tests/hwsim/test_ap_vht.py
+++ b/tests/hwsim/test_ap_vht.py
@@ -463,12 +463,7 @@ def test_ap_vht160(dev, apdev):
                 raise HwsimSkip("80/160 MHz channel not supported in regulatory information")
         raise
     finally:
-        if hapd:
-            hapd.request("DISABLE")
-        dev[0].disconnect_and_stop_scan()
-        subprocess.call(['iw', 'reg', 'set', '00'])
-        dev[0].wait_event(["CTRL-EVENT-REGDOM-CHANGE"], timeout=0.5)
-        dev[0].flush_scan_cache()
+        clear_regdom(hapd, dev)
 
 def test_ap_vht160b(dev, apdev):
     """VHT with 160 MHz channel width (2)"""
@@ -725,15 +720,10 @@ def test_ap_vht80plus80(dev, apdev):
                 raise HwsimSkip("80/160 MHz channel not supported in regulatory information")
         raise
     finally:
-        dev[0].request("DISCONNECT")
-        dev[1].request("DISCONNECT")
-        if hapd:
-            hapd.request("DISABLE")
         if hapd2:
             hapd2.request("DISABLE")
-        subprocess.call(['iw', 'reg', 'set', '00'])
-        dev[0].flush_scan_cache()
-        dev[1].flush_scan_cache()
+        clear_regdom(hapd, dev, count=2)
+
 
 def test_ap_vht80plus80_invalid(dev, apdev):
     """VHT with invalid 80+80 MHz channel"""
@@ -879,13 +869,7 @@ def test_ap_vht_csa_vht80p80(dev, apdev):
         sig = dev[0].request("SIGNAL_POLL").splitlines()
         logger.info("SIGNAL_POLL(0): " + str(sig))
     finally:
-        dev[0].request("DISCONNECT")
-        dev[1].request("DISCONNECT")
-        if hapd:
-            hapd.request("DISABLE")
-        subprocess.call(['iw', 'reg', 'set', '00'])
-        dev[0].flush_scan_cache()
-        dev[1].flush_scan_cache()
+        clear_regdom(hapd, dev, count=2)
 
 def test_ap_vht_csa_vht40(dev, apdev):
     """VHT CSA with VHT40 getting enabled"""
@@ -927,13 +911,7 @@ def test_ap_vht_csa_vht40(dev, apdev):
         if dev[1].get_status_field("ieee80211ac") != '1':
             raise Exception("VHT not enabled as part of channel switch")
     finally:
-        dev[0].request("DISCONNECT")
-        dev[1].request("DISCONNECT")
-        if hapd:
-            hapd.request("DISABLE")
-        subprocess.call(['iw', 'reg', 'set', '00'])
-        dev[0].flush_scan_cache()
-        dev[1].flush_scan_cache()
+        clear_regdom(hapd, dev, count=2)
 
 def test_ap_vht_csa_vht20(dev, apdev):
     """VHT CSA with VHT20 getting enabled"""
@@ -968,13 +946,7 @@ def test_ap_vht_csa_vht20(dev, apdev):
         if dev[1].get_status_field("ieee80211ac") != '1':
             raise Exception("VHT not enabled as part of channel switch")
     finally:
-        dev[0].request("DISCONNECT")
-        dev[1].request("DISCONNECT")
-        if hapd:
-            hapd.request("DISABLE")
-        subprocess.call(['iw', 'reg', 'set', '00'])
-        dev[0].flush_scan_cache()
-        dev[1].flush_scan_cache()
+        clear_regdom(hapd, dev, 2)
 
 def test_ap_vht_csa_vht40_disable(dev, apdev):
     """VHT CSA with VHT40 getting disabled"""
@@ -1024,13 +996,7 @@ def test_ap_vht_csa_vht40_disable(dev, apdev):
         if dev[1].get_status_field("ieee80211ac") == '1':
             raise Exception("VHT not disabled as part of channel switch")
     finally:
-        dev[0].request("DISCONNECT")
-        dev[1].request("DISCONNECT")
-        if hapd:
-            hapd.request("DISABLE")
-        subprocess.call(['iw', 'reg', 'set', '00'])
-        dev[0].flush_scan_cache()
-        dev[1].flush_scan_cache()
+        clear_regdom(hapd, dev, count=2)
 
 def test_ap_vht_on_24ghz(dev, apdev):
     """Subset of VHT features on 2.4 GHz"""
@@ -1189,12 +1155,7 @@ def test_ap_vht80_pwr_constraint(dev, apdev):
                 raise HwsimSkip("80 MHz channel not supported in regulatory information")
         raise
     finally:
-        if hapd:
-            hapd.request("DISABLE")
-        dev[0].disconnect_and_stop_scan()
-        subprocess.call(['iw', 'reg', 'set', '00'])
-        dev[0].wait_event(["CTRL-EVENT-REGDOM-CHANGE"], timeout=0.5)
-        dev[0].flush_scan_cache()
+        clear_regdom(hapd, dev)
 
 def test_ap_vht_use_sta_nsts(dev, apdev):
     """VHT with 80 MHz channel width and use_sta_nsts=1"""
@@ -1377,6 +1338,4 @@ def test_ap_vht_csa_invalid(dev, apdev):
         hapd.request("CHAN_SWITCH 5 5765 bandwidth=160 center_freq1=5775 sec_channel_offset=-1")
         time.sleep(1)
     finally:
-        if hapd:
-            hapd.request("DISABLE")
-        subprocess.call(['iw', 'reg', 'set', '00'])
+        clear_regdom(hapd, dev)
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 11 01:47:24 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 11 Jun 2025 10:47:24 +0200
Subject: [PATCH 2/7] tests: Ensure property change notifications happen in VM
In-Reply-To: <20250611084729.1640913-1-benjamin@sipsolutions.net>
References: <20250611084729.1640913-1-benjamin@sipsolutions.net>
Message-ID: <20250611084729.1640913-3-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

When running in a VM (UML time-travel to be exact), it may happen that
not enough time passes and the 5ms timeout for property notification
causes compression of events. However, some tests will check that all
property notifications were done and this compression can cause test
failures.

Work around this by simply inserting a 5 ms sleep for any signal handler
call.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 tests/hwsim/test_dbus.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/tests/hwsim/test_dbus.py b/tests/hwsim/test_dbus.py
index ab535a3ef0..ffc59a181c 100644
--- a/tests/hwsim/test_dbus.py
+++ b/tests/hwsim/test_dbus.py
@@ -73,7 +73,13 @@ class TestDbus(object):
             s.remove()
 
     def add_signal(self, handler, interface, name, byte_arrays=False):
-        s = self.bus.add_signal_receiver(handler, dbus_interface=interface,
+        # Insert sleep to ensure WPA_DBUS_SEND_PROP_CHANGED_TIMEOUT passes
+        def int_handler(*args):
+            nonlocal handler
+            time.sleep(0.005)
+            handler(*args)
+
+        s = self.bus.add_signal_receiver(int_handler, dbus_interface=interface,
                                          signal_name=name,
                                          byte_arrays=byte_arrays)
         self.signals.append(s)
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 11 01:47:25 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 11 Jun 2025 10:47:25 +0200
Subject: [PATCH 3/7] tests: Add sleep to ensure wlantest processed packets
In-Reply-To: <20250611084729.1640913-1-benjamin@sipsolutions.net>
References: <20250611084729.1640913-1-benjamin@sipsolutions.net>
Message-ID: <20250611084729.1640913-4-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

At least with EEVDF it appears to occasionally happen that wlantest has
not processed all packets but does respond to the command to query the
BSS counter. Fix this by inserting a sleep.

An alternative approach could be to force command handling into a
timeout or introducing a priority concept to eloop.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 tests/hwsim/test_ap_ciphers.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tests/hwsim/test_ap_ciphers.py b/tests/hwsim/test_ap_ciphers.py
index a3d8aa0f3a..9078e4497c 100644
--- a/tests/hwsim/test_ap_ciphers.py
+++ b/tests/hwsim/test_ap_ciphers.py
@@ -63,6 +63,7 @@ def check_group_mgmt_cipher(dev, ap, cipher, sta_req_cipher=None):
     hwsim_utils.test_connectivity(dev, hapd)
     hapd.request("DEAUTHENTICATE ff:ff:ff:ff:ff:ff")
     dev.wait_disconnected()
+    time.sleep(0.1)
     if wt.get_bss_counter('valid_bip_mmie', ap['bssid']) < 1:
         raise Exception("No valid BIP MMIE seen")
     if wt.get_bss_counter('bip_deauth', ap['bssid']) < 1:
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 11 01:47:26 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 11 Jun 2025 10:47:26 +0200
Subject: [PATCH 4/7] tests: improve tshark boolean output support
In-Reply-To: <20250611084729.1640913-1-benjamin@sipsolutions.net>
References: <20250611084729.1640913-1-benjamin@sipsolutions.net>
Message-ID: <20250611084729.1640913-5-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

Newer tshark versions are exporting some values as boolean with a
False/True string value. Add a helper that accepts an integer of any
base and also a "True"/"False" string.

Update various places to use the new helper.

Fixes: bf67d09e587a ("tests: Handle newer tshark version returning boolean values")
Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 tests/hwsim/test_ap_open.py | 15 +++++----------
 tests/hwsim/test_ap_vlan.py | 13 ++++---------
 tests/hwsim/utils.py        | 14 ++++++++++++++
 3 files changed, 23 insertions(+), 19 deletions(-)

diff --git a/tests/hwsim/test_ap_open.py b/tests/hwsim/test_ap_open.py
index ceba146a03..8e90683b4f 100644
--- a/tests/hwsim/test_ap_open.py
+++ b/tests/hwsim/test_ap_open.py
@@ -539,7 +539,7 @@ def test_ap_open_ps_mc_buf(dev, apdev, params):
                    bg_scan_period="0")
     hapd.wait_sta()
 
-    buffered_mcast = 0
+    buffered_mcast = False
     try:
         dev[0].cmd_execute(['iw', 'dev', dev[0].ifname,
                             'set', 'power_save', 'on'])
@@ -555,21 +555,16 @@ def test_ap_open_ps_mc_buf(dev, apdev, params):
                              "wlan.fc.type_subtype == 0x0008",
                              ["wlan.tim.bmapctl.multicast"])
             for line in out.splitlines():
-                if line == "True":
-                    buffered_mcast = 1
-                elif line == "False":
-                    buffered_mcast = 0
-                else:
-                    buffered_mcast = int(line)
-                if buffered_mcast == 1:
+                buffered_mcast = parse_bool(line)
+                if buffered_mcast:
                     break
-            if buffered_mcast == 1:
+            if buffered_mcast:
                 break
     finally:
         dev[0].cmd_execute(['iw', 'dev', dev[0].ifname,
                             'set', 'power_save', 'off'])
 
-    if buffered_mcast != 1:
+    if not buffered_mcast:
         raise Exception("AP did not buffer multicast frames")
 
 @remote_compatible
diff --git a/tests/hwsim/test_ap_vlan.py b/tests/hwsim/test_ap_vlan.py
index f4001bbefa..029b978591 100644
--- a/tests/hwsim/test_ap_vlan.py
+++ b/tests/hwsim/test_ap_vlan.py
@@ -626,8 +626,8 @@ def test_ap_vlan_without_station(dev, apdev, p):
                 logger.info("first frame not observed")
             state = 1
             for l in lines:
-                is_protected = int(l, 16)
-                if is_protected != 1:
+                is_protected = parse_bool(l)
+                if not is_protected:
                     state = 0
             if state != 1:
                 raise Exception("Broadcast packets were not encrypted when no station was connected")
@@ -644,13 +644,8 @@ def test_ap_vlan_without_station(dev, apdev, p):
                 raise Exception("second frame not observed")
             state = 1
             for l in lines:
-                if l == "True":
-                    is_protected = 1
-                elif l == "False":
-                    is_protected = 0
-                else:
-                    is_protected = int(l, 16)
-                if is_protected != 1:
+                is_protected = parse_bool(l)
+                if not is_protected:
                     state = 0
             if state != 1:
                 raise Exception("Broadcast packets were not encrypted when station was connected")
diff --git a/tests/hwsim/utils.py b/tests/hwsim/utils.py
index 62371b0d77..5c2af56352 100644
--- a/tests/hwsim/utils.py
+++ b/tests/hwsim/utils.py
@@ -344,3 +344,17 @@ def disable_ipv6(fn):
             sysctl_write('net.ipv6.conf.all.disable_ipv6=0')
             sysctl_write('net.ipv6.conf.default.disable_ipv6=0')
     return cloned_wrapper(wrapper, fn)
+
+def parse_bool(s):
+    # Try parsing as integer of any base (expected 10 or 16),
+    # if that fails, try "True"/"False" literals
+    s = s.strip()
+    try:
+        return bool(int(s, 0))
+    except ValueError as e:
+        if s == 'True':
+            return True
+        elif s == 'False':
+            return False
+        else:
+            raise e
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 11 01:47:27 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 11 Jun 2025 10:47:27 +0200
Subject: [PATCH 5/7] tests: generate proper dependencies for all tests
In-Reply-To: <20250611084729.1640913-1-benjamin@sipsolutions.net>
References: <20250611084729.1640913-1-benjamin@sipsolutions.net>
Message-ID: <20250611084729.1640913-6-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

The object files were not getting any dependency information as they
were using BUILDOBJ directly. Move the rules into a separate include
file and also generate the proper dependency include so that the tests
are rebuild correctly.

This changes building of some tests slightly as their rules were using
$< instead of $^. However, it should not cause any harm.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 tests/Makefile | 54 ++++++++++++++++++++++++++------------------------
 tests/test.mk  |  6 ++++++
 2 files changed, 34 insertions(+), 26 deletions(-)
 create mode 100644 tests/test.mk

diff --git a/tests/Makefile b/tests/Makefile
index 8ec154bb3e..ea2fa843ff 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -46,41 +46,41 @@ LLIBS = -Wl,--start-group $(DLIBS) -Wl,--end-group $(SLIBS)
 # glibc < 2.17 needs -lrt for clock_gettime()
 LLIBS += -lrt
 
-test-aes: $(call BUILDOBJ,test-aes.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $^ $(LLIBS)
+TEST=test-aes
+include test.mk
 
-test-base64: $(call BUILDOBJ,test-base64.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $^ $(LLIBS)
+TEST=test-base64
+include test.mk
 
-test-https: $(call BUILDOBJ,test-https.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $< $(LLIBS)
+TEST=test-https
+include test.mk
 
-test-https_server: $(call BUILDOBJ,test-https_server.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $< $(LLIBS)
+TEST=test-https_server
+include test.mk
 
-test-list: $(call BUILDOBJ,test-list.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $^ $(LLIBS)
+TEST=test-list
+include test.mk
 
-test-md4: $(call BUILDOBJ,test-md4.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $^ $(LLIBS)
+TEST=test-md4
+include test.mk
 
-test-milenage: $(call BUILDOBJ,test-milenage.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $^ $(LLIBS)
+TEST=test-milenage
+include test.mk
 
-test-rc4: $(call BUILDOBJ,test-rc4.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $^ $(LLIBS)
+TEST=test-rc4
+include test.mk
 
-test-rsa-sig-ver: $(call BUILDOBJ,test-rsa-sig-ver.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $< $(LLIBS)
+TEST=test-rsa-sig-ver
+include test.mk
 
-test-sha1: $(call BUILDOBJ,test-sha1.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $^ $(LLIBS)
+TEST=test-sha1
+include test.mk
 
-test-sha256: $(call BUILDOBJ,test-sha256.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $^ $(LLIBS)
+TEST=test-sha256
+include test.mk
 
-test-x509v3: $(call BUILDOBJ,test-x509v3.o) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $< $(LLIBS)
+TEST=test-x509v3
+include test.mk
 
 
 # We could cut this down more by enabling fewer options (above)
@@ -137,8 +137,10 @@ include ../src/objs.mk
 
 LIBS=$(SLIBS) $(DLIBS) $(WPA_LIBS) $(ELIBS)
 
-test-bss: $(call BUILDOBJ,test-bss.o) $(WPA_OBJS) $(LIBS)
-	$(LDO) $(LDFLAGS) -o $@ $< $(LLIBS) $(WPA_CFLAGS) $(WPA_OBJS) $(LIBS)
+TEST=test-bss
+CFLAGS_test-bss=$(WPA_CFLAGS)
+OBJS_test-bss=$(WPA_OBJS)
+include test.mk
 
 run-tests: $(ALL)
 	./test-aes
diff --git a/tests/test.mk b/tests/test.mk
new file mode 100644
index 0000000000..9c3cc2cd8a
--- /dev/null
+++ b/tests/test.mk
@@ -0,0 +1,6 @@
+TEST_OBJ = $(TEST).o
+_OBJS_VAR := TEST_OBJ
+include ../src/objs.mk
+
+$(TEST): $(TEST_OBJ) $(OBJS_$(TEST)) $(LIBS)
+	$(LDO) $(LDFLAGS) -o $@ $^ $(LLIBS) $(CFLAGS_$(TEST))
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 11 01:47:28 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 11 Jun 2025 10:47:28 +0200
Subject: [PATCH 6/7] tests: ensure the RoC is setup when the NAN frame arrives
In-Reply-To: <20250611084729.1640913-1-benjamin@sipsolutions.net>
References: <20250611084729.1640913-1-benjamin@sipsolutions.net>
Message-ID: <20250611084729.1640913-7-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

At least in UML it could sometimes happen that the RoC is ready only
slightly after the NAN frame is TXed (a few microseconds). Insert
a short sleep to prevent this race from happening.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 tests/hwsim/test_dbus.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/tests/hwsim/test_dbus.py b/tests/hwsim/test_dbus.py
index ffc59a181c..e07c6b94d1 100644
--- a/tests/hwsim/test_dbus.py
+++ b/tests/hwsim/test_dbus.py
@@ -6598,6 +6598,9 @@ def test_dbus_nan_usd_subscribe_followup(dev, apdev):
                                                     'srv_proto_type': 3,
                                                     'ssi': dbus.ByteArray(b'test')})
 
+            # Setting up the RoC takes a little bit of time. Avoid racing with
+            # the TX here by sleeping a little bit.
+            time.sleep(0.01)
             cmd = "NAN_PUBLISH service_name=_test srv_proto_type=3 ssi=6677 ttl=10"
             self.id1 = dev[1].request(cmd)
             if "FAIL" in self.id1:
@@ -6664,6 +6667,9 @@ def test_dbus_nan_usd_publish_followup(dev, apdev):
             if "FAIL" in id1:
                 raise Exception("NAN_SUBSCRIBE failed")
 
+            # Setting up the RoC takes a little bit of time. Avoid racing with
+            # the TX here by sleeping a little bit.
+            time.sleep(0.01)
             self.publish_id = iface.NANPublish({'srv_name': '_test',
                                                 'srv_proto_type': 2,
                                                 'ssi': dbus.ByteArray(b'test')})
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 11 01:47:29 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 11 Jun 2025 10:47:29 +0200
Subject: [PATCH 7/7] tests: permit encryption of DEAUTH frame
In-Reply-To: <20250611084729.1640913-1-benjamin@sipsolutions.net>
References: <20250611084729.1640913-1-benjamin@sipsolutions.net>
Message-ID: <20250611084729.1640913-8-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

The test does not intentionally send an unencrypted frame. This seems to
simply be done because the parameter value is not obvious. Change it to
permit encryption so that the frame can be sent directly rather than
going through the monitor interface.

This really doesn't make a difference overall, but it is closer to what
was intended here.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 tests/hwsim/test_dbus.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/hwsim/test_dbus.py b/tests/hwsim/test_dbus.py
index e07c6b94d1..dfc20db3a5 100644
--- a/tests/hwsim/test_dbus.py
+++ b/tests/hwsim/test_dbus.py
@@ -5042,7 +5042,7 @@ def _test_dbus_p2p_group_idle_timeout(dev, apdev):
             # Force disassociation with different reason code so that the
             # P2P Client using D-Bus does not get normal group termination event
             # from the GO.
-            dev[1].group_request("DEAUTHENTICATE " + ifaddr + " reason=0 test=0")
+            dev[1].group_request("DEAUTHENTICATE " + ifaddr + " reason=0 test=1")
             dev[1].remove_group()
 
         def groupFinished(self, properties):
-- 
2.49.0



From pmartin-gomez at freebox.fr  Wed Jun 11 07:19:18 2025
From: pmartin-gomez at freebox.fr (Pablo MARTIN-GOMEZ)
Date: Wed, 11 Jun 2025 16:19:18 +0200
Subject: [PATCH] AP: Do not set reserved bits in HE Capabilities elements
Message-ID: <bd4a5118-7778-4c80-afd0-bcc7979fdc94@freebox.fr>

Currently, bits are set in the Supported Channel Width Set subfields in
the HE Capabilities elements regardless of the current band. However,
table 9-322b from 802.11ax standard indicates that some bits are
reserved depending on the current band. Convention states that reserved
bits shall be set to zero unless otherwise stated.

Set bits according to current band and supported widths.

Signed-off-by: Pablo Martin-Gomez <pmartin-gomez at freebox.fr>
---
 ?src/ap/ieee802_11_he.c | 48 ++++++++++++++++++++++++++----------------
 ?1 file changed, 30 insertions(+), 18 deletions(-)

diff --git a/src/ap/ieee802_11_he.c b/src/ap/ieee802_11_he.c
index cc731b914..f4adeada9 100644
--- a/src/ap/ieee802_11_he.c
+++ b/src/ap/ieee802_11_he.c
@@ -91,7 +91,7 @@ u8 * hostapd_eid_he_capab(struct hostapd_data *hapd, 
u8 *eid,
 ?{
 ???? struct ieee80211_he_capabilities *cap;
 ???? struct hostapd_hw_modes *mode = hapd->iface->current_mode;
-??? u8 he_oper_chwidth = ~HE_PHYCAP_CHANNEL_WIDTH_MASK;
+??? u8 he_oper_chwidth = 0;
 ???? u8 *pos = eid;
 ???? u8 ie_size = 0, mcs_nss_size = 4, ppet_size = 0;

@@ -102,23 +102,35 @@ u8 * hostapd_eid_he_capab(struct hostapd_data 
*hapd, u8 *eid,
 ???? ppet_size = ieee80211_he_ppet_size(mode->he_capab[opmode].ppet[0],
 ??????????????????????? mode->he_capab[opmode].phy_cap);

-??? switch (hapd->iface->conf->he_oper_chwidth) {
-??? case CONF_OPER_CHWIDTH_80P80MHZ:
-??????? he_oper_chwidth |=
-??????????? HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G;
-??????? mcs_nss_size += 4;
-??????? /* fall through */
-??? case CONF_OPER_CHWIDTH_160MHZ:
-??????? he_oper_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
-??????? mcs_nss_size += 4;
-??????? /* fall through */
-??? case CONF_OPER_CHWIDTH_80MHZ:
-??? case CONF_OPER_CHWIDTH_USE_HT:
-??????? he_oper_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G |
-??????????? HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G;
-??????? break;
-??? default:
-??????? break;
+??? if (mode->mode == HOSTAPD_MODE_IEEE80211A) {
+??????? switch (hapd->iface->conf->he_oper_chwidth) {
+??????? case CONF_OPER_CHWIDTH_80P80MHZ:
+??????????? he_oper_chwidth |=
+??????????????? HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G;
+??????????? mcs_nss_size += 4;
+??????????? /* fall through */
+??????? case CONF_OPER_CHWIDTH_160MHZ:
+??????????? he_oper_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
+??????????? mcs_nss_size += 4;
+??????????? /* fall through */
+??????? case CONF_OPER_CHWIDTH_80MHZ:
+??????? case CONF_OPER_CHWIDTH_USE_HT:
+??????????? he_oper_chwidth |= 
HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G;
+??????????? break;
+??????? default:
+??????????? break;
+??????? }
+??? } else? {
+??????? switch (hapd->iface->conf->he_oper_chwidth) {
+??????? case CONF_OPER_CHWIDTH_80P80MHZ:
+??????? case CONF_OPER_CHWIDTH_160MHZ:
+??????? case CONF_OPER_CHWIDTH_80MHZ:
+??????? case CONF_OPER_CHWIDTH_USE_HT:
+??????????? he_oper_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G;
+??????????? break;
+??????? default:
+??????????? break;
+??????? }
 ???? }

 ???? ie_size += mcs_nss_size + ppet_size;
-- 
2.43.0



From pmartin-gomez at freebox.fr  Wed Jun 11 11:38:05 2025
From: pmartin-gomez at freebox.fr (Pablo Martin-Gomez)
Date: Wed, 11 Jun 2025 20:38:05 +0200
Subject: [PATCH 0/3] Copy IE coherently in assoc
Message-ID: <cover.1749664902.git.pmartin-gomez@freebox.fr>

During a STA association, its IEs are checked; for some IEs, if their check 
fails, the whole check is failed, for others, if the IE is invalid, the IE is 
dropped and its corresponding capability deactivated. However there are 
dependencies between those capabilities and currently the capabilities are 
activated even if their dependencies are not met.

This patchset checks that the dependencies are met before copying IEs and 
activating capabilities.

Pablo Martin-Gomez (3):
  AP: check a STA is HT before copying VHT capabilities
  AP: check a STA is VHT before copying VHT operation and vendor
    elements
  AP: check a STA is HE before copying EHT/6GHz capabilities

 src/ap/ieee802_11_eht.c | 3 ++-
 src/ap/ieee802_11_he.c  | 3 ++-
 src/ap/ieee802_11_vht.c | 7 ++++---
 3 files changed, 8 insertions(+), 5 deletions(-)

-- 
2.43.0



From pmartin-gomez at freebox.fr  Wed Jun 11 11:38:06 2025
From: pmartin-gomez at freebox.fr (Pablo Martin-Gomez)
Date: Wed, 11 Jun 2025 20:38:06 +0200
Subject: [PATCH 1/3] AP: check a STA is HT before copying VHT capabilities
In-Reply-To: <cover.1749664902.git.pmartin-gomez@freebox.fr>
References: <cover.1749664902.git.pmartin-gomez@freebox.fr>
Message-ID: <ce942f4eab1e97d205564831ed2e916b9c4422a9.1749664902.git.pmartin-gomez@freebox.fr>

A VHT STA is a HT STA. If a VHT STA has an invalid HT Capabilities
element, the HT capability of the STA is deactivated but the VHT
capability is still activated and the element copied.

Deactivate the VHT capability of a STA if it is not a HT STA.

Signed-off-by: Pablo Martin-Gomez <pmartin-gomez at freebox.fr>
---
 src/ap/ieee802_11_vht.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/ap/ieee802_11_vht.c b/src/ap/ieee802_11_vht.c
index df5f8cf76..7415635bf 100644
--- a/src/ap/ieee802_11_vht.c
+++ b/src/ap/ieee802_11_vht.c
@@ -190,7 +190,8 @@ u16 copy_sta_vht_capab(struct hostapd_data *hapd, struct sta_info *sta,
 	/* Disable VHT caps for STAs associated to no-VHT BSSes. */
 	if (!vht_capab || !(sta->flags & WLAN_STA_WMM) ||
 	    !hapd->iconf->ieee80211ac || hapd->conf->disable_11ac ||
-	    !check_valid_vht_mcs(hapd->iface->current_mode, vht_capab)) {
+	    !check_valid_vht_mcs(hapd->iface->current_mode, vht_capab) ||
+	    !(sta->flags & WLAN_STA_HT)) {
 		sta->flags &= ~WLAN_STA_VHT;
 		os_free(sta->vht_capabilities);
 		sta->vht_capabilities = NULL;
-- 
2.43.0



From pmartin-gomez at freebox.fr  Wed Jun 11 11:38:07 2025
From: pmartin-gomez at freebox.fr (Pablo Martin-Gomez)
Date: Wed, 11 Jun 2025 20:38:07 +0200
Subject: [PATCH 2/3] AP: check a STA is VHT before copying VHT operation and vendor elements
In-Reply-To: <cover.1749664902.git.pmartin-gomez@freebox.fr>
References: <cover.1749664902.git.pmartin-gomez@freebox.fr>
Message-ID: <c62910864353dd44a8a5e67f151d173875bff0fc.1749664902.git.pmartin-gomez@freebox.fr>

If a VHT STA has an invalid VHT Capabilities element, the VHT
capability of the STA is deactivated but the VHT operation and vendor
elements might still be copied without the VHT Capabilities element
being present.

Only copy the VHT operation and vendor element if a STA is VHT.

Signed-off-by: Pablo Martin-Gomez <pmartin-gomez at freebox.fr>
---
 src/ap/ieee802_11_vht.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/ap/ieee802_11_vht.c b/src/ap/ieee802_11_vht.c
index 7415635bf..8d760418d 100644
--- a/src/ap/ieee802_11_vht.c
+++ b/src/ap/ieee802_11_vht.c
@@ -216,7 +216,7 @@ u16 copy_sta_vht_capab(struct hostapd_data *hapd, struct sta_info *sta,
 u16 copy_sta_vht_oper(struct hostapd_data *hapd, struct sta_info *sta,
 		      const u8 *vht_oper)
 {
-	if (!vht_oper) {
+	if (!vht_oper || !(sta->flags & WLAN_STA_VHT)) {
 		os_free(sta->vht_operation);
 		sta->vht_operation = NULL;
 		return WLAN_STATUS_SUCCESS;
@@ -243,7 +243,7 @@ u16 copy_sta_vendor_vht(struct hostapd_data *hapd, struct sta_info *sta,
 	unsigned int vht_capab_len;
 
 	if (!ie || len < 5 + 2 + sizeof(struct ieee80211_vht_capabilities) ||
-	    hapd->conf->disable_11ac)
+	    hapd->conf->disable_11ac || !(sta->flags & WLAN_STA_VHT))
 		goto no_capab;
 
 	/* The VHT Capabilities element embedded in vendor VHT */
-- 
2.43.0



From pmartin-gomez at freebox.fr  Wed Jun 11 11:38:08 2025
From: pmartin-gomez at freebox.fr (Pablo Martin-Gomez)
Date: Wed, 11 Jun 2025 20:38:08 +0200
Subject: [PATCH 3/3] AP: check a STA is HE before copying EHT/6GHz capabilities
In-Reply-To: <cover.1749664902.git.pmartin-gomez@freebox.fr>
References: <cover.1749664902.git.pmartin-gomez@freebox.fr>
Message-ID: <c7443229b3908092dacb8de54616c9d344c9069c.1749664902.git.pmartin-gomez@freebox.fr>

A EHT STA and/or a 6GHz capable STA is a HE STA. If a STA has an
invalid HE capabilities, the HE capability of the STA is
deactivated but the EHT/6GHz capability is still activated and their
elements copied.

Deactivate the EHT/6GHz capability of a STA if it is not a HE STA.

Signed-off-by: Pablo Martin-Gomez <pmartin-gomez at freebox.fr>
---
 src/ap/ieee802_11_eht.c | 3 ++-
 src/ap/ieee802_11_he.c  | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/ap/ieee802_11_eht.c b/src/ap/ieee802_11_eht.c
index fe4d0ce56..b8955ce4f 100644
--- a/src/ap/ieee802_11_eht.c
+++ b/src/ap/ieee802_11_eht.c
@@ -401,7 +401,8 @@ u16 copy_sta_eht_capab(struct hostapd_data *hapd, struct sta_info *sta,
 	    ieee80211_invalid_eht_cap_size(mode, hapd->iconf->op_class,
 					   he_capab, eht_capab,
 					   eht_capab_len) ||
-	    !check_valid_eht_mcs(hapd, eht_capab, opmode)) {
+	    !check_valid_eht_mcs(hapd, eht_capab, opmode) ||
+	    !(sta->flags & WLAN_STA_HE)) {
 		sta->flags &= ~WLAN_STA_EHT;
 		os_free(sta->eht_capab);
 		sta->eht_capab = NULL;
diff --git a/src/ap/ieee802_11_he.c b/src/ap/ieee802_11_he.c
index f4adeada9..69fa4bebb 100644
--- a/src/ap/ieee802_11_he.c
+++ b/src/ap/ieee802_11_he.c
@@ -531,7 +531,8 @@ u16 copy_sta_he_6ghz_capab(struct hostapd_data *hapd, struct sta_info *sta,
 {
 	if (!he_6ghz_capab || !hapd->iconf->ieee80211ax ||
 	    hapd->conf->disable_11ax ||
-	    !is_6ghz_op_class(hapd->iconf->op_class)) {
+	    !is_6ghz_op_class(hapd->iconf->op_class) ||
+	    !(sta->flags & WLAN_STA_HE)) {
 		sta->flags &= ~WLAN_STA_6GHZ;
 		os_free(sta->he_6ghz_capab);
 		sta->he_6ghz_capab = NULL;
-- 
2.43.0



From benjamin at sipsolutions.net  Thu Jun 12 01:10:19 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Thu, 12 Jun 2025 10:10:19 +0200
Subject: [PATCH 1/3] WPS: fix leak of dh_ctx in error cases
Message-ID: <20250612081021.130450-1-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

Usually the dh_ctx is passed into the wps state and is free'ed in that
case. However, under some circumstances this may not happen and then a
leak can occur.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 src/ap/wps_hostapd.c            | 2 ++
 wpa_supplicant/wps_supplicant.c | 1 +
 2 files changed, 3 insertions(+)

diff --git a/src/ap/wps_hostapd.c b/src/ap/wps_hostapd.c
index 82d4d5fdd5..ceaf9a4538 100644
--- a/src/ap/wps_hostapd.c
+++ b/src/ap/wps_hostapd.c
@@ -20,6 +20,7 @@
 #include "wps/wps_defs.h"
 #include "wps/wps_dev_attr.h"
 #include "wps/wps_attr_parse.h"
+#include "crypto/dh_group5.h"
 #include "hostapd.h"
 #include "ap_config.h"
 #include "ap_drv_ops.h"
@@ -1071,6 +1072,7 @@ static void hostapd_free_wps(struct wps_context *wps)
 	wps_device_data_free(&wps->dev);
 	bin_clear_free(wps->network_key, wps->network_key_len);
 	hostapd_wps_nfc_clear(wps);
+	dh5_free(wps->dh_ctx);
 	wpabuf_free(wps->dh_pubkey);
 	wpabuf_free(wps->dh_privkey);
 	forced_memzero(wps->psk, sizeof(wps->psk));
diff --git a/wpa_supplicant/wps_supplicant.c b/wpa_supplicant/wps_supplicant.c
index d332f000c3..405c5d6051 100644
--- a/wpa_supplicant/wps_supplicant.c
+++ b/wpa_supplicant/wps_supplicant.c
@@ -1709,6 +1709,7 @@ void wpas_wps_deinit(struct wpa_supplicant *wpa_s)
 #endif /* CONFIG_WPS_ER */
 
 	wps_registrar_deinit(wpa_s->wps->registrar);
+	dh5_free(wpa_s->wps->dh_ctx);
 	wpabuf_free(wpa_s->wps->dh_pubkey);
 	wpabuf_free(wpa_s->wps->dh_privkey);
 	wpabuf_free(wpa_s->wps->dev.vendor_ext_m1);
-- 
2.49.0



From benjamin at sipsolutions.net  Thu Jun 12 01:10:20 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Thu, 12 Jun 2025 10:10:20 +0200
Subject: [PATCH 2/3] RRM: send a refuse reply if the measurement scan failed
In-Reply-To: <20250612081021.130450-1-benjamin@sipsolutions.net>
References: <20250612081021.130450-1-benjamin@sipsolutions.net>
Message-ID: <20250612081021.130450-2-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

If the scan is aborted or missing the expected TSF BSSID, then still
send a REFUSED reply to the AP to let it know. It can request another
measurement if it wants to.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Ilan Peer <ilan.peer at intel.com>
---
 wpa_supplicant/rrm.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/wpa_supplicant/rrm.c b/wpa_supplicant/rrm.c
index 88241e7360..d467ce6bf4 100644
--- a/wpa_supplicant/rrm.c
+++ b/wpa_supplicant/rrm.c
@@ -1555,7 +1555,7 @@ int wpas_beacon_rep_scan_process(struct wpa_supplicant *wpa_s,
 
 	/* If the measurement was aborted, don't report partial results */
 	if (info->aborted)
-		goto out;
+		goto out_refuse;
 
 	wpa_printf(MSG_DEBUG, "RRM: TSF BSSID: " MACSTR " current BSS: " MACSTR,
 		   MAC2STR(info->scan_start_tsf_bssid),
@@ -1564,7 +1564,7 @@ int wpas_beacon_rep_scan_process(struct wpa_supplicant *wpa_s,
 	    !wpas_beacon_rep_scan_match(wpa_s, info->scan_start_tsf_bssid)) {
 		wpa_printf(MSG_DEBUG,
 			   "RRM: Ignore scan results due to mismatching TSF BSSID");
-		goto out;
+		goto out_refuse;
 	}
 
 	for (i = 0; i < scan_res->num; i++) {
@@ -1634,6 +1634,10 @@ int wpas_beacon_rep_scan_process(struct wpa_supplicant *wpa_s,
 	wpas_rrm_send_msr_report(wpa_s, buf);
 	wpabuf_free(buf);
 
+	goto out;
+
+out_refuse:
+	wpas_rrm_refuse_request(wpa_s);
 out:
 	wpas_clear_beacon_rep_data(wpa_s);
 	return 1;
-- 
2.49.0



From benjamin at sipsolutions.net  Thu Jun 12 01:10:21 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Thu, 12 Jun 2025 10:10:21 +0200
Subject: [PATCH 3/3] tests: permit multiple GO negotiation failures
In-Reply-To: <20250612081021.130450-1-benjamin@sipsolutions.net>
References: <20250612081021.130450-1-benjamin@sipsolutions.net>
Message-ID: <20250612081021.130450-3-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

In this test the station will do a P2P find and then send a GO
negotiation request. However, the other station may not be on the listen
channel at the time when the action frame is transmitted. In this case,
the find/GO neg request sequence is restarted.

The test currently times out if this sequence fails twice in a row. Be
more tolerant to this type of failure and increase the timeout.

It may also be possible to improve the retransmit strategy.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 tests/hwsim/test_p2p_grpform.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/hwsim/test_p2p_grpform.py b/tests/hwsim/test_p2p_grpform.py
index 6b18c42e79..178bd73817 100644
--- a/tests/hwsim/test_p2p_grpform.py
+++ b/tests/hwsim/test_p2p_grpform.py
@@ -958,7 +958,7 @@ def test_grpform_not_ready2(dev):
     if not dev[1].discover_peer(addr0):
         raise Exception("Could not discover peer")
     dev[1].global_request("P2P_CONNECT " + addr0 + " pbc")
-    ev = dev[0].wait_global_event(["P2P-GO-NEG-REQUEST"], timeout=5)
+    ev = dev[0].wait_global_event(["P2P-GO-NEG-REQUEST"], timeout=10)
     if ev is None:
         raise Exception("No P2P-GO-NEG-REQUEST event")
     dev[0].dump_monitor()
-- 
2.49.0



From pmartin-gomez at freebox.fr  Thu Jun 12 09:33:05 2025
From: pmartin-gomez at freebox.fr (Pablo Martin-Gomez)
Date: Thu, 12 Jun 2025 18:33:05 +0200
Subject: [PATCH] AP: Do not set reserved bits in HE Capabilities elements
Message-ID: <20250612163305.4048330-1-pmartin-gomez@freebox.fr>

Currently, bits are set in the Supported Channel Width Set subfields in
the HE Capabilities elements regardless of the current band. However,
table 9-322b from 802.11ax standard indicates that some bits are
reserved depending on the current band. Convention states that reserved
bits shall be set to zero unless otherwise stated.

Set bits according to current band and supported widths.

Signed-off-by: Pablo Martin-Gomez <pmartin-gomez at freebox.fr>
---
 src/ap/ieee802_11_he.c | 48 ++++++++++++++++++++++++++----------------
 1 file changed, 30 insertions(+), 18 deletions(-)

diff --git a/src/ap/ieee802_11_he.c b/src/ap/ieee802_11_he.c
index cc731b914..f4adeada9 100644
--- a/src/ap/ieee802_11_he.c
+++ b/src/ap/ieee802_11_he.c
@@ -91,7 +91,7 @@ u8 * hostapd_eid_he_capab(struct hostapd_data *hapd, u8 *eid,
 {
 	struct ieee80211_he_capabilities *cap;
 	struct hostapd_hw_modes *mode = hapd->iface->current_mode;
-	u8 he_oper_chwidth = ~HE_PHYCAP_CHANNEL_WIDTH_MASK;
+	u8 he_oper_chwidth = 0;
 	u8 *pos = eid;
 	u8 ie_size = 0, mcs_nss_size = 4, ppet_size = 0;
 
@@ -102,23 +102,35 @@ u8 * hostapd_eid_he_capab(struct hostapd_data *hapd, u8 *eid,
 	ppet_size = ieee80211_he_ppet_size(mode->he_capab[opmode].ppet[0],
 					   mode->he_capab[opmode].phy_cap);
 
-	switch (hapd->iface->conf->he_oper_chwidth) {
-	case CONF_OPER_CHWIDTH_80P80MHZ:
-		he_oper_chwidth |=
-			HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G;
-		mcs_nss_size += 4;
-		/* fall through */
-	case CONF_OPER_CHWIDTH_160MHZ:
-		he_oper_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
-		mcs_nss_size += 4;
-		/* fall through */
-	case CONF_OPER_CHWIDTH_80MHZ:
-	case CONF_OPER_CHWIDTH_USE_HT:
-		he_oper_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G |
-			HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G;
-		break;
-	default:
-		break;
+	if (mode->mode == HOSTAPD_MODE_IEEE80211A) {
+		switch (hapd->iface->conf->he_oper_chwidth) {
+		case CONF_OPER_CHWIDTH_80P80MHZ:
+			he_oper_chwidth |=
+				HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G;
+			mcs_nss_size += 4;
+			/* fall through */
+		case CONF_OPER_CHWIDTH_160MHZ:
+			he_oper_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
+			mcs_nss_size += 4;
+			/* fall through */
+		case CONF_OPER_CHWIDTH_80MHZ:
+		case CONF_OPER_CHWIDTH_USE_HT:
+			he_oper_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G;
+			break;
+		default:
+			break;
+		}
+	} else  {
+		switch (hapd->iface->conf->he_oper_chwidth) {
+		case CONF_OPER_CHWIDTH_80P80MHZ:
+		case CONF_OPER_CHWIDTH_160MHZ:
+		case CONF_OPER_CHWIDTH_80MHZ:
+		case CONF_OPER_CHWIDTH_USE_HT:
+			he_oper_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G;
+			break;
+		default:
+			break;
+		}
 	}
 
 	ie_size += mcs_nss_size + ppet_size;
-- 
2.43.0



From benjamin at sipsolutions.net  Tue Jun 17 10:39:58 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Tue, 17 Jun 2025 19:39:58 +0200
Subject: [PATCH] AP MLD: never keep a per-link wpa_sm
Message-ID: <20250617173958.882395-1-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

The __checK_assoc_ies function needs to create a per-link wpa_sm
temporarily. However, later on sta->wpa_sm should just be a reference to
the main wpa_sm.

Change the code so that the temporary state is self-contained to
__check_assoc_ies. This fixes a memory leak should __check_assoc_ies
return an error.

Also change the function to pass the wpa_sm for the assoc_link instead
of just a link boolean. The internal lookup appears to not have worked
and this also simplifies the code.

Fixes: 5f5db9366cde ("AP: MLO: Process Multi-Link element from (Re)Association Request frame")
Fixes: 84d2a36da02a ("AP MLD: Require same AKM and pairwise cipher for all links")

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 src/ap/ieee802_11.c | 134 ++++++++++++++++++++++++++------------------
 1 file changed, 80 insertions(+), 54 deletions(-)

diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index bf36d7f9e3..7551310e4e 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -4220,53 +4220,52 @@ static bool check_sa_query(struct hostapd_data *hapd, struct sta_info *sta,
 static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			     const u8 *ies, size_t ies_len,
 			     struct ieee802_11_elems *elems, int reassoc,
-			     bool link)
+			     struct wpa_state_machine *assoc_wpa_sm)
 {
 	int resp;
 	const u8 *wpa_ie;
 	size_t wpa_ie_len;
 	const u8 *p2p_dev_addr = NULL;
-	struct hostapd_data *assoc_hapd;
-	struct sta_info *assoc_sta = NULL;
 
 	resp = check_ssid(hapd, sta, elems->ssid, elems->ssid_len);
 	if (resp != WLAN_STATUS_SUCCESS)
-		return resp;
+		goto out;
 	resp = check_wmm(hapd, sta, elems->wmm, elems->wmm_len);
 	if (resp != WLAN_STATUS_SUCCESS)
-		return resp;
+		goto out;
 	resp = check_ext_capab(hapd, sta, elems->ext_capab,
 			       elems->ext_capab_len);
 	if (resp != WLAN_STATUS_SUCCESS)
-		return resp;
+		goto out;
 	resp = copy_supp_rates(hapd, sta, elems);
 	if (resp != WLAN_STATUS_SUCCESS)
-		return resp;
+		goto out;
 
 	resp = check_multi_ap(hapd, sta, elems->multi_ap, elems->multi_ap_len);
 	if (resp != WLAN_STATUS_SUCCESS)
-		return resp;
+		goto out;
 
 	resp = copy_sta_ht_capab(hapd, sta, elems->ht_capabilities);
 	if (resp != WLAN_STATUS_SUCCESS)
-		return resp;
+		goto out;
 	if (hapd->iconf->ieee80211n && hapd->iconf->require_ht &&
 	    !(sta->flags & WLAN_STA_HT)) {
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_INFO, "Station does not support "
 			       "mandatory HT PHY - reject association");
-		return WLAN_STATUS_ASSOC_DENIED_NO_HT;
+		resp = WLAN_STATUS_ASSOC_DENIED_NO_HT;
+		goto out;
 	}
 
 #ifdef CONFIG_IEEE80211AC
 	if (hapd->iconf->ieee80211ac) {
 		resp = copy_sta_vht_capab(hapd, sta, elems->vht_capabilities);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 
 		resp = set_sta_vht_opmode(hapd, sta, elems->opmode_notif);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 	}
 
 	if (hapd->iconf->ieee80211ac && hapd->iconf->require_vht &&
@@ -4274,14 +4273,15 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_INFO, "Station does not support "
 			       "mandatory VHT PHY - reject association");
-		return WLAN_STATUS_ASSOC_DENIED_NO_VHT;
+		resp = WLAN_STATUS_ASSOC_DENIED_NO_VHT;
+		goto out;
 	}
 
 	if (hapd->conf->vendor_vht && !elems->vht_capabilities) {
 		resp = copy_sta_vendor_vht(hapd, sta, elems->vendor_vht,
 					   elems->vendor_vht_len);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 	}
 #endif /* CONFIG_IEEE80211AC */
 #ifdef CONFIG_IEEE80211AX
@@ -4290,14 +4290,15 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					 elems->he_capabilities,
 					 elems->he_capabilities_len);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 
 		if (hapd->iconf->require_he && !(sta->flags & WLAN_STA_HE)) {
 			hostapd_logger(hapd, sta->addr,
 				       HOSTAPD_MODULE_IEEE80211,
 				       HOSTAPD_LEVEL_INFO,
 				       "Station does not support mandatory HE PHY - reject association");
-			return WLAN_STATUS_DENIED_HE_NOT_SUPPORTED;
+			resp = WLAN_STATUS_DENIED_HE_NOT_SUPPORTED;
+			goto out;
 		}
 
 		if (is_6ghz_op_class(hapd->iconf->op_class)) {
@@ -4306,12 +4307,13 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					       HOSTAPD_MODULE_IEEE80211,
 					       HOSTAPD_LEVEL_INFO,
 					       "Station does not support mandatory HE PHY - reject association");
-				return WLAN_STATUS_DENIED_HE_NOT_SUPPORTED;
+				resp = WLAN_STATUS_DENIED_HE_NOT_SUPPORTED;
+				goto out;
 			}
 			resp = copy_sta_he_6ghz_capab(hapd, sta,
 						      elems->he_6ghz_band_cap);
 			if (resp != WLAN_STATUS_SUCCESS)
-				return resp;
+				goto out;
 		}
 	}
 #endif /* CONFIG_IEEE80211AX */
@@ -4323,12 +4325,12 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					  elems->eht_capabilities,
 					  elems->eht_capabilities_len);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 
-		if (!link) {
+		if (!assoc_wpa_sm) {
 			resp = hostapd_process_ml_assoc_req(hapd, elems, sta);
 			if (resp != WLAN_STATUS_SUCCESS)
-				return resp;
+				goto out;
 		}
 	}
 #endif /* CONFIG_IEEE80211BE */
@@ -4376,7 +4378,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		if (sta->wps_ie && wps_validate_assoc_req(sta->wps_ie) < 0) {
 			wpa_printf(MSG_DEBUG, "WPS: Invalid WPS IE in "
 				   "(Re)Association Request - reject");
-			return WLAN_STATUS_INVALID_IE;
+			resp = WLAN_STATUS_INVALID_IE;
+			goto out;
 		}
 	} else if (hapd->conf->wps_state && wpa_ie == NULL) {
 		wpa_printf(MSG_DEBUG, "STA did not include WPA/RSN IE in "
@@ -4388,7 +4391,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_INFO,
 			       "No WPA/RSN IE in association request");
-		return WLAN_STATUS_INVALID_IE;
+		resp = WLAN_STATUS_INVALID_IE;
+		goto out;
 	}
 
 	if (hapd->conf->wpa && wpa_ie) {
@@ -4402,10 +4406,10 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		wpa_ie_len += 2;
 
 		if (!sta->wpa_sm) {
-			if (!link)
-				assoc_sta = hostapd_ml_get_assoc_sta(
-					hapd, sta, &assoc_hapd);
-
+			/* NOTE: For links other than the assoc-link the
+			 * separate wpa_sm is only allocated internally to this
+			 * function.
+			 */
 			sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth,
 							sta->addr,
 							p2p_dev_addr);
@@ -4413,7 +4417,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			if (!sta->wpa_sm) {
 				wpa_printf(MSG_WARNING,
 					   "Failed to initialize RSN state machine");
-				return WLAN_STATUS_UNSPECIFIED_FAILURE;
+				resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+				goto out;
 			}
 		}
 
@@ -4440,11 +4445,11 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					  0,
 					  elems->mdie, elems->mdie_len,
 					  elems->owe_dh, elems->owe_dh_len,
-					  assoc_sta ? assoc_sta->wpa_sm : NULL,
+					  assoc_wpa_sm,
 					  ap_sta_is_mld(hapd, sta));
 		resp = wpa_res_to_status_code(res);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 
 		if (wpa_auth_uses_mfp(sta->wpa_sm))
 			sta->flags |= WLAN_STA_MFP;
@@ -4463,17 +4468,18 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					   "to use association (not "
 					   "re-association) with FT auth_alg",
 					   MAC2STR(sta->addr));
-				return WLAN_STATUS_UNSPECIFIED_FAILURE;
+				resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+				goto out;
 			}
 
 			resp = wpa_ft_validate_reassoc(sta->wpa_sm, ies,
 						       ies_len);
 			if (resp != WLAN_STATUS_SUCCESS)
-				return resp;
+				goto out;
 		}
 #endif /* CONFIG_IEEE80211R_AP */
 
-		if (link)
+		if (assoc_wpa_sm)
 			goto skip_sae_owe;
 #ifdef CONFIG_SAE
 		if (wpa_auth_uses_sae(sta->wpa_sm) && sta->sae &&
@@ -4488,7 +4494,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 				wpa_printf(MSG_DEBUG,
 					   "SAE: No PMKSA cache entry found for "
 					   MACSTR, MAC2STR(sta->addr));
-				return WLAN_STATUS_INVALID_PMKID;
+				resp = WLAN_STATUS_INVALID_PMKID;
+				goto out;
 			}
 			wpa_printf(MSG_DEBUG, "SAE: " MACSTR
 				   " using PMKSA caching", MAC2STR(sta->addr));
@@ -4499,7 +4506,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			wpa_printf(MSG_DEBUG, "SAE: " MACSTR " tried to use "
 				   "SAE AKM after non-SAE auth_alg %u",
 				   MAC2STR(sta->addr), sta->auth_alg);
-			return WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG;
+			resp = WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG;
+			goto out;
 		}
 
 		if (hapd->conf->sae_pwe == SAE_PWE_BOTH &&
@@ -4510,7 +4518,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			wpa_printf(MSG_INFO, "SAE: " MACSTR
 				   " indicates support for SAE H2E, but did not use it",
 				   MAC2STR(sta->addr));
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 		}
 #endif /* CONFIG_SAE */
 
@@ -4521,7 +4530,7 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			resp = owe_process_assoc_req(hapd, sta, elems->owe_dh,
 						     elems->owe_dh_len);
 			if (resp != WLAN_STATUS_SUCCESS)
-				return resp;
+				goto out;
 		}
 #endif /* CONFIG_OWE */
 	skip_sae_owe:
@@ -4549,7 +4558,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					    elems->owe_dh_len) < 0) {
 				dpp_pfs_free(sta->dpp_pfs);
 				sta->dpp_pfs = NULL;
-				return WLAN_STATUS_UNSPECIFIED_FAILURE;
+				resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+				goto out;
 			}
 		}
 
@@ -4565,7 +4575,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 				       HOSTAPD_LEVEL_INFO,
 				       "Station tried to use TKIP with HT "
 				       "association");
-			return WLAN_STATUS_CIPHER_REJECTED_PER_POLICY;
+			resp = WLAN_STATUS_CIPHER_REJECTED_PER_POLICY;
+			goto out;
 		}
 
 		wpa_auth_set_ssid_protection(
@@ -4595,7 +4606,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			wpa_printf(MSG_DEBUG,
 				   "HS 2.0: PMF not negotiated by release %d station "
 				   MACSTR, release, MAC2STR(sta->addr));
-			return WLAN_STATUS_ROBUST_MGMT_FRAME_POLICY_VIOLATION;
+			resp = WLAN_STATUS_ROBUST_MGMT_FRAME_POLICY_VIOLATION;
+			goto out;
 		}
 	} else {
 		sta->hs20_ie = NULL;
@@ -4626,7 +4638,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 	    hapd->conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) {
 		wpa_printf(MSG_INFO,
 			   "MBO: Reject WPA2 association without PMF");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 #endif /* CONFIG_MBO */
 
@@ -4643,14 +4656,17 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		if (hostapd_drv_channel_info(hapd, &ci) != 0) {
 			wpa_printf(MSG_WARNING,
 				   "Failed to get channel info to validate received OCI in FILS (Re)Association Request frame");
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 		}
 
 		if (get_sta_tx_parameters(sta->wpa_sm,
 					  channel_width_to_int(ci.chanwidth),
 					  ci.seg1_idx, &tx_chanwidth,
-					  &tx_seg1_idx) < 0)
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+					  &tx_seg1_idx) < 0) {
+			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
+		}
 
 		res = ocv_verify_tx_params(elems->oci, elems->oci_len, &ci,
 					   tx_chanwidth, tx_seg1_idx);
@@ -4666,7 +4682,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			wpa_msg(hapd->msg_ctx, MSG_INFO, OCV_FAILURE "addr="
 				MACSTR " frame=fils-reassoc-req error=%s",
 				MAC2STR(sta->addr), ocv_errorstr);
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 		}
 	}
 #endif /* CONFIG_FILS && CONFIG_OCV */
@@ -4705,7 +4722,21 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		hostapd_wfa_capab(hapd, sta, elems->wfa_capab,
 				  elems->wfa_capab + elems->wfa_capab_len);
 
-	return WLAN_STATUS_SUCCESS;
+out:
+	if (resp != WLAN_STATUS_SUCCESS || assoc_wpa_sm) {
+		wpa_auth_sta_deinit(sta->wpa_sm);
+
+		/* Only keep a reference to the main wpa_sm and drop the
+		 * per-link instance.
+		 * This reference is needed during group rekey handling.
+		 */
+		if (resp == WLAN_STATUS_SUCCESS)
+			sta->wpa_sm = assoc_wpa_sm;
+		else
+			sta->wpa_sm = NULL;
+	}
+
+	return resp;
 }
 
 
@@ -4722,7 +4753,7 @@ static int check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 	}
 
 	return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, reassoc,
-				 false);
+				 NULL);
 }
 
 
@@ -4841,7 +4872,8 @@ static int ieee80211_ml_process_link(struct hostapd_data *hapd,
 	sta->mld_assoc_link_id = origin_sta->mld_assoc_link_id;
 	ap_sta_set_mld(sta, true);
 
-	status = __check_assoc_ies(hapd, sta, NULL, 0, &elems, reassoc, true);
+	status = __check_assoc_ies(hapd, sta, NULL, 0, &elems, reassoc,
+				   origin_sta->wpa_sm);
 	if (status != WLAN_STATUS_SUCCESS) {
 		wpa_printf(MSG_DEBUG, "MLD: link: Element check failed");
 		goto out;
@@ -4872,12 +4904,6 @@ static int ieee80211_ml_process_link(struct hostapd_data *hapd,
 			ieee802_11_update_beacons(hapd->iface);
 	}
 
-	/* Maintain state machine reference on all link STAs, this is needed
-	 * during group rekey handling.
-	 */
-	wpa_auth_sta_deinit(sta->wpa_sm);
-	sta->wpa_sm = origin_sta->wpa_sm;
-
 	/*
 	 * Do not initialize the EAPOL state machine.
 	 * TODO: Maybe it is needed?
-- 
2.49.0



From dodgy-cane-freebee at duck.com  Thu Jun  5 23:41:30 2025
From: dodgy-cane-freebee at duck.com (dodgy-cane-freebee at duck.com)
Date: Fri, 06 Jun 2025 02:41:30 -0400
Subject: CTRL-EVENT-SIGNAL-CHANGE flood in logs
Message-ID: <F9E9E687-2F39-47F1-B87D-DF3474CEE8AA.1@smtp-inbound1.duck.com>

Hello. I am using wpa_supplicant version v2.11-hostap_2_11+ (package version 2:2.11-3) with NetworkManager on Arch Linux with default settings. I'm experiencing an issue where my entire journalctl log is constantly flooded with repetitive messages:
$ journalctl -u wpa_supplicant | grep CTRL-EVENT-SIGNAL-CHANGE | wc -l
3363932
$ journalctl -b | tail -n 20
??? 06 09:33:42 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800
??? 06 09:33:45 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800
??? 06 09:33:48 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-55 noise=9999 txrate=864800
??? 06 09:33:51 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-55 noise=9999 txrate=864800
??? 06 09:33:54 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-56 noise=9999 txrate=864800
??? 06 09:33:57 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800
??? 06 09:34:00 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800
??? 06 09:34:03 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800
??? 06 09:34:06 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800
??? 06 09:34:09 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800
??? 06 09:34:12 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=720600
??? 06 09:34:15 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=720600
??? 06 09:34:18 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800
??? 06 09:34:21 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800
??? 06 09:34:24 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800
??? 06 09:34:27 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800
??? 06 09:34:30 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-56 noise=9999 txrate=864800
??? 06 09:34:33 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-58 noise=9999 txrate=864800
??? 06 09:34:36 workstation wpa_supplicant[1566]: wlp38s0: CTRL-EVENT-SIGNAL-CHANGE above=1 signal=-57 noise=9999 txrate=864800



From aswathisivan89 at gmail.com  Mon Jun 16 05:28:57 2025
From: aswathisivan89 at gmail.com (Aswathi Sivalingam)
Date: Mon, 16 Jun 2025 17:58:57 +0530
Subject: Persistent STA Reconnection Failure After CTRL-EVENT-CHANNEL-SWITCH
 (wpa_supplicant 2.10 + BCM43430)
Message-ID: <CAK0h4nKQCGMyU+TYHJnSKRi+D188fpQ8SQ90yPXw1yLH7isVVg@mail.gmail.com>

Hi all,

I'm debugging a persistent STA reconnection failure observed in
production testing on an embedded Linux device using wpa_supplicant
v2.10 and the BCM43430 (CYFMAC43430) Wi-Fi chipset.

Summary:

    After a power cycle (both device and AP rebooted), the STA
interface (wlan0) fails to reconnect to a saved SSID. The failure
persists for hours, with repeated association attempts, until the
device is manually rebooted.
    The logs show a rare CTRL-EVENT-CHANNEL-SWITCH event immediately
before the failure loop begins. We suspect this might trigger an
unrecoverable path in the supplicant or driver.

Environment:

    - SoC: i.MX6UL
    - Kernel: 5.15.71 (Yocto Kirkstone)
    - Chipset: BCM43430 (CYFMAC43430 over SDIO)
    - Driver: cyfmac43430-sdio
    - wpa_supplicant: v2.10 (with NetworkManager)
    - AP+STA concurrency: yes (interfaces: wlan-ap, wlan0)
    - Regulatory domain: GB ? DE during association

Relevant Log Excerpt:

    wlan0: CTRL-EVENT-DISCONNECTED reason=0 locally_generated=1
    wlan0: CTRL-EVENT-REGDOM-CHANGE init=USER type=COUNTRY alpha2=GB
    wlan0: Trying to associate with SSID 'ASUS'
    wlan0: CTRL-EVENT-REGDOM-CHANGE init=COUNTRY_IE type=COUNTRY alpha2=DE
    wlan0: CTRL-EVENT-CHANNEL-SWITCH freq=2447 ht_enabled=1
    NetworkManager: ip-config failed; device state change to disconnected
    wlan0: CTRL-EVENT-DISCONNECTED reason=3 locally_generated=1
    ...
    [This loop repeats until device is rebooted]

Observations:

    - In normal cases, changing the AP channel or toggling AP state
does not cause issues ? the STA adapts and reconnects.
    - However, this specific failure only occurs in production and is
not easily reproducible.
    - I have never seen CTRL-EVENT-CHANNEL-SWITCH under normal
conditions ? it only appears in the failure case.
    - AP+STA always operate on the same channel.

Questions:

    1. Could CTRL-EVENT-CHANNEL-SWITCH trigger a code path in
wpa_supplicant or the driver that leads to a persistent reconnection
failure?
    2. Are there known issues in v2.10 related to channel switching or
regulatory domain transitions (especially in AP+STA mode)?
    3. Is there a way to reliably simulate a channel switch on the AP
(e.g. via hostapd) to help reproduce this?
    4. Based on the logs above, do you see any signs that might point
to a root cause?

Any advice or pointers (or confirmation this is known behavior) would
be much appreciated.

---
Thanks,
Aswathi Sivalingam


From benjamin at sipsolutions.net  Wed Jun 18 05:22:04 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 18 Jun 2025 14:22:04 +0200
Subject: [PATCH] tests: use logger.exception to dump full traceback
Message-ID: <20250618122204.957868-1-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

The log sometimes does not contain relevant information. Switch to use
logger.exception which dumps the full exception information. Note that
the logging happens with level ERROR, so it will be dumped to the
stdout/stderr by default.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 tests/hwsim/run-tests.py | 41 +++++++++++++---------------------------
 1 file changed, 13 insertions(+), 28 deletions(-)

diff --git a/tests/hwsim/run-tests.py b/tests/hwsim/run-tests.py
index ea00316ee9..eafec24797 100755
--- a/tests/hwsim/run-tests.py
+++ b/tests/hwsim/run-tests.py
@@ -49,8 +49,7 @@ def reset_devs(dev, apdev):
         try:
             d.reset()
         except Exception as e:
-            logger.info("Failed to reset device " + d.ifname)
-            print(str(e))
+            logger.exception("Failed to reset device " + d.ifname)
             ok = False
 
     wpas = None
@@ -75,8 +74,7 @@ def reset_devs(dev, apdev):
                 hapd.remove(iface)
         hapd.remove('as-erp')
     except Exception as e:
-        logger.info("Failed to remove hostapd interface")
-        print(str(e))
+        logger.exception("Failed to remove hostapd interface")
         ok = False
     return ok
 
@@ -94,8 +92,8 @@ def add_log_file(conn, test, run, type, path):
         conn.execute(sql, params)
         conn.commit()
     except Exception as e:
-        print("sqlite: " + str(e))
-        print("sql: %r" % (params, ))
+        logger.exception("sqlite:")
+        logger.error("sql: %r" % (params, ))
 
 def report(conn, prefill, build, commit, run, test, result, duration, logdir,
            sql_commit=True):
@@ -113,8 +111,8 @@ def report(conn, prefill, build, commit, run, test, result, duration, logdir,
             if sql_commit:
                 conn.commit()
         except Exception as e:
-            print("sqlite: " + str(e))
-            print("sql: %r" % (params, ))
+            logger.exception("sqlite:")
+            logger.error("sql: %r" % (params, ))
 
         if result == "FAIL":
             for log in ["log", "log0", "log1", "log2", "log3", "log5",
@@ -213,8 +211,7 @@ def rename_log(logdir, basename, testname, dev):
             dev.relog()
             subprocess.call(['chown', '-f', getpass.getuser(), srcname])
     except Exception as e:
-        logger.info("Failed to rename log files")
-        logger.info(e)
+        logger.exception("Failed to rename log files")
 
 def is_long_duration_test(t):
     return hasattr(t, "long_duration_test") and t.long_duration_test
@@ -377,8 +374,8 @@ def main():
                 try:
                     conn.execute(sql, params)
                 except Exception as e:
-                    print("sqlite: " + str(e))
-                    print("sql: %r" % (params,))
+                    logger.exception("sqlite:")
+                    logger.error("sql: %r" % (params,))
         if conn:
             conn.commit()
             conn.close()
@@ -568,8 +565,7 @@ def main():
                         raise Exception("Global PING failed for {}".format(d.ifname))
                     d.request("NOTE TEST-START " + name)
                 except Exception as e:
-                    logger.info("Failed to issue TEST-START before " + name + " for " + d.ifname)
-                    logger.info(e)
+                    logger.exception("Failed to issue TEST-START before " + name + " for " + d.ifname)
                     print("FAIL " + name + " - could not start test")
                     if conn:
                         conn.close()
@@ -622,17 +618,8 @@ def main():
                 logger.info("Skip test case: %s" % e)
                 skip_reason = e
                 result = "SKIP"
-            except NameError as e:
-                import traceback
-                logger.info(e)
-                traceback.print_exc()
-                result = "FAIL"
             except Exception as e:
-                import traceback
-                logger.info(e)
-                traceback.print_exc()
-                if args.loglevel == logging.WARNING:
-                    print("Exception: " + str(e))
+                logger.exception(f"Exception during test execution: {str(e)}")
                 result = "FAIL"
 
             # Work around some objects having __del__, we really should
@@ -649,8 +636,7 @@ def main():
                     d.dump_monitor()
                     d.request("NOTE TEST-STOP " + name)
                 except Exception as e:
-                    logger.info("Failed to issue TEST-STOP after {} for {}".format(name, d.ifname))
-                    logger.info(e)
+                    logger.exception("Failed to issue TEST-STOP after {} for {}".format(name, d.ifname))
                     result = "FAIL"
             if args.no_reset:
                 print("Leaving devices in current state")
@@ -674,8 +660,7 @@ def main():
             try:
                 hapd = HostapdGlobal()
             except Exception as e:
-                print("Failed to connect to hostapd interface")
-                print(str(e))
+                logger.exception("Failed to connect to hostapd interface")
                 reset_ok = False
                 result = "FAIL"
                 hapd = None
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 18 05:35:23 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 18 Jun 2025 14:35:23 +0200
Subject: [PATCH 1/9] BSS: use correct AP MLD ID
Message-ID: <20250618123531.962016-1-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

We need to be more careful about the ID used when resolving the RNR and
doing an ML probe request. The thing to keep in mind is that the AP MLD
ID is local to the reported beacon and can change depending on who is
reporting the information. On the other hand, the Multiple BSSID-Index
is static.

As such, the AP MLD ID that is local to the information should be used
for parsing the RNR. This AP MLD ID can simply be retrieved from the
Multi-Link Element. However, if an AP MLD ID is not included, then it
must be assumed to be identical to the Multiple-BSSID Index Element.

Note that an AP MLD ID will only ever be included if the underlying code
that does the inheritance chooses to include it in the Multi-Link
Element that it inserts. This code may also rewrite the RNR entries to
ensure they match the expected value and leave out the AP MLD ID in the
Multi-Link Element.

In contrast, for the purpose of sending a Multi-Link Probe Request we
need to know whether or not the AP is transmitting. For this information
we must purely rely on the Multiple-BSSID Index Element.

Fixes: de5e01010cb2 ("wpa_supplicant: Support ML probe request")
Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 tests/test-bss.c        |  67 ++++++++++++++------
 wpa_supplicant/bss.c    | 132 +++++++++++++++++++++++++++++-----------
 wpa_supplicant/bss.h    |   2 +-
 wpa_supplicant/events.c |   6 +-
 4 files changed, 151 insertions(+), 56 deletions(-)

diff --git a/tests/test-bss.c b/tests/test-bss.c
index 97af7e4a1e..3c72f10106 100644
--- a/tests/test-bss.c
+++ b/tests/test-bss.c
@@ -17,55 +17,82 @@
 #define ASSERT_CMP_INT(a, cmp, b) { \
 	ssize_t __a = (a); ssize_t __b = (b);		\
 		if (!(__a cmp __b)) {			\
-			wpa_printf(MSG_ERROR, "Assertion failed: %ld %s %ld", \
-				   __a, #cmp, __b);	\
+			wpa_printf(MSG_ERROR,		\
+				   "Assertion failed: %ld %s %ld (%s %s %s)", \
+				   __a, #cmp, __b, #a, #cmp, #b);	\
 			abort();			\
 		}					\
 	}
 
-void test_parse_basic_ml(struct wpa_supplicant *wpa_s, u8 mld_id)
+void test_parse_basic_ml(struct wpa_supplicant *wpa_s, u8 mld_id,
+			 int mbssid_idx)
 {
-	const u8 mld_ie[] = {
+	u8 params_link = RNR_BSS_PARAM_SAME_SSID;
+	u8 params_bss = RNR_BSS_PARAM_SAME_SSID |
+			((mbssid_idx >= 0) ?
+				RNR_BSS_PARAM_MULTIPLE_BSSID : 0) |
+			((mbssid_idx == 0) ?
+				RNR_BSS_PARAM_TRANSMITTED_BSSID : 0);
+	const u8 rnr_ie[] = {
 		/* RNR */
 		WLAN_EID_REDUCED_NEIGHBOR_REPORT, 40,
 		0x00, 0x10, 0x51, 0x01, 0xff, 0x00, 0x11, 0x22,
-		0x33, 0x44, 0x01, 0x68, 0x05, 0x2d, 0xa6, 0x42,
+		0x33, 0x44, 0x01, 0x68, 0x05, 0x2d, 0xa6, params_bss,
 		0xfe, mld_id, 0x10, 0x00, 0x00, 0x10, 0x51, 0x06,
 		0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x02, 0x68,
-		0x05, 0x2d, 0xa6, 0x42, 0xfe, mld_id, 0x11, 0x00,
+		0x05, 0x2d, 0xa6, params_link, 0xfe, mld_id, 0x11, 0x00,
+	};
+	const u8 ml_ie[] = {
 		/* basic ML */
 		WLAN_EID_EXTENSION, 1 + 15, WLAN_EID_EXT_MULTI_LINK,
 		0xb0, 0x01, 0x0d, 0x02, 0x00, 0x00, 0x00, 0x07,
 		0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
 	};
+	const u8 ml_ie_mld_id[] = {
+		/* basic ML */
+		WLAN_EID_EXTENSION, 1 + 16, WLAN_EID_EXT_MULTI_LINK,
+		0xb0, 0x03, 0x0e, 0x02, 0x00, 0x00, 0x00, 0x07,
+		0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, mld_id
+	};
 	const u8 mbssid_idx_ie[] = {
-		WLAN_EID_MULTIPLE_BSSID_INDEX, 1, mld_id,
+		WLAN_EID_MULTIPLE_BSSID_INDEX, 1, mbssid_idx,
 	};
 	struct {
 		struct wpa_bss bss;
-		u8 ies[sizeof(mld_ie) + sizeof(mbssid_idx_ie)];
+		u8 ies[sizeof(rnr_ie) + sizeof(ml_ie_mld_id) +
+		       sizeof(mbssid_idx_ie)];
 	} bss;
 	u8 ap_mld_addr[ETH_ALEN];
 	u16 missing_links;
 	u8 ret;
-	u8 ap_mld_id;
+	bool nontransmitted;
 
-	memcpy(bss.bss.ies, mld_ie, sizeof(mld_ie));
-	bss.bss.ie_len = sizeof(mld_ie);
+	memcpy(bss.bss.ies, rnr_ie, sizeof(rnr_ie));
+	bss.bss.ie_len = sizeof(rnr_ie);
+
+	if (mld_id == (mbssid_idx < 0 ? 0 : mbssid_idx)) {
+		memcpy(bss.bss.ies + bss.bss.ie_len, ml_ie, sizeof(ml_ie));
+		bss.bss.ie_len += sizeof(ml_ie);
+	} else {
+		memcpy(bss.bss.ies + bss.bss.ie_len, ml_ie_mld_id,
+		       sizeof(ml_ie_mld_id));
+		bss.bss.ie_len += sizeof(ml_ie_mld_id);
+	}
 
-	if (mld_id > 0) {
-		memcpy(bss.bss.ies + sizeof(mld_ie), mbssid_idx_ie,
+	if (mbssid_idx > 0) {
+		memcpy(bss.bss.ies + bss.bss.ie_len, mbssid_idx_ie,
 		       sizeof(mbssid_idx_ie));
 		bss.bss.ie_len += sizeof(mbssid_idx_ie);
 	}
 
 	ret = wpa_bss_parse_basic_ml_element(wpa_s, &bss.bss, ap_mld_addr,
-					     &missing_links, NULL, &ap_mld_id);
+					     &missing_links, NULL,
+					     &nontransmitted);
 
 	ASSERT_CMP_INT(ret, ==, 0);
 	ASSERT_CMP_INT(bss.bss.valid_links, ==, 1);
 	ASSERT_CMP_INT(missing_links, ==, 0x0002);
-	ASSERT_CMP_INT(ap_mld_id, ==, mld_id);
+	ASSERT_CMP_INT(nontransmitted, ==, mbssid_idx > 0);
 }
 
 #define RUN_TEST(func, ...) do {			\
@@ -89,8 +116,14 @@ int main(void)
 	wpa_s = wpa_supplicant_add_iface(global, &iface, NULL);
 	assert(wpa_s);
 
-	RUN_TEST(test_parse_basic_ml, 0);
-	RUN_TEST(test_parse_basic_ml, 1);
+	RUN_TEST(test_parse_basic_ml, 0, -1);
+	RUN_TEST(test_parse_basic_ml, 1, -1);
+	RUN_TEST(test_parse_basic_ml, 0, 0);
+	RUN_TEST(test_parse_basic_ml, 1, 0);
+	RUN_TEST(test_parse_basic_ml, 0, 1);
+	RUN_TEST(test_parse_basic_ml, 1, 1);
+	RUN_TEST(test_parse_basic_ml, 2, 0);
+	RUN_TEST(test_parse_basic_ml, 2, 1);
 
 	return 0;
 }
diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c
index 58adaf7449..fa5323468d 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -1637,7 +1637,7 @@ int wpa_bss_ext_capab(const struct wpa_bss *bss, unsigned int capab)
 
 static void
 wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
-			     struct wpa_bss *bss, u8 mbssid_idx,
+			     struct wpa_bss *bss, u8 ap_mld_id,
 			     const struct ieee80211_neighbor_ap_info *ap_info,
 			     size_t len, u16 *seen, u16 *missing,
 			     struct wpa_ssid *ssid)
@@ -1673,7 +1673,7 @@ wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
 		if (link_id >= MAX_NUM_MLD_LINKS)
 			continue;
 
-		if (*mld_params != mbssid_idx) {
+		if (*mld_params != ap_mld_id) {
 			wpa_printf(MSG_DEBUG,
 				   "MLD: Reported link not part of MLD");
 		} else if (!(BIT(link_id) & *seen)) {
@@ -1796,29 +1796,34 @@ wpa_bss_validate_rsne_ml(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
  *   should be initialized and #MAX_NUM_MLD_LINKS elements long
  * @missing_links: Result bitmask of links that were not discovered (or %NULL)
  * @ssid: Target SSID (or %NULL)
- * @ap_mld_id: On return would hold the corresponding AP MLD ID (or %NULL)
+ * @nontransmitted: Out parameter denoting whether the BSSID is nontransmitted
+ *    (or %NULL)
  * Returns: 0 on success or -1 for non-MLD or parsing failures
  *
  * Parses the Basic Multi-Link element of the BSS into @link_info using the scan
  * information stored in the wpa_supplicant data to fill in information for
  * links where possible. The @missing_links out parameter will contain any links
  * for which no corresponding BSS was found.
+ *
+ * The @nontransmitted out parameter can be used to create a well formatted ML
+ * Probe Request for the AP. If this out parameter is set to %true, then the
+ * AP MLD ID should not be included in an ML Probe Request sent to its BSSID,
+ * otherwise it should be included and set to zero.
  */
 int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 				   struct wpa_bss *bss,
 				   u8 *ap_mld_addr,
 				   u16 *missing_links,
 				   struct wpa_ssid *ssid,
-				   u8 *ap_mld_id)
+				   bool *nontransmitted)
 {
 	struct ieee802_11_elems elems;
 	struct wpabuf *mlbuf;
 	const struct element *elem;
-	u8 mbssid_idx = 0;
 	size_t ml_ie_len;
 	const struct ieee80211_eht_ml *eht_ml;
 	const struct eht_ml_basic_common_info *ml_basic_common_info;
-	u8 i, link_id;
+	u8 i, pos, link_id, ap_mld_id;
 	const u16 control_mask =
 		MULTI_LINK_CONTROL_TYPE_MASK |
 		BASIC_MULTI_LINK_CTRL_PRES_LINK_ID |
@@ -1860,10 +1865,9 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 	}
 
 	/*
-	 * for ext ID + 2 control + common info len + MLD address +
-	 * link info
+	 * for ext ID + 2 control + common info len
 	 */
-	if (ml_ie_len < 2UL + 1UL + ETH_ALEN + 1UL)
+	if (ml_ie_len < sizeof(*eht_ml) + sizeof(*ml_basic_common_info))
 		goto out;
 
 	eht_ml = (const struct ieee80211_eht_ml *) wpabuf_head(mlbuf);
@@ -1878,8 +1882,41 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 	ml_basic_common_info =
 		(const struct eht_ml_basic_common_info *) eht_ml->variable;
 
-	/* Common info length should be valid */
-	if (ml_basic_common_info->len < ETH_ALEN + 1UL)
+	if (ml_ie_len < sizeof(*eht_ml) + ml_basic_common_info->len)
+		goto out;
+
+	/* Minimum Common info length to be valid */
+	if (ml_basic_common_info->len <
+	    sizeof(*ml_basic_common_info) + 1 + 1 + 2)
+		goto out;
+
+	/* LINK_ID, BSS_PARAM_CH_COUNT, MLD_CAPA (see control/control_mask) */
+	link_id = ml_basic_common_info->variable[0] & EHT_ML_LINK_ID_MSK;
+	pos = 1 + 1 + 2;
+
+	if (le_to_host16(eht_ml->ml_control) &
+	    BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO)
+		pos += 2;
+
+	if (le_to_host16(eht_ml->ml_control) &
+	    BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA)
+		pos += 2;
+
+	/* AP MLD ID from ML-Element if present (see comment below) */
+	if (le_to_host16(eht_ml->ml_control) &
+	    BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID) {
+		if (ml_basic_common_info->len <
+		    sizeof(*ml_basic_common_info) + pos + 1)
+			goto out;
+
+		ap_mld_id = ml_basic_common_info->variable[pos];
+
+		pos += 1;
+	} else {
+		ap_mld_id = 0;
+	}
+
+	if (ml_basic_common_info->len < sizeof(*ml_basic_common_info) + pos)
 		goto out;
 
 	/* Get the MLD address and MLD link ID */
@@ -1897,30 +1934,57 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 	l->freq = bss->freq;
 
 
-	/*
-	 * The AP MLD ID in the RNR corresponds to the MBSSID index, see
-	 * IEEE P802.11be/D4.0, 9.4.2.169.2 (Neighbor AP Information field).
-	 *
-	 * For the transmitting BSSID it is clear that both the MBSSID index
-	 * and the AP MLD ID in the RNR are zero.
-	 *
-	 * For nontransmitted BSSIDs we will have a BSS generated from the
-	 * MBSSID element(s) using inheritance rules. Included in the elements
-	 * is the MBSSID Index Element. The RNR is copied from the Beacon/Probe
-	 * Response frame that was send by the transmitting BSSID. As such, the
-	 * reported AP MLD ID in the RNR will match the value in the MBSSID
-	 * Index Element.
-	 */
-	elem = (const struct element *)
-		wpa_bss_get_ie(bss, WLAN_EID_MULTIPLE_BSSID_INDEX);
-	if (elem && elem->datalen >= 1)
-		mbssid_idx = elem->data[0];
+	/* Read Multiple BSSID Index for *nontransmitted and AP MLD ID */
+	if (nontransmitted || !(le_to_host16(eht_ml->ml_control) &
+				BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID)) {
+		const u8 *mbssid_idx_elem;
+
+		/*
+		 * We should be able to rely on the Multiple BSSID Index element
+		 * to be included if the BSS is nontransmitted. Both if it was
+		 * extracted from a beacon and if it came from an ML probe
+		 * response (i.e. not listed in Draft P802.11be_D4.1, 35.3.3.4).
+		 *
+		 * Note that the AP MLD ID and the Multiple-BSSID Index will be
+		 * identical if the information was reported by the
+		 * corresponding transmitting AP (P802.11be_D4.1, 9.4.2.169.2).
+		 * As an AP MLD ID will not be explicitly provided we need to
+		 * rely on the Multiple-BSSID Index. This is generally the case
+		 * when the BSS information was read from a
+		 * Multiple-BSSID Element.
+		 *
+		 * The alternative scenario is a BSS discovered using a
+		 * Multi-Link Probe Response. In that case, we can still
+		 * determine whether the BSS is nontransmitted or not using the
+		 * Multiple BSSID Index Element. However, the AP MLD ID may be
+		 * different inside the ML Probe Response and the driver also
+		 * needs to deal with this during inheritance.
+		 *
+		 * We assume the driver either
+		 *  - includes the appropriate AP MLD ID in the ML Element
+		 *    it generates (see above), or
+		 *  - rewrites the RNR so that the AP MLD ID matches the
+		 *    Multiple-BSSID Index.
+		 */
+		mbssid_idx_elem = wpa_bss_get_ie(bss,
+						 WLAN_EID_MULTIPLE_BSSID_INDEX);
+		if (mbssid_idx_elem && mbssid_idx_elem[1] >= 1) {
+			if (!(le_to_host16(eht_ml->ml_control) &
+			    BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID))
+				ap_mld_id = mbssid_idx_elem[2];
+			if (nontransmitted)
+				*nontransmitted = !!mbssid_idx_elem[2];
+		} else {
+			if (nontransmitted)
+				*nontransmitted = false;
+		}
+	}
 
 	for_each_element_id(elem, WLAN_EID_REDUCED_NEIGHBOR_REPORT,
 			    wpa_bss_ie_ptr(bss),
 			    bss->ie_len ? bss->ie_len : bss->beacon_ie_len) {
 		const struct ieee80211_neighbor_ap_info *ap_info;
-		const u8 *pos = elem->data;
+		const u8 *ap_info_pos = elem->data;
 		size_t len = elem->datalen;
 
 		/* RNR IE may contain more than one Neighbor AP Info */
@@ -1929,18 +1993,18 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 			u8 count;
 
 			ap_info = (const struct ieee80211_neighbor_ap_info *)
-				pos;
+				  ap_info_pos;
 			count = RNR_TBTT_INFO_COUNT_VAL(ap_info->tbtt_info_hdr) + 1;
 			ap_info_len += count * ap_info->tbtt_info_len;
 
 			if (ap_info_len > len)
 				goto out;
 
-			wpa_bss_parse_ml_rnr_ap_info(wpa_s, bss, mbssid_idx,
+			wpa_bss_parse_ml_rnr_ap_info(wpa_s, bss, ap_mld_id,
 						     ap_info, ap_info_len,
 						     &seen, &missing, ssid);
 
-			pos += ap_info_len;
+			ap_info_pos += ap_info_len;
 			len -= ap_info_len;
 		}
 	}
@@ -1994,8 +2058,6 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 	if (missing_links)
 		*missing_links = missing;
 
-	if (ap_mld_id)
-		*ap_mld_id = mbssid_idx;
 
 	ret = 0;
 out:
diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h
index 74ef3c8587..b75ae06b7a 100644
--- a/wpa_supplicant/bss.h
+++ b/wpa_supplicant/bss.h
@@ -222,7 +222,7 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 				   u8 *ap_mld_addr,
 				   u16 *missing_links,
 				   struct wpa_ssid *ssid,
-				   u8 *ap_mld_id);
+				   bool *nontransmitted);
 u16 wpa_bss_parse_reconf_ml_element(struct wpa_supplicant *wpa_s,
 				    struct wpa_bss *bss);
 
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 00b733a1c8..39a4e0b226 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -1889,7 +1889,7 @@ static int wpa_supplicant_connect_ml_missing(struct wpa_supplicant *wpa_s,
 {
 	int *freqs;
 	u16 missing_links = 0, removed_links;
-	u8 ap_mld_id;
+	bool nontransmitted;
 
 	if (!((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) &&
 	      (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME)))
@@ -1897,7 +1897,7 @@ static int wpa_supplicant_connect_ml_missing(struct wpa_supplicant *wpa_s,
 
 	if (wpa_bss_parse_basic_ml_element(wpa_s, selected, NULL,
 					   &missing_links, ssid,
-					   &ap_mld_id) ||
+					   &nontransmitted) ||
 	    !missing_links)
 		return 0;
 
@@ -1937,7 +1937,7 @@ static int wpa_supplicant_connect_ml_missing(struct wpa_supplicant *wpa_s,
 	 * In case the ML probe requested is intended to retrieve information
 	 * from a non-transmitted BSS, the AP MLD ID should not be included.
 	 */
-	if (ap_mld_id)
+	if (nontransmitted)
 		wpa_s->ml_probe_mld_id = -1;
 	else
 		wpa_s->ml_probe_mld_id = 0;
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 18 05:35:24 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 18 Jun 2025 14:35:24 +0200
Subject: [PATCH 2/9] BSS: Drop AP MLD ADDR from return parameters for ML element parsing
In-Reply-To: <20250618123531.962016-1-benjamin@sipsolutions.net>
References: <20250618123531.962016-1-benjamin@sipsolutions.net>
Message-ID: <20250618123531.962016-2-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

The AP MLD ADDR is already parsed out when updating the BSS and stored
within the structure. As such, there is no point in parsing it out again
and adding a separate argument just for that.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 tests/test-bss.c        | 3 +--
 wpa_supplicant/bss.c    | 7 -------
 wpa_supplicant/bss.h    | 1 -
 wpa_supplicant/events.c | 4 ++--
 wpa_supplicant/sme.c    | 5 +++--
 5 files changed, 6 insertions(+), 14 deletions(-)

diff --git a/tests/test-bss.c b/tests/test-bss.c
index 3c72f10106..0b82bd6e9d 100644
--- a/tests/test-bss.c
+++ b/tests/test-bss.c
@@ -62,7 +62,6 @@ void test_parse_basic_ml(struct wpa_supplicant *wpa_s, u8 mld_id,
 		u8 ies[sizeof(rnr_ie) + sizeof(ml_ie_mld_id) +
 		       sizeof(mbssid_idx_ie)];
 	} bss;
-	u8 ap_mld_addr[ETH_ALEN];
 	u16 missing_links;
 	u8 ret;
 	bool nontransmitted;
@@ -85,7 +84,7 @@ void test_parse_basic_ml(struct wpa_supplicant *wpa_s, u8 mld_id,
 		bss.bss.ie_len += sizeof(mbssid_idx_ie);
 	}
 
-	ret = wpa_bss_parse_basic_ml_element(wpa_s, &bss.bss, ap_mld_addr,
+	ret = wpa_bss_parse_basic_ml_element(wpa_s, &bss.bss,
 					     &missing_links, NULL,
 					     &nontransmitted);
 
diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c
index fa5323468d..711dc7db26 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -1791,7 +1791,6 @@ wpa_bss_validate_rsne_ml(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
  * wpa_bss_parse_basic_ml_element - Parse the Basic Multi-Link element
  * @wpa_s: Pointer to wpa_supplicant data
  * @bss: BSS table entry
- * @mld_addr: AP MLD address (or %NULL)
  * @link_info: Array to store link information (or %NULL),
  *   should be initialized and #MAX_NUM_MLD_LINKS elements long
  * @missing_links: Result bitmask of links that were not discovered (or %NULL)
@@ -1812,7 +1811,6 @@ wpa_bss_validate_rsne_ml(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
  */
 int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 				   struct wpa_bss *bss,
-				   u8 *ap_mld_addr,
 				   u16 *missing_links,
 				   struct wpa_ssid *ssid,
 				   bool *nontransmitted)
@@ -1919,11 +1917,6 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 	if (ml_basic_common_info->len < sizeof(*ml_basic_common_info) + pos)
 		goto out;
 
-	/* Get the MLD address and MLD link ID */
-	if (ap_mld_addr)
-		os_memcpy(ap_mld_addr, ml_basic_common_info->mld_addr,
-			  ETH_ALEN);
-
 	link_id = ml_basic_common_info->variable[0] & EHT_ML_LINK_ID_MSK;
 
 	bss->mld_link_id = link_id;
diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h
index b75ae06b7a..fd560a9a8d 100644
--- a/wpa_supplicant/bss.h
+++ b/wpa_supplicant/bss.h
@@ -219,7 +219,6 @@ void calculate_update_time(const struct os_reltime *fetch_time,
 
 int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 				   struct wpa_bss *bss,
-				   u8 *ap_mld_addr,
 				   u16 *missing_links,
 				   struct wpa_ssid *ssid,
 				   bool *nontransmitted);
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 39a4e0b226..fe9e358f5d 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -1176,7 +1176,7 @@ static bool wpas_valid_ml_bss(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
 {
 	u16 removed_links;
 
-	if (wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL, NULL, NULL, NULL))
+	if (wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL, NULL, NULL))
 		return true;
 
 	if (!bss->valid_links)
@@ -1895,7 +1895,7 @@ static int wpa_supplicant_connect_ml_missing(struct wpa_supplicant *wpa_s,
 	      (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME)))
 		return 0;
 
-	if (wpa_bss_parse_basic_ml_element(wpa_s, selected, NULL,
+	if (wpa_bss_parse_basic_ml_element(wpa_s, selected,
 					   &missing_links, ssid,
 					   &nontransmitted) ||
 	    !missing_links)
diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index 47c965bfdc..d32d315766 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -528,6 +528,7 @@ static void wpas_sme_set_mlo_links(struct wpa_supplicant *wpa_s,
 {
 	u8 i;
 
+	os_memcpy(wpa_s->ap_mld_addr, bss->mld_addr, ETH_ALEN);
 	wpa_s->valid_links = 0;
 	wpa_s->mlo_assoc_link_id = bss->mld_link_id;
 
@@ -613,8 +614,8 @@ static void sme_send_authentication(struct wpa_supplicant *wpa_s,
 	os_memset(&params, 0, sizeof(params));
 
 	if ((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) &&
-	    !wpa_bss_parse_basic_ml_element(wpa_s, bss, wpa_s->ap_mld_addr,
-					    NULL, ssid, NULL) &&
+	    !wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL,
+					    ssid, NULL) &&
 	    bss->valid_links) {
 		wpa_printf(MSG_DEBUG, "MLD: In authentication");
 		wpas_sme_set_mlo_links(wpa_s, bss, ssid);
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 18 05:35:25 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 18 Jun 2025 14:35:25 +0200
Subject: [PATCH 3/9] BSS: Set valid_links for all links and return usable links
In-Reply-To: <20250618123531.962016-1-benjamin@sipsolutions.net>
References: <20250618123531.962016-1-benjamin@sipsolutions.net>
Message-ID: <20250618123531.962016-3-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

This commit is a preparation to better define valid_links in the bss
structure and move parsing to wpa_bss_update. Before this, the value
of valid_links would depend on whether a neighbor is known and if an
SSID struct was passed to the parser.

With this change, valid_links is purely defined on whether there is an
entry in the RNR for the corresponding link.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 tests/test-bss.c        |  4 +--
 wpa_supplicant/bss.c    | 62 ++++++++++++++++++++---------------------
 wpa_supplicant/bss.h    |  4 +--
 wpa_supplicant/events.c | 14 ++++------
 wpa_supplicant/sme.c    | 21 ++++++++++----
 5 files changed, 56 insertions(+), 49 deletions(-)

diff --git a/tests/test-bss.c b/tests/test-bss.c
index 0b82bd6e9d..10bcd17787 100644
--- a/tests/test-bss.c
+++ b/tests/test-bss.c
@@ -88,8 +88,8 @@ void test_parse_basic_ml(struct wpa_supplicant *wpa_s, u8 mld_id,
 					     &missing_links, NULL,
 					     &nontransmitted);
 
-	ASSERT_CMP_INT(ret, ==, 0);
-	ASSERT_CMP_INT(bss.bss.valid_links, ==, 1);
+	ASSERT_CMP_INT(ret, ==, 1);
+	ASSERT_CMP_INT(bss.bss.valid_links, ==, 3);
 	ASSERT_CMP_INT(missing_links, ==, 0x0002);
 	ASSERT_CMP_INT(nontransmitted, ==, mbssid_idx > 0);
 }
diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c
index 711dc7db26..b8fe141935 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -1639,7 +1639,7 @@ static void
 wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
 			     struct wpa_bss *bss, u8 ap_mld_id,
 			     const struct ieee80211_neighbor_ap_info *ap_info,
-			     size_t len, u16 *seen, u16 *missing,
+			     size_t len, u16 *seen, u16 *usable, u16 *missing,
 			     struct wpa_ssid *ssid)
 {
 	const u8 *pos, *end;
@@ -1678,6 +1678,7 @@ wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
 				   "MLD: Reported link not part of MLD");
 		} else if (!(BIT(link_id) & *seen)) {
 			struct wpa_bss *neigh_bss;
+			struct mld_link *l;
 
 			if (ssid && ssid->ssid_len)
 				neigh_bss = wpa_bss_get(wpa_s, pos + 1,
@@ -1690,6 +1691,15 @@ wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
 			wpa_printf(MSG_DEBUG, "MLD: mld ID=%u, link ID=%u",
 				   *mld_params, link_id);
 
+			bss->valid_links |= BIT(link_id);
+			l = &bss->mld_links[link_id];
+			os_memcpy(l->bssid, pos + 1, ETH_ALEN);
+			l->disabled = mld_params[2] &
+				RNR_TBTT_INFO_MLD_PARAM2_LINK_DISABLED;
+			l->freq = ieee80211_chan_to_freq(NULL,
+							 ap_info->op_class,
+							 ap_info->channel);
+
 			if (!neigh_bss) {
 				*missing |= BIT(link_id);
 			} else if ((!ssid ||
@@ -1697,14 +1707,8 @@ wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
 						       ssid, 1, 0, true)) &&
 				   !wpa_bssid_ignore_is_listed(
 					   wpa_s, neigh_bss->bssid)) {
-				struct mld_link *l;
-
-				bss->valid_links |= BIT(link_id);
-				l = &bss->mld_links[link_id];
-				os_memcpy(l->bssid, pos + 1, ETH_ALEN);
 				l->freq = neigh_bss->freq;
-				l->disabled = mld_params[2] &
-					RNR_TBTT_INFO_MLD_PARAM2_LINK_DISABLED;
+				*usable |= BIT(link_id);
 			}
 		}
 	}
@@ -1797,7 +1801,7 @@ wpa_bss_validate_rsne_ml(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
  * @ssid: Target SSID (or %NULL)
  * @nontransmitted: Out parameter denoting whether the BSSID is nontransmitted
  *    (or %NULL)
- * Returns: 0 on success or -1 for non-MLD or parsing failures
+ * Returns: Usable links bitmask, or 0 for non-MLD or parsing failures
  *
  * Parses the Basic Multi-Link element of the BSS into @link_info using the scan
  * information stored in the wpa_supplicant data to fill in information for
@@ -1809,14 +1813,14 @@ wpa_bss_validate_rsne_ml(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
  * AP MLD ID should not be included in an ML Probe Request sent to its BSSID,
  * otherwise it should be included and set to zero.
  */
-int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
+u16 wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 				   struct wpa_bss *bss,
 				   u16 *missing_links,
 				   struct wpa_ssid *ssid,
 				   bool *nontransmitted)
 {
 	struct ieee802_11_elems elems;
-	struct wpabuf *mlbuf;
+	struct wpabuf *mlbuf = NULL;
 	const struct element *elem;
 	size_t ml_ie_len;
 	const struct ieee80211_eht_ml *eht_ml;
@@ -1833,23 +1837,22 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 		BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT |
 		BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA;
 	u16 missing = 0;
-	u16 seen;
+	u16 seen, usable = 0;
 	const u8 *ies_pos = wpa_bss_ie_ptr(bss);
 	size_t ies_len = bss->ie_len ? bss->ie_len : bss->beacon_ie_len;
-	int ret = -1, rsne_type, key_mgmt;
+	int rsne_type, key_mgmt;
 	struct mld_link *l;
-	u16 valid_links;
 
 	if (ieee802_11_parse_elems(ies_pos, ies_len, &elems, 1) ==
 	    ParseFailed) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: Failed to parse elements");
-		return ret;
+		goto out;
 	}
 
 	mlbuf = ieee802_11_defrag(elems.basic_mle, elems.basic_mle_len, true);
 	if (!mlbuf) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No Multi-Link element");
-		return ret;
+		goto out;
 	}
 
 	ml_ie_len = wpabuf_len(mlbuf);
@@ -1920,7 +1923,8 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 	link_id = ml_basic_common_info->variable[0] & EHT_ML_LINK_ID_MSK;
 
 	bss->mld_link_id = link_id;
-	seen = bss->valid_links = BIT(link_id);
+	bss->valid_links = BIT(link_id);
+	usable = seen = bss->valid_links;
 
 	l = &bss->mld_links[link_id];
 	os_memcpy(l->bssid, bss->bssid, ETH_ALEN);
@@ -1995,18 +1999,15 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 
 			wpa_bss_parse_ml_rnr_ap_info(wpa_s, bss, ap_mld_id,
 						     ap_info, ap_info_len,
-						     &seen, &missing, ssid);
+						     &seen, &usable, &missing,
+						     ssid);
 
 			ap_info_pos += ap_info_len;
 			len -= ap_info_len;
 		}
 	}
 
-	wpa_printf(MSG_DEBUG, "MLD: valid_links=%04hx (unresolved: 0x%04hx)",
-		   bss->valid_links, missing);
-
-	valid_links = bss->valid_links;
-	for_each_link(bss->valid_links, i) {
+	for_each_link(usable, i) {
 		struct wpa_bss *neigh_bss;
 		int neigh_key_mgmt;
 
@@ -2032,16 +2033,13 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 			wpa_printf(MSG_DEBUG,
 				   "MLD: Discard link %u due to RSN parameter mismatch",
 				   i);
-			valid_links &= ~BIT(i);
+			usable &= ~BIT(i);
 			continue;
 		}
 	}
 
-	if (valid_links != bss->valid_links) {
-		wpa_printf(MSG_DEBUG, "MLD: Updated valid links=%04hx",
-			   valid_links);
-		bss->valid_links = valid_links;
-	}
+	wpa_printf(MSG_DEBUG, "MLD: valid_links=0x%04hx usable=0x%04hx (unresolved: 0x%04hx)",
+		   bss->valid_links, usable, missing);
 
 	for_each_link(bss->valid_links, i) {
 		wpa_printf(MSG_DEBUG, "MLD: link=%u, bssid=" MACSTR,
@@ -2051,11 +2049,13 @@ int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 	if (missing_links)
 		*missing_links = missing;
 
+	wpabuf_free(mlbuf);
+	return usable;
 
-	ret = 0;
 out:
+	bss->valid_links = 0;
 	wpabuf_free(mlbuf);
-	return ret;
+	return 0;
 }
 
 
diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h
index fd560a9a8d..f01accc50b 100644
--- a/wpa_supplicant/bss.h
+++ b/wpa_supplicant/bss.h
@@ -126,7 +126,7 @@ struct wpa_bss {
 	/** Link ID of this affiliated AP of the AP MLD */
 	u8 mld_link_id;
 
-	/** An array of MLD links */
+	/** An array of MLD links, any link found in the RNR is "valid" */
 	u16 valid_links;
 	struct mld_link {
 		u8 bssid[ETH_ALEN];
@@ -217,7 +217,7 @@ void calculate_update_time(const struct os_reltime *fetch_time,
 			   unsigned int age_ms,
 			   struct os_reltime *update_time);
 
-int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
+u16 wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 				   struct wpa_bss *bss,
 				   u16 *missing_links,
 				   struct wpa_ssid *ssid,
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index fe9e358f5d..b8db56921d 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -1176,9 +1176,7 @@ static bool wpas_valid_ml_bss(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
 {
 	u16 removed_links;
 
-	if (wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL, NULL, NULL))
-		return true;
-
+	wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL, NULL, NULL);
 	if (!bss->valid_links)
 		return true;
 
@@ -1888,17 +1886,17 @@ static int wpa_supplicant_connect_ml_missing(struct wpa_supplicant *wpa_s,
 					     struct wpa_ssid *ssid)
 {
 	int *freqs;
-	u16 missing_links = 0, removed_links;
+	u16 missing_links = 0, removed_links, usable_links;
 	bool nontransmitted;
 
 	if (!((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) &&
 	      (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME)))
 		return 0;
 
-	if (wpa_bss_parse_basic_ml_element(wpa_s, selected,
-					   &missing_links, ssid,
-					   &nontransmitted) ||
-	    !missing_links)
+	usable_links = wpa_bss_parse_basic_ml_element(wpa_s, selected,
+						      &missing_links, ssid,
+						      &nontransmitted);
+	if (!usable_links || !missing_links)
 		return 0;
 
 	removed_links = wpa_bss_parse_reconf_ml_element(wpa_s, selected);
diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index d32d315766..50b3d635df 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -526,13 +526,24 @@ static int wpas_sme_ml_auth(struct wpa_supplicant *wpa_s,
 static void wpas_sme_set_mlo_links(struct wpa_supplicant *wpa_s,
 				   struct wpa_bss *bss, struct wpa_ssid *ssid)
 {
+	u16 usable_links;
 	u8 i;
 
+	wpas_reset_mlo_info(wpa_s);
+
+	if (!(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO))
+		return;
+
+	usable_links = wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL,
+						      ssid, NULL);
+	if (!usable_links)
+		return;
+
 	os_memcpy(wpa_s->ap_mld_addr, bss->mld_addr, ETH_ALEN);
 	wpa_s->valid_links = 0;
 	wpa_s->mlo_assoc_link_id = bss->mld_link_id;
 
-	for_each_link(bss->valid_links, i) {
+	for_each_link(usable_links, i) {
 		const u8 *bssid = bss->mld_links[i].bssid;
 
 		wpa_s->valid_links |= BIT(i);
@@ -613,12 +624,10 @@ static void sme_send_authentication(struct wpa_supplicant *wpa_s,
 
 	os_memset(&params, 0, sizeof(params));
 
-	if ((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) &&
-	    !wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL,
-					    ssid, NULL) &&
-	    bss->valid_links) {
+	wpas_sme_set_mlo_links(wpa_s, bss, ssid);
+
+	if (wpa_s->valid_links) {
 		wpa_printf(MSG_DEBUG, "MLD: In authentication");
-		wpas_sme_set_mlo_links(wpa_s, bss, ssid);
 
 #ifdef CONFIG_TESTING_OPTIONS
 		bss = wpas_ml_connect_pref(wpa_s, bss, ssid);
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 18 05:35:26 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 18 Jun 2025 14:35:26 +0200
Subject: [PATCH 4/9] BSS: Parse ML information when updating the BSS
In-Reply-To: <20250618123531.962016-1-benjamin@sipsolutions.net>
References: <20250618123531.962016-1-benjamin@sipsolutions.net>
Message-ID: <20250618123531.962016-4-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

There is no point in re-parsing the ML element all the time if the
relevant information is being cached in the BSS anyway. As such, move
the parsing part to be done when adding/updating the BSS and add a
separate function to retrieve the links that are usable or missing in
the BSS cache.

Overall, this is not going to save work, but it means that the
multi-link information in the BSS is always up-to-date and also
detangles the RNR/ML parsing from the later runtime checks whether the
links are valid for the selected SSID.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 tests/test-bss.c        |  10 +-
 wpa_supplicant/bss.c    | 282 +++++++++++++++++++---------------------
 wpa_supplicant/bss.h    |  13 +-
 wpa_supplicant/events.c |   9 +-
 wpa_supplicant/sme.c    |   3 +-
 5 files changed, 150 insertions(+), 167 deletions(-)

diff --git a/tests/test-bss.c b/tests/test-bss.c
index 10bcd17787..058cc1fad7 100644
--- a/tests/test-bss.c
+++ b/tests/test-bss.c
@@ -62,9 +62,8 @@ void test_parse_basic_ml(struct wpa_supplicant *wpa_s, u8 mld_id,
 		u8 ies[sizeof(rnr_ie) + sizeof(ml_ie_mld_id) +
 		       sizeof(mbssid_idx_ie)];
 	} bss;
-	u16 missing_links;
+	u16 missing_links = 0;
 	u8 ret;
-	bool nontransmitted;
 
 	memcpy(bss.bss.ies, rnr_ie, sizeof(rnr_ie));
 	bss.bss.ie_len = sizeof(rnr_ie);
@@ -84,14 +83,13 @@ void test_parse_basic_ml(struct wpa_supplicant *wpa_s, u8 mld_id,
 		bss.bss.ie_len += sizeof(mbssid_idx_ie);
 	}
 
-	ret = wpa_bss_parse_basic_ml_element(wpa_s, &bss.bss,
-					     &missing_links, NULL,
-					     &nontransmitted);
+	wpa_bss_parse_basic_ml_element(wpa_s, &bss.bss);
+	ret = wpa_bss_get_usable_links(wpa_s, &bss.bss, NULL, &missing_links);
 
 	ASSERT_CMP_INT(ret, ==, 1);
 	ASSERT_CMP_INT(bss.bss.valid_links, ==, 3);
 	ASSERT_CMP_INT(missing_links, ==, 0x0002);
-	ASSERT_CMP_INT(nontransmitted, ==, mbssid_idx > 0);
+	ASSERT_CMP_INT(bss.bss.mld_bss_non_transmitted, ==, mbssid_idx > 0);
 }
 
 #define RUN_TEST(func, ...) do {			\
diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c
index b8fe141935..b5b6305f13 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -590,10 +590,8 @@ static struct wpa_bss * wpa_bss_add(struct wpa_supplicant *wpa_s,
 {
 	struct wpa_bss *bss;
 	char extra[100];
-	const u8 *ml_ie;
 	char *pos, *end;
 	int ret = 0;
-	const u8 *mld_addr;
 
 	bss = os_zalloc(sizeof(*bss) + res->ie_len + res->beacon_ie_len);
 	if (bss == NULL)
@@ -608,13 +606,7 @@ static struct wpa_bss * wpa_bss_add(struct wpa_supplicant *wpa_s,
 	os_memcpy(bss->ies, res + 1, res->ie_len + res->beacon_ie_len);
 	wpa_bss_set_hessid(bss);
 
-	os_memset(bss->mld_addr, 0, ETH_ALEN);
-	ml_ie = wpa_scan_get_ml_ie(res, MULTI_LINK_CONTROL_TYPE_BASIC);
-	if (ml_ie) {
-		mld_addr = get_basic_mle_mld_addr(&ml_ie[3], ml_ie[1] - 1);
-		if (mld_addr)
-			os_memcpy(bss->mld_addr, mld_addr, ETH_ALEN);
-	}
+	wpa_bss_parse_basic_ml_element(wpa_s, bss);
 
 	if (wpa_s->num_bss + 1 > wpa_s->conf->bss_max_count &&
 	    wpa_bss_remove_oldest(wpa_s) != 0) {
@@ -905,17 +897,9 @@ wpa_bss_update(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
 		dl_list_add(prev, &bss->list_id);
 	}
 	if (changes & WPA_BSS_IES_CHANGED_FLAG) {
-		const u8 *ml_ie, *mld_addr;
-
 		wpa_bss_set_hessid(bss);
-		os_memset(bss->mld_addr, 0, ETH_ALEN);
-		ml_ie = wpa_scan_get_ml_ie(res, MULTI_LINK_CONTROL_TYPE_BASIC);
-		if (ml_ie) {
-			mld_addr = get_basic_mle_mld_addr(&ml_ie[3],
-							  ml_ie[1] - 1);
-			if (mld_addr)
-				os_memcpy(bss->mld_addr, mld_addr, ETH_ALEN);
-		}
+
+		wpa_bss_parse_basic_ml_element(wpa_s, bss);
 	}
 	dl_list_add_tail(&wpa_s->bss, &bss->list);
 
@@ -1639,8 +1623,7 @@ static void
 wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
 			     struct wpa_bss *bss, u8 ap_mld_id,
 			     const struct ieee80211_neighbor_ap_info *ap_info,
-			     size_t len, u16 *seen, u16 *usable, u16 *missing,
-			     struct wpa_ssid *ssid)
+			     size_t len, u16 *seen)
 {
 	const u8 *pos, *end;
 	const u8 *mld_params;
@@ -1677,16 +1660,8 @@ wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
 			wpa_printf(MSG_DEBUG,
 				   "MLD: Reported link not part of MLD");
 		} else if (!(BIT(link_id) & *seen)) {
-			struct wpa_bss *neigh_bss;
 			struct mld_link *l;
 
-			if (ssid && ssid->ssid_len)
-				neigh_bss = wpa_bss_get(wpa_s, pos + 1,
-							ssid->ssid,
-							ssid->ssid_len);
-			else
-				neigh_bss = wpa_bss_get_bssid(wpa_s, pos + 1);
-
 			*seen |= BIT(link_id);
 			wpa_printf(MSG_DEBUG, "MLD: mld ID=%u, link ID=%u",
 				   *mld_params, link_id);
@@ -1699,17 +1674,6 @@ wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
 			l->freq = ieee80211_chan_to_freq(NULL,
 							 ap_info->op_class,
 							 ap_info->channel);
-
-			if (!neigh_bss) {
-				*missing |= BIT(link_id);
-			} else if ((!ssid ||
-				    wpa_scan_res_match(wpa_s, 0, neigh_bss,
-						       ssid, 1, 0, true)) &&
-				   !wpa_bssid_ignore_is_listed(
-					   wpa_s, neigh_bss->bssid)) {
-				l->freq = neigh_bss->freq;
-				*usable |= BIT(link_id);
-			}
 		}
 	}
 }
@@ -1792,15 +1756,93 @@ wpa_bss_validate_rsne_ml(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
 
 
 /**
- * wpa_bss_parse_basic_ml_element - Parse the Basic Multi-Link element
+ * wpa_bss_get_usable_links - Retrieve the usable links of the AP MLD
  * @wpa_s: Pointer to wpa_supplicant data
  * @bss: BSS table entry
- * @link_info: Array to store link information (or %NULL),
- *   should be initialized and #MAX_NUM_MLD_LINKS elements long
- * @missing_links: Result bitmask of links that were not discovered (or %NULL)
  * @ssid: Target SSID (or %NULL)
- * @nontransmitted: Out parameter denoting whether the BSSID is nontransmitted
- *    (or %NULL)
+ * @missing_links: Result bitmask of links that were not discovered (or %NULL)
+ * Returns: bitmap of links that are usable, or 0 for non-MLD or failure
+ *
+ * Validate each link of the MLD to verify that it is compatible and connection
+ * to each of the links is allowed.
+ */
+u16 wpa_bss_get_usable_links(struct wpa_supplicant *wpa_s,
+			     struct wpa_bss *bss,
+			     struct wpa_ssid *ssid,
+			     u16 *missing_links)
+{
+	int rsne_type, key_mgmt;
+	u16 usable_links = 0;
+	u8 link_id;
+
+	if (!bss->valid_links)
+		return 0;
+
+	rsne_type = -1;
+	if (ssid &&
+	    !wpa_bss_validate_rsne_ml(wpa_s, ssid, bss, &key_mgmt,
+				      &rsne_type)) {
+		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No valid key management");
+		return 0;
+	}
+
+	usable_links = BIT(bss->mld_link_id);
+
+	for_each_link(bss->valid_links, link_id) {
+		struct wpa_bss *neigh_bss;
+
+		if (link_id == bss->mld_link_id)
+			continue;
+
+		if (ssid && ssid->ssid_len)
+			neigh_bss = wpa_bss_get(wpa_s,
+						bss->mld_links[link_id].bssid,
+						ssid->ssid,
+						ssid->ssid_len);
+		else
+			neigh_bss = wpa_bss_get_bssid(wpa_s,
+						      bss->mld_links[link_id].bssid);
+
+		if (!neigh_bss) {
+			if (missing_links)
+				*missing_links |= BIT(link_id);
+			continue;
+		}
+
+		if (ssid) {
+			int neigh_key_mgmt;
+
+			/* As per IEEE P802.11be/D7.0, 12.6.2 (RSNA selection), all APs
+			 * affiliated with an AP MLD shall advertise at least one common
+			 * AKM suite selector in the AKM Suite List field of an RSNE or
+			 * RSNXE. Discard links that do not have compatibility
+			 * configuration with the association link.
+			 */
+			if (!wpa_bss_validate_rsne_ml(wpa_s, ssid, neigh_bss,
+						      &neigh_key_mgmt, &rsne_type) ||
+			    !(key_mgmt & neigh_key_mgmt)) {
+				wpa_printf(MSG_DEBUG,
+					   "MLD: Discard link %u due to RSN parameter mismatch",
+					   link_id);
+				continue;
+			}
+		}
+
+		if ((!ssid ||
+		     wpa_scan_res_match(wpa_s, 0, neigh_bss, ssid, 1, 0, true)) &&
+		    !wpa_bssid_ignore_is_listed(wpa_s, neigh_bss->bssid)) {
+			usable_links |= BIT(link_id);
+		}
+	}
+
+	return usable_links;
+}
+
+
+/**
+ * wpa_bss_parse_basic_ml_element - Parse the Basic Multi-Link element
+ * @wpa_s: Pointer to wpa_supplicant data
+ * @bss: BSS table entry
  * Returns: Usable links bitmask, or 0 for non-MLD or parsing failures
  *
  * Parses the Basic Multi-Link element of the BSS into @link_info using the scan
@@ -1813,11 +1855,8 @@ wpa_bss_validate_rsne_ml(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
  * AP MLD ID should not be included in an ML Probe Request sent to its BSSID,
  * otherwise it should be included and set to zero.
  */
-u16 wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
-				   struct wpa_bss *bss,
-				   u16 *missing_links,
-				   struct wpa_ssid *ssid,
-				   bool *nontransmitted)
+void wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
+				    struct wpa_bss *bss)
 {
 	struct ieee802_11_elems elems;
 	struct wpabuf *mlbuf = NULL;
@@ -1825,6 +1864,7 @@ u16 wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 	size_t ml_ie_len;
 	const struct ieee80211_eht_ml *eht_ml;
 	const struct eht_ml_basic_common_info *ml_basic_common_info;
+	const u8 *mbssid_idx_elem;
 	u8 i, pos, link_id, ap_mld_id;
 	const u16 control_mask =
 		MULTI_LINK_CONTROL_TYPE_MASK |
@@ -1836,11 +1876,9 @@ u16 wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 		BASIC_MULTI_LINK_CTRL_PRES_LINK_ID |
 		BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT |
 		BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA;
-	u16 missing = 0;
-	u16 seen, usable = 0;
+	u16 seen;
 	const u8 *ies_pos = wpa_bss_ie_ptr(bss);
 	size_t ies_len = bss->ie_len ? bss->ie_len : bss->beacon_ie_len;
-	int rsne_type, key_mgmt;
 	struct mld_link *l;
 
 	if (ieee802_11_parse_elems(ies_pos, ies_len, &elems, 1) ==
@@ -1857,14 +1895,6 @@ u16 wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 
 	ml_ie_len = wpabuf_len(mlbuf);
 
-	rsne_type = -1;
-	if (ssid &&
-	    !wpa_bss_validate_rsne_ml(wpa_s, ssid, bss, &key_mgmt,
-				      &rsne_type)) {
-		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No valid key management");
-		goto out;
-	}
-
 	/*
 	 * for ext ID + 2 control + common info len
 	 */
@@ -1922,59 +1952,51 @@ u16 wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 
 	link_id = ml_basic_common_info->variable[0] & EHT_ML_LINK_ID_MSK;
 
+	os_memcpy(bss->mld_addr, ml_basic_common_info->mld_addr, ETH_ALEN);
+
 	bss->mld_link_id = link_id;
 	bss->valid_links = BIT(link_id);
-	usable = seen = bss->valid_links;
+	seen = bss->valid_links;
 
 	l = &bss->mld_links[link_id];
 	os_memcpy(l->bssid, bss->bssid, ETH_ALEN);
 	l->freq = bss->freq;
 
+	bss->mld_bss_non_transmitted = false;
 
-	/* Read Multiple BSSID Index for *nontransmitted and AP MLD ID */
-	if (nontransmitted || !(le_to_host16(eht_ml->ml_control) &
-				BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID)) {
-		const u8 *mbssid_idx_elem;
-
-		/*
-		 * We should be able to rely on the Multiple BSSID Index element
-		 * to be included if the BSS is nontransmitted. Both if it was
-		 * extracted from a beacon and if it came from an ML probe
-		 * response (i.e. not listed in Draft P802.11be_D4.1, 35.3.3.4).
-		 *
-		 * Note that the AP MLD ID and the Multiple-BSSID Index will be
-		 * identical if the information was reported by the
-		 * corresponding transmitting AP (P802.11be_D4.1, 9.4.2.169.2).
-		 * As an AP MLD ID will not be explicitly provided we need to
-		 * rely on the Multiple-BSSID Index. This is generally the case
-		 * when the BSS information was read from a
-		 * Multiple-BSSID Element.
-		 *
-		 * The alternative scenario is a BSS discovered using a
-		 * Multi-Link Probe Response. In that case, we can still
-		 * determine whether the BSS is nontransmitted or not using the
-		 * Multiple BSSID Index Element. However, the AP MLD ID may be
-		 * different inside the ML Probe Response and the driver also
-		 * needs to deal with this during inheritance.
-		 *
-		 * We assume the driver either
-		 *  - includes the appropriate AP MLD ID in the ML Element
-		 *    it generates (see above), or
-		 *  - rewrites the RNR so that the AP MLD ID matches the
-		 *    Multiple-BSSID Index.
-		 */
-		mbssid_idx_elem = wpa_bss_get_ie(bss,
-						 WLAN_EID_MULTIPLE_BSSID_INDEX);
-		if (mbssid_idx_elem && mbssid_idx_elem[1] >= 1) {
-			if (!(le_to_host16(eht_ml->ml_control) &
-			    BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID))
-				ap_mld_id = mbssid_idx_elem[2];
-			if (nontransmitted)
-				*nontransmitted = !!mbssid_idx_elem[2];
-		} else {
-			if (nontransmitted)
-				*nontransmitted = false;
-		}
+	/*
+	 * We should be able to rely on the Multiple BSSID Index element
+	 * to be included if the BSS is nontransmitted. Both if it was
+	 * extracted from a beacon and if it came from an ML probe
+	 * response (i.e. not listed in Draft P802.11be_D4.1, 35.3.3.4).
+	 *
+	 * Note that the AP MLD ID and the Multiple-BSSID Index will be
+	 * identical if the information was reported by the
+	 * corresponding transmitting AP (P802.11be_D4.1, 9.4.2.169.2).
+	 * As an AP MLD ID will not be explicitly provided we need to
+	 * rely on the Multiple-BSSID Index. This is generally the case
+	 * when the BSS information was read from a
+	 * Multiple-BSSID Element.
+	 *
+	 * The alternative scenario is a BSS discovered using a
+	 * Multi-Link Probe Response. In that case, we can still
+	 * determine whether the BSS is nontransmitted or not using the
+	 * Multiple BSSID Index Element. However, the AP MLD ID may be
+	 * different inside the ML Probe Response and the driver also
+	 * needs to deal with this during inheritance.
+	 *
+	 * We assume the driver either
+	 *  - includes the appropriate AP MLD ID in the ML Element
+	 *    it generates (see above), or
+	 *  - rewrites the RNR so that the AP MLD ID matches the
+	 *    Multiple-BSSID Index.
+	 */
+	mbssid_idx_elem = wpa_bss_get_ie(bss, WLAN_EID_MULTIPLE_BSSID_INDEX);
+	if (mbssid_idx_elem && mbssid_idx_elem[1] >= 1) {
+		if (!(le_to_host16(eht_ml->ml_control) &
+		      BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID))
+			ap_mld_id = mbssid_idx_elem[2];
+		bss->mld_bss_non_transmitted = !!mbssid_idx_elem[2];
 	}
 
 	for_each_element_id(elem, WLAN_EID_REDUCED_NEIGHBOR_REPORT,
@@ -1998,64 +2020,28 @@ u16 wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 				goto out;
 
 			wpa_bss_parse_ml_rnr_ap_info(wpa_s, bss, ap_mld_id,
-						     ap_info, ap_info_len,
-						     &seen, &usable, &missing,
-						     ssid);
+						     ap_info, len, &seen);
 
 			ap_info_pos += ap_info_len;
 			len -= ap_info_len;
 		}
 	}
 
-	for_each_link(usable, i) {
-		struct wpa_bss *neigh_bss;
-		int neigh_key_mgmt;
-
-		if (!ssid)
-			break;
-
-		if (i == link_id)
-			continue;
-
-		neigh_bss = wpa_bss_get_bssid(wpa_s, bss->mld_links[i].bssid);
-		if (!neigh_bss) /* cannot be NULL at this point */
-			continue;
-
-		/* As per IEEE P802.11be/D7.0, 12.6.2 (RSNA selection), all APs
-		 * affiliated with an AP MLD shall advertise at least one common
-		 * AKM suite selector in the AKM Suite List field of an RSNE or
-		 * RSNXE. Discard links that do not have compatibility
-		 * configuration with the association link.
-		 */
-		if (!wpa_bss_validate_rsne_ml(wpa_s, ssid, neigh_bss,
-					      &neigh_key_mgmt, &rsne_type) ||
-		    !(key_mgmt & neigh_key_mgmt)) {
-			wpa_printf(MSG_DEBUG,
-				   "MLD: Discard link %u due to RSN parameter mismatch",
-				   i);
-			usable &= ~BIT(i);
-			continue;
-		}
-	}
-
-	wpa_printf(MSG_DEBUG, "MLD: valid_links=0x%04hx usable=0x%04hx (unresolved: 0x%04hx)",
-		   bss->valid_links, usable, missing);
+	wpa_printf(MSG_DEBUG, "MLD: valid_links=0x%04hx",
+		   bss->valid_links);
 
 	for_each_link(bss->valid_links, i) {
 		wpa_printf(MSG_DEBUG, "MLD: link=%u, bssid=" MACSTR,
 			   i, MAC2STR(bss->mld_links[i].bssid));
 	}
 
-	if (missing_links)
-		*missing_links = missing;
-
 	wpabuf_free(mlbuf);
-	return usable;
+	return;
 
 out:
+	os_memset(bss->mld_addr, 0, ETH_ALEN);
 	bss->valid_links = 0;
 	wpabuf_free(mlbuf);
-	return 0;
 }
 
 
diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h
index f01accc50b..4bd752fda2 100644
--- a/wpa_supplicant/bss.h
+++ b/wpa_supplicant/bss.h
@@ -125,6 +125,8 @@ struct wpa_bss {
 	u8 mld_addr[ETH_ALEN];
 	/** Link ID of this affiliated AP of the AP MLD */
 	u8 mld_link_id;
+	/** For MLD, denotes if BSS is non-TX; useful for ML probe */
+	bool mld_bss_non_transmitted;
 
 	/** An array of MLD links, any link found in the RNR is "valid" */
 	u16 valid_links;
@@ -217,11 +219,12 @@ void calculate_update_time(const struct os_reltime *fetch_time,
 			   unsigned int age_ms,
 			   struct os_reltime *update_time);
 
-u16 wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
-				   struct wpa_bss *bss,
-				   u16 *missing_links,
-				   struct wpa_ssid *ssid,
-				   bool *nontransmitted);
+u16 wpa_bss_get_usable_links(struct wpa_supplicant *wpa_s,
+			     struct wpa_bss *bss,
+			     struct wpa_ssid *ssid,
+			     u16 *missing_links);
+void wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
+				    struct wpa_bss *bss);
 u16 wpa_bss_parse_reconf_ml_element(struct wpa_supplicant *wpa_s,
 				    struct wpa_bss *bss);
 
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index b8db56921d..a411ea8f37 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -1176,7 +1176,6 @@ static bool wpas_valid_ml_bss(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
 {
 	u16 removed_links;
 
-	wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL, NULL, NULL);
 	if (!bss->valid_links)
 		return true;
 
@@ -1887,15 +1886,13 @@ static int wpa_supplicant_connect_ml_missing(struct wpa_supplicant *wpa_s,
 {
 	int *freqs;
 	u16 missing_links = 0, removed_links, usable_links;
-	bool nontransmitted;
 
 	if (!((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) &&
 	      (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME)))
 		return 0;
 
-	usable_links = wpa_bss_parse_basic_ml_element(wpa_s, selected,
-						      &missing_links, ssid,
-						      &nontransmitted);
+	usable_links = wpa_bss_get_usable_links(wpa_s, selected, NULL,
+						&missing_links);
 	if (!usable_links || !missing_links)
 		return 0;
 
@@ -1935,7 +1932,7 @@ static int wpa_supplicant_connect_ml_missing(struct wpa_supplicant *wpa_s,
 	 * In case the ML probe requested is intended to retrieve information
 	 * from a non-transmitted BSS, the AP MLD ID should not be included.
 	 */
-	if (nontransmitted)
+	if (selected->mld_bss_non_transmitted)
 		wpa_s->ml_probe_mld_id = -1;
 	else
 		wpa_s->ml_probe_mld_id = 0;
diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index 50b3d635df..55dcf41efc 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -534,8 +534,7 @@ static void wpas_sme_set_mlo_links(struct wpa_supplicant *wpa_s,
 	if (!(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO))
 		return;
 
-	usable_links = wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL,
-						      ssid, NULL);
+	usable_links = wpa_bss_get_usable_links(wpa_s, bss, ssid, NULL);
 	if (!usable_links)
 		return;
 
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 18 05:35:27 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 18 Jun 2025 14:35:27 +0200
Subject: [PATCH 5/9] defs: Add ML element Extended MLD operations and capabilities definitions
In-Reply-To: <20250618123531.962016-1-benjamin@sipsolutions.net>
References: <20250618123531.962016-1-benjamin@sipsolutions.net>
Message-ID: <20250618123531.962016-5-benjamin@sipsolutions.net>

From: Ilan Peer <ilan.peer at intel.com>

They will be needed later.

Signed-off-by: Ilan Peer <ilan.peer at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 src/common/ieee802_11_defs.h | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index 6cf8b146d6..9ec2c116f5 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -2840,6 +2840,7 @@ struct eht_ml_basic_common_info {
 	 * Medium Synchronization Delay Information: 2 octets
 	 * EML Capabilities: 2 octets
 	 * MLD Capabilities and Operations: 2 octets
+	 * Extended MLD Capabilities and Operations: 2 octets
 	 * AP MLD ID: 1 octet
 	 */
 	u8 variable[];
@@ -2868,6 +2869,12 @@ struct eht_ml_basic_common_info {
 #define EHT_ML_MLD_CAPA_AAR_SUPP                      0x1000
 #define EHT_ML_MLD_CAPA_LINK_RECONF_OP_SUPPORT        0x2000
 
+#define EHT_ML_EXT_MLD_CAPA_OP_PARAM_UPDATE           0x0001
+#define EHT_ML_EXT_MLD_CAPA_OP_RECO_MAX_LINKS_MASK    0x001e
+#define EHT_ML_EXT_MLD_CAPA_NSTR_UPDATE               0x0020
+#define EHT_ML_EXT_MLD_CAPA_EMLSR_ENA_ONE_LINK        0x0040
+#define EHT_ML_EXT_MLD_CAPA_BTM_MLD_RECO_MULTI_AP     0x0080
+
 #define EHT_PER_STA_CTRL_LINK_ID_MSK                  0x000f
 #define EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK         0x0010
 #define EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK         0x0020
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 18 05:35:28 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 18 Jun 2025 14:35:28 +0200
Subject: [PATCH 6/9] BSS: Verify the ML common info for links
In-Reply-To: <20250618123531.962016-1-benjamin@sipsolutions.net>
References: <20250618123531.962016-1-benjamin@sipsolutions.net>
Message-ID: <20250618123531.962016-6-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

Add a check that the AP MLD ADDR, the link ID, the MLD Capabilities and
Operations the Extended MLD Capabilities as well as the EML Capabilities
match between links.
If this is not the case, then refuse to use the offending link.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 wpa_supplicant/bss.c | 78 ++++++++++++++++++++++++++++++++++++++++++--
 wpa_supplicant/bss.h |  6 ++++
 2 files changed, 81 insertions(+), 3 deletions(-)

diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c
index b5b6305f13..ce250655a5 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -1790,6 +1790,7 @@ u16 wpa_bss_get_usable_links(struct wpa_supplicant *wpa_s,
 
 	for_each_link(bss->valid_links, link_id) {
 		struct wpa_bss *neigh_bss;
+		u16 ext_mld_capa_mask;
 
 		if (link_id == bss->mld_link_id)
 			continue;
@@ -1809,6 +1810,55 @@ u16 wpa_bss_get_usable_links(struct wpa_supplicant *wpa_s,
 			continue;
 		}
 
+		/* Check that the links is MLD and the information matches */
+		if (!neigh_bss->valid_links) {
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"MLD: neighbor without Multi-Link support");
+			continue;
+		}
+
+		if (neigh_bss->mld_link_id != link_id) {
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"MLD: neighbor has unexpected link ID");
+			continue;
+		}
+
+		if (!ether_addr_equal(bss->mld_addr, neigh_bss->mld_addr)) {
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"MLD: neighbor has a different MLD addr");
+			continue;
+		}
+
+		if (bss->mld_capa != neigh_bss->mld_capa) {
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"MLD: neighbors MLD Capabilities are not matching");
+			continue;
+		}
+
+		if (bss->eml_capa != neigh_bss->eml_capa) {
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"MLD: neighbors EML Capabilities are not matching");
+			continue;
+		}
+
+		/*
+		 * Check well-defined values in Extended MLD Capabilities.
+		 * In particular the Recommended Max Simultaneous Links
+		 * subfield may change over time and is reserved depending on
+		 * the frame that it is carried in.
+		 * See IEEE P802.11be/D7.0, Table 9-417o.
+		 */
+		ext_mld_capa_mask =
+			EHT_ML_EXT_MLD_CAPA_OP_PARAM_UPDATE |
+			EHT_ML_EXT_MLD_CAPA_NSTR_UPDATE |
+			EHT_ML_EXT_MLD_CAPA_EMLSR_ENA_ONE_LINK;
+		if ((bss->ext_mld_capa & ext_mld_capa_mask) !=
+		    (neigh_bss->ext_mld_capa & ext_mld_capa_mask)) {
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"MLD: neighbors Extended MLD Capabilities are not matching");
+			continue;
+		}
+
 		if (ssid) {
 			int neigh_key_mgmt;
 
@@ -1921,17 +1971,25 @@ void wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 	    sizeof(*ml_basic_common_info) + 1 + 1 + 2)
 		goto out;
 
-	/* LINK_ID, BSS_PARAM_CH_COUNT, MLD_CAPA (see control/control_mask) */
+	/* LINK_ID, BSS_PARAM_CH_COUNT (see control/control_mask) */
 	link_id = ml_basic_common_info->variable[0] & EHT_ML_LINK_ID_MSK;
-	pos = 1 + 1 + 2;
+	pos = 1 + 1;
 
 	if (le_to_host16(eht_ml->ml_control) &
 	    BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO)
 		pos += 2;
 
+	bss->eml_capa = 0;
 	if (le_to_host16(eht_ml->ml_control) &
-	    BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA)
+	    BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) {
+		bss->eml_capa =
+			WPA_GET_LE16(&ml_basic_common_info->variable[pos]);
 		pos += 2;
+	}
+
+	/* MLD_CAPA (always present, see control/control_mask) */
+	bss->mld_capa = WPA_GET_LE16(&ml_basic_common_info->variable[pos]);
+	pos += 2;
 
 	/* AP MLD ID from ML-Element if present (see comment below) */
 	if (le_to_host16(eht_ml->ml_control) &
@@ -1947,6 +2005,20 @@ void wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 		ap_mld_id = 0;
 	}
 
+	/* Extended MLD Capabilities */
+	bss->ext_mld_capa = 0;
+	if (le_to_host16(eht_ml->ml_control) &
+	    BASIC_MULTI_LINK_CTRL_PRES_EXT_MLD_CAP) {
+		if (ml_basic_common_info->len <
+		    sizeof(*ml_basic_common_info) + pos + 2)
+			goto out;
+
+		bss->ext_mld_capa =
+			WPA_GET_LE16(&ml_basic_common_info->variable[pos]);
+
+		pos += 2;
+	}
+
 	if (ml_basic_common_info->len < sizeof(*ml_basic_common_info) + pos)
 		goto out;
 
diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h
index 4bd752fda2..8a86489a04 100644
--- a/wpa_supplicant/bss.h
+++ b/wpa_supplicant/bss.h
@@ -127,6 +127,12 @@ struct wpa_bss {
 	u8 mld_link_id;
 	/** For MLD, denotes if BSS is non-TX; useful for ML probe */
 	bool mld_bss_non_transmitted;
+	/** MLD Capabilities and Operations */
+	u16 mld_capa;
+	/** Extended MLD Capabilities and Operations */
+	u16 ext_mld_capa;
+	/** EML Capabilities */
+	u16 eml_capa;
 
 	/** An array of MLD links, any link found in the RNR is "valid" */
 	u16 valid_links;
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 18 05:35:29 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 18 Jun 2025 14:35:29 +0200
Subject: [PATCH 7/9] BSS: MLD: Add verification for the pairwise cipher
In-Reply-To: <20250618123531.962016-1-benjamin@sipsolutions.net>
References: <20250618123531.962016-1-benjamin@sipsolutions.net>
Message-ID: <20250618123531.962016-7-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

Check that there is a valid pairwise cipher for an EHT AP. Also verify
that each potential link has a pairwise cipher in common with the
association link.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 wpa_supplicant/bss.c | 51 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 38 insertions(+), 13 deletions(-)

diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c
index ce250655a5..e4fa8ee6d2 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -1686,14 +1686,15 @@ wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
  * @bss: BSS table entry
  * Returns: true if the BSS configuration matches local profile and the elements
  * meet MLO requirements, false otherwise
- * @key_mgmt: Pointer to store key management
  * @rsne_type_p: Type of RSNE to validate. If -1 is given, choose as per the
  *	presence of RSN elements (association link); otherwise, validate
  *	against the requested type (other affiliated links).
+ * @ref_wpa_ie: WPA IE data from the main link to compare against
  */
 static bool
 wpa_bss_validate_rsne_ml(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
-			 struct wpa_bss *bss, int *key_mgmt, int *rsne_type_p)
+			 struct wpa_bss *bss, int *rsne_type_p,
+			 struct wpa_ie_data *ref_wpa_ie)
 {
 	struct ieee802_11_elems elems;
 	struct wpa_ie_data wpa_ie;
@@ -1744,14 +1745,41 @@ wpa_bss_validate_rsne_ml(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
 
 	wpa_ie.key_mgmt &= ~(WPA_KEY_MGMT_PSK | WPA_KEY_MGMT_FT_PSK |
 			     WPA_KEY_MGMT_PSK_SHA256);
+	if (!(wpa_ie.key_mgmt & ssid->key_mgmt)) {
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"MLD: No valid key management");
+		return false;
+	}
 	wpa_dbg(wpa_s, MSG_DEBUG, "MLD: key_mgmt=0x%x", wpa_ie.key_mgmt);
 
-	if (key_mgmt)
-		*key_mgmt = wpa_ie.key_mgmt;
+	wpa_ie.pairwise_cipher &= ~(WPA_CIPHER_NONE | WPA_CIPHER_WEP40 |
+				WPA_CIPHER_WEP104 | WPA_CIPHER_TKIP);
+	if (!(wpa_ie.pairwise_cipher & ssid->pairwise_cipher)) {
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"MLD: No valid pairwise cipher");
+		return false;
+	}
+
+	if (*rsne_type_p == -1) {
+		memcpy(ref_wpa_ie, &wpa_ie, sizeof(wpa_ie));
+
+		*rsne_type_p = rsne_type;
+	} else {
+		/* Verifying the neighbor given rsne_type_p and ref_wpa_ie */
+		if (!(wpa_ie.key_mgmt & ref_wpa_ie->key_mgmt)) {
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"MLD: neighbor without common AKM");
+			return false;
+		}
 
-	*rsne_type_p = rsne_type;
+		if (!(wpa_ie.pairwise_cipher & ref_wpa_ie->pairwise_cipher)) {
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"MLD: neighbor without common pairwise cipher");
+			return false;
+		}
+	}
 
-	return !!(wpa_ie.key_mgmt & ssid->key_mgmt);
+	return true;
 }
 
 
@@ -1771,7 +1799,8 @@ u16 wpa_bss_get_usable_links(struct wpa_supplicant *wpa_s,
 			     struct wpa_ssid *ssid,
 			     u16 *missing_links)
 {
-	int rsne_type, key_mgmt;
+	struct wpa_ie_data wpa_ie;
+	int rsne_type;
 	u16 usable_links = 0;
 	u8 link_id;
 
@@ -1780,8 +1809,7 @@ u16 wpa_bss_get_usable_links(struct wpa_supplicant *wpa_s,
 
 	rsne_type = -1;
 	if (ssid &&
-	    !wpa_bss_validate_rsne_ml(wpa_s, ssid, bss, &key_mgmt,
-				      &rsne_type)) {
+	    !wpa_bss_validate_rsne_ml(wpa_s, ssid, bss, &rsne_type, &wpa_ie)) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No valid key management");
 		return 0;
 	}
@@ -1860,8 +1888,6 @@ u16 wpa_bss_get_usable_links(struct wpa_supplicant *wpa_s,
 		}
 
 		if (ssid) {
-			int neigh_key_mgmt;
-
 			/* As per IEEE P802.11be/D7.0, 12.6.2 (RSNA selection), all APs
 			 * affiliated with an AP MLD shall advertise at least one common
 			 * AKM suite selector in the AKM Suite List field of an RSNE or
@@ -1869,8 +1895,7 @@ u16 wpa_bss_get_usable_links(struct wpa_supplicant *wpa_s,
 			 * configuration with the association link.
 			 */
 			if (!wpa_bss_validate_rsne_ml(wpa_s, ssid, neigh_bss,
-						      &neigh_key_mgmt, &rsne_type) ||
-			    !(key_mgmt & neigh_key_mgmt)) {
+						      &rsne_type, &wpa_ie)) {
 				wpa_printf(MSG_DEBUG,
 					   "MLD: Discard link %u due to RSN parameter mismatch",
 					   link_id);
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 18 05:35:30 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 18 Jun 2025 14:35:30 +0200
Subject: [PATCH 8/9] tests: test MLO rejection for incorrectly configured MLD APs
In-Reply-To: <20250618123531.962016-1-benjamin@sipsolutions.net>
References: <20250618123531.962016-1-benjamin@sipsolutions.net>
Message-ID: <20250618123531.962016-8-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

Add a test that injects various problematic ML elements and verify that
MLO is disabled in that case.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 tests/hwsim/test_eht.py      | 117 +++++++++++++++++++++++++++++++++++
 tests/hwsim/wpasupplicant.py |  44 +++++++++++++
 2 files changed, 161 insertions(+)

diff --git a/tests/hwsim/test_eht.py b/tests/hwsim/test_eht.py
index f254d00b3f..42904a9401 100644
--- a/tests/hwsim/test_eht.py
+++ b/tests/hwsim/test_eht.py
@@ -2411,6 +2411,123 @@ def test_eht_mlo_color_change(dev, apdev):
         hapd0.dump_monitor()
         hapd1.dump_monitor()
 
+def test_eht_mld_invalid_ml_elem(dev, apdev):
+    """EHT AP MLD with various errors in its beacon"""
+
+    cases = {
+        '2nd link misses ML elem': {
+            'hapd1_ml_elem': '',
+            'hapd1_parse_error': True,
+        },
+        '2nd link has wrong MLD capa with 3 maximum links' : {
+            'hapd1_ml_elem': 'ff106bb0010d__MLD_ADDR__010181000200',
+        },
+        '2nd link has incorrect MLD addr': {
+            'hapd1_ml_elem': 'ff106bb0010d112233445566010181000100',
+        },
+        '2nd link missing EMLSR': {
+            'hapd1_ml_elem': 'ff106bb0010d__MLD_ADDR__010101000100',
+        },
+        '2nd link missing MLD capa': {
+            'hapd1_ml_elem': 'ff0e6bb0000b__MLD_ADDR__01018100',
+            'hapd1_parse_error': True
+        },
+        '2nd link added/different extended MLD capa': {
+            'hapd1_ml_elem': 'ff126bb0050f__MLD_ADDR__0101810001000100',
+            'hapd1_parse_error': True
+        },
+        '1st link missing MLD capa': {
+            'hapd0_ml_elem': 'ff0e6bb0000b__MLD_ADDR__00018100',
+            'hapd0_parse_error': True
+        },
+    }
+
+    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)
+
+        passphrase = 'qwertyuiop'
+        ssid = "mld_ap_two_links"
+
+        params = eht_mld_ap_wpa2_params(ssid, passphrase, key_mgmt="SAE", mfp="2", pwe="1")
+        hapd0 = eht_mld_enable_ap(hapd_iface, 0, params)
+
+        params = eht_mld_ap_wpa2_params(ssid, passphrase, key_mgmt="SAE", mfp="2", pwe="1")
+        params['channel'] = '6'
+        hapd1 = eht_mld_enable_ap(hapd_iface, 1, params)
+
+        # hapd0 as the primary link
+        wpas.set("mld_connect_bssid_pref", hapd0.own_addr())
+        wpas.set("sae_pwe", "1")
+        wpas.connect(ssid, scan_freq="2412 2437", psk=passphrase, key_mgmt="SAE", ieee80211w="2")
+
+        eht_verify_status(wpas, hapd0, 2412, 20, is_ht=True, mld=True, valid_links=3, active_links=3)
+        eht_verify_wifi_version(wpas)
+
+        # Grab the BSS information
+        hapd0_bss = wpas.get_bss(hapd0.own_addr())
+        hapd1_bss = wpas.get_bss(hapd1.own_addr())
+
+        assert wpas.request('DISCONNECT').strip() == 'OK'
+
+        # Iterate the testcases
+        for name, case in cases.items():
+            logger.info(f"Testing case: {name}")
+            wpas.scan()
+            time.sleep(5)
+
+            new_hapd1_bss = hapd1_bss.copy()
+            if 'beacon_ie' in new_hapd1_bss:
+                del new_hapd1_bss['beacon_ie']
+            if 'hapd1_ml_elem' in case:
+                ml_addr = hapd0.own_mld_addr().replace(":", "")
+                orig_ml_elem = f'ff106bb0010d{ml_addr}010181000100'
+                new_ml_elem = case['hapd1_ml_elem'].replace('__MLD_ADDR__', ml_addr)
+                new_hapd1_bss['ie'] = \
+                    hapd1_bss['ie'].replace(orig_ml_elem, new_ml_elem)
+
+            new_hapd1_bss['est_throughput'] = 1
+
+            # Always override hapd1 to lower est_throughput
+            override = {
+                new_hapd1_bss['bssid']: new_hapd1_bss
+            }
+
+            if 'hapd0_ml_elem' in case:
+                new_hapd0_bss = hapd0_bss.copy()
+                if 'beacon_ie' in new_hapd0_bss:
+                    del new_hapd0_bss['beacon_ie']
+
+                ml_addr = hapd0.own_mld_addr().replace(":", "")
+                orig_ml_elem = f'ff106bb0010d{ml_addr}000181000100'
+                new_ml_elem = case['hapd0_ml_elem'].replace('__MLD_ADDR__', ml_addr)
+                new_hapd0_bss['ie'] = \
+                    hapd0_bss['ie'].replace(orig_ml_elem, new_ml_elem)
+
+                override[new_hapd0_bss['bssid']] = new_hapd0_bss
+
+            wpas.override_scan(override)
+
+            # Ensure we could parse the ML information
+            if not case.get('hapd0_parse_error', False):
+                assert 'ap_mld_addr' in wpas.get_bss(hapd0_bss['bssid'])
+            else:
+                assert 'ap_mld_addr' not in wpas.get_bss(hapd0_bss['bssid'])
+                # Nothing else to test, not connecting in MLO mode
+                continue
+            if not case.get('hapd1_parse_error', False):
+                assert 'ap_mld_addr' in wpas.get_bss(hapd1_bss['bssid'])
+
+            # This will connect to hapd0, but the second link is ignored
+            wpas.connect(ssid, psk=passphrase, key_mgmt="SAE", ieee80211w="2")
+
+            eht_verify_status(wpas, hapd0, 2412, 20, is_ht=True, mld=True, valid_links=1, active_links=1)
+            eht_verify_wifi_version(wpas)
+
+            assert wpas.request('DISCONNECT').strip() == 'OK'
+
 def test_eht_mld_control_socket_connectivity(dev, apdev):
     """AP MLD control socket connectivity"""
     with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \
diff --git a/tests/hwsim/wpasupplicant.py b/tests/hwsim/wpasupplicant.py
index 4ea27a771b..6798748ca8 100644
--- a/tests/hwsim/wpasupplicant.py
+++ b/tests/hwsim/wpasupplicant.py
@@ -1242,6 +1242,50 @@ class WpaSupplicant:
             if len(res.splitlines()) > 1:
                 logger.info("flush_scan_cache: Could not clear all BSS entries. These remain:\n" + res)
 
+    def override_scan(self, inject):
+        """
+        Simulate a scan with all existing BSSs overriding/adding any BSS listed
+        in the inject dictionary.
+        """
+        attrs = {
+            'bssid': 'bssid',
+            'freq': 'freq',
+            'beacon_int': 'beacon_int',
+            'caps': 'capabilities',
+            'qual': 'qual',
+            'noise': 'noise',
+            'level': 'level',
+            'est_throughput': 'est_throughput',
+            'snr': 'snr',
+            'ie': 'ie',
+            'beacon_ie': 'beacon_ie',
+        }
+
+        all_aps = {}
+
+        # Grab all existing BSSs, so that they are part of the "last" scan
+        for bssid in re.findall('bssid=(?P<bssid>.*?)\n', self.request("BSS RANGE=ALL MASK=0x2")):
+            scan_bss = self.get_bss(bssid)
+            all_aps[bssid] = scan_bss
+
+        # Override the one we want to inject
+        all_aps.update(inject)
+
+        assert self.request('DRIVER_EVENT SCAN_RES START').strip() == 'OK'
+
+        # And, insert them all
+        for bss in all_aps.values():
+            bss_info = ' '.join(f'{k}={bss[v]}' for k, v in attrs.items() if v in bss)
+
+            if 'tsf' in bss:
+                bss_info += (f' tsf={int(bss["tsf"]):x}')
+
+            if bss['bssid'] in inject:
+                logger.info(f'Injecting BSS: {bss_info}')
+            assert self.request(f'DRIVER_EVENT SCAN_RES BSS {bss_info}').strip() == 'OK'
+
+        assert self.request('DRIVER_EVENT SCAN_RES END').strip() == 'OK'
+
     def disconnect_and_stop_scan(self):
         self.request("DISCONNECT")
         res = self.request("ABORT_SCAN")
-- 
2.49.0



From benjamin at sipsolutions.net  Wed Jun 18 05:35:31 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Wed, 18 Jun 2025 14:35:31 +0200
Subject: [PATCH 9/9] tests: add tests for MLD AKM and pairwise ciphers
In-Reply-To: <20250618123531.962016-1-benjamin@sipsolutions.net>
References: <20250618123531.962016-1-benjamin@sipsolutions.net>
Message-ID: <20250618123531.962016-9-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

Verify that the AKM and pairwise ciphers are checked for each link and
links are only included if they are valid and compatible with each
other.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 tests/hwsim/test_eht.py | 62 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/tests/hwsim/test_eht.py b/tests/hwsim/test_eht.py
index 42904a9401..f4044f95c2 100644
--- a/tests/hwsim/test_eht.py
+++ b/tests/hwsim/test_eht.py
@@ -2411,6 +2411,68 @@ def test_eht_mlo_color_change(dev, apdev):
         hapd0.dump_monitor()
         hapd1.dump_monitor()
 
+def _test_eht_mld_invalid_link(hapd_iface, wpas_iface, test_params,
+                               key_mgmt="SAE", pwe="1", rsn_pairwise=None):
+    wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
+    wpas.interface_add(wpas_iface)
+
+    passphrase = 'asdf-qwer-tzui-ghjk'
+    ssid = "mld_ap_two_links"
+
+    params = eht_mld_ap_wpa2_params(ssid, passphrase, key_mgmt=key_mgmt, mfp="2", pwe=pwe)
+    if rsn_pairwise is not None:
+        params['rsn_pairwise'] = rsn_pairwise
+    hapd0 = eht_mld_enable_ap(hapd_iface, 0, params)
+
+    params = eht_mld_ap_wpa2_params(ssid, passphrase, key_mgmt="SAE", mfp="2", pwe="1")
+    params['channel'] = '6'
+    hapd1 = eht_mld_enable_ap(hapd_iface, 1, params)
+
+    wpas.set("sae_pwe", "1")
+    wpas.set("sae_groups", "19 20 21")
+    wpas.connect(ssid, psk=passphrase, scan_freq="2437",
+                 key_mgmt="WPA-PSK SAE SAE-EXT-KEY",
+                 pairwise="CCMP GCMP-256", group="CCMP GCMP-256",
+                 ieee80211w="2")
+
+    filters = [
+        'wlan.fc.type_subtype == 0x0000 && wlan.ext_tag.number == 107 && wlan.eht.multi_link.type_0.sta_profile_count == 0',
+        f'wlan.fc.type_subtype == 0x0000 && wlan.ext_tag.number == 107 && wlan.ext_tag.data == 00:01:09:{wpas.own_addr()}:00:00'
+    ]
+    out = run_tshark(os.path.join(test_params['logdir'], 'hwsim0.pcapng'),
+                     filters, display=['frame.number'])
+    logger.debug("tshark output: " + str(out))
+    if not out.splitlines():
+        raise Exception('No ML association found or wrong number of STA profiles')
+
+    eht_verify_status(wpas, hapd1, 2437, 20, is_ht=True, mld=True, valid_links=2, active_links=2)
+    eht_verify_wifi_version(wpas)
+    traffic_test(wpas, hapd0)
+
+def test_eht_mld_invalid_link(dev, apdev, params):
+    """EHT AP MLD where one AP advertises only WPA-PSK and the other SAE"""
+
+    with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \
+        HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface):
+
+        _test_eht_mld_invalid_link(hapd_iface, wpas_iface, params, key_mgmt="WPA-PSK", pwe=None)
+
+def test_eht_mld_invalid_link_akm(dev, apdev, params):
+    """EHT AP MLD where one AP uses a different AKM cipher"""
+
+    with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \
+        HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface):
+
+        _test_eht_mld_invalid_link(hapd_iface, wpas_iface, params, key_mgmt="SAE-EXT-KEY", pwe="1")
+
+def test_eht_mld_invalid_link_pairwise(dev, apdev, params):
+    """EHT AP MLD where one AP uses a different pairwise cipher"""
+
+    with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \
+        HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface):
+
+        _test_eht_mld_invalid_link(hapd_iface, wpas_iface, params, rsn_pairwise="GCMP-256")
+
 def test_eht_mld_invalid_ml_elem(dev, apdev):
     """EHT AP MLD with various errors in its beacon"""
 
-- 
2.49.0



From j at w1.fi  Thu Jun 19 02:04:18 2025
From: j at w1.fi (Jouni Malinen)
Date: Thu, 19 Jun 2025 12:04:18 +0300
Subject: [PATCH 1/1] Fix NAN USD state machine stall seen with listen
 failure.
In-Reply-To: <fbc07bd3f6585b853f864ce358ffe128cdb31c72.1745914611.git.ajay.davanageri@synaptics.corp-partner.google.com>
References: <fbc07bd3f6585b853f864ce358ffe128cdb31c72.1745914611.git.ajay.davanageri@synaptics.corp-partner.google.com>
Message-ID: <aFPSkpDlf2DvtBax@w1.fi>

On Tue, Apr 29, 2025 at 01:48:41PM +0530, Ajay Davanageri wrote:
> If driver returns -EBUSY for remain_on_channel operation, NAN USD state-machine gets stuck and no further listen is configured from supplicant.
> This patch clears the states and re-triggers the nan_de_timer to restart the listen operation.

> diff --git a/src/common/nan_de.c b/src/common/nan_de.c
> @@ -165,7 +165,6 @@ void nan_de_flush(struct nan_de *de)
>  	nan_de_clear_pending(de);
>  }
>  
> -
>  static void nan_de_pause_state(struct nan_de_service *srv, const u8 *peer_addr,

Please do not include these unrelated whitespace changes in nan_de.c.

> diff --git a/wpa_supplicant/nan_usd.c b/wpa_supplicant/nan_usd.c

> +static void wpas_nan_usd_remain_on_channel_timeout(void *eloop_ctx, void *timeout_ctx)
> +{
> +	struct wpa_supplicant *wpa_s = (struct wpa_supplicant *)eloop_ctx;
> +	struct wpas_nan_usd_listen_work *lwork = (struct wpas_nan_usd_listen_work *)timeout_ctx;
> +
> +	wpas_nan_usd_cancel_remain_on_channel_cb(wpa_s, lwork->freq);
> +	return;
> +}

No need for those explicit typecasts from void pointers or that
"return;" at the end of the function.

> @@ -209,6 +218,9 @@ static void wpas_nan_usd_start_listen_cb(struct wpa_radio_work *work,
>  		wpas_nan_usd_listen_work_done(wpa_s);
> +                eloop_cancel_timeout(wpas_nan_usd_remain_on_channel_timeout, wpa_s, NULL);
> +		/* restart the listen state after a delay */
> +		eloop_register_timeout(0, 500, wpas_nan_usd_remain_on_channel_timeout, wpa_s, lwork)

wpas_nan_usd_listen_work_done() frees lwork. That would seem to result
in the callback function dereferencing freed memory (lwork->freq).

-- 
Jouni Malinen                                            PGP id EFC895FA


From j at w1.fi  Thu Jun 19 02:29:54 2025
From: j at w1.fi (Jouni Malinen)
Date: Thu, 19 Jun 2025 12:29:54 +0300
Subject: [PATCH v2] Fix ACS when using 20MHz channels in 6GHz
In-Reply-To: <c4c199dd-b3ec-4e1a-8a24-04181e84ae87@advantech.com>
References: <c4c199dd-b3ec-4e1a-8a24-04181e84ae87@advantech.com>
Message-ID: <aFPYklNo76xMW9H9@w1.fi>

On Tue, Apr 01, 2025 at 01:19:01PM +0000, Matej Vrba wrote:
> When configured to use ACS with 20MHz channels, hostapd incorrectly rejects
> half of the available channels with an error messages "Channel XX: not
> allowed as primary channel for 40 MHz bandwidth." This includes all PSC
> channels.

Thanks, applied.

-- 
Jouni Malinen                                            PGP id EFC895FA


From j at w1.fi  Thu Jun 19 02:30:21 2025
From: j at w1.fi (Jouni Malinen)
Date: Thu, 19 Jun 2025 12:30:21 +0300
Subject: [PATCH] DPP: Conditionally add cipher support based on driver
 capabilities
In-Reply-To: <20250415084925.78190-1-zihan.yan10@gmail.com>
References: <20250415084925.78190-1-zihan.yan10@gmail.com>
Message-ID: <aFPYrUmzN1AMl0Xr@w1.fi>

On Tue, Apr 15, 2025 at 04:49:25PM +0800, zihan.yan10 at gmail.com wrote:
> By default, wpa_supplicant sets the pairwise and group ciphers to CCMP (or CCMP+TKIP) when provisioning a network via DPP. As a result, DPP-provisioned clients are unable to connect to networks that require GCMP or GCMP-256 ciphers due to cipher mismatch. This issue does not affect manually-configured network profiles, where supported cipher suites can be explicitly specified.
> 
> This patch addresses the issue by conditionally setting the appropriate cipher suites in the DPP-generated network profile, based on driver capabilities.

Thanks, applied.
 
-- 
Jouni Malinen                                            PGP id EFC895FA


From j at w1.fi  Thu Jun 19 02:30:41 2025
From: j at w1.fi (Jouni Malinen)
Date: Thu, 19 Jun 2025 12:30:41 +0300
Subject: [PATCH] AP MLD: Ensure hostapd_deinit_driver() is called when
 driver_init() fails
In-Reply-To: <336a7e9b1ed54b08bb9e1e7808c99706@mitrastar.com.tw>
References: <336a7e9b1ed54b08bb9e1e7808c99706@mitrastar.com.tw>
Message-ID: <aFPYwWwrFyTdy12S@w1.fi>

On Tue, Apr 15, 2025 at 01:21:46PM +0000, Richard Yu-??? wrote:
> AP MLD: Ensure hostapd_deinit_driver() is called when driver_init() fails
> 
> Ensure hostapd_deinit_driver() is called when driver_init() fails in both
> hostapd_enable_iface() and hostapd_add_iface().
> 
> When initializing an AP MLD interface, driver_init() first assigns a valid
> private driver interface data pointer (drv_priv) to the hostapd_iface
> structure. It then attempts to add a link by calling hostapd_drv_link_add().
> This call may fail under certain conditions, such as ENETDOWN, EALREADY,
> or other transient errors.
> 
> In such failure cases, the hostapd interface retains a valid drv_priv
> pointer, but no cleanup is performed. This results in an untracked reference
> to the private driver interface data. While the memory backing drv_priv will
> eventually be freed when the last interface sharing it is deinitialized, the
> lack of cleanup in early failure paths can then lead to invalid memory access,
> potentially resulting in a segmentation fault.
> 
> This patch ensures that hostapd_deinit_driver() is invoked in both failure
> paths to properly release references to driver resources and maintain
> consistency across interface initialization routines.

Thanks, applied.
 
-- 
Jouni Malinen                                            PGP id EFC895FA


From j at w1.fi  Thu Jun 19 02:31:17 2025
From: j at w1.fi (Jouni Malinen)
Date: Thu, 19 Jun 2025 12:31:17 +0300
Subject: [PATCH] BSD: handle systems without legacy IP support
In-Reply-To: <202504222119.53MLJmgD060894@plan-b.pwste.edu.pl>
References: <202504222119.53MLJmgD060894@plan-b.pwste.edu.pl>
Message-ID: <aFPY5be2bDl_qD6F@w1.fi>

On Tue, Apr 22, 2025 at 11:13:42PM +0200, Marek Zarychta wrote:
> Currently, wpa_supplicant fails to operate when legacy IP support is
> disabled (i.e., the system is built with WITHOUT_INET and the kernel is
> configured with nooptions INET). This patch addresses the issue by
> enabling wpa_supplicant to run and connect to wireless networks even in
> the absence of AF_INET, as long as AF_INET6 is available.

Thanks, applied.
 
-- 
Jouni Malinen                                            PGP id EFC895FA


From j at w1.fi  Thu Jun 19 03:21:29 2025
From: j at w1.fi (Jouni Malinen)
Date: Thu, 19 Jun 2025 13:21:29 +0300
Subject: [PATCH] Check if a global NL80211 event is directed to a bridged
 interface before ignoring
In-Reply-To: <20250509000030.64239-1-andrew.pope@morsemicro.com>
References: <20250509000030.64239-1-andrew.pope@morsemicro.com>
Message-ID: <aFPkqXZSOQPZSgSU@w1.fi>

On Fri, May 09, 2025 at 10:00:30AM +1000, andrew.pope at morsemicro.com wrote:
> Global events originating/directed toward a bridged (i.e. WDS) station are ignored by HostAP. These events are currently considered 'foreign' and are dropped.
> 
> This problem was initially discovered while testing the configuration parameter 'disassoc_low_ack=1' for bridged stations. It was observed that the event 'STATION_LOW_ACK', communicated in 'NL80211_CMD_NOTIFY_CQM', would be ignored and the AP would fail to disassociate the low ack station from the BSS.

Thanks, applied.
 
-- 
Jouni Malinen                                            PGP id EFC895FA


From j at w1.fi  Thu Jun 19 03:21:44 2025
From: j at w1.fi (Jouni Malinen)
Date: Thu, 19 Jun 2025 13:21:44 +0300
Subject: [PATCH] dbus: Expose P2P auto_join behavior.
In-Reply-To: <20250511175513.342144-1-emilio@crisal.io>
References: <20250511175513.342144-1-emilio@crisal.io>
Message-ID: <aFPkuBldNwYYJlxK@w1.fi>

On Sun, May 11, 2025 at 07:54:40PM +0200, Emilio Cobos ?lvarez wrote:
> It's useful, without it you need multiple tries to join an existing
> group.

Thanks, applied.
 
-- 
Jouni Malinen                                            PGP id EFC895FA


From j at w1.fi  Thu Jun 19 03:22:00 2025
From: j at w1.fi (Jouni Malinen)
Date: Thu, 19 Jun 2025 13:22:00 +0300
Subject: [PATCH] Using wpa_psk_radius=3 can lead to malformed RADIUS
 packets that do not conform to the standard for "Long Extended Type" as
 defined in RFC 6929.
In-Reply-To: <20250523054612.16574-1-k3113n6@gmail.com>
References: <20250523054612.16574-1-k3113n6@gmail.com>
Message-ID: <aFPkyC6RE2HMxAEK@w1.fi>

On Thu, May 22, 2025 at 10:46:12PM -0700, Kellen Gattis wrote:
> This was observed when using wpa_psk_radius=3 in conjunction with
> wpa_key_mgmt=WPA-PSK FT-PSK that resulted in a RADIUS attribute for
> 245.26.11344.2 that was 256 octets in size.  The expected outcome was
> two fragments containing data sizes of 251 and 5 octets, but hostapd
> instead created a fragment of 256 octets (exceeded the size limit)
> followed by a fragment of 5 octets.
> 
> This patch will make it so the fragments preceding the final fragment
> will have the correct data size by using alen instead of data_len when
> running wpabuf_put_data.

Thanks, applied.
 
-- 
Jouni Malinen                                            PGP id EFC895FA


From j at w1.fi  Thu Jun 19 03:22:32 2025
From: j at w1.fi (Jouni Malinen)
Date: Thu, 19 Jun 2025 13:22:32 +0300
Subject: [PATCH 1/3] ctrl_iface: Report all TBTT info elements from RNR
In-Reply-To: <20250610150857.1579130-1-benjamin@sipsolutions.net>
References: <20250610150857.1579130-1-benjamin@sipsolutions.net>
Message-ID: <aFPk6IGm-2YZZalR@w1.fi>

On Tue, Jun 10, 2025 at 05:08:55PM +0200, Benjamin Berg wrote:
> The existing code would only report the first TBTT entry of the first
> RNR element. Add the code to first iterate all RNR elements and then
> also iterate all TBTT entries for each AP information block.

Thanks, all three patches applied.
 
-- 
Jouni Malinen                                            PGP id EFC895FA


From alex.gavin at candelatech.com  Wed Jun 18 16:00:42 2025
From: alex.gavin at candelatech.com (Alex Gavin)
Date: Wed, 18 Jun 2025 16:00:42 -0700
Subject: WPA3 compatibility as a STA
Message-ID: <d3f6bad6-a871-4c0b-b24b-360bf4ad2f77@candelatech.com>

Pablo MG:

Apologies for not replying in the thread. Having difficulty getting my email client to import the 
email to reply.

Based on a quick look, it doesn't seem like NetworkManager explicitly supports RSN overriding (in 
fairness, the feature was only recently introduced to WPA3 late last year). I'm also not familiar 
with how NetworkManager may be used with a pre-existing wpa_supplicant configuration file while 
wpa_supplicant uses its DBus interface, so I cannot provide much input there.

That said, I wanted to share a couple example configuration files that may help should you still be 
looking to setup WPA3 Compatibility Mode for your client.

Attached are two config files. One is a hostapd config which will configure an AP to advertise WPA3 
RSN Override and RSN Override 2 IEs while also advertising WPA2 in the regular RSN IE. The other 
file is a wpa_supplicant config which will support connecting to the configured AP in three 
different authentication configurations.

The first is uses SAE-EXT-KEY and GCMP-256 with RSN overriding (WPA3, AP RSN Override 2 IE), the 
second SAE and CCMP with RSN overriding (WPA3, AP RSN Override IE), and the third standard PSK and 
CCMP without RSN overriding (WPA2, RSN IE).

Example usage for hostapd is in the example config [1]. The wpa_supplicant example config [2] 
details the 'rsn_overriding' configurable, but you may be better served by referencing the 
wpa_supplicant hwsim test logic [3].

Hope this helps!

[1] https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf
[2] https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf
[3] https://w1.fi/cgit/hostap/tree/tests/hwsim/test_rsn_override.py#n374

-- 
Alex Gavin
Candela Technologies, USA (PST/GMT-8)
-------------- next part --------------
hw_mode=a
channel=36
country_code=US
ieee80211n=1
ieee80211ac=1
ieee80211ax=1
#ieee80211be=1

ssid=testssid

wpa=2
wpa_passphrase=password
wpa_key_mgmt=WPA-PSK
wpa_pairwise=CCMP

sae_pwe=2
ieee80211w=0

rsn_override_key_mgmt=SAE
rsn_override_pairwise=CCMP
rsn_override_mfp=2

rsn_override_key_mgmt_2=SAE-EXT-KEY
rsn_override_pairwise_2=GCMP-256
rsn_override_mfp_2=2
-------------- next part --------------
# WPA3 Compatibility Mode (SAE GROUP-DEPEND, GCMP-256)
network={
    ssid="testssid"
    sae_password="password"

    rsn_overriding=1

    pairwise=GCMP-256
    key_mgmt=SAE-EXT-KEY
    ieee80211w=2
}

# WPA3 Compatibility Mode (SAE, CCMP)
network={
    ssid="testssid"
    sae_password="password"

    rsn_overriding=1

    pairwise=CCMP
    key_mgmt=SAE
    ieee80211w=2
}

# WPA2 (PSK, CCMP)
network={
    ssid="testssid"
    #psk="password"
    psk=f5c5597eaf6f2374fd24bd4914981750c119ff97ba9dd67988eac97533e5d02b

    pairwise=CCMP
    key_mgmt=WPA-PSK
    ieee80211w=0
}

From peddolla at qti.qualcomm.com  Thu Jun 19 08:36:53 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:06:53 +0530
Subject: [PATCH 00/34] Introducing Proximity Ranging module
Message-ID: <20250619153727.998806-1-peddolla@qti.qualcomm.com>

These changes are done to add ability to discover peers capable of
Proximity Ranging using the USD frames and perform Proximity
Ranging negotiation which will be done by piggybacking on the PASN
frames. Proximity Ranging negotiation is done to decide ranging role,
ranging Type, ranging channel while PASN is done for Authentication.
The Draft spec used for this feature is "Proximity Ranging
Implementation Considerations for P2P Operation" 2025.

Peddolla Harshavardhan Reddy (34):
  PR: Initialize Proximity Ranging global context
  PR: Update PR device configs and capabilities from driver
  PR: Add API to initiate USD for Proximity Ranging
  PR: Add support to process PR elements in USD frames
  PR: API to add ranging capabilities in USD PR IE
  PR: API to add EDCA capabilities in USD PR IE
  PR: API to add NTB capabilities in USD PR IE
  PR: Fetch Device Identity Key from configuration file
  PR: API to derive and add DIRA in USD PR IE
  PR: APIs to add and clear Device Identity Key, password and PMK
  PR: Add support to parse attributes of ranging IE
  PR: API to process ranging capabilities from USD PR IE
  PR: API to process EDCA capabilities from USD PR IE
  PR: API to process NTB capabilities from USD PR IE
  PR: API to validate DIRA from USD PR IE
  PR: Add support to initiate PASN authentication for Proximity Ranging
  PR: Set password/PMK based on auth mode and configure RSNXE
  PR: Add PR IE with capabilities and operation mode in PASN auth1 frame
  PR: Add API to check the input params of PASN Auth request
  PR: Add support to handle PR PASN auth tx status
  PR: Add support to handle PR PASN auth rx frame
  PR: Add support to handle PR IE in Auth M1 and prepare M2
  PR: Add support to handle PR IE in Auth M2 and prepare M3
  PR: Add support to handle PR IE in Auth M3
  PR: Compare PR capabilites of Auth frame and USD info of peer
  PR: Add support to set keys of PASN authentication
  PR: Add support to notify PR PASN result on initiator and responder
  PR: Update PR device mac address on mac randomization
  PR: Control iface support to initiate USD with PR elements
  PR: Control iface support to trigger PR PASN Authentication
  PR: Control iface support to set and clear PR device identity context
  PR: wpa_cli support for PR discovery and PR secure negotiation
  PR tests: Proximity Ranging test cases
  PR tests: Enable Proximity Ranging hwsim tests

 src/common/ieee802_11_common.c            |    5 +
 src/common/ieee802_11_common.h            |    2 +
 src/common/ieee802_11_defs.h              |    7 +
 src/common/nan_de.c                       |   60 +-
 src/common/nan_de.h                       |   10 +
 src/common/proximity_ranging.c            | 2418 +++++++++++++++++++++
 src/common/proximity_ranging.h            |  460 ++++
 src/common/wpa_ctrl.h                     |    5 +
 src/drivers/driver.h                      |   23 +
 tests/hwsim/example-wpa_supplicant.config |    2 +
 tests/hwsim/test_pr.py                    |  246 +++
 wpa_supplicant/Android.mk                 |    6 +
 wpa_supplicant/Makefile                   |    6 +
 wpa_supplicant/config.c                   |    2 +
 wpa_supplicant/config.h                   |    9 +
 wpa_supplicant/config_file.c              |    4 +
 wpa_supplicant/ctrl_iface.c               |  123 ++
 wpa_supplicant/events.c                   |   16 +-
 wpa_supplicant/nan_usd.c                  |   40 +-
 wpa_supplicant/notify.c                   |   10 +
 wpa_supplicant/notify.h                   |    4 +-
 wpa_supplicant/pr_supplicant.c            |  420 ++++
 wpa_supplicant/pr_supplicant.h            |   90 +
 wpa_supplicant/wpa_cli.c                  |   37 +-
 wpa_supplicant/wpa_supplicant.c           |   23 +-
 wpa_supplicant/wpa_supplicant_i.h         |   19 +
 26 files changed, 4024 insertions(+), 23 deletions(-)
 create mode 100644 src/common/proximity_ranging.c
 create mode 100644 src/common/proximity_ranging.h
 create mode 100644 tests/hwsim/test_pr.py
 create mode 100644 wpa_supplicant/pr_supplicant.c
 create mode 100644 wpa_supplicant/pr_supplicant.h

-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:36:54 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:06:54 +0530
Subject: [PATCH 01/34] PR: Initialize Proximity Ranging global context
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-2-peddolla@qti.qualcomm.com>

Add changes to initialize and deinitialize the Proximity Ranging(PR)
global context and Makefile changes to enable the compilation of
this feature. The Proximity Ranging context will be global making it
common to all interfaces.

The compilation of changes related to Proximity Ranging can be
enabled using the CONFIG_PR flag.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c    | 53 +++++++++++++++++++++++++
 src/common/proximity_ranging.h    | 65 +++++++++++++++++++++++++++++++
 wpa_supplicant/Android.mk         |  6 +++
 wpa_supplicant/Makefile           |  6 +++
 wpa_supplicant/pr_supplicant.c    | 52 +++++++++++++++++++++++++
 wpa_supplicant/pr_supplicant.h    | 31 +++++++++++++++
 wpa_supplicant/wpa_supplicant.c   |  7 +++-
 wpa_supplicant/wpa_supplicant_i.h |  2 +
 8 files changed, 221 insertions(+), 1 deletion(-)
 create mode 100644 src/common/proximity_ranging.c
 create mode 100644 src/common/proximity_ranging.h
 create mode 100644 wpa_supplicant/pr_supplicant.c
 create mode 100644 wpa_supplicant/pr_supplicant.h

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
new file mode 100644
index 000000000..13d7846a2
--- /dev/null
+++ b/src/common/proximity_ranging.c
@@ -0,0 +1,53 @@
+/*
+ * Proxmity Ranging
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "common/ieee802_11_defs.h"
+#include "common/ieee802_11_common.h"
+#include "common/qca-vendor.h"
+#include "wps/wps_i.h"
+#include "proximity_ranging.h"
+
+struct pr_data * pr_init(const struct pr_config *cfg)
+{
+	struct pr_data *pr;
+
+	pr = os_zalloc(sizeof(*pr) + sizeof(*cfg));
+	if (!pr)
+		return NULL;
+
+	pr->cfg = (struct pr_config *) (pr + 1);
+	os_memcpy(pr->cfg, cfg, sizeof(*cfg));
+	if (cfg->dev_name)
+		pr->cfg->dev_name = os_strdup(cfg->dev_name);
+	else
+		pr->cfg->dev_name = NULL;
+
+	dl_list_init(&pr->devices);
+
+	return pr;
+}
+
+
+void pr_deinit(struct pr_data *pr)
+{
+	struct pr_device *dev, *prev;
+
+	if (!pr)
+		return;
+
+	os_free(pr->cfg->dev_name);
+
+	dl_list_for_each_safe(dev, prev, &pr->devices, struct pr_device, list)
+		dl_list_del(&dev->list);
+
+	os_free(pr);
+	wpa_printf(MSG_DEBUG, "PR: Deinit done");
+}
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
new file mode 100644
index 000000000..0e597f85e
--- /dev/null
+++ b/src/common/proximity_ranging.h
@@ -0,0 +1,65 @@
+/*
+ * Proxmity Ranging
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef PROXIMITY_RANGING_H
+#define PROXIMITY_RANGING_H
+
+
+#include "utils/list.h"
+#include "drivers/driver.h"
+
+
+/**
+ * PR_MAX_PEER - Maximum number of Proximity Ranging peers that device can store
+ */
+#define PR_MAX_PEER 100
+
+/**
+ * struct pr_device_info - Proximity ranging peer information
+ */
+struct pr_device {
+	struct dl_list list;
+	struct os_reltime last_seen;
+
+	/**
+	 * pr_device_addr - PR Device Address of the peer
+	 */
+	u8 pr_device_addr[ETH_ALEN];
+};
+
+
+struct pr_config {
+	u8 dev_addr[ETH_ALEN];
+
+	/**
+	 * dev_name - Device Name
+	 */
+	char *dev_name;
+
+	/**
+	 * cb_ctx - Context to use with callback functions
+	 */
+	void *cb_ctx;
+};
+
+struct pr_data {
+	/**
+	 * cfg - PR module configuration
+	 *
+	 * This is included in the same memory allocation with the
+	 * struct pr_data and as such, must not be freed separately.
+	 */
+	struct pr_config *cfg;
+
+	struct dl_list devices;
+};
+
+struct pr_data * pr_init(const struct pr_config *cfg);
+void pr_deinit(struct pr_data *pr);
+
+#endif /* PROXIMITY_RANGING_H */
diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk
index cb07e7364..e09768a9d 100644
--- a/wpa_supplicant/Android.mk
+++ b/wpa_supplicant/Android.mk
@@ -290,6 +290,12 @@ NEED_OFFCHANNEL=y
 L_CFLAGS += -DCONFIG_NAN_USD
 endif
 
+ifdef CONFIG_PR
+OBJS += src/common/proximity_ranging.c
+OBJS += pr_supplicant.c
+L_CFLAGS += -DCONFIG_PR
+endif
+
 ifdef CONFIG_OWE
 L_CFLAGS += -DCONFIG_OWE
 NEED_ECC=y
diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile
index fd7d7a0bd..f420e7130 100644
--- a/wpa_supplicant/Makefile
+++ b/wpa_supplicant/Makefile
@@ -324,6 +324,12 @@ NEED_OFFCHANNEL=y
 CFLAGS += -DCONFIG_NAN_USD
 endif
 
+ifdef CONFIG_PR
+OBJS += ../src/common/proximity_ranging.o
+OBJS += pr_supplicant.o
+CFLAGS += -DCONFIG_PR
+endif
+
 ifdef CONFIG_OWE
 CFLAGS += -DCONFIG_OWE
 NEED_ECC=y
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
new file mode 100644
index 000000000..ba8746589
--- /dev/null
+++ b/wpa_supplicant/pr_supplicant.c
@@ -0,0 +1,52 @@
+/*
+ * Proxmity Ranging
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "eloop.h"
+#include "wpa_supplicant_i.h"
+#include "config.h"
+#include "notify.h"
+#include "driver_i.h"
+#include "common/ieee802_11_common.h"
+#include "common/proximity_ranging.h"
+#include "pr_supplicant.h"
+
+int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s)
+{
+	struct pr_config pr;
+
+	if (global->pr)
+		return 0;
+
+	os_memset(&pr, 0, sizeof(pr));
+
+	os_memcpy(pr.dev_addr, wpa_s->own_addr, ETH_ALEN);
+	pr.cb_ctx = wpa_s;
+	pr.dev_name = wpa_s->conf->device_name;
+
+	global->pr = pr_init(&pr);
+	if (!global->pr) {
+		wpa_printf(MSG_DEBUG, "PR: Failed to init PR");
+		return -1;
+	}
+	global->pr_init_wpa_s = wpa_s;
+
+	return 0;
+}
+
+
+void wpas_pr_deinit(struct wpa_supplicant *wpa_s)
+{
+	if (wpa_s == wpa_s->global->pr_init_wpa_s) {
+		pr_deinit(wpa_s->global->pr);
+		wpa_s->global->pr = NULL;
+		wpa_s->global->pr_init_wpa_s = NULL;
+	}
+}
diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h
new file mode 100644
index 000000000..0fffd5c35
--- /dev/null
+++ b/wpa_supplicant/pr_supplicant.h
@@ -0,0 +1,31 @@
+/*
+ * Proxmity Ranging
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#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);
+void wpas_pr_deinit(struct wpa_supplicant *wpa_s);
+#else /* CONFIG_PR */
+static inline int wpas_pr_init(struct wpa_global *global,
+			       struct wpa_supplicant *wpa_s)
+{
+	return -1;
+}
+
+static inline void wpas_pr_deinit(struct wpa_supplicant *wpa_s)
+{
+}
+
+#endif /* CONFIG_PR */
+
+#endif /*PR_SUPPLICANT_H */
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index dfbd63163..fd2c41e60 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -66,6 +66,7 @@
 #include "mesh.h"
 #include "dpp_supplicant.h"
 #include "nan_usd.h"
+#include "pr_supplicant.h"
 #ifdef CONFIG_MESH
 #include "ap/ap_config.h"
 #include "ap/hostapd.h"
@@ -738,6 +739,8 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s)
 
 	wpas_p2p_deinit(wpa_s);
 
+	wpas_pr_deinit(wpa_s);
+
 #ifdef CONFIG_OFFCHANNEL
 	offchannel_deinit(wpa_s);
 #endif /* CONFIG_OFFCHANNEL */
@@ -7885,7 +7888,6 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,
 	if (wpas_nan_usd_init(wpa_s) < 0)
 		return -1;
 #endif /* CONFIG_NAN_USD */
-
 	if (wpa_supplicant_init_eapol(wpa_s) < 0)
 		return -1;
 	wpa_sm_set_eapol(wpa_s->wpa, wpa_s->eapol);
@@ -7917,6 +7919,9 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,
 		return -1;
 	}
 
+	if (wpas_pr_init(wpa_s->global, wpa_s) < 0)
+		return -1;
+
 	if (wpa_bss_init(wpa_s) < 0)
 		return -1;
 
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 3f844aff6..b0e6a743d 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -288,9 +288,11 @@ struct wpa_global {
 	size_t drv_count;
 	struct os_time suspend_time;
 	struct p2p_data *p2p;
+	struct pr_data *pr;
 	struct wpa_supplicant *p2p_init_wpa_s;
 	struct wpa_supplicant *p2p_group_formation;
 	struct wpa_supplicant *p2p_invite_group;
+	struct wpa_supplicant *pr_init_wpa_s;
 	u8 p2p_dev_addr[ETH_ALEN];
 	struct os_reltime p2p_go_wait_client;
 	struct dl_list p2p_srv_bonjour; /* struct p2p_srv_bonjour */
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:36:55 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:06:55 +0530
Subject: [PATCH 02/34] PR: Update PR device configs and capabilities from driver
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-3-peddolla@qti.qualcomm.com>

Add changes to fetch device configurations, capabilities, and
supported channels for ranging and store them in Proximity Ranging
global context.

This includes propagation of Enhanced Distributed channel Access
(EDCA-802.11mc) based ranging capabilities and channels as well
as Non-Trigger Based (NTB-802.11az) ranging capabilities and channels.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.h    | 86 ++++++++++++++++++++++++++++
 src/drivers/driver.h              | 23 ++++++++
 wpa_supplicant/config.c           |  2 +
 wpa_supplicant/config.h           |  9 +++
 wpa_supplicant/config_file.c      |  4 ++
 wpa_supplicant/pr_supplicant.c    | 93 +++++++++++++++++++++++++++++++
 wpa_supplicant/wpa_supplicant.c   | 15 +++++
 wpa_supplicant/wpa_supplicant_i.h | 16 ++++++
 8 files changed, 248 insertions(+)

diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 0e597f85e..dce2d0870 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -13,12 +13,51 @@
 #include "utils/list.h"
 #include "drivers/driver.h"
 
+/**
+ * PR_MAX_OP_CLASSES - Maximum number of operating classes
+ */
+#define PR_MAX_OP_CLASSES 15
+
+/**
+ * PR_MAX_OP_CLASS_CHANNELS - Maximum number of channels per operating class
+ */
+#define PR_MAX_OP_CLASS_CHANNELS 60
 
 /**
  * PR_MAX_PEER - Maximum number of Proximity Ranging peers that device can store
  */
 #define PR_MAX_PEER 100
 
+/**
+ * struct pr_channels - List of supported channels
+ */
+struct pr_channels {
+	/**
+	 * struct pr_op_class - Supported operating class
+	 */
+	struct pr_op_class {
+		/**
+		 * op_class - Regulatory class (IEEE 802.11-2020)
+		 */
+		u8 op_class;
+
+		/**
+		 * channel - Supported channels
+		 */
+		u8 channel[PR_MAX_OP_CLASS_CHANNELS];
+
+		/**
+		 * channels - Number of channel entries in use
+		 */
+		size_t channels;
+	} op_class[PR_MAX_OP_CLASSES];
+
+	/**
+	 * op_classes - Number of op_class entries in use
+	 */
+	size_t op_classes;
+};
+
 /**
  * struct pr_device_info - Proximity ranging peer information
  */
@@ -34,6 +73,12 @@ struct pr_device {
 
 
 struct pr_config {
+	u8 pasn_type;
+
+	int preferred_ranging_role;
+
+	char country[3];
+
 	u8 dev_addr[ETH_ALEN];
 
 	/**
@@ -41,6 +86,47 @@ struct pr_config {
 	 */
 	char *dev_name;
 
+	bool edca_ista_support;
+
+	bool edca_rsta_support;
+
+	u8 edca_format_and_bw;
+
+	u8 max_tx_antenna;
+
+	u8 max_rx_antenna;
+
+	struct pr_channels edca_channels;
+
+	bool ntb_ista_support;
+
+	bool ntb_rsta_support;
+
+	bool secure_he_ltf;
+
+	u8 max_tx_ltf_repetations;
+
+	u8 max_rx_ltf_repetations;
+
+	u8 max_tx_ltf_total;
+
+	u8 max_rx_ltf_total;
+
+	u8 max_rx_sts_le_80;
+
+	u8 max_rx_sts_gt_80;
+
+	u8 max_tx_sts_le_80;
+
+	u8 max_tx_sts_gt_80;
+
+	u8 ntb_format_and_bw;
+
+	struct pr_channels ntb_channels;
+
+	bool support_6ghz;
+
+
 	/**
 	 * cb_ctx - Context to use with callback functions
 	 */
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 54a3b694d..60f898317 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -2417,6 +2417,12 @@ struct wpa_driver_capa {
 #define WPA_DRIVER_FLAGS2_P2P_FEATURE_V2	0x0000000002000000ULL
 /** Driver supports P2P PCC mode */
 #define WPA_DRIVER_FLAGS2_P2P_FEATURE_PCC_MODE	0x0000000004000000ULL
+/** Driver supports FTM initiator functionality */
+#define WPA_DRIVER_FLAGS2_FTM_INITIATOR		0x0000000008000000ULL
+/** Driver supports non-trigger based ranging responder functionality */
+#define WPA_DRIVER_FLAGS2_NON_TRIGGER_BASED_RESPONDER   0x0000000010000000ULL
+/** Driver supports non-trigger based ranging initiator functionality */
+#define WPA_DRIVER_FLAGS2_NON_TRIGGER_BASED_INITIATOR	0x0000000020000000ULL
 	u64 flags2;
 
 #define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \
@@ -2541,6 +2547,23 @@ struct wpa_driver_capa {
 	/* Maximum number of bytes of extra IE(s) that can be added to Probe
 	 * Request frames */
 	size_t max_probe_req_ie_len;
+
+	/* EDCA based ranging capabilities */
+	u8 edca_format_and_bw;
+	u8 max_tx_antenna;
+	u8 max_rx_antenna;
+
+	/* NTB based ranging capabilities */
+	u8 ntb_format_and_bw;
+	u8 max_tx_ltf_repetations;
+	u8 max_rx_ltf_repetations;
+	u8 max_tx_ltf_total;
+	u8 max_rx_ltf_total;
+	u8 max_rx_sts_le_80;
+	u8 max_rx_sts_gt_80;
+	u8 max_tx_sts_le_80;
+	u8 max_tx_sts_gt_80;
+
 };
 
 
diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c
index 77eb9bf68..120c6b36c 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -5707,6 +5707,8 @@ static const struct global_parse_data global_fields[] = {
 	{ INT_RANGE(wfa_gen_capa, 0, 2), 0},
 	{ BIN(wfa_gen_capa_supp), 0 },
 	{ BIN(wfa_gen_capa_cert), 0 },
+	{ INT(pr_pasn_type), 0 },
+	{ INT_RANGE(pr_preferred_role, 0, 1), 0},
 	/* NOTE: When adding new parameters here, add_interface() in
 	 * wpa_supplicant/dbus_new_introspect.c may need to be modified to
 	 * increase the size of the iface->xml buffer. */
diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h
index 69946076e..42fe67388 100644
--- a/wpa_supplicant/config.h
+++ b/wpa_supplicant/config.h
@@ -1901,6 +1901,15 @@ struct wpa_config {
 	 * wfa_gen_capa_supp.
 	 */
 	struct wpabuf *wfa_gen_capa_cert;
+
+	/* Indicates the types of PASN supported for Proximity Ranging */
+	int pr_pasn_type;
+
+	/* Indicates the preferred Proximity Ranging Role
+	 * 0: Prefer ranging initiator role (default)
+	 * 1: Prefer ranging responder role
+	 */
+	int pr_preferred_role;
 };
 
 
diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c
index 26c7d44bc..a13365fd3 100644
--- a/wpa_supplicant/config_file.c
+++ b/wpa_supplicant/config_file.c
@@ -1756,6 +1756,10 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config)
 		fprintf(f, "wfa_gen_capa=%d\n", config->wfa_gen_capa);
 	write_global_bin(f, "wfa_gen_capa_supp", config->wfa_gen_capa_supp);
 	write_global_bin(f, "wfa_gen_capa_cert", config->wfa_gen_capa_cert);
+	if (config->pr_pasn_type)
+		fprintf(f, "pr_pasn_type=%d\n", config->pr_pasn_type);
+	if (config->pr_preferred_role)
+		fprintf(f, "pr_preferred_role=%d\n", config->pr_preferred_role);
 }
 
 static void wpa_config_write_identity(FILE *f, struct wpa_dev_ik *dev_ik)
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index ba8746589..63cd4bc4d 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -18,6 +18,62 @@
 #include "common/proximity_ranging.h"
 #include "pr_supplicant.h"
 
+
+static int wpas_pr_setup_channels(struct wpa_supplicant *wpa_s,
+				  struct pr_channels *chan, bool allow_6ghz)
+{
+	int cla = 0, op;
+	struct hostapd_hw_modes *mode;
+
+	for (op = 0; global_op_class[op].op_class; op++) {
+		const struct oper_class_map *o = &global_op_class[op];
+		unsigned int ch;
+		struct pr_op_class *op = NULL;
+
+		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)))
+			continue;
+
+		for (ch = o->min_chan; ch <= o->max_chan; ch += o->inc) {
+			enum chan_allowed res;
+
+			/* Check for non-continuous jump in channel index
+			 * increment.
+			 */
+			if ((o->op_class >= 128 && o->op_class <= 130) &&
+			    ch < 149 && ch + o->inc > 149)
+				ch = 149;
+
+			res = verify_channel(mode, o->op_class, ch, o->bw);
+
+			if (res == ALLOWED) {
+				if (!op) {
+					if (cla == PR_MAX_OP_CLASSES)
+						continue;
+					wpa_printf(MSG_DEBUG, "PR: Add operating class: %u",
+						   o->op_class);
+					op = &chan->op_class[cla];
+					cla++;
+					op->op_class = o->op_class;
+				}
+				if (op->channels == PR_MAX_OP_CLASS_CHANNELS)
+					continue;
+				op->channel[op->channels] = ch;
+				op->channels++;
+			}
+		}
+		if (op) {
+			wpa_hexdump(MSG_DEBUG, "PR: Channels",
+				    op->channel, op->channels);
+		}
+	}
+
+	chan->op_classes = cla;
+
+	return 0;
+}
+
 int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s)
 {
 	struct pr_config pr;
@@ -30,6 +86,43 @@ 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.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_format_and_bw = wpa_s->edca_format_and_bw;
+	pr.max_rx_antenna = wpa_s->max_rx_antenna;
+	pr.max_tx_antenna = wpa_s->max_tx_antenna;
+
+	wpas_pr_setup_channels(wpa_s, &pr.edca_channels, false);
+
+	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_format_and_bw = wpa_s->ntb_format_and_bw;
+	pr.max_tx_ltf_repetations = wpa_s->max_tx_ltf_repetations;
+	pr.max_rx_ltf_repetations = wpa_s->max_rx_ltf_repetations;
+	pr.max_tx_ltf_total = wpa_s->max_tx_ltf_total;
+	pr.max_rx_ltf_total = wpa_s->max_rx_ltf_total;
+	pr.max_rx_sts_le_80 = wpa_s->max_rx_sts_le_80;
+	pr.max_rx_sts_gt_80 = wpa_s->max_rx_sts_gt_80;
+	pr.max_tx_sts_le_80 = wpa_s->max_tx_sts_le_80;
+	pr.max_tx_sts_gt_80 = wpa_s->max_tx_sts_gt_80;
+
+	pr.support_6ghz = wpas_is_6ghz_supported(wpa_s, true);
+
+	pr.secure_he_ltf = wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SEC_LTF_STA;
+
+	wpas_pr_setup_channels(wpa_s, &pr.ntb_channels, pr.support_6ghz);
+	if (wpa_s->conf->country[0] && wpa_s->conf->country[1]) {
+		os_memcpy(pr.country, wpa_s->conf->country, 2);
+		pr.country[2] = 0x04;
+	} else
+		os_memcpy(pr.country, "XX\x04", 3);
 
 	global->pr = pr_init(&pr);
 	if (!global->pr) {
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index fd2c41e60..1524794aa 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -7791,6 +7791,21 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,
 		wpa_s->extended_capa_len = capa.extended_capa_len;
 		wpa_s->num_multichan_concurrent =
 			capa.num_multichan_concurrent;
+
+		wpa_s->edca_format_and_bw = capa.edca_format_and_bw;
+		wpa_s->max_tx_antenna = capa.max_tx_antenna;
+		wpa_s->max_rx_antenna = capa.max_rx_antenna;
+
+		wpa_s->ntb_format_and_bw = capa.ntb_format_and_bw;
+		wpa_s->max_tx_ltf_repetations = capa.max_tx_ltf_repetations;
+		wpa_s->max_rx_ltf_repetations = capa.max_rx_ltf_repetations;
+		wpa_s->max_tx_ltf_total = capa.max_tx_ltf_total;
+		wpa_s->max_rx_ltf_total = capa.max_rx_ltf_total;
+		wpa_s->max_rx_sts_le_80 = capa.max_rx_sts_le_80;
+		wpa_s->max_rx_sts_gt_80 = capa.max_rx_sts_gt_80;
+		wpa_s->max_tx_sts_le_80 = capa.max_tx_sts_le_80;
+		wpa_s->max_tx_sts_gt_80 = capa.max_tx_sts_gt_80;
+
 #ifndef CONFIG_NO_WMM_AC
 		wpa_s->wmm_ac_supported = capa.wmm_ac_supported;
 #endif /* CONFIG_NO_WMM_AC */
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index b0e6a743d..f1b0f31ae 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -955,6 +955,22 @@ struct wpa_supplicant {
 	unsigned int max_stations;
 	unsigned int max_num_akms;
 
+	/* EDCA based ranging capabilities */
+	u8 edca_format_and_bw;
+	u8 max_tx_antenna;
+	u8 max_rx_antenna;
+
+	/* NTB based ranging capabilities */
+	u8 ntb_format_and_bw;
+	u8 max_tx_ltf_repetations;
+	u8 max_rx_ltf_repetations;
+	u8 max_tx_ltf_total;
+	u8 max_rx_ltf_total;
+	u8 max_rx_sts_le_80;
+	u8 max_rx_sts_gt_80;
+	u8 max_tx_sts_le_80;
+	u8 max_tx_sts_gt_80;
+
 	int pending_mic_error_report;
 	int pending_mic_error_pairwise;
 	int mic_errors_seen; /* Michael MIC errors with the current PTK */
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:36:56 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:06:56 +0530
Subject: [PATCH 03/34] PR: Add API to initiate USD for Proximity Ranging
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-4-peddolla@qti.qualcomm.com>

USD is used to discover peers capable of Proximity Ranging(PR).
The changes include API's to encapsulate PR data with a new identifier
and also to add Proximity Ranging data to USD frames.

The unique identifiers to be used to form Proximity Ranging IE's are
represented by the macros PR_IE_VENDOR_TYPE and PR_OUI_TYPE.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/ieee802_11_defs.h   |  7 +++++
 src/common/nan_de.c            | 53 ++++++++++++++++++++++++++--------
 src/common/nan_de.h            |  6 ++++
 src/common/proximity_ranging.c | 51 ++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h |  1 +
 wpa_supplicant/nan_usd.c       | 25 +++++++++++++---
 wpa_supplicant/pr_supplicant.c |  6 ++++
 wpa_supplicant/pr_supplicant.h |  6 ++++
 8 files changed, 139 insertions(+), 16 deletions(-)

diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index 6cf8b146d..6e0f7fa24 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -1495,6 +1495,9 @@ struct ieee80211_ampe_ie {
 #define RSNXE_OVERRIDE_IE_VENDOR_TYPE 0x506f9a2b
 #define RSN_SELECTION_IE_VENDOR_TYPE 0x506f9a2c
 
+#define PR_IE_VENDOR_TYPE 0x506f9a2e
+
+
 #define MULTI_AP_SUB_ELEM_TYPE 0x06
 #define MULTI_AP_PROFILE_SUB_ELEM_TYPE 0x07
 #define MULTI_AP_VLAN_SUB_ELEM_TYPE 0x08
@@ -3214,6 +3217,10 @@ struct ieee80211_s1g_beacon_compat {
 	le32 tsf_completion;
 } STRUCT_PACKED;
 
+/* Proximity Ranging (PR) */
+
+#define PR_OUI_TYPE 0x2E
+
 #ifdef _MSC_VER
 #pragma pack(pop)
 #endif /* _MSC_VER */
diff --git a/src/common/nan_de.c b/src/common/nan_de.c
index 2833211f9..ba038094b 100644
--- a/src/common/nan_de.c
+++ b/src/common/nan_de.c
@@ -64,6 +64,7 @@ struct nan_de_service {
 	struct os_reltime next_publish_chan;
 	unsigned int next_publish_duration;
 	bool is_p2p;
+	bool is_pr;
 };
 
 struct nan_de {
@@ -1328,7 +1329,7 @@ int nan_de_publish(struct nan_de *de, const char *service_name,
 	int publish_id;
 	struct nan_de_service *srv;
 
-	if (!service_name) {
+	if (!service_name && !params->proximity_ranging) {
 		wpa_printf(MSG_DEBUG, "NAN: Publish() - no service_name");
 		return -1;
 	}
@@ -1339,6 +1340,12 @@ int nan_de_publish(struct nan_de *de, const char *service_name,
 		return -1;
 	}
 
+	if (params->proximity_ranging && params->solicited && !elems) {
+		wpa_printf(MSG_ERROR,
+			   "NAN: Unable to fetch proximity ranging params");
+		return -1;
+	}
+
 	publish_id = nan_de_get_handle(de);
 	if (publish_id < 1)
 		return -1;
@@ -1348,11 +1355,18 @@ int nan_de_publish(struct nan_de *de, const char *service_name,
 		return -1;
 	srv->type = NAN_DE_PUBLISH;
 	srv->freq = srv->default_freq = params->freq;
-	srv->service_name = os_strdup(service_name);
-	if (!srv->service_name)
-		goto fail;
-	if (nan_de_derive_service_id(srv) < 0)
+
+	if (service_name) {
+		srv->service_name = os_strdup(service_name);
+		if (!srv->service_name)
+			goto fail;
+	}
+
+	if (params->proximity_ranging && !service_name)
+		os_memset(srv->service_id, 0, NAN_SERVICE_ID_LEN);
+	else if (nan_de_derive_service_id(srv) < 0)
 		goto fail;
+
 	os_memcpy(&srv->publish, params, sizeof(*params));
 
 	if (params->freq_list) {
@@ -1383,9 +1397,10 @@ int nan_de_publish(struct nan_de *de, const char *service_name,
 	nan_de_start_new_publish_state(srv, true);
 
 	wpa_printf(MSG_DEBUG, "NAN: Assigned new publish handle %d for %s",
-		   publish_id, service_name);
+		   publish_id, service_name ? service_name : "Ranging");
 	srv->id = publish_id;
 	srv->is_p2p = p2p;
+	srv->is_pr = params->proximity_ranging && params->solicited;
 	nan_de_add_srv(de, srv);
 	nan_de_run_timer(de);
 	return publish_id;
@@ -1468,11 +1483,17 @@ int nan_de_subscribe(struct nan_de *de, const char *service_name,
 	int subscribe_id;
 	struct nan_de_service *srv;
 
-	if (!service_name) {
+	if (!service_name && !params->proximity_ranging) {
 		wpa_printf(MSG_DEBUG, "NAN: Subscribe() - no service_name");
 		return -1;
 	}
 
+	if (params->proximity_ranging && params->active && !elems) {
+		wpa_printf(MSG_ERROR,
+			   "NAN: Unable to fetch proximity ranging params");
+		return -1;
+	}
+
 	subscribe_id = nan_de_get_handle(de);
 	if (subscribe_id < 1)
 		return -1;
@@ -1482,11 +1503,18 @@ int nan_de_subscribe(struct nan_de *de, const char *service_name,
 		return -1;
 	srv->type = NAN_DE_SUBSCRIBE;
 	srv->freq = params->freq;
-	srv->service_name = os_strdup(service_name);
-	if (!srv->service_name)
-		goto fail;
-	if (nan_de_derive_service_id(srv) < 0)
+
+	if (service_name) {
+		srv->service_name = os_strdup(service_name);
+		if (!srv->service_name)
+			goto fail;
+	}
+
+	if (params->proximity_ranging && !service_name)
+		os_memset(srv->service_id, 0, NAN_SERVICE_ID_LEN);
+	else if (nan_de_derive_service_id(srv) < 0)
 		goto fail;
+
 	os_memcpy(&srv->subscribe, params, sizeof(*params));
 
 	if (params->freq_list) {
@@ -1512,9 +1540,10 @@ int nan_de_subscribe(struct nan_de *de, const char *service_name,
 	}
 
 	wpa_printf(MSG_DEBUG, "NAN: Assigned new subscribe handle %d for %s",
-		   subscribe_id, service_name);
+		   subscribe_id, service_name ? service_name : "Ranging");
 	srv->id = subscribe_id;
 	srv->is_p2p = p2p;
+	srv->is_pr = params->proximity_ranging && params->active;
 	nan_de_add_srv(de, srv);
 	nan_de_run_timer(de);
 	return subscribe_id;
diff --git a/src/common/nan_de.h b/src/common/nan_de.h
index 2900bab5c..7f0836332 100644
--- a/src/common/nan_de.h
+++ b/src/common/nan_de.h
@@ -110,6 +110,9 @@ struct nan_publish_params {
 
 	/* Announcement period in ms; 0 = use default */
 	unsigned int announcement_period;
+
+	/* Proximity ranging flag */
+	bool proximity_ranging;
 };
 
 /* Returns -1 on failure or >0 publish_id */
@@ -143,6 +146,9 @@ struct nan_subscribe_params {
 
 	/* Query period in ms; 0 = use default */
 	unsigned int query_period;
+
+	/*  Proximity ranging flag */
+	bool proximity_ranging;
 };
 
 /* Returns -1 on failure or >0 subscribe_id */
diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 13d7846a2..9d2cf6135 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -51,3 +51,54 @@ void pr_deinit(struct pr_data *pr)
 	os_free(pr);
 	wpa_printf(MSG_DEBUG, "PR: Deinit done");
 }
+
+
+static struct wpabuf * pr_encaps_ie(const struct wpabuf *subelems, u32 ie_type)
+{
+	struct wpabuf *ie = NULL;
+	const u8 *pos, *end;
+	size_t len = 0;
+
+	if (!subelems)
+		return NULL;
+
+	len = wpabuf_len(subelems) + 1000;
+
+	ie = wpabuf_alloc(len);
+
+	if (!ie)
+		return NULL;
+
+	pos = wpabuf_head(subelems);
+	end = pos + wpabuf_len(subelems);
+
+	while (end > pos) {
+		size_t frag_len = end - pos;
+
+		if (frag_len > 251)
+			frag_len = 251;
+		wpabuf_put_u8(ie, WLAN_EID_VENDOR_SPECIFIC);
+		wpabuf_put_u8(ie, 4 + frag_len);
+		wpabuf_put_be32(ie, ie_type);
+		wpabuf_put_data(ie, pos, frag_len);
+		pos += frag_len;
+	}
+	return ie;
+}
+
+
+struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country)
+{
+	u32 ie_type;
+	struct wpabuf *buf, *buf2;
+
+	buf = wpabuf_alloc(1000);
+	if (!buf)
+		return NULL;
+
+	ie_type = (OUI_WFA << 8) | PR_OUI_TYPE;
+	buf2 = pr_encaps_ie(buf, ie_type);
+	wpabuf_free(buf);
+
+	return buf2;
+}
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index dce2d0870..12387e434 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -147,5 +147,6 @@ struct pr_data {
 
 struct pr_data * pr_init(const struct pr_config *cfg);
 void pr_deinit(struct pr_data *pr);
+struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country);
 
 #endif /* PROXIMITY_RANGING_H */
diff --git a/wpa_supplicant/nan_usd.c b/wpa_supplicant/nan_usd.c
index b2d195ca7..68b2e6521 100644
--- a/wpa_supplicant/nan_usd.c
+++ b/wpa_supplicant/nan_usd.c
@@ -16,6 +16,7 @@
 #include "notify.h"
 #include "p2p_supplicant.h"
 #include "nan_usd.h"
+#include "pr_supplicant.h"
 
 
 static const char *
@@ -388,11 +389,19 @@ int wpas_nan_usd_publish(struct wpa_supplicant *wpa_s, const char *service_name,
 	if (!wpa_s->nan_de)
 		return -1;
 
+	if (params->proximity_ranging && !params->solicited) {
+		wpa_printf(MSG_ERROR,
+			   "PR unsolicited publish service discovery not allowed");
+		return -1;
+	}
+
+	addr = wpa_s->own_addr;
+
 	if (p2p) {
 		elems = wpas_p2p_usd_elems(wpa_s, service_name);
 		addr = wpa_s->global->p2p_dev_addr;
-	} else {
-		addr = wpa_s->own_addr;
+	} else if (params->proximity_ranging) {
+		elems = wpas_pr_usd_elems(wpa_s);
 	}
 
 	publish_id = nan_de_publish(wpa_s->nan_de, service_name, srv_proto_type,
@@ -460,11 +469,19 @@ int wpas_nan_usd_subscribe(struct wpa_supplicant *wpa_s,
 	if (!wpa_s->nan_de)
 		return -1;
 
+	if (params->proximity_ranging && !params->active) {
+		wpa_printf(MSG_ERROR,
+			   "PR passive subscriber service discovery not allowed");
+		return -1;
+	}
+
+	addr = wpa_s->own_addr;
+
 	if (p2p) {
 		elems = wpas_p2p_usd_elems(wpa_s, service_name);
 		addr = wpa_s->global->p2p_dev_addr;
-	} else {
-		addr = wpa_s->own_addr;
+	} else if (params->proximity_ranging) {
+		elems = wpas_pr_usd_elems(wpa_s);
 	}
 
 	subscribe_id = nan_de_subscribe(wpa_s->nan_de, service_name,
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index 63cd4bc4d..1610dfe49 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -74,6 +74,12 @@ static int wpas_pr_setup_channels(struct wpa_supplicant *wpa_s,
 	return 0;
 }
 
+struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s)
+{
+	return pr_prepare_usd_elems(wpa_s->global->pr, wpa_s->conf->country);
+}
+
+
 int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s)
 {
 	struct pr_config pr;
diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h
index 0fffd5c35..92f6087e8 100644
--- a/wpa_supplicant/pr_supplicant.h
+++ b/wpa_supplicant/pr_supplicant.h
@@ -15,6 +15,7 @@
 
 int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s);
 void wpas_pr_deinit(struct wpa_supplicant *wpa_s);
+struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s);
 #else /* CONFIG_PR */
 static inline int wpas_pr_init(struct wpa_global *global,
 			       struct wpa_supplicant *wpa_s)
@@ -26,6 +27,11 @@ static inline void wpas_pr_deinit(struct wpa_supplicant *wpa_s)
 {
 }
 
+static inline struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s)
+{
+	return NULL;
+}
+
 #endif /* CONFIG_PR */
 
 #endif /*PR_SUPPLICANT_H */
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:36:57 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:06:57 +0530
Subject: [PATCH 04/34] PR: Add support to process PR elements in USD frames
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-5-peddolla@qti.qualcomm.com>

Add changes to process Proximity Ranging attributes present in USD
frames. USD is performed with the PR attribute to exchange
capabilities specific to proximity ranging.

The discovered device capabilities along with the other details such
as address and name are stored in a list present within the proximity
ranging global context.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/ieee802_11_common.c |  5 +++
 src/common/ieee802_11_common.h |  2 +
 src/common/nan_de.c            |  7 +++-
 src/common/nan_de.h            |  4 ++
 src/common/proximity_ranging.c | 73 +++++++++++++++++++++++++++++++++-
 src/common/proximity_ranging.h |  3 ++
 wpa_supplicant/nan_usd.c       | 15 +++++++
 wpa_supplicant/pr_supplicant.c | 12 ++++++
 wpa_supplicant/pr_supplicant.h |  3 ++
 9 files changed, 121 insertions(+), 3 deletions(-)

diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index ae7a7647a..7974e4d17 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -165,6 +165,11 @@ static int ieee802_11_parse_vendor_specific(const u8 *pos, size_t elen,
 			elems->p2p2_ie = pos;
 			elems->p2p2_ie_len = elen;
 			break;
+		case PR_OUI_TYPE:
+			/* Wi-Fi Alliance - PR IE */
+			elems->proximity_ranging = pos;
+			elems->proximity_ranging_len = elen;
+			break;
 		default:
 			wpa_printf(MSG_MSGDUMP, "Unknown WFA "
 				   "information element ignored "
diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h
index 5f52b573c..906c13854 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -122,6 +122,7 @@ struct ieee802_11_elems {
 	const u8 *rsnxe_override;
 	const u8 *rsn_selection;
 	const u8 *wfa_capab;
+	const u8 *proximity_ranging;
 
 	u8 ssid_len;
 	u8 supp_rates_len;
@@ -191,6 +192,7 @@ struct ieee802_11_elems {
 	size_t rsnxe_override_len;
 	size_t rsn_selection_len;
 	u8 wfa_capab_len;
+	size_t proximity_ranging_len;
 
 	struct mb_ies_info mb_ies;
 
diff --git a/src/common/nan_de.c b/src/common/nan_de.c
index ba038094b..2247af895 100644
--- a/src/common/nan_de.c
+++ b/src/common/nan_de.c
@@ -843,7 +843,7 @@ static void nan_de_get_sdea(const u8 *buf, size_t len, u8 instance_id,
 
 static void nan_de_process_elem_container(struct nan_de *de, const u8 *buf,
 					  size_t len, const u8 *peer_addr,
-					  unsigned int freq, bool p2p)
+					  unsigned int freq, bool p2p, bool pr)
 {
 	const u8 *elem;
 	u16 elem_len;
@@ -866,6 +866,9 @@ static void nan_de_process_elem_container(struct nan_de *de, const u8 *buf,
 	if (p2p && de->cb.process_p2p_usd_elems)
 		de->cb.process_p2p_usd_elems(de->cb.ctx, elem, elem_len,
 					     peer_addr, freq);
+	if (pr && de->cb.process_pr_usd_elems)
+		de->cb.process_pr_usd_elems(de->cb.ctx, elem, elem_len,
+					     peer_addr, freq);
 }
 
 
@@ -1205,7 +1208,7 @@ static void nan_de_rx_sda(struct nan_de *de, const u8 *peer_addr, const u8 *a3,
 					    ssi, ssi_len);
 			}
 			nan_de_process_elem_container(de, buf, len, peer_addr,
-						      freq, srv->is_p2p);
+						      freq, srv->is_p2p, srv->is_pr);
 		}
 
 		switch (type) {
diff --git a/src/common/nan_de.h b/src/common/nan_de.h
index 7f0836332..29999b31f 100644
--- a/src/common/nan_de.h
+++ b/src/common/nan_de.h
@@ -60,6 +60,10 @@ struct nan_callbacks {
 	void (*process_p2p_usd_elems)(void *ctx, const u8 *buf,
 				      u16 buf_len, const u8 *peer_addr,
 				      unsigned int freq);
+
+	void (*process_pr_usd_elems)(void *ctx, const u8 *buf,
+				     u16 buf_len, const u8 *peer_addr,
+				     unsigned int freq);
 };
 
 bool nan_de_is_nan_network_id(const u8 *addr);
diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 9d2cf6135..a3f640bce 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -15,6 +15,59 @@
 #include "wps/wps_i.h"
 #include "proximity_ranging.h"
 
+static void pr_device_free(struct pr_data *pr, struct pr_device *dev)
+{
+	os_free(dev);
+}
+
+
+static struct pr_device * pr_get_device(struct pr_data *pr, const u8 *addr)
+{
+	struct pr_device *dev;
+
+	dl_list_for_each(dev, &pr->devices, struct pr_device, list) {
+		if (ether_addr_equal(dev->pr_device_addr, addr))
+			return dev;
+	}
+	return NULL;
+}
+
+
+static struct pr_device * pr_create_device(struct pr_data *pr, const u8 *addr)
+{
+	struct pr_device *dev, *oldest = NULL;
+	size_t count = 0;
+
+	dev = pr_get_device(pr, addr);
+	if (dev)
+		return dev;
+
+	dl_list_for_each(dev, &pr->devices, struct pr_device, list) {
+		count++;
+		if (!oldest ||
+		    os_reltime_before(&dev->last_seen, &oldest->last_seen))
+			oldest = dev;
+	}
+	if (count + 1 > PR_MAX_PEER && oldest) {
+		wpa_printf(MSG_DEBUG,
+			   "Remove oldest peer entry to make room for a new peer " MACSTR,
+			   MAC2STR(oldest->pr_device_addr));
+		dl_list_del(&oldest->list);
+		pr_device_free(pr, oldest);
+	}
+
+	dev = os_zalloc(sizeof(*dev));
+	if (!dev)
+		return NULL;
+
+	dl_list_add(&pr->devices, &dev->list);
+	os_memcpy(dev->pr_device_addr, addr, ETH_ALEN);
+	wpa_printf(MSG_DEBUG, "PR: New Proximity Ranging device add to list");
+
+	return dev;
+}
+
+
 struct pr_data * pr_init(const struct pr_config *cfg)
 {
 	struct pr_data *pr;
@@ -45,8 +98,10 @@ void pr_deinit(struct pr_data *pr)
 
 	os_free(pr->cfg->dev_name);
 
-	dl_list_for_each_safe(dev, prev, &pr->devices, struct pr_device, list)
+	dl_list_for_each_safe(dev, prev, &pr->devices, struct pr_device, list) {
 		dl_list_del(&dev->list);
+		pr_device_free(pr, dev);
+	}
 
 	os_free(pr);
 	wpa_printf(MSG_DEBUG, "PR: Deinit done");
@@ -102,3 +157,19 @@ struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country)
 
 	return buf2;
 }
+
+
+void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
+			  const u8 *peer_addr, unsigned int freq)
+{
+	struct pr_device *dev;
+
+	dev = pr_create_device(pr, peer_addr);
+	if (!dev) {
+		wpa_printf(MSG_ERROR, "PR: Failed to create a device");
+		return;
+	}
+
+	os_get_reltime(&dev->last_seen);
+	dev->listen_freq = freq;
+}
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 12387e434..ec6539eac 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -64,6 +64,7 @@ struct pr_channels {
 struct pr_device {
 	struct dl_list list;
 	struct os_reltime last_seen;
+	int listen_freq;
 
 	/**
 	 * pr_device_addr - PR Device Address of the peer
@@ -148,5 +149,7 @@ struct pr_data {
 struct pr_data * pr_init(const struct pr_config *cfg);
 void pr_deinit(struct pr_data *pr);
 struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country);
+void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
+			  const u8 *peer_addr, unsigned int freq);
 
 #endif /* PROXIMITY_RANGING_H */
diff --git a/wpa_supplicant/nan_usd.c b/wpa_supplicant/nan_usd.c
index 68b2e6521..cb8c6d6aa 100644
--- a/wpa_supplicant/nan_usd.c
+++ b/wpa_supplicant/nan_usd.c
@@ -322,6 +322,18 @@ static void wpas_nan_process_p2p_usd_elems(void *ctx, const u8 *buf,
 #endif /* CONFIG_P2P */
 
 
+#ifdef CONFIG_PR
+static void wpas_nan_process_pr_usd_elems(void *ctx, const u8 *buf, u16 buf_len,
+					  const u8 *peer_addr,
+					  unsigned int freq)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+
+	wpas_pr_process_usd_elems(wpa_s, buf, buf_len, peer_addr, freq);
+}
+#endif /* CONFIG_PR */
+
+
 int wpas_nan_usd_init(struct wpa_supplicant *wpa_s)
 {
 	struct nan_callbacks cb;
@@ -341,6 +353,9 @@ int wpas_nan_usd_init(struct wpa_supplicant *wpa_s)
 #ifdef CONFIG_P2P
 	cb.process_p2p_usd_elems = wpas_nan_process_p2p_usd_elems;
 #endif /* CONFIG_P2P */
+#ifdef CONFIG_PR
+	cb.process_pr_usd_elems = wpas_nan_process_pr_usd_elems;
+#endif /* CONFIG_PR */
 
 	wpa_s->nan_de = nan_de_init(wpa_s->own_addr, offload, false,
 				    wpa_s->max_remain_on_chan, &cb);
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index 1610dfe49..df416bd0a 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -80,6 +80,18 @@ struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s)
 }
 
 
+void wpas_pr_process_usd_elems(struct wpa_supplicant *wpa_s, const u8 *buf,
+			       u16 buf_len, const u8 *peer_addr,
+			       unsigned int freq)
+{
+	struct pr_data *pr = wpa_s->global->pr;
+
+	if (!pr)
+		return;
+	pr_process_usd_elems(pr, buf, buf_len, peer_addr, freq);
+}
+
+
 int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s)
 {
 	struct pr_config pr;
diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h
index 92f6087e8..6abe59331 100644
--- a/wpa_supplicant/pr_supplicant.h
+++ b/wpa_supplicant/pr_supplicant.h
@@ -16,6 +16,9 @@
 int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s);
 void wpas_pr_deinit(struct wpa_supplicant *wpa_s);
 struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s);
+void wpas_pr_process_usd_elems(struct wpa_supplicant *wpa_s, const u8 *buf,
+			       u16 buf_len, const u8 *peer_addr,
+			       unsigned int freq);
 #else /* CONFIG_PR */
 static inline int wpas_pr_init(struct wpa_global *global,
 			       struct wpa_supplicant *wpa_s)
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:36:58 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:06:58 +0530
Subject: [PATCH 05/34] PR: API to add ranging capabilities in USD PR IE
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-6-peddolla@qti.qualcomm.com>

Add changes to create Proximity Ranging capability buffer that will
be a part of a larger proximity ranging IE that is used to advertise
the ranging capabilities of device. The capabilities are fetched
from the global proximity ranging context. The capabilities include
name of the device, PASN authentication support, EDCA ranging support,
NTB ranging support and such.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 64 ++++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h | 35 +++++++++++++++++++
 2 files changed, 99 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index a3f640bce..da0ea65ee 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -142,15 +142,79 @@ static struct wpabuf * pr_encaps_ie(const struct wpabuf *subelems, u32 ie_type)
 }
 
 
+static void pr_get_ranging_capabilities(struct pr_data *pr,
+					struct pr_capabilities *capab)
+{
+	os_memset(capab, 0, sizeof(struct pr_capabilities));
+
+	if (pr->cfg->dev_name)
+		os_strlcpy(capab->device_name, pr->cfg->dev_name,
+			   WPS_DEV_NAME_MAX_LEN);
+
+	if (pr->cfg->edca_ista_support || pr->cfg->edca_rsta_support)
+		capab->edca_support = true;
+
+	if (pr->cfg->ntb_ista_support || pr->cfg->ntb_rsta_support)
+		capab->ntb_support = true;
+
+	capab->secure_he_ltf = pr->cfg->secure_he_ltf;
+	capab->pasn_type = pr->cfg->pasn_type;
+	capab->support_6ghz = pr->cfg->support_6ghz;
+}
+
+
+static void pr_buf_add_ranging_capa_info(struct wpabuf *buf,
+					 struct pr_capabilities *capab)
+{
+	u8 *len;
+	u8 capa_6g = 0;
+	u8 protocol_type = 0;
+
+	if (!buf || !capab)
+		return;
+
+	wpabuf_put_u8(buf, PR_ATTR_RANGING_CAPABILITY);
+	len = wpabuf_put(buf, 2); /* IE length to be filled */
+
+	if (capab->edca_support)
+		protocol_type |= PR_EDCA_BASED_RANGING;
+	if (capab->ntb_support && capab->secure_he_ltf)
+		protocol_type |= PR_NTB_SECURE_LTF_BASED_RANGING;
+	if (capab->ntb_support)
+		protocol_type |= PR_NTB_OPEN_BASED_RANGING;
+
+	wpabuf_put_u8(buf, protocol_type);
+	wpabuf_put_u8(buf, capab->pasn_type);
+
+	if (capab->support_6ghz)
+		capa_6g |= BIT(0);
+
+	wpabuf_put_u8(buf, capa_6g);
+
+	/*Device name*/
+	wpabuf_put_data(buf, capab->device_name, WPS_DEV_NAME_MAX_LEN);
+
+	wpa_printf(MSG_INFO, "PR: Device name: %s", capab->device_name);
+
+	WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2);
+	wpa_hexdump(MSG_DEBUG, "PR: * Device capability info",
+		    len + 2, (u8 *) wpabuf_put(buf, 0) - len - 2);
+}
+
+
 struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country)
 {
 	u32 ie_type;
 	struct wpabuf *buf, *buf2;
+	struct pr_capabilities pr_caps;
 
 	buf = wpabuf_alloc(1000);
 	if (!buf)
 		return NULL;
 
+	pr_get_ranging_capabilities(pr, &pr_caps);
+	pr_buf_add_ranging_capa_info(buf, &pr_caps);
+
 	ie_type = (OUI_WFA << 8) | PR_OUI_TYPE;
 	buf2 = pr_encaps_ie(buf, ie_type);
 	wpabuf_free(buf);
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index ec6539eac..831e731a1 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -58,6 +58,41 @@ struct pr_channels {
 	size_t op_classes;
 };
 
+struct pr_capabilities {
+	u8 pasn_type;
+
+	char device_name[WPS_DEV_NAME_MAX_LEN + 1];
+
+	bool edca_support;
+
+	bool ntb_support;
+
+	bool secure_he_ltf;
+
+	bool support_6ghz;
+};
+
+
+enum pr_attr_id {
+	PR_ATTR_STATUS = 0,
+	PR_ATTR_RANGING_CAPABILITY = 1,
+	PR_ATTR_EDCA_CAPABILITY = 2,
+	PR_ATTR_NTB_CAPABILITY = 3,
+	PR_ATTR_OPERATION_MODE = 4,
+	PR_ATTR_DEVICE_IDENTITY_RESOLUTION = 6,
+	PR_ATTR_DEVICE_IDENTITY_KEY = 7,
+	PR_ATTR_VENDOR_SPECIFIC = 221,
+};
+
+/**
+ * Proximity Ranging capabilities,
+ * Proximity Ranging Implementation Considerations for P2P Operation 2025,
+ * 3.3.2, Table 7.
+ */
+#define PR_EDCA_BASED_RANGING BIT(0)
+#define PR_NTB_SECURE_LTF_BASED_RANGING BIT(1)
+#define PR_NTB_OPEN_BASED_RANGING BIT(2)
+
 /**
  * struct pr_device_info - Proximity ranging peer information
  */
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:36:59 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:06:59 +0530
Subject: [PATCH 06/34] PR: API to add EDCA capabilities in USD PR IE
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-7-peddolla@qti.qualcomm.com>

Add changes to create buffer that contains EDCA(802.11mc) based
ranging capabilities of a device which will be part of a Proximity
Ranging IE used to advertise Ranging capabilities of a device. These
capabilities are added to USD frames to enable their exchange with
peer devices. The EDCA based ranging capabilities have attributes such
as the ability to act as an EDCA based ranging initiator, the ability
to act as a EDCA based ranging responder, the channels on which the
EDCA Ranging is supported by the device and other device specific
capabilities.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 75 ++++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h | 33 +++++++++++++++
 2 files changed, 108 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index da0ea65ee..fab74fa1a 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -163,6 +163,51 @@ static void pr_get_ranging_capabilities(struct pr_data *pr,
 }
 
 
+static void pr_get_edca_capabilities(struct pr_data *pr,
+				     struct edca_capabilities *capab)
+{
+	u16 edca_hw_caps = 0;
+
+	os_memset(capab, 0, sizeof(struct edca_capabilities));
+	capab->ista_support = pr->cfg->edca_ista_support;
+	capab->rsta_support = pr->cfg->edca_rsta_support;
+	os_memcpy(capab->country, pr->cfg->country, 3);
+
+	edca_hw_caps |= (pr->cfg->edca_format_and_bw & EDCA_FORMAT_AND_BW_MASK);
+	edca_hw_caps |= ((pr->cfg->max_tx_antenna & EDCA_MAX_TX_ANTENNA_MASK) << EDCA_MAX_TX_ANTENNA);
+	edca_hw_caps |= ((pr->cfg->max_rx_antenna & EDCA_MAX_RX_ANTENNA_MASK) << EDCA_MAX_RX_ANTENNA);
+
+	capab->edca_hw_caps = edca_hw_caps;
+	os_memcpy(&capab->channels, &pr->cfg->edca_channels,
+		  sizeof(struct pr_channels));
+}
+
+
+static void pr_buf_add_channel_list(struct wpabuf *buf, const char *country,
+				    struct pr_channels *chan)
+{
+	u8 *len;
+	size_t i;
+
+	wpabuf_put_data(buf, country, 3);
+	len = wpabuf_put(buf, 1); /* IE length to be filled */
+
+	/* Channel List */
+	for (i = 0; i < chan->op_classes; i++) {
+		struct pr_op_class *c = &chan->op_class[i];
+
+		wpabuf_put_u8(buf, c->op_class);
+		wpabuf_put_u8(buf, c->channels);
+		wpabuf_put_data(buf, c->channel, c->channels);
+	}
+
+	/* Update attribute length */
+	*len = chan->op_classes;
+	wpa_hexdump(MSG_DEBUG, "PR: * Channel List",
+		    len + 1, (u8 *) wpabuf_put(buf, 0) - len - 1);
+}
+
+
 static void pr_buf_add_ranging_capa_info(struct wpabuf *buf,
 					 struct pr_capabilities *capab)
 {
@@ -202,11 +247,36 @@ static void pr_buf_add_ranging_capa_info(struct wpabuf *buf,
 }
 
 
+static void pr_buf_add_edca_capa_info(struct wpabuf *buf,
+				      struct edca_capabilities *edca_data)
+{
+	u8 *len;
+	u8 ranging_role = 0;
+
+	wpabuf_put_u8(buf, PR_ATTR_EDCA_CAPABILITY);
+	len = wpabuf_put(buf, 2);
+
+	if (edca_data->ista_support)
+		ranging_role |= PR_ISTA_SUPPORT;
+	if (edca_data->rsta_support)
+		ranging_role |= PR_RSTA_SUPPORT;
+
+	wpabuf_put_u8(buf, ranging_role);
+	wpabuf_put_be16(buf, edca_data->edca_hw_caps);
+	pr_buf_add_channel_list(buf, edca_data->country, &edca_data->channels);
+
+	WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2);
+	wpa_hexdump(MSG_DEBUG, "PR: * EDCA data",
+		    len + 2, (u8 *) wpabuf_put(buf, 0) - len - 2);
+}
+
+
 struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country)
 {
 	u32 ie_type;
 	struct wpabuf *buf, *buf2;
 	struct pr_capabilities pr_caps;
+	struct edca_capabilities edca_caps;
 
 	buf = wpabuf_alloc(1000);
 	if (!buf)
@@ -215,6 +285,11 @@ struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country)
 	pr_get_ranging_capabilities(pr, &pr_caps);
 	pr_buf_add_ranging_capa_info(buf, &pr_caps);
 
+	if (pr->cfg->edca_ista_support || pr->cfg->edca_rsta_support) {
+		pr_get_edca_capabilities(pr, &edca_caps);
+		pr_buf_add_edca_capa_info(buf, &edca_caps);
+	}
+
 	ie_type = (OUI_WFA << 8) | PR_OUI_TYPE;
 	buf2 = pr_encaps_ie(buf, ie_type);
 	wpabuf_free(buf);
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 831e731a1..ea4a41bd6 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -28,6 +28,7 @@
  */
 #define PR_MAX_PEER 100
 
+
 /**
  * struct pr_channels - List of supported channels
  */
@@ -72,6 +73,30 @@ struct pr_capabilities {
 	bool support_6ghz;
 };
 
+struct edca_capabilities {
+	bool ista_support;
+
+	bool rsta_support;
+
+/**
+ * Device specific EDCA capabilities
+ * Proximity Ranging Implementation Considerations for P2P Operation 2025,
+ * 3.3.4, Table 8.
+ */
+#define EDCA_FORMAT_AND_BW  0
+#define EDCA_MAX_TX_ANTENNA 6
+#define EDCA_MAX_RX_ANTENNA 9
+
+#define EDCA_FORMAT_AND_BW_MASK  0x003F
+#define EDCA_MAX_TX_ANTENNA_MASK 0x0007
+#define EDCA_MAX_RX_ANTENNA_MASK 0x0007
+	u16 edca_hw_caps;
+
+	char country[3];
+
+	struct pr_channels channels;
+};
+
 
 enum pr_attr_id {
 	PR_ATTR_STATUS = 0,
@@ -93,6 +118,14 @@ enum pr_attr_id {
 #define PR_NTB_SECURE_LTF_BASED_RANGING BIT(1)
 #define PR_NTB_OPEN_BASED_RANGING BIT(2)
 
+/**
+ * Initiator and Responder support
+ * Proximity Ranging Implementation Considerations for P2P Operation 2025,
+ * 3.3.4, Table 8.
+ */
+#define PR_ISTA_SUPPORT BIT(0)
+#define PR_RSTA_SUPPORT BIT(1)
+
 /**
  * struct pr_device_info - Proximity ranging peer information
  */
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:00 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:00 +0530
Subject: [PATCH 07/34] PR: API to add NTB capabilities in USD PR IE
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-8-peddolla@qti.qualcomm.com>

Add changes to create a buffer that contains the NTB (802.11az)
ranging capabilities of a device. This buffer will be part of
the Proximity Ranging Information Element (IE) used to
advertise the device's ranging capabilities. These capabilities
are added to USD frames to enable their exchange with peer
devices.

The NTB-based ranging capabilities include attributes such as
the ability to act as an NTB ranging initiator, the ability to
act as an NTB ranging responder, the channels on which NTB
ranging is supported by the device, and other device-specific
capabilities.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 60 ++++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h | 54 ++++++++++++++++++++++++++++++
 2 files changed, 114 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index fab74fa1a..47d0efdb3 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -183,6 +183,36 @@ static void pr_get_edca_capabilities(struct pr_data *pr,
 }
 
 
+static void pr_get_ntb_capabilities(struct pr_data *pr,
+				    struct ntb_capabilities *capab)
+{
+	u32 ntb_hw_caps = 0;
+
+	os_memset(capab, 0, sizeof(struct ntb_capabilities));
+	capab->ista_support = pr->cfg->ntb_ista_support;
+	capab->rsta_support = pr->cfg->ntb_rsta_support;
+	os_memcpy(capab->country, pr->cfg->country, 3);
+	capab->secure_he_ltf = pr->cfg->secure_he_ltf;
+
+	ntb_hw_caps |= (pr->cfg->ntb_format_and_bw & NTB_FORMAT_AND_BW_MASK);
+	ntb_hw_caps |= ((pr->cfg->max_tx_ltf_repetations & MAX_TX_LTF_REPETATIONS_MASK) << MAX_TX_LTF_REPETATIONS);
+	ntb_hw_caps |= ((pr->cfg->max_rx_ltf_repetations & MAX_RX_LTF_REPETATIONS_MASK) << MAX_RX_LTF_REPETATIONS);
+
+	ntb_hw_caps |= ((pr->cfg->max_rx_ltf_total & MAX_RX_LTF_TOTAL_MASK) << MAX_RX_LTF_TOTAL);
+	ntb_hw_caps |= ((pr->cfg->max_tx_ltf_total & MAX_TX_LTF_TOTAL_MASK) << MAX_TX_LTF_TOTAL);
+
+	ntb_hw_caps |= ((pr->cfg->max_rx_sts_le_80 & MAX_RX_STS_LE_80_MASK) << MAX_RX_STS_LE_80);
+	ntb_hw_caps |= ((pr->cfg->max_rx_sts_gt_80 & MAX_RX_STS_GT_80_MASK) << MAX_RX_STS_GT_80);
+
+	ntb_hw_caps |= ((pr->cfg->max_tx_sts_le_80 & MAX_TX_STS_LE_80_MASK) << MAX_TX_STS_LE_80);
+	ntb_hw_caps |= ((pr->cfg->max_rx_sts_gt_80 & MAX_TX_STS_GT_80_MASK) << MAX_TX_STS_GT_80);
+
+	capab->ntb_hw_caps = ntb_hw_caps;
+	os_memcpy(&capab->channels, &pr->cfg->edca_channels,
+		  sizeof(struct pr_channels));
+}
+
+
 static void pr_buf_add_channel_list(struct wpabuf *buf, const char *country,
 				    struct pr_channels *chan)
 {
@@ -271,12 +301,37 @@ static void pr_buf_add_edca_capa_info(struct wpabuf *buf,
 }
 
 
+static void pr_buf_add_ntb_capa_info(struct wpabuf *buf,
+				     struct ntb_capabilities *ntb_data)
+{
+	u8 *len;
+	u8 ranging_role = 0;
+
+	wpabuf_put_u8(buf, PR_ATTR_NTB_CAPABILITY);
+	len = wpabuf_put(buf, 2);
+
+	if (ntb_data->ista_support)
+		ranging_role |= PR_ISTA_SUPPORT;
+	if (ntb_data->rsta_support)
+		ranging_role |= PR_RSTA_SUPPORT;
+
+	wpabuf_put_u8(buf, ranging_role);
+	wpabuf_put_be32(buf, ntb_data->ntb_hw_caps);
+	pr_buf_add_channel_list(buf, ntb_data->country, &ntb_data->channels);
+
+	WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2);
+	wpa_hexdump(MSG_DEBUG, "PR: * NTB data",
+		    len + 2, (u8 *) wpabuf_put(buf, 0) - len - 2);
+}
+
+
 struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country)
 {
 	u32 ie_type;
 	struct wpabuf *buf, *buf2;
 	struct pr_capabilities pr_caps;
 	struct edca_capabilities edca_caps;
+	struct ntb_capabilities ntb_caps;
 
 	buf = wpabuf_alloc(1000);
 	if (!buf)
@@ -290,6 +345,11 @@ struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country)
 		pr_buf_add_edca_capa_info(buf, &edca_caps);
 	}
 
+	if (pr->cfg->ntb_ista_support || pr->cfg->ntb_rsta_support) {
+		pr_get_ntb_capabilities(pr, &ntb_caps);
+		pr_buf_add_ntb_capa_info(buf, &ntb_caps);
+	}
+
 	ie_type = (OUI_WFA << 8) | PR_OUI_TYPE;
 	buf2 = pr_encaps_ie(buf, ie_type);
 	wpabuf_free(buf);
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index ea4a41bd6..b80f607f6 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -97,6 +97,60 @@ struct edca_capabilities {
 	struct pr_channels channels;
 };
 
+struct ntb_capabilities {
+	bool ista_support;
+
+	bool rsta_support;
+
+	bool secure_he_ltf;
+
+/**
+ * Device specific NTB capabilities
+ * Proximity Ranging Implementation Considerations for P2P Operation 2025,
+ * 3.3.5, Table 9.
+ */
+#define NTB_FORMAT_AND_BW 0
+#define MAX_TX_LTF_REPETATIONS	3
+#define MAX_RX_LTF_REPETATIONS	6
+#define MAX_RX_LTF_TOTAL		9
+#define MAX_TX_LTF_TOTAL		11
+#define MAX_RX_STS_LE_80		13
+#define MAX_RX_STS_GT_80		16
+#define MAX_TX_STS_LE_80		19
+#define MAX_TX_STS_GT_80		22
+
+
+#define NTB_FORMAT_AND_BW_MASK  0x00000007
+
+/* Max Tx LTF repetations supported for non trigger based ranging */
+#define MAX_TX_LTF_REPETATIONS_MASK	0x00000007
+
+/* Max Rx LTF repetations supported for non trigger based ranging */
+#define MAX_RX_LTF_REPETATIONS_MASK	0x00000007
+
+/* Max Rx LTF total supported for non trigger based ranging */
+#define MAX_RX_LTF_TOTAL_MASK		0x00000003
+
+/* Max Tx LTF total supported for non trigger based ranging */
+#define MAX_TX_LTF_TOTAL_MASK		0x00000003
+
+/* To configure max R2I STS for Bandwidth less than or equal to 80Mz */
+#define MAX_RX_STS_LE_80_MASK		0x00000007
+
+/* To configure max R2I STS for Bandwidth greater than 80Mz */
+#define MAX_RX_STS_GT_80_MASK		0x00000007
+
+/* To configure max I2R STS for Bandwidth less than or equal to 80Mz */
+#define MAX_TX_STS_LE_80_MASK		0x00000007
+
+/* To configure max I2R STS for Bandwidth greater than 80Mz */
+#define MAX_TX_STS_GT_80_MASK		0x00000007
+	u32 ntb_hw_caps;
+
+	char country[3];
+
+	struct pr_channels channels;
+};
 
 enum pr_attr_id {
 	PR_ATTR_STATUS = 0,
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:01 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:01 +0530
Subject: [PATCH 08/34] PR: Fetch Device Identity Key from configuration file
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-9-peddolla@qti.qualcomm.com>

Add support to get Device Identity Key from supplicant configuration
file. Generate a new DIK if it is not present in conf file.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.h | 13 +++++++++++++
 wpa_supplicant/pr_supplicant.c | 29 +++++++++++++++++++++++++++++
 2 files changed, 42 insertions(+)

diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index b80f607f6..9a1fd6b5b 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -13,6 +13,8 @@
 #include "utils/list.h"
 #include "drivers/driver.h"
 
+#define DEVICE_IDENTITY_KEY_LEN 16
+
 /**
  * PR_MAX_OP_CLASSES - Maximum number of operating classes
  */
@@ -249,6 +251,17 @@ struct pr_config {
 
 	bool support_6ghz;
 
+	/* Cipher version type */
+	int dik_cipher;
+
+	/* Buffer to hold the DevIK */
+	u8 dik_data[DEVICE_IDENTITY_KEY_LEN];
+
+	/* Length of DevIK in octets */
+	size_t dik_len;
+
+	/* DevIK expiration */
+	int expiration;
 
 	/**
 	 * cb_ctx - Context to use with callback functions
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index df416bd0a..e65e19871 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -142,6 +142,35 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s)
 	} else
 		os_memcpy(pr.country, "XX\x04", 3);
 
+	if (wpa_s->conf->dik &&
+	    wpabuf_len(wpa_s->conf->dik) <= DEVICE_IDENTITY_KEY_LEN) {
+		pr.dik_cipher = wpa_s->conf->dik_cipher;
+		pr.dik_len = wpabuf_len(wpa_s->conf->dik);
+		os_memcpy(pr.dik_data, wpabuf_head(wpa_s->conf->dik),
+			  pr.dik_len);
+		pr.expiration = 24; /* hours */
+	} else {
+		pr.dik_cipher = DIRA_CIPHER_VERSION_128;
+		pr.dik_len = DEVICE_IDENTITY_KEY_LEN;
+		pr.expiration = 24; /* hours */
+		if (os_get_random(pr.dik_data, pr.dik_len) < 0)
+			return -1;
+
+		wpa_s->conf->dik =
+			wpabuf_alloc_copy(pr.dik_data, pr.dik_len);
+		if (!wpa_s->conf->dik)
+			return -1;
+
+		wpa_s->conf->dik_cipher = pr.dik_cipher;
+
+		wpa_printf(MSG_DEBUG, "PR: PR init new DIRA set");
+
+		if (wpa_s->conf->update_config &&
+		    wpa_config_write(wpa_s->confname, wpa_s->conf))
+			wpa_printf(MSG_DEBUG,
+				   "PR: Failed to update configuration");
+	}
+
 	global->pr = pr_init(&pr);
 	if (!global->pr) {
 		wpa_printf(MSG_DEBUG, "PR: Failed to init PR");
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:02 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:02 +0530
Subject: [PATCH 09/34] PR: API to derive and add DIRA in USD PR IE
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-10-peddolla@qti.qualcomm.com>

Add changes to derive the Device Identity Resolution Attribute
(DIRA) and add it to the Proximity Ranging Information Element
(PR IE). The DIRA attribute is used to resolve the identity of a
device even when MAC randomization causes the peer device's
MAC address to change. The DIRA attribute includes fields such as
the cipher version, a nonce, and a tag.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 85 ++++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h | 22 +++++++++
 2 files changed, 107 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 47d0efdb3..02c1f82ea 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -14,6 +14,10 @@
 #include "common/qca-vendor.h"
 #include "wps/wps_i.h"
 #include "proximity_ranging.h"
+#include "crypto/sha256.h"
+#include "crypto/sha384.h"
+#include "crypto/crypto.h"
+
 
 static void pr_device_free(struct pr_data *pr, struct pr_device *dev)
 {
@@ -213,6 +217,62 @@ static void pr_get_ntb_capabilities(struct pr_data *pr,
 }
 
 
+static int pr_derive_dira(struct pr_data *pr, struct pr_dira *dira)
+{
+	u8 nonce[DEVICE_IDENTITY_NONCE_LEN];
+	u8 tag[DEVICE_MAX_HASH_LEN];
+	u8 data[DIR_STR_LEN + DEVICE_IDENTITY_NONCE_LEN + ETH_ALEN];
+
+	if (pr->cfg->dik_cipher != DIRA_CIPHER_VERSION_128) {
+		wpa_printf(MSG_ERROR,
+			   "PR: Unsupported DIRA Cipher version = %d",
+			   pr->cfg->dik_cipher);
+		return -1;
+	}
+
+	if (pr->cfg->dik_len != DEVICE_IDENTITY_KEY_LEN) {
+		wpa_printf(MSG_ERROR,
+			   "PR: Invalid DIK length = %zu", pr->cfg->dik_len);
+		return -1;
+	}
+
+	os_memset(data, 0, sizeof(data));
+
+	if (os_get_random(nonce, DEVICE_IDENTITY_NONCE_LEN) < 0) {
+		wpa_printf(MSG_ERROR, "PR: Failed to generate DIRA nonce");
+		return -1;
+	}
+
+	/* Tag = Truncate-64(HMAC-SHA-256(DevIK, "DIR" || PR Device Address ||
+	 *                                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 + ETH_ALEN], nonce,
+		  DEVICE_IDENTITY_NONCE_LEN);
+
+	if (hmac_sha256(pr->cfg->dik_data, pr->cfg->dik_len, data, sizeof(data),
+			tag) < 0) {
+		wpa_printf(MSG_ERROR, "PR: Could not derive DIRA tag");
+		return -1;
+	}
+
+	os_memset(dira, 0, sizeof(struct pr_dira));
+	dira->cipher_version = pr->cfg->dik_cipher;
+	dira->nonce_len = DEVICE_IDENTITY_NONCE_LEN;
+	os_memcpy(dira->nonce, nonce, DEVICE_IDENTITY_NONCE_LEN);
+	dira->tag_len = DEVICE_IDENTITY_TAG_LEN;
+	os_memcpy(dira->tag, tag, DEVICE_IDENTITY_TAG_LEN);
+
+	wpa_hexdump_key(MSG_DEBUG, "PR: DIK", pr->cfg->dik_data,
+			pr->cfg->dik_len);
+	wpa_hexdump_key(MSG_DEBUG, "PR: DIRA-NONCE", dira->nonce,
+			dira->nonce_len);
+	wpa_hexdump_key(MSG_DEBUG, "PR: DIRA-TAG", dira->tag, dira->tag_len);
+	return 0;
+}
+
+
 static void pr_buf_add_channel_list(struct wpabuf *buf, const char *country,
 				    struct pr_channels *chan)
 {
@@ -325,6 +385,26 @@ static void pr_buf_add_ntb_capa_info(struct wpabuf *buf,
 }
 
 
+static void pr_buf_add_dira(struct wpabuf *buf, struct pr_dira *dira)
+{
+	u8 *len;
+
+	wpabuf_put_u8(buf, PR_ATTR_DEVICE_IDENTITY_RESOLUTION);
+
+	/* Length to be filled */
+	len = wpabuf_put(buf, 2);
+
+	wpabuf_put_u8(buf, dira->cipher_version);
+	wpabuf_put_data(buf, dira->nonce, dira->nonce_len);
+	wpabuf_put_data(buf, dira->tag, dira->tag_len);
+
+	/* Update attribute length */
+	WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2);
+
+	wpa_printf(MSG_DEBUG, "PR: * DIRA");
+}
+
+
 struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country)
 {
 	u32 ie_type;
@@ -332,6 +412,7 @@ struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country)
 	struct pr_capabilities pr_caps;
 	struct edca_capabilities edca_caps;
 	struct ntb_capabilities ntb_caps;
+	struct pr_dira dira;
 
 	buf = wpabuf_alloc(1000);
 	if (!buf)
@@ -350,6 +431,10 @@ struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country)
 		pr_buf_add_ntb_capa_info(buf, &ntb_caps);
 	}
 
+	/* PR Device Identity Resolution attribute */
+	if (!pr_derive_dira(pr, &dira))
+		pr_buf_add_dira(buf, &dira);
+
 	ie_type = (OUI_WFA << 8) | PR_OUI_TYPE;
 	buf2 = pr_encaps_ie(buf, ie_type);
 	wpabuf_free(buf);
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 9a1fd6b5b..87b744d4c 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -14,6 +14,13 @@
 #include "drivers/driver.h"
 
 #define DEVICE_IDENTITY_KEY_LEN 16
+#define DEVICE_IDENTITY_TAG_LEN 8
+#define DEVICE_IDENTITY_NONCE_LEN 8
+#define DIR_STR_LEN 3
+#define DEVICE_MAX_HASH_LEN 32
+
+/* DIRA Cipher versions */
+#define DIRA_CIPHER_VERSION_128 0
 
 /**
  * PR_MAX_OP_CLASSES - Maximum number of operating classes
@@ -281,6 +288,21 @@ struct pr_data {
 	struct dl_list devices;
 };
 
+
+/* PR Device Identity Resolution Attribute parameters */
+struct pr_dira {
+	/* Cipher version type */
+	int cipher_version;
+	/* Nonce used in DIRA attribute */
+	u8 nonce[DEVICE_IDENTITY_NONCE_LEN];
+	/* Length of nonce */
+	size_t nonce_len;
+	/* Tag computed for nonce using NIK */
+	u8 tag[DEVICE_IDENTITY_TAG_LEN];
+	/* Length of tag in octets */
+	size_t tag_len;
+};
+
 struct pr_data * pr_init(const struct pr_config *cfg);
 void pr_deinit(struct pr_data *pr);
 struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country);
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:03 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:03 +0530
Subject: [PATCH 10/34] PR: APIs to add and clear Device Identity Key, password and PMK
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-11-peddolla@qti.qualcomm.com>

Add changes to add and clear the Device Identity Key (DIK),
password, and PMK. These attributes are associated with the DIK
and are stored in a list that is linked to the global Proximity
Ranging context.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 75 ++++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h | 31 ++++++++++++++
 wpa_supplicant/pr_supplicant.c | 23 +++++++++++
 wpa_supplicant/pr_supplicant.h | 14 +++++++
 4 files changed, 143 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 02c1f82ea..ebbacb798 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -88,6 +88,7 @@ struct pr_data * pr_init(const struct pr_config *cfg)
 		pr->cfg->dev_name = NULL;
 
 	dl_list_init(&pr->devices);
+	dl_list_init(&pr->dev_iks);
 
 	return pr;
 }
@@ -96,6 +97,7 @@ struct pr_data * pr_init(const struct pr_config *cfg)
 void pr_deinit(struct pr_data *pr)
 {
 	struct pr_device *dev, *prev;
+	struct pr_dev_ik *dev_ik, *prev_dev_ik;
 
 	if (!pr)
 		return;
@@ -106,12 +108,85 @@ void pr_deinit(struct pr_data *pr)
 		dl_list_del(&dev->list);
 		pr_device_free(pr, dev);
 	}
+	dl_list_for_each_safe(dev_ik, prev_dev_ik, &pr->dev_iks,
+			      struct pr_dev_ik, list) {
+		dl_list_del(&dev_ik->list);
+		os_free(dev_ik);
+	}
 
 	os_free(pr);
 	wpa_printf(MSG_DEBUG, "PR: Deinit done");
 }
 
 
+void pr_clear_dev_iks(struct pr_data *pr)
+{
+	struct pr_device *dev;
+	struct pr_dev_ik *dev_ik, *prev_dev_ik;
+
+	pr->cfg->dik_len = 0;
+	pr->cfg->global_password_valid = false;
+	os_memset(pr->cfg->dik_data, 0, DEVICE_IDENTITY_KEY_LEN);
+	os_memset(pr->cfg->global_password, 0,
+		  sizeof(pr->cfg->global_password));
+
+	dl_list_for_each(dev, &pr->devices, struct pr_device, list) {
+		dev->password_valid = false;
+		os_memset(dev->password, 0, sizeof(dev->password));
+	}
+
+	dl_list_for_each_safe(dev_ik, prev_dev_ik, &pr->dev_iks,
+			      struct pr_dev_ik, list) {
+		dl_list_del(&dev_ik->list);
+		os_free(dev_ik);
+	}
+}
+
+
+void pr_add_dev_ik(struct pr_data *pr, const u8 *dik, const char *password,
+		   const u8 *pmk, bool own)
+{
+	struct pr_dev_ik *dev_ik;
+
+	if (own) {
+		os_memcpy(pr->cfg->dik_data, dik, DEVICE_IDENTITY_KEY_LEN);
+		pr->cfg->dik_len = DEVICE_IDENTITY_KEY_LEN;
+		if (password) {
+			os_strlcpy(pr->cfg->global_password, password,
+				   sizeof(pr->cfg->global_password));
+			pr->cfg->global_password_valid = true;
+		}
+		return;
+	}
+
+	dl_list_for_each(dev_ik, &pr->dev_iks, struct pr_dev_ik, list) {
+		if (!os_memcmp(dik, dev_ik->dik, DEVICE_IDENTITY_KEY_LEN)) {
+			dl_list_del(&dev_ik->list);
+			os_free(dev_ik);
+			break;
+		}
+	}
+
+	dev_ik = os_zalloc(sizeof(*dev_ik));
+	if (!dev_ik)
+		return;
+
+	dl_list_add(&pr->dev_iks, &dev_ik->list);
+	os_memcpy(dev_ik->dik, dik, DEVICE_IDENTITY_KEY_LEN);
+	if (password) {
+		os_strlcpy(dev_ik->password, password,
+			   sizeof(dev_ik->password));
+		dev_ik->password_valid = true;
+	}
+	if (pmk) {
+		os_memcpy(dev_ik->pmk, pmk, WPA_PASN_PMK_LEN);
+		dev_ik->pmk_valid = true;
+	}
+
+	wpa_printf(MSG_DEBUG, "PR: New Device Identity add to list");
+}
+
+
 static struct wpabuf * pr_encaps_ie(const struct wpabuf *subelems, u32 ie_type)
 {
 	struct wpabuf *ie = NULL;
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 87b744d4c..057ecb79a 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -189,6 +189,15 @@ enum pr_attr_id {
 #define PR_ISTA_SUPPORT BIT(0)
 #define PR_RSTA_SUPPORT BIT(1)
 
+struct pr_dev_ik {
+	struct dl_list list;
+	u8 dik[DEVICE_IDENTITY_KEY_LEN];
+	char password[100];
+	bool password_valid;
+	u8 pmk[WPA_PASN_PMK_LEN];
+	bool pmk_valid;
+};
+
 /**
  * struct pr_device_info - Proximity ranging peer information
  */
@@ -201,6 +210,18 @@ struct pr_device {
 	 * pr_device_addr - PR Device Address of the peer
 	 */
 	u8 pr_device_addr[ETH_ALEN];
+
+	/* Password to be used in PASN-SAE by the Seeker
+	 * This is updated with valid password if DIRA matches for the peer
+	 */
+	char password[100];
+	bool password_valid;
+
+	/* PMK to be used in PASN-PMK by the Seeker
+	 * This is updated with valid PMK if DIRA matches for the peer
+	 */
+	u8 pmk[PMK_LEN_MAX];
+	bool pmk_valid;
 };
 
 
@@ -270,6 +291,11 @@ struct pr_config {
 	/* DevIK expiration */
 	int expiration;
 
+	/* Global Password to be used in PASN-SAE for Advertiser */
+	char global_password[100];
+
+	bool global_password_valid;
+
 	/**
 	 * cb_ctx - Context to use with callback functions
 	 */
@@ -286,6 +312,8 @@ struct pr_data {
 	struct pr_config *cfg;
 
 	struct dl_list devices;
+
+	struct dl_list dev_iks;
 };
 
 
@@ -305,6 +333,9 @@ struct pr_dira {
 
 struct pr_data * pr_init(const struct pr_config *cfg);
 void pr_deinit(struct pr_data *pr);
+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, bool own);
 struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country);
 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/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index e65e19871..c41e339f9 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -190,3 +190,26 @@ void wpas_pr_deinit(struct wpa_supplicant *wpa_s)
 		wpa_s->global->pr_init_wpa_s = NULL;
 	}
 }
+
+
+void wpas_pr_clear_dev_iks(struct wpa_supplicant *wpa_s)
+{
+	struct pr_data *pr = wpa_s->global->pr;
+
+	if (!pr)
+		return;
+
+	pr_clear_dev_iks(pr);
+}
+
+
+void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik,
+			const char *password, const u8 *pmk, bool own)
+{
+	struct pr_data *pr = wpa_s->global->pr;
+
+	if (!pr || !dik)
+		return;
+
+	pr_add_dev_ik(pr, dik, password, pmk, own);
+}
diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h
index 6abe59331..e263c17bb 100644
--- a/wpa_supplicant/pr_supplicant.h
+++ b/wpa_supplicant/pr_supplicant.h
@@ -15,6 +15,9 @@
 
 int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s);
 void wpas_pr_deinit(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,
+			const char *password, const u8 *pmk, bool own);
 struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s);
 void wpas_pr_process_usd_elems(struct wpa_supplicant *wpa_s, const u8 *buf,
 			       u16 buf_len, const u8 *peer_addr,
@@ -30,6 +33,17 @@ static inline void wpas_pr_deinit(struct wpa_supplicant *wpa_s)
 {
 }
 
+
+static inline void wpas_pr_clear_dev_iks(struct wpa_supplicant *wpa_s)
+{
+}
+
+static inline void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s,
+				      const u8 *dik, const char *password,
+				      const u8 *pmk, bool own)
+{
+}
+
 static inline struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s)
 {
 	return NULL;
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:04 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:04 +0530
Subject: [PATCH 11/34] PR: Add support to parse attributes of ranging IE
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-12-peddolla@qti.qualcomm.com>

Add changes to get the Proximity Ranging IE, and parse this IE
based on the attributes. This IE contains ranging capabilities,
Device identity resolution attribute and such. This is also stored in
a message structure to be processed further.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 187 +++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h |  28 +++++
 2 files changed, 215 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index ebbacb798..cc8cf0bb6 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -518,17 +518,204 @@ struct wpabuf * pr_prepare_usd_elems(struct pr_data *pr, const char *country)
 }
 
 
+static int pr_parse_attribute(u8 id, const u8 *data, u16 len,
+			      struct pr_message *msg)
+{
+	switch (id) {
+	case PR_ATTR_RANGING_CAPABILITY:
+		if (len < 34) {
+			wpa_printf(MSG_ERROR,
+				   "PR: Too short Capability attribute (length %d)",
+				   len);
+			return -1;
+		}
+		msg->pr_capability = data;
+		msg->pr_capability_len = len;
+		wpa_printf(MSG_ERROR,
+			   "PR: Device Capability: %02x Pasn Type: %02x",
+			   data[0], data[1]);
+		break;
+	case PR_ATTR_EDCA_CAPABILITY:
+		if (len < 11) {
+			wpa_printf(MSG_ERROR,
+				   "PR: Too short EDCA Capability attribute (length %d)",
+				   len);
+			return -1;
+		}
+		msg->edca_capability = data;
+		msg->edca_capability_len = len;
+		wpa_printf(MSG_DEBUG,
+			   "PR: EDCA capable roles %u, capability %u",
+			   data[0], WPA_GET_BE16(data + 1));
+		break;
+	case PR_ATTR_NTB_CAPABILITY:
+		if (len < 13) {
+			wpa_printf(MSG_ERROR,
+				   "PR: Too short NTB capability attribute (length %d)",
+				   len);
+			return -1;
+		}
+		msg->ntb_capability = data;
+		msg->ntb_capability_len = len;
+		wpa_printf(MSG_DEBUG,
+			   "PR: NTB capable roles %u, capabilities %u",
+			   data[0], WPA_GET_BE32(data + 1));
+		break;
+	case PR_ATTR_DEVICE_IDENTITY_RESOLUTION:
+		if (len < 17) {
+			wpa_printf(MSG_ERROR,
+				   "PR: Too short DIRA attribute (length %d)",
+				   len);
+			return -1;
+		}
+		msg->dira = data;
+		msg->dira_len = len;
+		wpa_printf(MSG_DEBUG, "PR: DIRA cipher version %u", data[0]);
+		break;
+	case PR_ATTR_STATUS:
+		if (len != 1) {
+			wpa_printf(MSG_ERROR,
+				   "PR: Invalid status attribute (length %d)",
+				   len);
+			return -1;
+		}
+		msg->status_ie = data;
+		msg->status_ie_len = len;
+		wpa_printf(MSG_DEBUG, "PR: PASN status=%u", data[0]);
+		break;
+	case PR_ATTR_OPERATION_MODE:
+		if (len < 7) {
+			wpa_printf(MSG_ERROR,
+				   "PR: Invalid operation mode attribute (length %d)",
+				   len);
+			return -1;
+		}
+		msg->op_mode = data;
+		msg->op_mode_len = len;
+		break;
+	default:
+		wpa_printf(MSG_DEBUG,
+			   "PR: Skipped unknown attribute %d (length %d)",
+			   id, len);
+		break;
+	}
+
+	return 0;
+}
+
+
+/**
+ * pr_parse_proximity_ranging_ie - Parse Proximity Ranging IE
+ * @buf: Concatenated PR IE(s) payload
+ * @msg: Buffer for returning parsed attributes
+ * Returns: 0 on success, -1 on failure
+ *
+ * Note: Caller is responsible for clearing the msg data structure before
+ * calling this function.
+ */
+static int pr_parse_proximity_ranging_ie(const struct wpabuf *buf,
+					 struct pr_message *msg)
+{
+	const u8 *pos = wpabuf_head_u8(buf);
+	const u8 *end = pos + wpabuf_len(buf);
+
+	wpa_printf(MSG_DEBUG, "PR: Parsing Proximity Ranging IE");
+
+	while (pos < end) {
+		u16 attr_len;
+		u8 id;
+
+		if (end - pos < 3) {
+			wpa_printf(MSG_DEBUG, "PR: Invalid PR attribute");
+			return -1;
+		}
+		id = *pos++;
+		attr_len = WPA_GET_LE16(pos);
+		pos += 2;
+		wpa_printf(MSG_DEBUG, "PR: Attribute %d length %u",
+			   id, attr_len);
+		if (attr_len > end - pos) {
+			wpa_printf(MSG_DEBUG,
+				   "PR: Attribute underflow (len=%u left=%d)",
+				   attr_len, (int) (end - pos));
+			wpa_hexdump(MSG_MSGDUMP, "PR: Data", pos, end - pos);
+			return -1;
+		}
+		if (pr_parse_attribute(id, pos, attr_len, msg))
+			return -1;
+		pos += attr_len;
+	}
+
+	return 0;
+}
+
+
+static void pr_parse_free(struct pr_message *msg)
+{
+	wpabuf_free(msg->pr_attributes);
+	msg->pr_attributes = NULL;
+}
+
+
+/**
+ * pr_parse_ies - Parse PR message IEs
+ * @data: IEs from the message
+ * @len: Length of data buffer in octets
+ * @msg: Buffer for returning parsed attributes
+ * Returns: 0 on success, -1 on failure
+ *
+ * Note: Caller is responsible for clearing the msg data structure before
+ * calling this function.
+ *
+ * Note: Caller must free temporary memory allocations by calling
+ * pr_parse_free() when the parsed data is not needed anymore.
+ */
+static int pr_parse_ies(const u8 *data, size_t len, struct pr_message *msg)
+{
+	struct ieee802_11_elems elems;
+
+	if (ieee802_11_parse_elems(data, len, &elems, true) == ParseFailed)
+		return -1;
+
+	msg->pr_attributes = ieee802_11_vendor_ie_concat(data, len,
+							 PR_IE_VENDOR_TYPE);
+	if (msg->pr_attributes &&
+	    pr_parse_proximity_ranging_ie(msg->pr_attributes, msg)) {
+		wpa_printf(MSG_ERROR, "PR: Failed to parse PR IE data");
+		if (msg->pr_attributes)
+			wpa_hexdump_buf(MSG_MSGDUMP, "PR: PR IE data",
+					msg->pr_attributes);
+		pr_parse_free(msg);
+		return -1;
+	}
+	return 0;
+}
+
+
 void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 			  const u8 *peer_addr, unsigned int freq)
 {
 	struct pr_device *dev;
+	struct pr_message msg;
+
+	os_memset(&msg, 0, sizeof(msg));
+	os_memcpy(msg.pr_device_addr, peer_addr, ETH_ALEN);
+
+	if (pr_parse_ies(ies, ies_len, &msg)) {
+		wpa_printf(MSG_ERROR, "PR: Failed to parse PR IEs data");
+		pr_parse_free(&msg);
+		return;
+	}
 
 	dev = pr_create_device(pr, peer_addr);
 	if (!dev) {
+		pr_parse_free(&msg);
 		wpa_printf(MSG_ERROR, "PR: Failed to create a device");
 		return;
 	}
 
 	os_get_reltime(&dev->last_seen);
 	dev->listen_freq = freq;
+
+	pr_parse_free(&msg);
 }
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 057ecb79a..3d66a0b67 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -225,6 +225,34 @@ struct pr_device {
 };
 
 
+/**
+ * struct pr_message - Proximity ranging peer information
+ */
+struct pr_message {
+	struct wpabuf *pr_attributes;
+
+	u8 pr_device_addr[ETH_ALEN];
+
+	const u8 *pr_capability;
+	size_t pr_capability_len;
+
+	const u8 *edca_capability;
+	size_t edca_capability_len;
+
+	const u8 *ntb_capability;
+	size_t ntb_capability_len;
+
+	const u8 *dira;
+	size_t dira_len;
+
+	const u8 *status_ie;
+	size_t status_ie_len;
+
+	const u8 *op_mode;
+	size_t op_mode_len;
+};
+
+
 struct pr_config {
 	u8 pasn_type;
 
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:05 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:05 +0530
Subject: [PATCH 12/34] PR: API to process ranging capabilities from USD PR IE
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-13-peddolla@qti.qualcomm.com>

Add changes to parse part of Proximity Ranging IE corresponding to the
Proximity Ranging capability attribute and store the corresponding
data in a structure. This attribute IE contains capabilities such
as EDCA based ranging support, NTB based ranging support and such.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 56 ++++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h |  2 ++
 2 files changed, 58 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index cc8cf0bb6..b1b93cf75 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -692,6 +692,52 @@ static int pr_parse_ies(const u8 *data, size_t len, struct pr_message *msg)
 }
 
 
+
+
+static void pr_process_ranging_capabilities(const u8 *caps, size_t caps_len,
+					    struct pr_capabilities *pr_caps)
+{
+	const u8 *pos;
+
+	if (!caps || caps_len < 34) {
+		wpa_printf(MSG_ERROR, "PR: Invalid args in PR capabilities");
+		return;
+	}
+
+	pos = caps;
+
+	if (*pos & PR_EDCA_BASED_RANGING)
+		pr_caps->edca_support = true;
+	if (*pos & PR_NTB_SECURE_LTF_BASED_RANGING) {
+		pr_caps->secure_he_ltf = true;
+		pr_caps->ntb_support = true;
+	}
+	if (*pos & PR_NTB_OPEN_BASED_RANGING)
+		pr_caps->ntb_support = true;
+
+	pos++;
+	pr_caps->pasn_type = *pos;
+
+	pos++;
+
+	if (*pos & BIT(0))
+		pr_caps->support_6ghz = true;
+	else
+		pr_caps->support_6ghz = false;
+
+	pos++;
+
+	os_memset(pr_caps->device_name, 0, WPS_DEV_NAME_MAX_LEN + 1);
+	os_memcpy(pr_caps->device_name, pos, WPS_DEV_NAME_MAX_LEN);
+
+	wpa_printf(MSG_INFO, "PR: Device name=%s, edca capability=%u, ntb capability= %u,"
+		   " secure ltf capability= %u, 6Ghz support= %u",
+		   pr_caps->device_name, pr_caps->edca_support,
+		   pr_caps->ntb_support, pr_caps->secure_he_ltf,
+		   pr_caps->support_6ghz);
+}
+
+
 void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 			  const u8 *peer_addr, unsigned int freq)
 {
@@ -707,6 +753,13 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 		return;
 	}
 
+	if (!msg.pr_capability) {
+		wpa_printf(MSG_DEBUG,
+			   "PR: Ranging caps not present, Ignoring proximity device");
+		pr_parse_free(&msg);
+		return;
+	}
+
 	dev = pr_create_device(pr, peer_addr);
 	if (!dev) {
 		pr_parse_free(&msg);
@@ -717,5 +770,8 @@ 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;
 
+	pr_process_ranging_capabilities(msg.pr_capability,
+					msg.pr_capability_len, &dev->pr_caps);
+
 	pr_parse_free(&msg);
 }
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 3d66a0b67..719d3fec0 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -211,6 +211,8 @@ struct pr_device {
 	 */
 	u8 pr_device_addr[ETH_ALEN];
 
+	struct pr_capabilities pr_caps;
+
 	/* Password to be used in PASN-SAE by the Seeker
 	 * This is updated with valid password if DIRA matches for the peer
 	 */
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:06 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:06 +0530
Subject: [PATCH 13/34] PR: API to process EDCA capabilities from USD PR IE
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-14-peddolla@qti.qualcomm.com>

Add changes to parse part of Proximity Ranging IE corresponding to the
EDCA based ranging capability attribute and store the corresponding
data in a structure. This attribute IE contains capabilities that tell
if the device can act as an EDCA based ranging initiator, EDCA based
ranging responder and list of channels on which the device supports
EDCA based ranging and other device specific capabilities.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 81 ++++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h |  1 +
 2 files changed, 82 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index b1b93cf75..d2ad2ef98 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -692,6 +692,48 @@ static int pr_parse_ies(const u8 *data, size_t len, struct pr_message *msg)
 }
 
 
+static int pr_process_channels(const u8 *channel_list, size_t channel_list_len,
+			       struct pr_channels *ch)
+{
+	u8 channels;
+	const u8 *pos, *end;
+	u8 op_class_count;
+
+	pos = channel_list;
+	end = channel_list + channel_list_len;
+
+	/* Get total count of the operational classes */
+	op_class_count = pos[0];
+	wpa_printf(MSG_ERROR, "PR: Total operational classes: %u",
+		   op_class_count);
+	pos++;
+
+	ch->op_classes = 0;
+	while (end - pos > 2 && (ch->op_classes <= op_class_count)) {
+		struct pr_op_class *cl = &ch->op_class[ch->op_classes];
+
+		cl->op_class = *pos++;
+		channels = *pos++;
+		if (channels > end - pos) {
+			wpa_printf(MSG_ERROR,
+				   "PR: Invalid channel list channel %d, size: %ld",
+				   channels, end - pos);
+			return -1;
+		}
+		cl->channels = channels > PR_MAX_OP_CLASS_CHANNELS ?
+				PR_MAX_OP_CLASS_CHANNELS : channels;
+		os_memcpy(cl->channel, pos, cl->channels);
+		pos += channels;
+		ch->op_classes++;
+	}
+
+	if (ch->op_classes != op_class_count) {
+		wpa_printf(MSG_ERROR, "PR: Channel list count mismatch %lu, %d",
+			   ch->op_classes, op_class_count);
+		return -1;
+	}
+	return 0;
+}
 
 
 static void pr_process_ranging_capabilities(const u8 *caps, size_t caps_len,
@@ -738,6 +780,40 @@ static void pr_process_ranging_capabilities(const u8 *caps, size_t caps_len,
 }
 
 
+static void pr_process_edca_capabilities(const u8 *caps, size_t caps_len,
+					 struct edca_capabilities *edca_caps)
+{
+	const u8 *pos, *end;
+
+	if (!caps)
+		return;
+
+	pos = caps;
+	end = caps + caps_len;
+
+	if (*pos & PR_ISTA_SUPPORT)
+		edca_caps->ista_support = true;
+	if (*pos & PR_RSTA_SUPPORT)
+		edca_caps->rsta_support = true;
+
+	pos++;
+	edca_caps->edca_hw_caps = WPA_GET_BE16(pos);
+
+	pos += 2;
+	os_memcpy(edca_caps->country, pos, 3);
+
+	pos += 3;
+	pr_process_channels(pos, end - pos, &edca_caps->channels);
+
+	wpa_printf(MSG_INFO, "PR: EDCA ISTA support=%u, EDCA RSTA support= %u, "
+		   "op classes count=%lu, country=%c%c",
+		   edca_caps->ista_support, edca_caps->rsta_support,
+		   edca_caps->channels.op_classes,
+		   edca_caps->country[0] ? edca_caps->country[0] : '_',
+		   edca_caps->country[1] ? edca_caps->country[1] : '_');
+}
+
+
 void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 			  const u8 *peer_addr, unsigned int freq)
 {
@@ -773,5 +849,10 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 	pr_process_ranging_capabilities(msg.pr_capability,
 					msg.pr_capability_len, &dev->pr_caps);
 
+	if (dev->pr_caps.edca_support && msg.edca_capability)
+		pr_process_edca_capabilities(msg.edca_capability,
+					     msg.edca_capability_len,
+					     &dev->edca_caps);
+
 	pr_parse_free(&msg);
 }
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 719d3fec0..ab6c0f965 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -212,6 +212,7 @@ struct pr_device {
 	u8 pr_device_addr[ETH_ALEN];
 
 	struct pr_capabilities pr_caps;
+	struct edca_capabilities edca_caps;
 
 	/* Password to be used in PASN-SAE by the Seeker
 	 * This is updated with valid password if DIRA matches for the peer
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:07 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:07 +0530
Subject: [PATCH 14/34] PR: API to process NTB capabilities from USD PR IE
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-15-peddolla@qti.qualcomm.com>

Add changes to parse part of Proximity Ranging IE corresponding to the
NTB ranging capability attribute and store the corresponding
data in a structure. This attribute IE contains capabilities that tell
if the device can act as an NTB ranging initiator, NTB ranging
responder and list of channels on which the device supports NTB
ranging and other device specific capabilities.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 51 ++++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h |  1 +
 2 files changed, 52 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index d2ad2ef98..1feeaede8 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -814,6 +814,44 @@ static void pr_process_edca_capabilities(const u8 *caps, size_t caps_len,
 }
 
 
+static void pr_process_ntb_capabilities(const u8 *caps, size_t caps_len,
+					struct ntb_capabilities *ntb_caps,
+					bool secure_ltf)
+{
+	const u8 *pos, *end;
+
+	if (!caps)
+		return;
+
+	pos = caps;
+	end = caps + caps_len;
+
+	if (*pos & PR_ISTA_SUPPORT)
+		ntb_caps->ista_support = true;
+	if (*pos & PR_RSTA_SUPPORT)
+		ntb_caps->rsta_support = true;
+	if (secure_ltf)
+		ntb_caps->secure_he_ltf = true;
+
+	pos++;
+	ntb_caps->ntb_hw_caps = WPA_GET_BE32(pos);
+
+	pos += 4;
+	os_memcpy(ntb_caps->country, pos, 3);
+
+	pos += 3;
+	pr_process_channels(pos, end - pos, &ntb_caps->channels);
+
+	wpa_printf(MSG_INFO, "PR: NTB ISTA support=%u, NTB RSTA support=%u, "
+		   "op classes count=%lu, secure HE-LTF=%u, country=%c%c",
+		   ntb_caps->ista_support, ntb_caps->rsta_support,
+		   ntb_caps->channels.op_classes,
+		   ntb_caps->secure_he_ltf,
+		   ntb_caps->country[0] ? ntb_caps->country[0] : '_',
+		   ntb_caps->country[1] ? ntb_caps->country[1] : '_');
+}
+
+
 void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 			  const u8 *peer_addr, unsigned int freq)
 {
@@ -836,6 +874,13 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 		return;
 	}
 
+	if (!msg.edca_capability && !msg.ntb_capability) {
+		wpa_printf(MSG_DEBUG,
+			   "PR: EDCA caps not present, Ignoring proximity device");
+		pr_parse_free(&msg);
+		return;
+	}
+
 	dev = pr_create_device(pr, peer_addr);
 	if (!dev) {
 		pr_parse_free(&msg);
@@ -854,5 +899,11 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 					     msg.edca_capability_len,
 					     &dev->edca_caps);
 
+	if (dev->pr_caps.ntb_support && msg.ntb_capability)
+		pr_process_ntb_capabilities(msg.ntb_capability,
+					    msg.ntb_capability_len,
+					    &dev->ntb_caps,
+					    dev->pr_caps.secure_he_ltf);
+
 	pr_parse_free(&msg);
 }
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index ab6c0f965..85a5727a3 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -213,6 +213,7 @@ struct pr_device {
 
 	struct pr_capabilities pr_caps;
 	struct edca_capabilities edca_caps;
+	struct ntb_capabilities ntb_caps;
 
 	/* Password to be used in PASN-SAE by the Seeker
 	 * This is updated with valid password if DIRA matches for the peer
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:08 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:08 +0530
Subject: [PATCH 15/34] PR: API to validate DIRA from USD PR IE
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-16-peddolla@qti.qualcomm.com>

Add changes to parse and validate the Device Identity Resolution
Attribute (DIRA). The tag is derived using each DIK in the list
stored in the global Proximity Ranging context, along with the
nonce from the DIRA and the device address.

The derived tag is then compared with the tag present in the
DIRA. If the tags match, the device identity is confirmed, and
any data associated with the matching DIK is retrieved for
later use.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 64 ++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 1feeaede8..11693dc5d 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -348,6 +348,67 @@ static int pr_derive_dira(struct pr_data *pr, struct pr_dira *dira)
 }
 
 
+static int pr_validate_dira(struct pr_data *pr, struct pr_device *dev,
+			    const u8 *dira, u16 dira_len)
+{
+	int ret;
+	size_t len[3];
+	const u8 *addr[3];
+	struct pr_dev_ik *dev_ik;
+	u8 tag[DEVICE_MAX_HASH_LEN];
+	const char *label = "DIR";
+	const u8 *dira_nonce, *dira_tag;
+
+	if (dira_len < 1 || dira[0] != DIRA_CIPHER_VERSION_128) {
+		wpa_printf(MSG_DEBUG, "Unsupported DIRA cipher version %d",
+			   dira[0]);
+		return -1;
+	}
+
+	if (dira_len < 1 + DEVICE_IDENTITY_NONCE_LEN + DEVICE_IDENTITY_TAG_LEN)
+	{
+		wpa_printf(MSG_DEBUG, "Truncated DIRA (length %u)", dira_len);
+		return -1;
+	}
+
+	dira_nonce = &dira[1];
+	dira_tag = &dira[1 + DEVICE_IDENTITY_NONCE_LEN];
+
+	addr[0] = (const u8 *) label;
+	len[0] = DIR_STR_LEN;
+	addr[1] = dev->pr_device_addr;
+	len[1] = ETH_ALEN;
+	addr[2] = dira_nonce;
+	len[2] = DEVICE_IDENTITY_NONCE_LEN;
+
+	dl_list_for_each(dev_ik, &pr->dev_iks, struct pr_dev_ik, list) {
+		ret = hmac_sha256_vector(dev_ik->dik, DEVICE_IDENTITY_KEY_LEN,
+					 3, addr, len, tag);
+		if (ret < 0) {
+			wpa_printf(MSG_ERROR,
+				   "PR: Failed to derive DIRA Tag");
+			return -1;
+		}
+
+		if (os_memcmp(tag, dira_tag, DEVICE_IDENTITY_TAG_LEN) == 0) {
+			wpa_printf(MSG_DEBUG, "PR: DIRA Tag matched");
+			if (dev_ik->password_valid) {
+				os_strlcpy(dev->password, dev_ik->password,
+					   sizeof(dev->password));
+				dev->password_valid = true;
+			}
+			if (dev_ik->pmk_valid) {
+				os_memcpy(dev->pmk, dev_ik->pmk,
+					  WPA_PASN_PMK_LEN);
+				dev->pmk_valid = true;
+			}
+			return 0;
+		}
+	}
+	return -1;
+}
+
+
 static void pr_buf_add_channel_list(struct wpabuf *buf, const char *country,
 				    struct pr_channels *chan)
 {
@@ -905,5 +966,8 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 					    &dev->ntb_caps,
 					    dev->pr_caps.secure_he_ltf);
 
+	if (msg.dira && msg.dira_len)
+		pr_validate_dira(pr, dev, msg.dira, msg.dira_len);
+
 	pr_parse_free(&msg);
 }
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:09 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:09 +0530
Subject: [PATCH 16/34] PR: Add support to initiate PASN authentication for Proximity Ranging
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-17-peddolla@qti.qualcomm.com>

Proximity Ranging negotiation is performed by piggybacking PR
Information Elements (IEs) onto PASN frames. These changes are
introduced to initiate PASN and enable this negotiation process.

The negotiation determines the roles, the type of ranging, and
the channel on which the ranging should be conducted.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c    | 125 +++++++++++++++++++++++++++
 src/common/proximity_ranging.h    |  42 ++++++++++
 wpa_supplicant/pr_supplicant.c    | 135 ++++++++++++++++++++++++++++++
 wpa_supplicant/pr_supplicant.h    |  13 +++
 wpa_supplicant/wpa_supplicant_i.h |   1 +
 5 files changed, 316 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 11693dc5d..580e97f7e 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -13,14 +13,22 @@
 #include "common/ieee802_11_common.h"
 #include "common/qca-vendor.h"
 #include "wps/wps_i.h"
+#include "common/sae.h"
 #include "proximity_ranging.h"
 #include "crypto/sha256.h"
 #include "crypto/sha384.h"
 #include "crypto/crypto.h"
+#include "pasn/pasn_common.h"
 
 
 static void pr_device_free(struct pr_data *pr, struct pr_device *dev)
 {
+#ifdef CONFIG_PASN
+	if (dev->pasn) {
+		wpa_pasn_reset(dev->pasn);
+		pasn_data_deinit(dev->pasn);
+	}
+#endif /* CONFIG_PASN */
 	os_free(dev);
 }
 
@@ -971,3 +979,120 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 
 	pr_parse_free(&msg);
 }
+
+
+#ifdef CONFIG_PASN
+
+static int pr_pasn_initialize(struct pr_data *pr, struct pr_device *dev,
+			      const u8 *addr, u8 auth_mode, int freq)
+{
+	struct pasn_data *pasn;
+
+	if (!pr || !dev)
+		return -1;
+
+	if (dev->pasn) {
+		wpa_pasn_reset(dev->pasn);
+	} else {
+		dev->pasn = pasn_data_init();
+		if (!dev->pasn)
+			return -1;
+	}
+
+	pasn = dev->pasn;
+	os_memcpy(pasn->own_addr, pr->cfg->dev_addr, ETH_ALEN);
+	os_memcpy(pasn->peer_addr, addr, ETH_ALEN);
+
+	if (dev->pasn_role == PR_ROLE_PASN_INITIATOR) {
+		os_memcpy(pasn->bssid, pasn->peer_addr, ETH_ALEN);
+	} else {
+		os_memcpy(pasn->bssid, pasn->own_addr, ETH_ALEN);
+	}
+
+	pasn->noauth = 1;
+
+	if ((pr->cfg->pasn_type & 0xc) &&
+	    (dev->pr_caps.pasn_type & 0xc)) {
+		pasn->group = 20;
+		pasn->cipher = WPA_CIPHER_GCMP_256;
+	} else {
+		pasn->group = 19;
+		pasn->cipher = WPA_CIPHER_CCMP;
+	}
+
+	if (pr->cfg->secure_he_ltf)
+		pasn_enable_kdk_derivation(pasn);
+	else
+		pasn_disable_kdk_derivation(pasn);
+
+	wpa_printf(MSG_DEBUG, "PASN: kdk_len=%zu", pasn->kdk_len);
+
+	if (pr->cfg->secure_he_ltf)
+		pasn->secure_ltf = true;
+
+	if (auth_mode == PR_PASN_AUTH_MODE_SAE) {
+		pasn->akmp = WPA_KEY_MGMT_SAE;
+	} else if (auth_mode == PR_PASN_AUTH_MODE_PMK) {
+		pasn->akmp = WPA_KEY_MGMT_SAE;
+	} else {
+		pasn->akmp = WPA_KEY_MGMT_PASN;
+	}
+
+	pasn->rsn_pairwise = pasn->cipher;
+	pasn->wpa_key_mgmt = pasn->akmp;
+
+	pasn->cb_ctx = pr->cfg->cb_ctx;
+	pasn->send_mgmt = pr->cfg->pasn_send_mgmt;
+	pasn->freq = freq;
+	return 0;
+}
+
+
+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)
+{
+	int ret = 0;
+	struct pasn_data *pasn;
+	struct pr_device *dev;
+
+	if (!addr) {
+		wpa_printf(MSG_DEBUG, "Peer address NULL");
+		return -1;
+	}
+
+	dev = pr_get_device(pr, addr);
+	if (!dev) {
+		wpa_printf(MSG_DEBUG, "Peer not known");
+		return -1;
+	}
+
+	if (freq == 0)
+		freq = dev->listen_freq;
+
+	dev->pasn_role = PR_ROLE_PASN_INITIATOR;
+
+	if (pr_pasn_initialize(pr, dev, addr, auth_mode, freq)) {
+		wpa_printf(MSG_ERROR, "PR PASN: Initialize failed");
+		return -1;
+	}
+	pasn = dev->pasn;
+
+	if (auth_mode == PR_PASN_AUTH_MODE_PMK) {
+		ret = wpa_pasn_verify(pasn, pasn->own_addr, pasn->peer_addr,
+				      pasn->bssid, pasn->akmp, pasn->cipher,
+				      pasn->group, pasn->freq, NULL, 0, NULL, 0,
+				      NULL);
+	} else {
+		ret = wpas_pasn_start(pasn, pasn->own_addr, pasn->peer_addr,
+				      pasn->bssid, pasn->akmp, pasn->cipher,
+				      pasn->group, pasn->freq, NULL, 0, NULL, 0,
+				      NULL);
+	}
+	if (ret)
+		wpa_printf(MSG_ERROR, "PR: Failed to start PASN");
+
+	return ret;
+}
+
+#endif /* CONFIG_PASN */
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 85a5727a3..3448c5296 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -38,6 +38,12 @@
 #define PR_MAX_PEER 100
 
 
+enum pr_pasn_role {
+	PR_ROLE_IDLE = 0,
+	PR_ROLE_PASN_INITIATOR,
+	PR_ROLE_PASN_RESPONDER,
+};
+
 /**
  * struct pr_channels - List of supported channels
  */
@@ -189,6 +195,21 @@ enum pr_attr_id {
 #define PR_ISTA_SUPPORT BIT(0)
 #define PR_RSTA_SUPPORT BIT(1)
 
+/**
+ * PASN capabilities
+ * Proximity Ranging Implementation Considerations for P2P Operation 2025,
+ * 3.3.2, Table 7.
+ */
+#define PR_PASN_DH19_AUTH BIT(0)
+#define PR_PASN_DH19_UNAUTH BIT(1)
+#define PR_PASN_DH20_AUTH BIT(2)
+#define PR_PASN_DH20_UNAUTH BIT(3)
+
+/* Authentication Mode */
+#define PR_PASN_AUTH_MODE_PASN   0
+#define PR_PASN_AUTH_MODE_SAE    1
+#define PR_PASN_AUTH_MODE_PMK    2
+
 struct pr_dev_ik {
 	struct dl_list list;
 	u8 dik[DEVICE_IDENTITY_KEY_LEN];
@@ -226,6 +247,10 @@ struct pr_device {
 	 */
 	u8 pmk[PMK_LEN_MAX];
 	bool pmk_valid;
+
+	/* PASN data structure */
+	struct pasn_data *pasn;
+	enum pr_pasn_role pasn_role;
 };
 
 
@@ -332,6 +357,20 @@ struct pr_config {
 	 * cb_ctx - Context to use with callback functions
 	 */
 	void *cb_ctx;
+
+	/**
+	 * pasn_send_mgmt - Function handler to transmit a Management frame
+	 * @ctx: Callback context from cb_ctx
+	 * @data: Frame to transmit
+	 * @data_len: Length of frame to transmit
+	 * @noack: No ack flag
+	 * @freq: Frequency in MHz for the channel on which to transmit
+	 * @wait: How many milliseconds to wait for a response frame
+	 * 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);
+
 };
 
 struct pr_data {
@@ -371,5 +410,8 @@ 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, const char *country);
 void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 			  const u8 *peer_addr, unsigned 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);
 
 #endif /* PROXIMITY_RANGING_H */
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index c41e339f9..00ab9f341 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -74,6 +74,17 @@ static int wpas_pr_setup_channels(struct wpa_supplicant *wpa_s,
 	return 0;
 }
 
+
+static int wpas_pr_pasn_send_mgmt(void *ctx, const u8 *data, size_t data_len,
+				  int noack, unsigned int freq,
+				  unsigned int wait)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+
+	return wpa_drv_send_mlme(wpa_s, data, data_len, noack, freq, wait);
+}
+
+
 struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s)
 {
 	return pr_prepare_usd_elems(wpa_s->global->pr, wpa_s->conf->country);
@@ -133,6 +144,8 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s)
 
 	pr.support_6ghz = wpas_is_6ghz_supported(wpa_s, true);
 
+	pr.pasn_send_mgmt = wpas_pr_pasn_send_mgmt;
+
 	pr.secure_he_ltf = wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SEC_LTF_STA;
 
 	wpas_pr_setup_channels(wpa_s, &pr.ntb_channels, pr.support_6ghz);
@@ -213,3 +226,125 @@ void wpas_pr_set_dev_ik(struct wpa_supplicant *wpa_s, const u8 *dik,
 
 	pr_add_dev_ik(pr, dik, password, pmk, own);
 }
+
+#ifdef CONFIG_PASN
+
+struct wpa_pr_pasn_auth_work {
+	u8 peer_addr[ETH_ALEN];
+	u8 auth_mode;
+	int freq;
+	enum pr_pasn_role role;
+	u8 ranging_role;
+	u8 ranging_type;
+	u8 *ssid;
+	size_t ssid_len;
+	u8 bssid[ETH_ALEN];
+	int forced_pr_freq;
+};
+
+
+static void wpas_pr_pasn_free_auth_work(struct wpa_pr_pasn_auth_work *awork)
+{
+	if (!awork)
+		return;
+	os_free(awork->ssid);
+	os_free(awork);
+}
+
+
+static void wpas_pr_pasn_cancel_auth_work(struct wpa_supplicant *wpa_s)
+{
+	wpa_printf(MSG_DEBUG, "PR PASN: Cancel pr-pasn-start-auth work");
+
+	/* Remove pending/started work */
+	radio_remove_works(wpa_s, "pr-pasn-start-auth", 0);
+}
+
+
+static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+	struct wpa_supplicant *wpa_s = eloop_ctx;
+
+	if (wpa_s->pr_pasn_auth_work) {
+		wpas_pr_pasn_cancel_auth_work(wpa_s);
+		wpa_s->pr_pasn_auth_work = NULL;
+	}
+	wpa_printf(MSG_DEBUG, "PR: PASN timed out");
+}
+
+
+static void wpas_pr_pasn_auth_start_cb(struct wpa_radio_work *work, int deinit)
+{
+	int ret;
+	struct wpa_supplicant *wpa_s = work->wpa_s;
+	struct wpa_pr_pasn_auth_work *awork = work->ctx;
+	struct pr_data *pr = wpa_s->global->pr;
+	const u8 *peer_addr = NULL;
+
+	if (deinit) {
+		if (!work->started)
+			eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
+
+		wpas_pr_pasn_free_auth_work(awork);
+		return;
+	}
+
+	if (!is_zero_ether_addr(awork->peer_addr))
+		peer_addr = awork->peer_addr;
+
+	ret = pr_initiate_pasn_auth(pr, peer_addr, awork->freq,
+				    awork->auth_mode, awork->ranging_role,
+				    awork->ranging_type, awork->forced_pr_freq);
+	if (ret) {
+		wpa_printf(MSG_DEBUG,
+			   "PR PASN: Failed to start PASN authentication");
+		goto fail;
+	}
+	eloop_cancel_timeout(wpas_pr_pasn_timeout,
+			     wpa_s, NULL);
+	eloop_register_timeout(2, 0,
+			       wpas_pr_pasn_timeout,
+			       wpa_s, NULL);
+	wpa_s->pr_pasn_auth_work = work;
+	return;
+
+fail:
+	wpas_pr_pasn_free_auth_work(awork);
+	work->ctx = NULL;
+	radio_work_done(work);
+}
+
+
+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)
+{
+	struct wpa_pr_pasn_auth_work *awork;
+
+	wpas_pr_pasn_cancel_auth_work(wpa_s);
+	wpa_s->pr_pasn_auth_work = NULL;
+
+	awork = os_zalloc(sizeof(*awork));
+	if (!awork)
+		return -1;
+
+	awork->freq = freq;
+	os_memcpy(awork->peer_addr, peer_addr, ETH_ALEN);
+	awork->ranging_role = ranging_role;
+	awork->ranging_type = ranging_type;
+	awork->auth_mode = auth_mode;
+	awork->forced_pr_freq = forced_pr_freq;
+
+	if (radio_add_work(wpa_s, freq, "pr-pasn-start-auth", 1,
+			   wpas_pr_pasn_auth_start_cb, awork) < 0) {
+		wpas_pr_pasn_free_auth_work(awork);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "PR PASN: Authentication work successfully added");
+	return 0;
+}
+
+#endif /* CONFIG_PASN */
diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h
index e263c17bb..499f640e9 100644
--- a/wpa_supplicant/pr_supplicant.h
+++ b/wpa_supplicant/pr_supplicant.h
@@ -22,6 +22,10 @@ struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s);
 void wpas_pr_process_usd_elems(struct wpa_supplicant *wpa_s, const u8 *buf,
 			       u16 buf_len, const u8 *peer_addr,
 			       unsigned int freq);
+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);
 #else /* CONFIG_PR */
 static inline int wpas_pr_init(struct wpa_global *global,
 			       struct wpa_supplicant *wpa_s)
@@ -49,6 +53,15 @@ static inline struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s)
 	return NULL;
 }
 
+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)
+{
+	return 0;
+}
+
 #endif /* CONFIG_PR */
 
 #endif /*PR_SUPPLICANT_H */
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index f1b0f31ae..bee6b9e61 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -1613,6 +1613,7 @@ struct wpa_supplicant {
 #ifdef CONFIG_P2P
 	struct wpa_radio_work *p2p_pasn_auth_work;
 #endif /* CONFIG_P2P */
+	struct wpa_radio_work *pr_pasn_auth_work;
 #endif /* CONFIG_PASN */
 
 	bool is_6ghz_enabled;
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:10 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:10 +0530
Subject: [PATCH 17/34] PR: Set password/PMK based on auth mode and configure RSNXE
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-18-peddolla@qti.qualcomm.com>

Add changes to set parameters such as password/PMK needed for
PASN-SAE and configure rsnxe for PASN negotiation for
Proximity Ranging.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 141 ++++++++++++++++++++++++++++++++-
 src/common/proximity_ranging.h |   4 +
 2 files changed, 142 insertions(+), 3 deletions(-)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 580e97f7e..7fb64d47a 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -98,6 +98,11 @@ struct pr_data * pr_init(const struct pr_config *cfg)
 	dl_list_init(&pr->devices);
 	dl_list_init(&pr->dev_iks);
 
+#ifdef CONFIG_PASN
+	pr->initiator_pmksa = pasn_initiator_pmksa_cache_init();
+	pr->responder_pmksa = pasn_responder_pmksa_cache_init();
+#endif /* CONFIG_PASN */
+
 	return pr;
 }
 
@@ -122,6 +127,11 @@ void pr_deinit(struct pr_data *pr)
 		os_free(dev_ik);
 	}
 
+#ifdef CONFIG_PASN
+	pasn_initiator_pmksa_cache_deinit(pr->initiator_pmksa);
+	pasn_responder_pmksa_cache_deinit(pr->responder_pmksa);
+#endif /* CONFIG_PASN */
+
 	os_free(pr);
 	wpa_printf(MSG_DEBUG, "PR: Deinit done");
 }
@@ -983,9 +993,81 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 
 #ifdef CONFIG_PASN
 
+
+
+static struct wpabuf * pr_pasn_generate_rsnxe(struct pr_data *pr, int akmp)
+{
+	u32 capab;
+	size_t flen = 0;
+	struct wpabuf *buf;
+
+	capab = BIT(WLAN_RSNX_CAPAB_KEK_IN_PASN);
+
+	if (wpa_key_mgmt_sae(akmp))
+		capab |= BIT(WLAN_RSNX_CAPAB_SAE_H2E);
+	if (pr->cfg->secure_he_ltf)
+		capab |= BIT(WLAN_RSNX_CAPAB_SECURE_LTF);
+
+	while (capab >> flen * 8)
+		flen++;
+
+	buf = wpabuf_alloc(2 + flen);
+	if (!buf)
+		return NULL;
+
+	if (wpabuf_tailroom(buf) < 2 + flen) {
+		wpa_printf(MSG_ERROR, "wpabuf tail room too small");
+		wpabuf_free(buf);
+		return NULL;
+	}
+	capab |= flen - 1; /* bit 0-3 = Field length (n - 1) */
+
+	wpa_printf(MSG_ERROR, "RSNXE capabilities: %04x", capab);
+	wpabuf_put_u8(buf, WLAN_EID_RSNX);
+	wpabuf_put_u8(buf, flen);
+	while (flen--) {
+		wpabuf_put_u8(buf, (capab & 0xff));
+		capab = capab >> 8;
+	}
+	return buf;
+}
+
+
+/* SSID used for deriving SAE pt for PR security */
+#define PR_PASN_SSID "516F9A010000"
+
+static void pr_pasn_set_password(struct pasn_data *pasn, u8 pasn_type,
+				 const char *passphrase)
+{
+	int pasn_groups[4] = { 0 };
+	size_t len;
+
+	if (!passphrase)
+		return;
+
+	len = os_strlen(passphrase);
+
+	if (pasn_type & 0xc && pasn_type & 0x3) {
+		pasn_groups[0] = 20;
+		pasn_groups[1] = 19;
+	} else if (pasn_type & 0xc) {
+		pasn_groups[0] = 20;
+	} else {
+		pasn_groups[0] = 19;
+	}
+	pasn->pt = sae_derive_pt(pasn_groups, (const u8 *) PR_PASN_SSID,
+				 os_strlen(PR_PASN_SSID),
+				 (const u8 *) passphrase, len, NULL);
+	/* Set passphrase for PASN responder to validate Auth 1 frame */
+	pasn->password = passphrase;
+}
+
+
 static int pr_pasn_initialize(struct pr_data *pr, struct pr_device *dev,
-			      const u8 *addr, u8 auth_mode, int freq)
+			      const u8 *addr, u8 auth_mode, int freq,
+			      const u8 *pmkid)
 {
+	struct wpabuf *rsnxe;
 	struct pasn_data *pasn;
 
 	if (!pr || !dev)
@@ -1004,8 +1086,10 @@ static int pr_pasn_initialize(struct pr_data *pr, struct pr_device *dev,
 	os_memcpy(pasn->peer_addr, addr, ETH_ALEN);
 
 	if (dev->pasn_role == PR_ROLE_PASN_INITIATOR) {
+		pasn->pmksa = pr->initiator_pmksa;
 		os_memcpy(pasn->bssid, pasn->peer_addr, ETH_ALEN);
 	} else {
+		pasn->pmksa = pr->responder_pmksa;
 		os_memcpy(pasn->bssid, pasn->own_addr, ETH_ALEN);
 	}
 
@@ -1032,7 +1116,41 @@ static int pr_pasn_initialize(struct pr_data *pr, struct pr_device *dev,
 
 	if (auth_mode == PR_PASN_AUTH_MODE_SAE) {
 		pasn->akmp = WPA_KEY_MGMT_SAE;
-	} else if (auth_mode == PR_PASN_AUTH_MODE_PMK) {
+		if (dev->password_valid) {
+			pr_pasn_set_password(pasn, pr->cfg->pasn_type,
+					     dev->password);
+		} else if (pr->cfg->global_password_valid) {
+			pr_pasn_set_password(pasn, pr->cfg->pasn_type,
+					     pr->cfg->global_password);
+		} else {
+			wpa_printf(MSG_ERROR, "PASN init: Password not available");
+			return -1;
+		}
+#ifdef CONFIG_TESTING_OPTIONS
+		if (dev->pasn_role == PR_ROLE_PASN_INITIATOR)
+			pasn_initiator_pmksa_cache_flush(pr->initiator_pmksa);
+		else
+			pasn_responder_pmksa_cache_flush(pr->responder_pmksa);
+#endif /* CONFIG_TESTING_OPTIONS */
+	} else if (auth_mode == PR_PASN_AUTH_MODE_PMK && dev->pmk_valid) {
+		if (!dev->pmk_valid) {
+			wpa_printf(MSG_ERROR, "PASN init: PMK not available");
+			return -1;
+		}
+		if (dev->pasn_role == PR_ROLE_PASN_INITIATOR)
+			pasn_initiator_pmksa_cache_add(pr->initiator_pmksa,
+						       pasn->own_addr,
+						       pasn->peer_addr,
+						       dev->pmk,
+						       WPA_PASN_PMK_LEN,
+						       pmkid);
+		else
+			pasn_responder_pmksa_cache_add(pr->responder_pmksa,
+						       pasn->own_addr,
+						       pasn->peer_addr,
+						       dev->pmk,
+						       WPA_PASN_PMK_LEN,
+						       pmkid);
 		pasn->akmp = WPA_KEY_MGMT_SAE;
 	} else {
 		pasn->akmp = WPA_KEY_MGMT_PASN;
@@ -1041,6 +1159,18 @@ static int pr_pasn_initialize(struct pr_data *pr, struct pr_device *dev,
 	pasn->rsn_pairwise = pasn->cipher;
 	pasn->wpa_key_mgmt = pasn->akmp;
 
+	rsnxe = pr_pasn_generate_rsnxe(pr, pasn->akmp);
+	if (rsnxe) {
+		os_free(pasn->rsnxe_ie);
+		pasn->rsnxe_ie = os_memdup(wpabuf_head_u8(rsnxe),
+					   wpabuf_len(rsnxe));
+		if (!pasn->rsnxe_ie) {
+			wpabuf_free(rsnxe);
+			return -1;
+		}
+		wpabuf_free(rsnxe);
+	}
+
 	pasn->cb_ctx = pr->cfg->cb_ctx;
 	pasn->send_mgmt = pr->cfg->pasn_send_mgmt;
 	pasn->freq = freq;
@@ -1055,6 +1185,7 @@ int pr_initiate_pasn_auth(struct pr_data *pr, const u8 *addr, int freq,
 	int ret = 0;
 	struct pasn_data *pasn;
 	struct pr_device *dev;
+	u8 pmkid[PMKID_LEN];
 
 	if (!addr) {
 		wpa_printf(MSG_DEBUG, "Peer address NULL");
@@ -1072,7 +1203,11 @@ int pr_initiate_pasn_auth(struct pr_data *pr, const u8 *addr, int freq,
 
 	dev->pasn_role = PR_ROLE_PASN_INITIATOR;
 
-	if (pr_pasn_initialize(pr, dev, addr, auth_mode, freq)) {
+	if (auth_mode == PR_PASN_AUTH_MODE_PMK && dev->pmk_valid) {
+		if (os_get_random(pmkid, PMKID_LEN) < 0)
+			return -1;
+	}
+	if (pr_pasn_initialize(pr, dev, addr, auth_mode, freq, pmkid)) {
 		wpa_printf(MSG_ERROR, "PR PASN: Initialize failed");
 		return -1;
 	}
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 3448c5296..9a898d2bb 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -385,6 +385,10 @@ struct pr_data {
 	struct dl_list devices;
 
 	struct dl_list dev_iks;
+
+	/* PMKSA cache for PASN-PMK authentication */
+	struct rsn_pmksa_cache *initiator_pmksa;
+	struct rsn_pmksa_cache *responder_pmksa;
 };
 
 
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:11 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:11 +0530
Subject: [PATCH 18/34] PR: Add PR IE with capabilities and operation mode in PASN auth1 frame
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-19-peddolla@qti.qualcomm.com>

Add changes to the Proximity Ranging Information Element (PR IE)
required for the PASN Auth1 frame. This frame includes ranging
capabilities, capabilities specific to ranging type (EDCA/NTB), and
the proposed operation mode.

The operation mode contains the proposed ranging role, ranging
type, and channel list. These parameters will be negotiated
through subsequent PASN frames.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 138 +++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h |  12 +++
 2 files changed, 150 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 7fb64d47a..f2e7dbf60 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -427,6 +427,30 @@ static int pr_validate_dira(struct pr_data *pr, struct pr_device *dev,
 }
 
 
+#ifdef CONFIG_PASN
+
+static void pr_copy_channels(struct pr_channels *dst,
+			     const struct pr_channels *src, bool allow_6ghz)
+{
+	size_t i, j;
+
+	if (allow_6ghz) {
+		os_memcpy(dst, src, sizeof(struct pr_channels));
+		return;
+	}
+
+	for (i = 0, j = 0; i < src->op_classes; i++) {
+		if (is_6ghz_op_class(src->op_class[i].op_class))
+			continue;
+		os_memcpy(&dst->op_class[j], &src->op_class[i],
+			  sizeof(struct pr_op_class));
+		j++;
+	}
+	dst->op_classes = j;
+}
+
+#endif /* CONFIG_PASN */
+
 static void pr_buf_add_channel_list(struct wpabuf *buf, const char *country,
 				    struct pr_channels *chan)
 {
@@ -993,6 +1017,103 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 
 #ifdef CONFIG_PASN
 
+static void pr_buf_add_operation_mode(struct wpabuf *buf,
+				      struct operation_mode *mode)
+{
+	u8 *len;
+
+	wpabuf_put_u8(buf, PR_ATTR_OPERATION_MODE);
+	/* Length to be filled */
+	len = wpabuf_put(buf, 2);
+
+	wpabuf_put_u8(buf, mode->protocol_type);
+	wpabuf_put_u8(buf, mode->role);
+	pr_buf_add_channel_list(buf, mode->country, &mode->channels);
+
+	/* Update attribute length */
+	WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2);
+	wpa_hexdump(MSG_DEBUG, "PR: * Operation Mode",
+		    len + 2, (u8 *) wpabuf_put(buf, 0) - len - 2);
+}
+
+
+static int pr_prepare_pasn_pr_ie(struct pr_data *pr, struct wpabuf *extra_ies,
+				 bool add_dira, u8 ranging_role,
+				 u8 ranging_type, int forced_pr_freq)
+{
+	u32 ie_type;
+	struct wpabuf *buf, *buf2;
+	struct pr_dira dira;
+	struct pr_capabilities pr_caps;
+	struct edca_capabilities edca_caps;
+	struct ntb_capabilities ntb_caps;
+	struct operation_mode op_mode;
+	struct pr_channels op_channels;
+	u8 forced_op_class = 0, forced_op_channel = 0;
+	enum hostapd_hw_mode hw_mode;
+
+	buf = wpabuf_alloc(1000);
+	if (!buf)
+		return -1;
+
+	pr_get_ranging_capabilities(pr, &pr_caps);
+	pr_buf_add_ranging_capa_info(buf, &pr_caps);
+
+	if (ranging_type & PR_EDCA_BASED_RANGING) {
+		pr_get_edca_capabilities(pr, &edca_caps);
+		pr_buf_add_edca_capa_info(buf, &edca_caps);
+		pr_copy_channels(&op_channels, &edca_caps.channels, false);
+	} else if (ranging_type & PR_NTB_OPEN_BASED_RANGING ||
+		   ranging_type & PR_NTB_SECURE_LTF_BASED_RANGING) {
+		pr_get_ntb_capabilities(pr, &ntb_caps);
+		pr_buf_add_ntb_capa_info(buf, &ntb_caps);
+		pr_copy_channels(&op_channels, &ntb_caps.channels, false);
+	}
+
+	os_memset(&op_mode, 0, sizeof(struct operation_mode));
+	op_mode.role = ranging_role;
+	op_mode.protocol_type = ranging_type;
+	os_memcpy(op_mode.country, pr->cfg->country, 3);
+
+	if (forced_pr_freq) {
+		hw_mode = ieee80211_freq_to_channel_ext(forced_pr_freq, 0, 0,
+							&forced_op_class,
+							&forced_op_channel);
+		if (hw_mode == NUM_HOSTAPD_MODES) {
+			wpa_printf(MSG_DEBUG, "PR: Invalid PR freq received");
+			wpabuf_free(buf);
+			return -1;
+		}
+
+		op_mode.channels.op_classes = 1;
+		op_mode.channels.op_class[0].channels = 1;
+		op_mode.channels.op_class[0].channel[0] = forced_op_channel;
+		op_mode.channels.op_class[0].op_class = forced_op_class;
+	} else {
+		pr_copy_channels(&op_mode.channels, &op_channels, false);
+	}
+
+	pr_buf_add_operation_mode(buf, &op_mode);
+
+	/* PR Device Identity Resolution attribute */
+	if (!pr_derive_dira(pr, &dira))
+		pr_buf_add_dira(buf, &dira);
+
+	ie_type = (OUI_WFA << 8) | PR_OUI_TYPE;
+	buf2 = pr_encaps_ie(buf, ie_type);
+	wpabuf_free(buf);
+
+	if (wpabuf_tailroom(extra_ies) < wpabuf_len(buf2)) {
+		wpa_printf(MSG_ERROR,
+			   "Not enough room for PR IE in PASN Frame");
+		wpabuf_free(buf2);
+		return -1;
+	}
+	wpabuf_put_buf(extra_ies, buf2);
+	wpabuf_free(buf2);
+
+	return 0;
+}
 
 
 static struct wpabuf * pr_pasn_generate_rsnxe(struct pr_data *pr, int akmp)
@@ -1186,6 +1307,7 @@ int pr_initiate_pasn_auth(struct pr_data *pr, const u8 *addr, int freq,
 	struct pasn_data *pasn;
 	struct pr_device *dev;
 	u8 pmkid[PMKID_LEN];
+	struct wpabuf *extra_ies;
 
 	if (!addr) {
 		wpa_printf(MSG_DEBUG, "Peer address NULL");
@@ -1213,6 +1335,20 @@ int pr_initiate_pasn_auth(struct pr_data *pr, const u8 *addr, int freq,
 	}
 	pasn = dev->pasn;
 
+	extra_ies = wpabuf_alloc(1500);
+	if (!extra_ies)
+		return -1;
+
+	if (pr_prepare_pasn_pr_ie(pr, extra_ies, false, ranging_role,
+				  ranging_type, forced_pr_freq)) {
+		wpa_printf(MSG_ERROR, "Failed to prepare PASN extra elements");
+		ret = -1;
+		goto out;
+	}
+
+	pasn_set_extra_ies(dev->pasn, wpabuf_head_u8(extra_ies),
+			   wpabuf_len(extra_ies));
+
 	if (auth_mode == PR_PASN_AUTH_MODE_PMK) {
 		ret = wpa_pasn_verify(pasn, pasn->own_addr, pasn->peer_addr,
 				      pasn->bssid, pasn->akmp, pasn->cipher,
@@ -1227,6 +1363,8 @@ int pr_initiate_pasn_auth(struct pr_data *pr, const u8 *addr, int freq,
 	if (ret)
 		wpa_printf(MSG_ERROR, "PR: Failed to start PASN");
 
+out:
+	wpabuf_free(extra_ies);
 	return ret;
 }
 
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 9a898d2bb..498697828 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -406,6 +406,18 @@ struct pr_dira {
 	size_t tag_len;
 };
 
+struct operation_mode {
+	/* Bit map for Ranging Protocol type */
+	u8 protocol_type;
+
+	/* Bit map for Role-ISTA/RSTA */
+	u8 role;
+
+	char country[3];
+
+	struct pr_channels channels;
+};
+
 struct pr_data * pr_init(const struct pr_config *cfg);
 void pr_deinit(struct pr_data *pr);
 void pr_clear_dev_iks(struct pr_data *pr);
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:12 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:12 +0530
Subject: [PATCH 19/34] PR: Add API to check the input params of PASN Auth request
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-20-peddolla@qti.qualcomm.com>

Proximity Ranging parameters present in PASN request frame need to be
validated. This validation is done be comparing the capabilities
present in PASN frames with the capabilities advertised in USD, if the
proposed role is not feasible then the PASN is deemed to be failure.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 92 ++++++++++++++++++++++++++++++++++
 1 file changed, 92 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index f2e7dbf60..3d2c90222 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -1299,6 +1299,92 @@ static int pr_pasn_initialize(struct pr_data *pr, struct pr_device *dev,
 }
 
 
+static int pr_validate_pasn_request(struct pr_data *pr, struct pr_device *dev,
+				    u8 auth_mode, u8 ranging_role,
+				    u8 ranging_type)
+{
+	if (!ranging_role || !ranging_type)
+		return -1;
+
+	if (auth_mode == PR_PASN_AUTH_MODE_PASN) {
+		if (!(pr->cfg->pasn_type & 0x5) ||
+		    !(dev->pr_caps.pasn_type & 0x5)) {
+			wpa_printf(MSG_DEBUG, "Dev/Peer doesn't support PASN-UNAUTH");
+			return -1;
+		}
+	} else if (auth_mode == PR_PASN_AUTH_MODE_PMK ||
+		   auth_mode == PR_PASN_AUTH_MODE_SAE) {
+		if (!(pr->cfg->pasn_type & 0xa) ||
+		    !(dev->pr_caps.pasn_type & 0xa)) {
+			wpa_printf(MSG_DEBUG, "Dev/Peer doesn't support PASN-SAE/PMK");
+			return -1;
+		}
+	}
+
+	if (ranging_type == PR_NTB_SECURE_LTF_BASED_RANGING ||
+	    ranging_type == PR_NTB_OPEN_BASED_RANGING) {
+		if (ranging_type == PR_NTB_SECURE_LTF_BASED_RANGING &&
+		    (!pr->cfg->secure_he_ltf || !dev->ntb_caps.secure_he_ltf)) {
+			wpa_printf(MSG_DEBUG, "Dev/Peer doesn't support HE-LTF");
+			return -1;
+		}
+
+		if (ranging_role == PR_ISTA_SUPPORT &&
+		    !pr->cfg->ntb_ista_support) {
+			wpa_printf(MSG_DEBUG, "Device doesn't support NTB ISTA role");
+			return -1;
+		} else if (ranging_role == PR_RSTA_SUPPORT &&
+			!pr->cfg->ntb_rsta_support) {
+			wpa_printf(MSG_DEBUG, "Device doesn't support NTB RSTA role");
+			return -1;
+		}
+
+		if (ranging_role == PR_ISTA_SUPPORT &&
+		    !dev->ntb_caps.rsta_support &&
+		    !pr->cfg->ntb_rsta_support) {
+			wpa_printf(MSG_DEBUG, "Device and Peer doesn't support NTB RSTA role,"
+				   " No possiblity for Negotiation update");
+			return -1;
+		}
+
+		if (ranging_role == PR_RSTA_SUPPORT &&
+		    !dev->ntb_caps.ista_support &&
+		    !pr->cfg->ntb_ista_support) {
+			wpa_printf(MSG_DEBUG, "Device and Peer doesn't support NTB ISTA role,"
+				   " No possiblity for Negotiation update");
+			return -1;
+		}
+	} else if (ranging_type == PR_EDCA_BASED_RANGING) {
+		if (ranging_role == PR_ISTA_SUPPORT &&
+		    !pr->cfg->edca_ista_support) {
+			wpa_printf(MSG_DEBUG, "Device doesn't support EDCA ISTA role");
+			return -1;
+		} else if (ranging_role == PR_RSTA_SUPPORT &&
+			   !pr->cfg->edca_rsta_support) {
+			wpa_printf(MSG_DEBUG, "Device doesn't support EDCA RSTA role");
+			return -1;
+		}
+
+		if (ranging_role == PR_ISTA_SUPPORT &&
+		    !dev->edca_caps.rsta_support &&
+		    !pr->cfg->edca_rsta_support) {
+			wpa_printf(MSG_DEBUG, "Device and Peer doesn't support EDCA RSTA role,"
+				   " No possiblity for Negotiation update");
+			return -1;
+		}
+
+		if (ranging_role == PR_RSTA_SUPPORT &&
+		    !dev->edca_caps.ista_support &&
+		    !pr->cfg->edca_ista_support) {
+			wpa_printf(MSG_DEBUG, "Device and Peer doesn't support EDCA ISTA role,"
+				   " No possiblity for Negotiation update");
+			return -1;
+		}
+	}
+	return 0;
+}
+
+
 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)
@@ -1320,6 +1406,12 @@ 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,
+				     ranging_type) < 0) {
+		wpa_printf(MSG_ERROR, "PR PASN: Invalid params to initiate Authentication");
+		return -1;
+	}
+
 	if (freq == 0)
 		freq = dev->listen_freq;
 
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:13 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:13 +0530
Subject: [PATCH 20/34] PR: Add support to handle PR PASN auth tx status
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-21-peddolla@qti.qualcomm.com>

Add changes to check if an ACK is received for transmitted PASN frame
that contains Proximity Ranging IE's.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 35 ++++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h |  2 ++
 wpa_supplicant/events.c        | 12 ++++++++++++
 wpa_supplicant/pr_supplicant.c | 12 ++++++++++++
 wpa_supplicant/pr_supplicant.h |  9 +++++++++
 5 files changed, 70 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 3d2c90222..80e6b8b22 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -1460,4 +1460,39 @@ out:
 	return ret;
 }
 
+
+int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
+			   bool acked)
+{
+	int ret = 0;
+	struct pr_device *dev;
+	struct pasn_data *pasn;
+	const struct ieee80211_mgmt *mgmt =
+		(const struct ieee80211_mgmt *) data;
+
+	if (!pr)
+		return -1;
+
+	dev = pr_get_device(pr, mgmt->da);
+	if (!dev || !dev->pasn) {
+		wpa_printf(MSG_DEBUG, "PR PASN: Peer not found " MACSTR,
+			   MAC2STR(mgmt->da));
+		return -1;
+	}
+
+	pasn = dev->pasn;
+	wpa_printf(MSG_DEBUG, "PR PASN: ACK received From " MACSTR,
+		   MAC2STR(mgmt->da));
+
+	ret = wpa_pasn_auth_tx_status(pasn, data, data_len, acked);
+	if (ret != 1 && !acked && pasn->frame)
+		return pasn->send_mgmt(pasn->cb_ctx, wpabuf_head(pasn->frame),
+				       wpabuf_len(pasn->frame), 0, pasn->freq,
+				       1000);
+	wpabuf_free(pasn->frame);
+	pasn->frame = NULL;
+
+	return 0;
+}
+
 #endif /* CONFIG_PASN */
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 498697828..0ed9fa65e 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -429,5 +429,7 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 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);
+int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
+			   bool acked);
 
 #endif /* PROXIMITY_RANGING_H */
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 00b733a1c..ddc099572 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -53,6 +53,7 @@
 #include "wmm_ac.h"
 #include "nan_usd.h"
 #include "dpp_supplicant.h"
+#include "pr_supplicant.h"
 
 
 #define MAX_OWE_TRANSITION_BSS_SELECT_COUNT 5
@@ -6485,6 +6486,17 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
 						 data->tx_status.ack) == 0)
 			break;
 #endif /* CONFIG_P2P */
+#ifdef CONFIG_PR
+		if (data->tx_status.type == WLAN_FC_TYPE_MGMT &&
+		    data->tx_status.stype == WLAN_FC_STYPE_AUTH &&
+		    !wpa_s->pasn_auth_work &&
+		    wpa_s->pr_pasn_auth_work &&
+		    wpas_pr_pasn_auth_tx_status(wpa_s,
+						data->tx_status.data,
+						data->tx_status.data_len,
+						data->tx_status.ack) == 0)
+			break;
+#endif /* CONFIG_PR */
 		if (data->tx_status.type == WLAN_FC_TYPE_MGMT &&
 		    data->tx_status.stype == WLAN_FC_STYPE_AUTH &&
 		    wpas_pasn_auth_tx_status(wpa_s, data->tx_status.data,
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index 00ab9f341..8450ed571 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -347,4 +347,16 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s,
 	return 0;
 }
 
+
+int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data,
+				size_t data_len, bool acked)
+{
+	struct pr_data *pr = wpa_s->global->pr;
+
+	if (!wpa_s->pr_pasn_auth_work)
+		return -1;
+
+	return pr_pasn_auth_tx_status(pr, data, data_len, acked);
+}
+
 #endif /* CONFIG_PASN */
diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h
index 499f640e9..3ec81cc8d 100644
--- a/wpa_supplicant/pr_supplicant.h
+++ b/wpa_supplicant/pr_supplicant.h
@@ -26,6 +26,8 @@ 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 wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data,
+				size_t data_len, bool acked);
 #else /* CONFIG_PR */
 static inline int wpas_pr_init(struct wpa_global *global,
 			       struct wpa_supplicant *wpa_s)
@@ -62,6 +64,13 @@ static inline int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s,
 	return 0;
 }
 
+static inline int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s,
+					      const u8 *data, size_t data_len,
+					      bool acked)
+{
+	return 0;
+}
+
 #endif /* CONFIG_PR */
 
 #endif /*PR_SUPPLICANT_H */
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:14 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:14 +0530
Subject: [PATCH 21/34] PR: Add support to handle PR PASN auth rx frame
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-22-peddolla@qti.qualcomm.com>

Add changes to handle to received PR PASN auth frame.
If proximity ranging IE is present in the PASN frame, it is processed
by proximity ranging API's.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 32 ++++++++++++++++++++++++++++++++
 src/common/proximity_ranging.h |  2 ++
 wpa_supplicant/events.c        |  4 +++-
 wpa_supplicant/pr_supplicant.c | 12 ++++++++++++
 wpa_supplicant/pr_supplicant.h | 10 ++++++++++
 5 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 80e6b8b22..98ede4ac9 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -1495,4 +1495,36 @@ int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
 	return 0;
 }
 
+
+int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
+		    size_t len, int freq)
+{
+	struct pr_device *dev;
+	u16 auth_alg;
+
+	dev = pr_get_device(pr, mgmt->sa);
+	if (!dev) {
+		wpa_printf(MSG_ERROR, "PR: Peer not found " MACSTR,
+			   MAC2STR(mgmt->sa));
+		return -1;
+	}
+
+	if (os_memcmp(mgmt->da, pr->cfg->dev_addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_ERROR, "PR PASN: Not our frame");
+		return -1;
+	}
+
+	if (len < offsetof(struct ieee80211_mgmt, u.auth.variable))
+		return -1;
+
+	auth_alg = le_to_host16(mgmt->u.auth.auth_alg);
+	if (auth_alg != WLAN_AUTH_PASN) {
+		wpa_printf(MSG_ERROR,
+			   "PR: Unexpected AUTH frame, auth_alg=%d", auth_alg);
+		return -1;
+	}
+
+	return 0;
+}
+
 #endif /* CONFIG_PASN */
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 0ed9fa65e..fe9deb6e6 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -431,5 +431,7 @@ int pr_initiate_pasn_auth(struct pr_data *pr, const u8 *addr, int freq,
 			  int forced_pr_freq);
 int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
 			   bool acked);
+int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
+		    size_t len, int freq);
 
 #endif /* PROXIMITY_RANGING_H */
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index ddc099572..16bd26097 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -6220,7 +6220,6 @@ static int wpas_pasn_auth(struct wpa_supplicant *wpa_s,
 			  const struct ieee80211_mgmt *mgmt, size_t len,
 			  int freq)
 {
-#ifdef CONFIG_P2P
 	struct ieee802_11_elems elems;
 
 	if (len < 24) {
@@ -6237,9 +6236,12 @@ static int wpas_pasn_auth(struct wpa_supplicant *wpa_s,
 		return -2;
 	}
 
+#ifdef CONFIG_P2P
 	if (elems.p2p2_ie && elems.p2p2_ie_len)
 		return wpas_p2p_pasn_auth_rx(wpa_s, mgmt, len, freq);
 #endif /* CONFIG_P2P */
+	if (elems.proximity_ranging && elems.proximity_ranging_len)
+		return wpas_pr_pasn_auth_rx(wpa_s, mgmt, len, freq);
 
 	return wpas_pasn_auth_rx(wpa_s, mgmt, len);
 }
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index 8450ed571..b9fb60076 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -359,4 +359,16 @@ int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data,
 	return pr_pasn_auth_tx_status(pr, data, data_len, acked);
 }
 
+
+int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s,
+			 const struct ieee80211_mgmt *mgmt, size_t len,
+			 int freq)
+{
+	struct pr_data *pr = wpa_s->global->pr;
+
+	if (!pr)
+		return -2;
+	return pr_pasn_auth_rx(pr, mgmt, len, freq);
+}
+
 #endif /* CONFIG_PASN */
diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h
index 3ec81cc8d..633de38b6 100644
--- a/wpa_supplicant/pr_supplicant.h
+++ b/wpa_supplicant/pr_supplicant.h
@@ -28,6 +28,9 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s,
 			       int forced_pr_freq);
 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,
+			 const struct ieee80211_mgmt *mgmt, size_t len,
+			 int freq);
 #else /* CONFIG_PR */
 static inline int wpas_pr_init(struct wpa_global *global,
 			       struct wpa_supplicant *wpa_s)
@@ -71,6 +74,13 @@ static inline int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s,
 	return 0;
 }
 
+static inline int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s,
+				       const struct ieee80211_mgmt *mgmt,
+				       size_t len, int freq)
+{
+	return 0;
+}
+
 #endif /* CONFIG_PR */
 
 #endif /*PR_SUPPLICANT_H */
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:15 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:15 +0530
Subject: [PATCH 22/34] PR: Add support to handle PR IE in Auth M1 and prepare M2
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-23-peddolla@qti.qualcomm.com>

Add changes to validate the PR IE received in Auth M1. For Auth M1
attributes such as capabilities and ranging capabilities are
validated. In case where validation of PR IE's fail or processing of
M1 frame fails or operation mode could not be derived then status is
set as failure and M2 is sent. In case a PR IE's could be validated
and operation mode could be derived then capabilities and preferred
ranging type capabilities and operation mode and status as success are
added to the PR IE for M2 and sent.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 497 ++++++++++++++++++++++++++++++++-
 src/common/proximity_ranging.h |  15 +
 2 files changed, 510 insertions(+), 2 deletions(-)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 98ede4ac9..99b481799 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -23,6 +23,7 @@
 
 static void pr_device_free(struct pr_data *pr, struct pr_device *dev)
 {
+	wpabuf_free(dev->ranging_wrapper);
 #ifdef CONFIG_PASN
 	if (dev->pasn) {
 		wpa_pasn_reset(dev->pasn);
@@ -449,6 +450,65 @@ static void pr_copy_channels(struct pr_channels *dst,
 	dst->op_classes = j;
 }
 
+
+static void pr_op_class_intersect(const struct pr_op_class *a,
+				   const struct pr_op_class *b,
+				   struct pr_op_class *res)
+{
+	size_t i, j;
+
+	res->op_class = a->op_class;
+	for (i = 0; i < a->channels; i++) {
+		for (j = 0; j < b->channels; j++) {
+			if (a->channel[i] != b->channel[j])
+				continue;
+			res->channel[res->channels] = a->channel[i];
+			res->channels++;
+			if (res->channels == PR_MAX_OP_CLASS_CHANNELS)
+				return;
+		}
+	}
+}
+
+
+/**
+ * pr_channels_intersect - Intersection of supported channel lists
+ * @a: First set of supported channels
+ * @b: Second set of supported channels
+ * @res: Data structure for returning the intersection of support channels
+ *
+ * This function can be used to find a common set of supported channels. Both
+ * input channels sets are assumed to use the same country code. If different
+ * country codes are used, the operating class numbers may not be matched
+ * correctly and results are undefined.
+ */
+static void pr_channels_intersect(const struct pr_channels *a,
+				  const struct pr_channels *b,
+				  struct pr_channels *res)
+{
+	size_t i, j;
+	const struct pr_op_class *a_op;
+	const struct pr_op_class *b_op;
+
+	os_memset(res, 0, sizeof(*res));
+
+	for (i = 0; i < a->op_classes; i++) {
+		a_op = &a->op_class[i];
+		for (j = 0; j < b->op_classes; j++) {
+			b_op = &b->op_class[j];
+			if (a_op->op_class != b_op->op_class)
+				continue;
+			pr_op_class_intersect(a_op, b_op,
+					&res->op_class[res->op_classes]);
+			if (res->op_class[res->op_classes].channels) {
+				res->op_classes++;
+				if (res->op_classes == PR_MAX_OP_CLASSES)
+					return;
+			}
+		}
+	}
+}
+
 #endif /* CONFIG_PASN */
 
 static void pr_buf_add_channel_list(struct wpabuf *buf, const char *country,
@@ -1037,6 +1097,229 @@ static void pr_buf_add_operation_mode(struct wpabuf *buf,
 }
 
 
+static void pr_buf_add_ranging_neg_status(struct wpabuf *buf, u8 status)
+{
+	u8 *len;
+
+	wpabuf_put_u8(buf, PR_ATTR_STATUS);
+	len = wpabuf_put(buf, 2);
+
+	wpabuf_put_u8(buf, status);
+
+	WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2);
+	wpa_hexdump(MSG_DEBUG, "PR: * Role negotiation status",
+		    len + 2, (u8 *) wpabuf_put(buf, 0) - len - 2);
+}
+
+
+static void pr_process_op_mode(const u8 *caps, size_t caps_len,
+			       struct operation_mode *op_mode)
+{
+	const u8 *pos, *end;
+
+	if (!caps)
+		return;
+
+	pos = caps;
+	end = caps + caps_len;
+
+	op_mode->protocol_type = *pos;
+
+	pos++;
+	op_mode->role = *pos;
+
+	pos++;
+	os_memcpy(op_mode->country, pos, 3);
+
+	pos += 3;
+	pr_process_channels(pos, end - pos, &op_mode->channels);
+
+	wpa_printf(MSG_INFO, "protocol type=%u, role type=%u, "
+		   "country=%c%c, channel size=%lu",
+		   op_mode->protocol_type, op_mode->role,
+		   op_mode->country[0] ? op_mode->country[0] : '_',
+		   op_mode->country[1] ? op_mode->country[1] : '_',
+		   op_mode->channels.op_classes);
+}
+
+
+static u8 pr_pasn_get_best_op_mode(struct pr_data *pr, u8 supp_roles,
+				    struct operation_mode *op_mode,
+				    struct operation_mode *res_op_mode)
+{
+	int status = PR_NEGOTIATION_FAIL;
+	struct pr_channels common_chan;
+
+	if (op_mode->protocol_type & PR_NTB_SECURE_LTF_BASED_RANGING ||
+	    op_mode->protocol_type & PR_NTB_OPEN_BASED_RANGING) {
+		if ((!pr->cfg->ntb_ista_support &&
+		     !pr->cfg->ntb_rsta_support) ||
+		    (op_mode->protocol_type & PR_NTB_SECURE_LTF_BASED_RANGING &&
+				 !pr->cfg->secure_he_ltf)) {
+			wpa_printf(MSG_DEBUG,
+				   "PR: Device can't fulfill requested NTB role");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		if (op_mode->protocol_type & PR_NTB_SECURE_LTF_BASED_RANGING &&
+		    !pr->cfg->secure_he_ltf) {
+			wpa_printf(MSG_DEBUG, "PR: Secure HE-LTF not supported");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		pr_channels_intersect(&pr->cfg->ntb_channels,
+				      &op_mode->channels, &common_chan);
+		if (!common_chan.op_classes) {
+			wpa_printf(MSG_DEBUG,
+				   "PR: No common channels found to do ranging");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		if (op_mode->role == PR_ISTA_SUPPORT &&
+		    !pr->cfg->ntb_rsta_support &&
+		    !(supp_roles & PR_RSTA_SUPPORT)) {
+			wpa_printf(MSG_DEBUG, "PR: Device cannot act as NTB RSTA");
+			return PR_NEGOTIATION_FAIL;
+		} else if (op_mode->role == PR_RSTA_SUPPORT &&
+			   !pr->cfg->ntb_ista_support &&
+			   !(supp_roles & PR_ISTA_SUPPORT)) {
+			wpa_printf(MSG_DEBUG, "PR: Device cannot act as NTB ISTA");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		if (op_mode->role == PR_ISTA_SUPPORT &&
+		    pr->cfg->ntb_rsta_support) {
+			res_op_mode->role = PR_RSTA_SUPPORT;
+			status = PR_NEGOTIATION_SUCCESS;
+		} else if (op_mode->role == PR_RSTA_SUPPORT &&
+			   pr->cfg->ntb_ista_support) {
+			res_op_mode->role = PR_ISTA_SUPPORT;
+			status = PR_NEGOTIATION_SUCCESS;
+		} else if (op_mode->role == PR_ISTA_SUPPORT &&
+			   !pr->cfg->ntb_rsta_support &&
+			   supp_roles & PR_RSTA_SUPPORT &&
+			   pr->cfg->ntb_ista_support) {
+			res_op_mode->role = PR_ISTA_SUPPORT;
+			status = PR_NEGOTIATION_UPDATE;
+		} else if (op_mode->role == PR_RSTA_SUPPORT &&
+			   !pr->cfg->ntb_ista_support &&
+			   (supp_roles & PR_ISTA_SUPPORT) &&
+			   pr->cfg->ntb_rsta_support) {
+			res_op_mode->role = PR_RSTA_SUPPORT;
+			status = PR_NEGOTIATION_UPDATE;
+		} else if (op_mode->role == (PR_RSTA_SUPPORT |
+					     PR_ISTA_SUPPORT)) {
+			if (pr->cfg->ntb_rsta_support &&
+			    (pr->cfg->preferred_ranging_role ||
+			    !pr->cfg->ntb_ista_support)) {
+				res_op_mode->role = PR_RSTA_SUPPORT;
+				status = PR_NEGOTIATION_SUCCESS;
+				wpa_printf(MSG_DEBUG, "PR: Device choosing RSTA role");
+			} else if (pr->cfg->ntb_ista_support &&
+				   (!pr->cfg->preferred_ranging_role ||
+				   !pr->cfg->ntb_rsta_support)) {
+				res_op_mode->role = PR_ISTA_SUPPORT;
+				status = PR_NEGOTIATION_SUCCESS;
+				wpa_printf(MSG_DEBUG, "PR: Device choosing ISTA role");
+			}
+		}
+
+		res_op_mode->protocol_type = op_mode->protocol_type;
+		os_memcpy(res_op_mode->country, pr->cfg->country, 3);
+		pr_copy_channels(&res_op_mode->channels, &common_chan, false);
+
+		wpa_printf(MSG_DEBUG,
+			   "NTB attr Ranging Protocol=%u, Role=%u, country=%c%c,"
+			   " status=%d, op class size= %lu",
+			   res_op_mode->protocol_type, res_op_mode->role,
+			   res_op_mode->country[0] ? res_op_mode->country[0] : '_',
+			   res_op_mode->country[1] ? res_op_mode->country[1] : '_',
+			   status, res_op_mode->channels.op_classes);
+
+		return status;
+	} else if (op_mode->protocol_type & PR_EDCA_BASED_RANGING) {
+		if (!pr->cfg->edca_ista_support &&
+		    !pr->cfg->edca_rsta_support) {
+			wpa_printf(MSG_DEBUG,
+				   "PR: Device can't fulfill requested EDCA role");
+			return PR_NEGOTIATION_FAIL;
+		}
+		pr_channels_intersect(&pr->cfg->edca_channels,
+				      &op_mode->channels, &common_chan);
+		if (!common_chan.op_classes) {
+			wpa_printf(MSG_DEBUG,
+				   "PR: No common channels to perform ranging");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		if (op_mode->role == PR_ISTA_SUPPORT &&
+		    !pr->cfg->edca_rsta_support &&
+		    !(supp_roles & PR_RSTA_SUPPORT)) {
+			wpa_printf(MSG_DEBUG, "PR: Device cannot act as NTB RSTA");
+			return PR_NEGOTIATION_FAIL;
+		} else if (op_mode->role == PR_RSTA_SUPPORT &&
+			   !pr->cfg->edca_ista_support &&
+			   !(supp_roles & PR_ISTA_SUPPORT)) {
+			wpa_printf(MSG_DEBUG, "PR: Device cannot act as NTB ISTA");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		if (op_mode->role == PR_ISTA_SUPPORT &&
+		    pr->cfg->edca_rsta_support) {
+			res_op_mode->role = PR_RSTA_SUPPORT;
+			status = PR_NEGOTIATION_SUCCESS;
+		} else if (op_mode->role == PR_RSTA_SUPPORT &&
+			   pr->cfg->edca_ista_support) {
+			res_op_mode->role = PR_ISTA_SUPPORT;
+			status = PR_NEGOTIATION_SUCCESS;
+		} else if (op_mode->role == PR_ISTA_SUPPORT &&
+			   !pr->cfg->edca_rsta_support &&
+			   (supp_roles & PR_RSTA_SUPPORT) &&
+			   pr->cfg->edca_ista_support) {
+			res_op_mode->role = PR_ISTA_SUPPORT;
+			status = PR_NEGOTIATION_UPDATE;
+		} else if (op_mode->role == PR_RSTA_SUPPORT &&
+			   !pr->cfg->edca_ista_support &&
+			   (supp_roles & PR_ISTA_SUPPORT) &&
+			   pr->cfg->edca_rsta_support) {
+			res_op_mode->role = PR_RSTA_SUPPORT;
+			status = PR_NEGOTIATION_UPDATE;
+		} else if (op_mode->role == (PR_RSTA_SUPPORT |
+					     PR_ISTA_SUPPORT)) {
+			if (pr->cfg->edca_rsta_support &&
+			    (pr->cfg->preferred_ranging_role ||
+			    !pr->cfg->edca_ista_support)) {
+				res_op_mode->role = PR_RSTA_SUPPORT;
+				status = PR_NEGOTIATION_SUCCESS;
+				wpa_printf(MSG_DEBUG, "PR: Device choosing RSTA role");
+			} else if (pr->cfg->edca_ista_support &&
+				   (!pr->cfg->preferred_ranging_role ||
+				   !pr->cfg->edca_rsta_support)) {
+				res_op_mode->role = PR_ISTA_SUPPORT;
+				status = PR_NEGOTIATION_SUCCESS;
+				wpa_printf(MSG_DEBUG, "PR: Device choosing ISTA role");
+			}
+		}
+
+		res_op_mode->protocol_type = op_mode->protocol_type;
+		os_memcpy(res_op_mode->country, pr->cfg->country, 3);
+		pr_copy_channels(&res_op_mode->channels, &common_chan, false);
+
+		wpa_printf(MSG_DEBUG,
+			   "EDCA attr Ranging Protocol=%u, Role=%u, country=%c%c,"
+			   " status=%d, op class size=%lu",
+			   res_op_mode->protocol_type, res_op_mode->role,
+			   res_op_mode->country[0] ? res_op_mode->country[0] : '_',
+			   res_op_mode->country[1] ? res_op_mode->country[1] : '_',
+			   status, res_op_mode->channels.op_classes);
+
+		return status;
+	}
+
+	return status;
+}
+
+
 static int pr_prepare_pasn_pr_ie(struct pr_data *pr, struct wpabuf *extra_ies,
 				 bool add_dira, u8 ranging_role,
 				 u8 ranging_type, int forced_pr_freq)
@@ -1496,11 +1779,217 @@ int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
 }
 
 
+static int pr_process_pasn_ranging_wrapper(struct pr_data *pr,
+					   struct pr_device *dev,
+					   const struct ieee80211_mgmt *mgmt,
+					   size_t len, int trans_seq)
+{
+	u32 ie_type;
+	const u8 *ies;
+	size_t ies_len;
+	u8 status = PR_NEGOTIATION_FAIL;
+	bool success = false;
+	struct wpabuf *buf, *buf2;
+	struct pr_message msg;
+	struct pr_capabilities caps;
+	struct edca_capabilities edca;
+	struct ntb_capabilities ntb;
+	struct operation_mode op_mode;
+	u8 supp_ranging_role = 0;
+	struct operation_mode res_op_mode;
+	bool ntb_caps_valid = false, edca_caps_valid = false;
+
+	buf = wpabuf_alloc(1000);
+	if (!buf) {
+		wpa_printf(MSG_DEBUG, "PR: Memory allocation failed");
+		return -1;
+	}
+
+	os_memset(&msg, 0, sizeof(msg));
+	ies = mgmt->u.auth.variable;
+	ies_len = len - offsetof(struct ieee80211_mgmt, u.auth.variable);
+
+	if (pr_parse_ies(ies, ies_len, &msg) || !msg.op_mode) {
+		wpa_printf(MSG_DEBUG,
+			   "PR PASN: Failed to parse PR IE in Auth1");
+		goto end;
+	}
+
+	if (msg.dira && msg.dira_len)
+		pr_validate_dira(pr, dev, msg.dira, msg.dira_len);
+
+	if (!msg.op_mode || !msg.op_mode_len ||
+	    !msg.pr_capability || !msg.pr_capability_len ||
+	    ((!msg.edca_capability || !msg.edca_capability_len) &&
+	     (!msg.ntb_capability || !msg.ntb_capability_len))) {
+		goto end;
+	}
+
+	pr_process_op_mode(msg.op_mode, msg.op_mode_len, &op_mode);
+	if (!op_mode.channels.op_classes) {
+		wpa_printf(MSG_DEBUG, "PR: Received empty channel list");
+		goto end;
+	}
+
+	os_memset(&caps, 0, sizeof(struct pr_capabilities));
+	pr_process_ranging_capabilities(msg.pr_capability,
+					msg.pr_capability_len, &caps);
+
+	pr_get_ranging_capabilities(pr, &caps);
+
+	if ((op_mode.protocol_type & PR_EDCA_BASED_RANGING) &&
+	    (!msg.edca_capability || !msg.edca_capability_len)) {
+		goto end;
+	} else if ((op_mode.protocol_type & PR_NTB_OPEN_BASED_RANGING) &&
+		   (!msg.ntb_capability || !msg.ntb_capability_len)) {
+		goto end;
+	} else if ((op_mode.protocol_type & PR_NTB_SECURE_LTF_BASED_RANGING) &&
+		   (!msg.ntb_capability || !msg.ntb_capability_len)) {
+		goto end;
+	}
+
+	if (op_mode.protocol_type & PR_NTB_SECURE_LTF_BASED_RANGING ||
+	    op_mode.protocol_type & PR_NTB_OPEN_BASED_RANGING) {
+		pr_process_ntb_capabilities(msg.ntb_capability,
+					    msg.ntb_capability_len, &ntb,
+					    caps.secure_he_ltf);
+
+		if (dev->ntb_caps.ista_support)
+			supp_ranging_role |= PR_ISTA_SUPPORT;
+		if (dev->ntb_caps.rsta_support)
+			supp_ranging_role |= PR_RSTA_SUPPORT;
+
+		pr_get_ntb_capabilities(pr, &ntb);
+		ntb_caps_valid = true;
+
+	} else if (op_mode.protocol_type & PR_EDCA_BASED_RANGING) {
+		pr_process_edca_capabilities(msg.edca_capability,
+					     msg.edca_capability_len, &edca);
+
+		if (dev->edca_caps.ista_support)
+			supp_ranging_role |= PR_ISTA_SUPPORT;
+		if (dev->edca_caps.rsta_support)
+			supp_ranging_role |= PR_RSTA_SUPPORT;
+
+		pr_get_edca_capabilities(pr, &edca);
+		edca_caps_valid = true;
+	}
+
+	if (trans_seq == 1)
+		status = pr_pasn_get_best_op_mode(pr, supp_ranging_role,
+						  &op_mode, &res_op_mode);
+
+	if (status != PR_NEGOTIATION_SUCCESS &&
+	    status != PR_NEGOTIATION_UPDATE) {
+		wpa_printf(MSG_DEBUG, "PR: Couldn't derive suitable operation mode");
+		goto end;
+	}
+
+	pr_buf_add_ranging_capa_info(buf, &caps);
+	if (edca_caps_valid)
+		pr_buf_add_edca_capa_info(buf, &edca);
+	if (ntb_caps_valid)
+		pr_buf_add_ntb_capa_info(buf, &ntb);
+
+	pr_buf_add_ranging_neg_status(buf, status);
+	pr_buf_add_operation_mode(buf, &res_op_mode);
+
+	if (trans_seq == 2) {
+		dev->ranging_role = res_op_mode.role;
+		dev->protocol_type = res_op_mode.protocol_type;
+		dev->final_op_channel = res_op_mode.channels.op_class[0].channel[0];
+		dev->final_op_class = res_op_mode.channels.op_class[0].op_class;
+	}
+	success = true;
+end:
+	if (!success)
+		pr_buf_add_ranging_neg_status(buf, PR_NEGOTIATION_FAIL);
+
+	ie_type = (OUI_WFA << 8) | PR_OUI_TYPE;
+	buf2 = pr_encaps_ie(buf, ie_type);
+	wpabuf_free(buf);
+
+	dev->ranging_wrapper = buf2;
+	pr_parse_free(&msg);
+	return 0;
+}
+
+
+static int pr_pasn_handle_auth_1(struct pr_data *pr, struct pr_device *dev,
+				 const struct ieee80211_mgmt *mgmt, size_t len,
+				 int freq)
+{
+	int ret = -1;
+	u8 pasn_type;
+	u8 auth_mode = 0;
+	int pasn_groups[4] = { 0 };
+	struct wpa_ie_data rsn_data;
+	struct ieee802_11_elems elems;
+
+	pasn_type = pr->cfg->pasn_type;
+	if (pasn_type & 0xc && pasn_type & 0x3) {
+		pasn_groups[0] = 20;
+		pasn_groups[1] = 19;
+	} else if (pasn_type & 0xc) {
+		pasn_groups[0] = 20;
+	} else {
+		pasn_groups[0] = 19;
+	}
+
+	if (pr_process_pasn_ranging_wrapper(pr, dev, mgmt, len, 1)) {
+		wpa_printf(MSG_ERROR,
+			   "PR PASN: Handle Auth1 action wrapper failed");
+		return -1;
+	}
+
+	if (ieee802_11_parse_elems(mgmt->u.auth.variable,
+				   len - offsetof(struct ieee80211_mgmt,
+				   u.auth.variable),
+				   &elems, 0) == ParseFailed) {
+		wpa_printf(MSG_DEBUG,
+			   "PR PASN: Failed parsing elements in Auth1 frame");
+		goto fail;
+	}
+
+	if (wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2, elems.rsn_ie_len + 2,
+				 &rsn_data) == 0) {
+		if (rsn_data.key_mgmt == WPA_KEY_MGMT_SAE && rsn_data.num_pmkid)
+			auth_mode = PR_PASN_AUTH_MODE_PMK;
+		else if (rsn_data.key_mgmt == WPA_KEY_MGMT_SAE)
+			auth_mode = PR_PASN_AUTH_MODE_SAE;
+		else
+			auth_mode = PR_PASN_AUTH_MODE_PASN;
+	}
+
+	dev->pasn_role = PR_ROLE_PASN_RESPONDER;
+	if (pr_pasn_initialize(pr, dev, mgmt->sa, auth_mode, freq,
+			       rsn_data.pmkid)) {
+		wpa_printf(MSG_ERROR, "PR PASN: Initialize failed");
+		goto fail;
+	}
+	pasn_set_extra_ies(dev->pasn, wpabuf_head_u8(dev->ranging_wrapper),
+			   wpabuf_len(dev->ranging_wrapper));
+	dev->pasn->pasn_groups = pasn_groups;
+	if (handle_auth_pasn_1(dev->pasn, pr->cfg->dev_addr, mgmt->sa, mgmt,
+			       len, false) < 0) {
+		wpa_printf(MSG_ERROR, "PR PASN: Handle Auth1 failed");
+		goto fail;
+	}
+
+	ret = 0;
+
+fail:
+	wpabuf_free(dev->ranging_wrapper);
+	dev->ranging_wrapper = NULL;
+	return ret;
+}
+
+
 int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
 		    size_t len, int freq)
 {
 	struct pr_device *dev;
-	u16 auth_alg;
+	u16 auth_alg, auth_transaction;
 
 	dev = pr_get_device(pr, mgmt->sa);
 	if (!dev) {
@@ -1524,7 +2013,11 @@ int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
 		return -1;
 	}
 
-	return 0;
+	auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
+
+	if (auth_transaction == 1)
+		return pr_pasn_handle_auth_1(pr, dev, mgmt, len, freq);
+	return -1;
 }
 
 #endif /* CONFIG_PASN */
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index fe9deb6e6..d8468e580 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -38,6 +38,15 @@
 #define PR_MAX_PEER 100
 
 
+/**
+ * Proximity Ranging negotiation status
+ * Proximity Ranging Implementation Considerations for P2P Operation 2025,
+ * 3.3.6, Table 11.
+ */
+#define PR_NEGOTIATION_SUCCESS 0
+#define PR_NEGOTIATION_UPDATE 1
+#define PR_NEGOTIATION_FAIL 2
+
 enum pr_pasn_role {
 	PR_ROLE_IDLE = 0,
 	PR_ROLE_PASN_INITIATOR,
@@ -250,7 +259,13 @@ struct pr_device {
 
 	/* PASN data structure */
 	struct pasn_data *pasn;
+	struct wpabuf *ranging_wrapper;
+
 	enum pr_pasn_role pasn_role;
+	u8 ranging_role;
+	u8 protocol_type;
+	u8 final_op_class;
+	u8 final_op_channel;
 };
 
 
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:16 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:16 +0530
Subject: [PATCH 23/34] PR: Add support to handle PR IE in Auth M2 and prepare M3
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-24-peddolla@qti.qualcomm.com>

PASN Auth M2 frame is processed and then M3 is prepared based on the
operation mode received in M2 and capabilities received in M2 are also
validated. If a operation mode could not be decided or validation of
capabilities has failed, Then status is set as failure and M3 is sent.
If PR capabilities are valid and operation mode received in M2 can be
honored then status is set as success and operation mode is set in
M3.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 187 +++++++++++++++++++++++++++++++++
 1 file changed, 187 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 99b481799..ea002f8c5 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -1320,6 +1320,136 @@ static u8 pr_pasn_get_best_op_mode(struct pr_data *pr, u8 supp_roles,
 }
 
 
+static void pr_choose_best_channel(struct pr_channels *common_channel,
+				   u8 *op_class, u8 *op_channel)
+{
+	*op_class = common_channel->op_class[0].op_class;
+	*op_channel = common_channel->op_class[0].channel[0];
+}
+
+
+static u8 pr_pasn_get_final_op_mode(struct pr_data *pr, u8 supp_roles,
+				     struct operation_mode *op_mode,
+				     struct operation_mode *res_op_mode)
+{
+	u8 status = PR_NEGOTIATION_FAIL;
+	u8 op_class, op_channel;
+	struct pr_channels common_chan;
+
+	if (op_mode->protocol_type & PR_NTB_SECURE_LTF_BASED_RANGING ||
+	    op_mode->protocol_type & PR_NTB_OPEN_BASED_RANGING) {
+		if (!pr->cfg->ntb_ista_support && !pr->cfg->ntb_rsta_support) {
+			wpa_printf(MSG_DEBUG,
+				   "PR: Device can't fulfill requested NTB Role");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		if (op_mode->protocol_type & PR_NTB_SECURE_LTF_BASED_RANGING &&
+		    !pr->cfg->secure_he_ltf) {
+			wpa_printf(MSG_DEBUG, "PR: Secure HE-LTF not supported");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		pr_channels_intersect(&pr->cfg->ntb_channels,
+				      &op_mode->channels, &common_chan);
+		if (!common_chan.op_classes) {
+			wpa_printf(MSG_DEBUG,
+				   "PR: No common channels to perform ranging");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		pr_choose_best_channel(&common_chan, &op_class, &op_channel);
+		if (!op_class || !op_channel) {
+			wpa_printf(MSG_DEBUG,
+				   "PR: Couldn't choose a common channel for ranging");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		if (op_mode->role == PR_ISTA_SUPPORT &&
+		    !pr->cfg->ntb_rsta_support &&
+		    !(supp_roles & PR_RSTA_SUPPORT)) {
+			wpa_printf(MSG_DEBUG, "PR: Device cannot act as NTB RSTA");
+			return PR_NEGOTIATION_FAIL;
+		} else if (op_mode->role == PR_RSTA_SUPPORT &&
+			   !pr->cfg->ntb_ista_support &&
+			   !(supp_roles & PR_ISTA_SUPPORT)) {
+			wpa_printf(MSG_DEBUG, "PR: Device cannot act as NTB ISTA");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		if (op_mode->role == PR_ISTA_SUPPORT &&
+		    pr->cfg->ntb_rsta_support) {
+			res_op_mode->role = PR_RSTA_SUPPORT;
+			status = PR_NEGOTIATION_SUCCESS;
+		} else if (op_mode->role == PR_RSTA_SUPPORT &&
+			   pr->cfg->ntb_ista_support) {
+			res_op_mode->role = PR_ISTA_SUPPORT;
+			status = PR_NEGOTIATION_SUCCESS;
+		}
+
+		res_op_mode->protocol_type = op_mode->protocol_type;
+		os_memcpy(res_op_mode->country, pr->cfg->country, 3);
+		res_op_mode->channels.op_classes = 1;
+		res_op_mode->channels.op_class[0].channels = 1;
+		res_op_mode->channels.op_class[0].channel[0] = op_channel;
+		res_op_mode->channels.op_class[0].op_class = op_class;
+
+		return status;
+	} else if (op_mode->protocol_type & PR_EDCA_BASED_RANGING) {
+		if (!pr->cfg->edca_ista_support &&
+		    !pr->cfg->edca_rsta_support) {
+			wpa_printf(MSG_DEBUG,
+				   "PR: Device can't fulfill EDCA role");
+			return PR_NEGOTIATION_FAIL;
+		}
+		pr_channels_intersect(&pr->cfg->edca_channels,
+				      &op_mode->channels, &common_chan);
+		if (!common_chan.op_classes) {
+			wpa_printf(MSG_DEBUG,
+				   "PR: No common channels to perform ranging");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		pr_choose_best_channel(&common_chan, &op_class, &op_channel);
+		if (!op_class || !op_channel) {
+			wpa_printf(MSG_DEBUG,
+				   "PR: Couldn't choose a common channel for ranging");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		if (op_mode->role == PR_ISTA_SUPPORT &&
+		    !pr->cfg->edca_rsta_support &&
+		    !(supp_roles & PR_RSTA_SUPPORT)) {
+			wpa_printf(MSG_DEBUG, "PR: Device cannot act as NTB RSTA");
+			return PR_NEGOTIATION_FAIL;
+		} else if (op_mode->role == PR_RSTA_SUPPORT &&
+			   !pr->cfg->edca_ista_support &&
+			   !(supp_roles & PR_ISTA_SUPPORT)) {
+			wpa_printf(MSG_DEBUG, "PR: Device cannot act as NTB ISTA");
+			return PR_NEGOTIATION_FAIL;
+		}
+
+		if (op_mode->role == PR_ISTA_SUPPORT &&
+		    pr->cfg->edca_rsta_support) {
+			res_op_mode->role = PR_RSTA_SUPPORT;
+			status = PR_NEGOTIATION_SUCCESS;
+		} else if (op_mode->role == PR_RSTA_SUPPORT &&
+			   pr->cfg->edca_ista_support) {
+			res_op_mode->role = PR_ISTA_SUPPORT;
+			status = PR_NEGOTIATION_SUCCESS;
+		}
+
+		res_op_mode->protocol_type = op_mode->protocol_type;
+		os_memcpy(res_op_mode->country, pr->cfg->country, 3);
+		res_op_mode->channels.op_classes = 1;
+		res_op_mode->channels.op_class[0].channels = 1;
+		res_op_mode->channels.op_class[0].channel[0] = op_channel;
+		res_op_mode->channels.op_class[0].op_class = op_class;
+	}
+	return status;
+}
+
+
 static int pr_prepare_pasn_pr_ie(struct pr_data *pr, struct wpabuf *extra_ies,
 				 bool add_dira, u8 ranging_role,
 				 u8 ranging_type, int forced_pr_freq)
@@ -1815,6 +1945,20 @@ static int pr_process_pasn_ranging_wrapper(struct pr_data *pr,
 		goto end;
 	}
 
+	if (trans_seq == 2) {
+		if (!msg.status_ie || !msg.status_ie_len) {
+			wpa_printf(MSG_DEBUG, "PR PASN: * No status attribute");
+			wpabuf_free(buf);
+			return -1;
+		}
+		if (*msg.status_ie == PR_NEGOTIATION_FAIL) {
+			wpa_printf(MSG_DEBUG,
+				   "PR PASN: * Ranging Negotiation status fail");
+			wpabuf_free(buf);
+			return -1;
+		}
+	}
+
 	if (msg.dira && msg.dira_len)
 		pr_validate_dira(pr, dev, msg.dira, msg.dira_len);
 
@@ -1878,6 +2022,9 @@ static int pr_process_pasn_ranging_wrapper(struct pr_data *pr,
 	if (trans_seq == 1)
 		status = pr_pasn_get_best_op_mode(pr, supp_ranging_role,
 						  &op_mode, &res_op_mode);
+	else if (trans_seq == 2)
+		status = pr_pasn_get_final_op_mode(pr, supp_ranging_role,
+						   &op_mode, &res_op_mode);
 
 	if (status != PR_NEGOTIATION_SUCCESS &&
 	    status != PR_NEGOTIATION_UPDATE) {
@@ -1985,6 +2132,44 @@ fail:
 }
 
 
+static int pr_pasn_handle_auth_2(struct pr_data *pr, struct pr_device *dev,
+				 const struct ieee80211_mgmt *mgmt, size_t len)
+{
+	int ret = -1;
+	struct wpa_pasn_params_data pasn_data;
+
+	if (dev->pasn_role != PR_ROLE_PASN_INITIATOR) {
+		wpa_printf(MSG_ERROR, "PR PASN: Auth2 not expected on responder");
+		return -1;
+	}
+
+	if (!dev->pasn)
+		return -1;
+
+	if (pr_process_pasn_ranging_wrapper(pr, dev, mgmt, len, 2)) {
+		wpa_printf(MSG_ERROR,
+			   "PR PASN: Handle Auth2 action wrapper failed");
+		return -1;
+	}
+	pasn_set_extra_ies(dev->pasn, wpabuf_head_u8(dev->ranging_wrapper),
+			   wpabuf_len(dev->ranging_wrapper));
+
+	if (wpa_pasn_auth_rx(dev->pasn, (const u8 *) mgmt, len,
+			     &pasn_data) < 0) {
+		wpa_printf(MSG_ERROR, "PR PASN: wpa_pasn_auth_rx() failed");
+		dev->pasn_role = PR_ROLE_IDLE;
+		goto fail;
+	}
+
+	ret = 0;
+
+fail:
+	wpabuf_free(dev->ranging_wrapper);
+	dev->ranging_wrapper = NULL;
+	return ret;
+}
+
+
 int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
 		    size_t len, int freq)
 {
@@ -2017,6 +2202,8 @@ int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
 
 	if (auth_transaction == 1)
 		return pr_pasn_handle_auth_1(pr, dev, mgmt, len, freq);
+	else if (auth_transaction == 2)
+		return pr_pasn_handle_auth_2(pr, dev, mgmt, len);
 	return -1;
 }
 
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:17 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:17 +0530
Subject: [PATCH 24/34] PR: Add support to handle PR IE in Auth M3
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-25-peddolla@qti.qualcomm.com>

Auth M3 will contain operation mode attribute and status attribute
with value set a success attribute in case of success or just status
attribute with value set as failure in case of negotiation failure.
The received operation mode is validated. The successful M3 validation
will give the agreed upon ranging role, ranging type, ranging channel.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 122 +++++++++++++++++++++++++++++++++
 1 file changed, 122 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index ea002f8c5..7c6a54685 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -2062,6 +2062,104 @@ end:
 }
 
 
+static int pr_process_pasn_ranging_wrapper_result(struct pr_data *pr,
+					struct pr_device *dev,
+					const struct ieee80211_mgmt *mgmt,
+					size_t len)
+{
+	int ret = -1;
+	const u8 *ies;
+	size_t ies_len;
+	struct pr_message msg;
+	struct operation_mode op_mode;
+	struct pr_channels common_chan;
+
+	os_memset(&msg, 0, sizeof(msg));
+	ies = mgmt->u.auth.variable;
+	ies_len = len - offsetof(struct ieee80211_mgmt, u.auth.variable);
+
+	if (pr_parse_ies(ies, ies_len, &msg) || !msg.op_mode) {
+		wpa_printf(MSG_DEBUG,
+			   "PR PASN: Failed to parse PR IE in Auth3");
+		goto fail;
+	}
+
+	if (!msg.status_ie || !msg.status_ie_len) {
+		wpa_printf(MSG_DEBUG, "PR PASN: * No status attribute");
+		goto fail;
+	}
+
+	if (*msg.status_ie == PR_NEGOTIATION_FAIL) {
+		wpa_printf(MSG_DEBUG, "PR PASN: * Ranging Negotiation status fail");
+		goto fail;
+	}
+
+	if (!msg.op_mode || !msg.op_mode_len ||
+	    !msg.status_ie || !msg.status_ie_len) {
+		goto fail;
+	}
+
+	pr_process_op_mode(msg.op_mode, msg.op_mode_len, &op_mode);
+	if (op_mode.channels.op_classes != 1) {
+		wpa_printf(MSG_DEBUG, "PR: PASN received Invalid channel list");
+		goto fail;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "PASN: PR *  Frame 3- Operating mode data: Role=%u, protocol type=%u, "
+		   "operating class= %u, channel= %u",
+		   op_mode.role, op_mode.protocol_type,
+		   op_mode.channels.op_class[0].op_class,
+		   op_mode.channels.op_class[0].channel[0]);
+
+	if (op_mode.protocol_type & PR_NTB_SECURE_LTF_BASED_RANGING ||
+	    op_mode.protocol_type & PR_NTB_OPEN_BASED_RANGING) {
+		if (op_mode.role & PR_ISTA_SUPPORT &&
+		    !pr->cfg->ntb_rsta_support)
+			goto fail;
+
+		if (op_mode.role & PR_RSTA_SUPPORT &&
+		    !pr->cfg->ntb_ista_support)
+			goto fail;
+
+		if (op_mode.protocol_type & PR_NTB_SECURE_LTF_BASED_RANGING &&
+		    !pr->cfg->secure_he_ltf)
+			goto fail;
+
+		pr_channels_intersect(&pr->cfg->ntb_channels, &op_mode.channels,
+				      &common_chan);
+		if (common_chan.op_classes == 0)
+			goto fail;
+	} else if (op_mode.protocol_type & PR_EDCA_BASED_RANGING) {
+		if (op_mode.role & PR_ISTA_SUPPORT &&
+		    !pr->cfg->edca_rsta_support)
+			goto fail;
+
+		if (op_mode.role & PR_RSTA_SUPPORT &&
+		    !pr->cfg->edca_ista_support)
+			goto fail;
+
+		pr_channels_intersect(&pr->cfg->edca_channels,
+				      &op_mode.channels, &common_chan);
+		if (common_chan.op_classes == 0)
+			goto fail;
+	}
+
+	if (op_mode.role & PR_RSTA_SUPPORT)
+		dev->ranging_role = PR_ISTA_SUPPORT;
+	else
+		dev->ranging_role = PR_RSTA_SUPPORT;
+	dev->protocol_type = op_mode.protocol_type;
+	dev->final_op_channel = op_mode.channels.op_class[0].channel[0];
+	dev->final_op_class = op_mode.channels.op_class[0].op_class;
+	ret = 0;
+
+fail:
+	pr_parse_free(&msg);
+	return ret;
+}
+
+
 static int pr_pasn_handle_auth_1(struct pr_data *pr, struct pr_device *dev,
 				 const struct ieee80211_mgmt *mgmt, size_t len,
 				 int freq)
@@ -2170,6 +2268,27 @@ fail:
 }
 
 
+static int pr_pasn_handle_auth_3(struct pr_data *pr, struct pr_device *dev,
+				 const struct ieee80211_mgmt *mgmt, size_t len)
+{
+	if (!dev->pasn)
+		return -1;
+
+	if (pr_process_pasn_ranging_wrapper_result(pr, dev, mgmt, len)) {
+		wpa_printf(MSG_ERROR,
+			   "PR PASN: Handle Auth3 action wrapper failed");
+		return -1;
+	}
+
+	if (handle_auth_pasn_3(dev->pasn, pr->cfg->dev_addr, mgmt->sa, mgmt,
+			       len) < 0) {
+		wpa_printf(MSG_ERROR, "PR PASN: Handle Auth3 failed");
+		return -1;
+	}
+	return 0;
+}
+
+
 int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
 		    size_t len, int freq)
 {
@@ -2204,6 +2323,9 @@ int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
 		return pr_pasn_handle_auth_1(pr, dev, mgmt, len, freq);
 	else if (auth_transaction == 2)
 		return pr_pasn_handle_auth_2(pr, dev, mgmt, len);
+	else if (auth_transaction == 3)
+		return pr_pasn_handle_auth_3(pr, dev, mgmt, len);
+
 	return -1;
 }
 
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:18 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:18 +0530
Subject: [PATCH 25/34] PR: Compare PR capabilites of Auth frame and USD info of peer
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-26-peddolla@qti.qualcomm.com>

Proximity Ranging capabilities present in PR IE of PASN Auth frames
need to match with capabilities that were advertised by the peer the
discovery frames.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 55 ++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 7c6a54685..dfa2dca89 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -1077,6 +1077,48 @@ void pr_process_usd_elems(struct pr_data *pr, const u8 *ies, u16 ies_len,
 
 #ifdef CONFIG_PASN
 
+static int pr_compare_ranging_capa_params(struct pr_device *dev,
+					  struct pr_capabilities *caps)
+{
+	if (dev->pr_caps.edca_support != caps->edca_support ||
+	    dev->pr_caps.ntb_support != caps->ntb_support ||
+	    dev->pr_caps.pasn_type != caps->pasn_type ||
+	    dev->pr_caps.secure_he_ltf != caps->secure_he_ltf ||
+	    dev->pr_caps.support_6ghz != caps->support_6ghz ||
+	    os_strcmp(dev->pr_caps.device_name, caps->device_name))
+		return -1;
+
+	return 0;
+}
+
+
+static int pr_compare_edca_params(struct pr_device *dev,
+				  struct edca_capabilities *edca_caps)
+{
+	if (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 ||
+	    os_memcmp(dev->edca_caps.country, edca_caps->country, 3))
+		return -1;
+
+	return 0;
+}
+
+
+static int pr_compare_ntb_params(struct pr_device *dev,
+				 struct ntb_capabilities *ntb_caps)
+{
+	if (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 ||
+	    dev->ntb_caps.secure_he_ltf != ntb_caps->secure_he_ltf ||
+	    os_memcmp(dev->ntb_caps.country, ntb_caps->country, 3))
+		return -1;
+
+	return 0;
+}
+
+
 static void pr_buf_add_operation_mode(struct wpabuf *buf,
 				      struct operation_mode *mode)
 {
@@ -1978,6 +2020,10 @@ static int pr_process_pasn_ranging_wrapper(struct pr_data *pr,
 	os_memset(&caps, 0, sizeof(struct pr_capabilities));
 	pr_process_ranging_capabilities(msg.pr_capability,
 					msg.pr_capability_len, &caps);
+	if (pr_compare_ranging_capa_params(dev, &caps)) {
+		wpa_printf(MSG_DEBUG, "PR: Ranging capabilities not matching");
+		goto end;
+	}
 
 	pr_get_ranging_capabilities(pr, &caps);
 
@@ -1998,6 +2044,11 @@ static int pr_process_pasn_ranging_wrapper(struct pr_data *pr,
 					    msg.ntb_capability_len, &ntb,
 					    caps.secure_he_ltf);
 
+		if (pr_compare_ntb_params(dev, &ntb)) {
+			wpa_printf(MSG_DEBUG, "PR: NTB capabilities not matching");
+			goto end;
+		}
+
 		if (dev->ntb_caps.ista_support)
 			supp_ranging_role |= PR_ISTA_SUPPORT;
 		if (dev->ntb_caps.rsta_support)
@@ -2009,6 +2060,10 @@ static int pr_process_pasn_ranging_wrapper(struct pr_data *pr,
 	} else if (op_mode.protocol_type & PR_EDCA_BASED_RANGING) {
 		pr_process_edca_capabilities(msg.edca_capability,
 					     msg.edca_capability_len, &edca);
+		if (pr_compare_edca_params(dev, &edca)) {
+			wpa_printf(MSG_DEBUG, "PR: EDCA capabilities not matching");
+			goto end;
+		}
 
 		if (dev->edca_caps.ista_support)
 			supp_ranging_role |= PR_ISTA_SUPPORT;
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:19 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:19 +0530
Subject: [PATCH 26/34] PR: Add support to set keys of PASN authentication
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-27-peddolla@qti.qualcomm.com>

Add call backs and API's to send the keys derived as part of PASN for
Proximity Ranging to the driver.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c | 19 +++++++++++++++++--
 src/common/proximity_ranging.h |  5 +++++
 wpa_supplicant/pr_supplicant.c | 29 +++++++++++++++++++++++++++++
 3 files changed, 51 insertions(+), 2 deletions(-)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index dfa2dca89..63bfa4f40 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -2276,6 +2276,10 @@ static int pr_pasn_handle_auth_1(struct pr_data *pr, struct pr_device *dev,
 		goto fail;
 	}
 
+	if (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);
 	ret = 0;
 
 fail:
@@ -2314,6 +2318,10 @@ static int pr_pasn_handle_auth_2(struct pr_data *pr, struct pr_device *dev,
 		goto fail;
 	}
 
+	if (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);
 	ret = 0;
 
 fail:
@@ -2332,15 +2340,22 @@ static int pr_pasn_handle_auth_3(struct pr_data *pr, struct pr_device *dev,
 	if (pr_process_pasn_ranging_wrapper_result(pr, dev, mgmt, len)) {
 		wpa_printf(MSG_ERROR,
 			   "PR PASN: Handle Auth3 action wrapper failed");
-		return -1;
+		goto fail;
 	}
 
 	if (handle_auth_pasn_3(dev->pasn, pr->cfg->dev_addr, mgmt->sa, mgmt,
 			       len) < 0) {
 		wpa_printf(MSG_ERROR, "PR PASN: Handle Auth3 failed");
-		return -1;
+		goto fail;
 	}
 	return 0;
+fail:
+
+	/* Clear the keys as M3 processing failed */
+	if (pr->cfg->clear_keys)
+		pr->cfg->clear_keys(pr->cfg->cb_ctx, pr->cfg->dev_addr,
+				    dev->pr_device_addr);
+	return -1;
 }
 
 
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index d8468e580..3953a46cc 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -386,6 +386,11 @@ struct pr_config {
 	int (*pasn_send_mgmt)(void *ctx, const u8 *data, size_t data_len,
 			      int noack, unsigned int freq, unsigned int wait);
 
+
+	void (*set_keys)(void *ctx, const u8 *own_addr, const u8 *peer_addr,
+			 int cipher, int akmp, struct wpa_ptk *ptk);
+
+	void (*clear_keys)(void *ctx, const u8 *own_addr, const u8 *peer_addr);
 };
 
 struct pr_data {
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index b9fb60076..491ca39d6 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -85,6 +85,33 @@ static int wpas_pr_pasn_send_mgmt(void *ctx, const u8 *data, size_t data_len,
 }
 
 
+static void wpas_pr_pasn_set_keys(void *ctx, const u8 *own_addr,
+				  const u8 *peer_addr, int cipher, int akmp,
+				  struct wpa_ptk *ptk)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+
+	wpa_printf(MSG_DEBUG, "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);
+}
+
+
+static void wpas_pr_pasn_clear_keys(void *ctx, const u8 *own_addr,
+				    const u8 *peer_addr)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+
+	wpa_printf(MSG_DEBUG, "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);
+}
+
+
 struct wpabuf * wpas_pr_usd_elems(struct wpa_supplicant *wpa_s)
 {
 	return pr_prepare_usd_elems(wpa_s->global->pr, wpa_s->conf->country);
@@ -145,6 +172,8 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s)
 	pr.support_6ghz = wpas_is_6ghz_supported(wpa_s, true);
 
 	pr.pasn_send_mgmt = wpas_pr_pasn_send_mgmt;
+	pr.set_keys = wpas_pr_pasn_set_keys;
+	pr.clear_keys = wpas_pr_pasn_clear_keys;
 
 	pr.secure_he_ltf = wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SEC_LTF_STA;
 
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:20 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:20 +0530
Subject: [PATCH 27/34] PR: Add support to notify PR PASN result on initiator and responder
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-28-peddolla@qti.qualcomm.com>

Add call backs and changes to notify Proximity Ranging negotiation
result done as part of PASN.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c |  9 +++++++++
 src/common/proximity_ranging.h |  2 ++
 src/common/wpa_ctrl.h          |  5 +++++
 wpa_supplicant/notify.c        | 10 ++++++++++
 wpa_supplicant/notify.h        |  4 +++-
 wpa_supplicant/pr_supplicant.c | 11 +++++++++++
 6 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 63bfa4f40..6fd8e7003 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -1944,6 +1944,10 @@ int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
 		return pasn->send_mgmt(pasn->cb_ctx, wpabuf_head(pasn->frame),
 				       wpabuf_len(pasn->frame), 0, pasn->freq,
 				       1000);
+	if (ret == 1 && acked && pr->cfg->pasn_result)
+		pr->cfg->pasn_result(pr->cfg->cb_ctx, dev->ranging_role,
+				     dev->protocol_type, dev->final_op_class,
+				     dev->final_op_channel, pr->cfg->country);
 	wpabuf_free(pasn->frame);
 	pasn->frame = NULL;
 
@@ -2348,6 +2352,11 @@ static int pr_pasn_handle_auth_3(struct pr_data *pr, struct pr_device *dev,
 		wpa_printf(MSG_ERROR, "PR PASN: Handle Auth3 failed");
 		goto fail;
 	}
+
+	if (pr->cfg->pasn_result)
+		pr->cfg->pasn_result(pr->cfg->cb_ctx, dev->ranging_role,
+				     dev->protocol_type, dev->final_op_class,
+				     dev->final_op_channel, pr->cfg->country);
 	return 0;
 fail:
 
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 3953a46cc..e6943355f 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -386,6 +386,8 @@ struct pr_config {
 	int (*pasn_send_mgmt)(void *ctx, const u8 *data, size_t data_len,
 			      int noack, unsigned int freq, unsigned int wait);
 
+	void (*pasn_result)(void *ctx, u8 role, u8 protocol_type, u8 op_class,
+			    u8 op_channel, const char *country);
 
 	void (*set_keys)(void *ctx, const u8 *own_addr, const u8 *peer_addr,
 			 int cipher, int akmp, struct wpa_ptk *ptk);
diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h
index ae5711aa6..63e2c176a 100644
--- a/src/common/wpa_ctrl.h
+++ b/src/common/wpa_ctrl.h
@@ -471,6 +471,11 @@ extern "C" {
 /* PASN authentication status */
 #define PASN_AUTH_STATUS "PASN-AUTH-STATUS "
 
+/* Result of PASN performed for Proximity Ranging
+ * <role> <protocol type> <op class> <op channel>
+ */
+#define PR_PASN_RESULT "PR-PASN-RESULT "
+
 /* BSS command information masks */
 
 #define WPA_BSS_MASK_ALL		0xFFFDFFFF
diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c
index fd8783d25..bac25c111 100644
--- a/wpa_supplicant/notify.c
+++ b/wpa_supplicant/notify.c
@@ -1208,3 +1208,13 @@ void wpas_notify_nan_subscribe_terminated(struct wpa_supplicant *wpa_s,
 }
 
 #endif /* CONFIG_NAN_USD */
+
+
+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)
+{
+	wpa_msg_global(wpa_s, MSG_INFO, PR_PASN_RESULT
+	       "SUCCESS role=%u, protocol=%u, opclass=%u, channel=%u, cc=%s",
+	       role, protocol_type, op_class, op_channel, country);
+}
diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h
index 9e5047c81..ea27875f5 100644
--- a/wpa_supplicant/notify.h
+++ b/wpa_supplicant/notify.h
@@ -204,5 +204,7 @@ 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_pr_pasn_result(struct wpa_supplicant *wpa_s, u8 role,
+				u8 protocol_type, u8 op_class, u8 op_channel,
+				const char *country);
 #endif /* NOTIFY_H */
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index 491ca39d6..9385215b5 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -85,6 +85,16 @@ static int wpas_pr_pasn_send_mgmt(void *ctx, const u8 *data, size_t data_len,
 }
 
 
+static void wpas_pr_pasn_result(void *ctx, u8 role, u8 protocol_type,
+				u8 op_class, u8 op_channel, const char *country)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+
+	wpas_notify_pr_pasn_result(wpa_s, role, protocol_type, op_class,
+				   op_channel, country);
+}
+
+
 static void wpas_pr_pasn_set_keys(void *ctx, const u8 *own_addr,
 				  const u8 *peer_addr, int cipher, int akmp,
 				  struct wpa_ptk *ptk)
@@ -172,6 +182,7 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s)
 	pr.support_6ghz = wpas_is_6ghz_supported(wpa_s, true);
 
 	pr.pasn_send_mgmt = wpas_pr_pasn_send_mgmt;
+	pr.pasn_result = wpas_pr_pasn_result;
 	pr.set_keys = wpas_pr_pasn_set_keys;
 	pr.clear_keys = wpas_pr_pasn_clear_keys;
 
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:21 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:21 +0530
Subject: [PATCH 28/34] PR: Update PR device mac address on mac randomization
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-29-peddolla@qti.qualcomm.com>

Changes to simulate mac randomization. To validate the working of
changes related to Device Identity Key.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 src/common/proximity_ranging.c  | 7 +++++++
 src/common/proximity_ranging.h  | 1 +
 wpa_supplicant/pr_supplicant.c  | 6 ++++++
 wpa_supplicant/pr_supplicant.h  | 4 ++++
 wpa_supplicant/wpa_supplicant.c | 1 +
 5 files changed, 19 insertions(+)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 6fd8e7003..544cb9dfb 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -138,6 +138,13 @@ void pr_deinit(struct pr_data *pr)
 }
 
 
+void pr_set_dev_addr(struct pr_data *pr, const u8 *addr)
+{
+	if (pr && addr)
+		os_memcpy(pr->cfg->dev_addr, addr, ETH_ALEN);
+}
+
+
 void pr_clear_dev_iks(struct pr_data *pr)
 {
 	struct pr_device *dev;
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index e6943355f..77ce4cd49 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -442,6 +442,7 @@ struct operation_mode {
 
 struct pr_data * pr_init(const struct pr_config *cfg);
 void pr_deinit(struct pr_data *pr);
+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, bool own);
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index 9385215b5..36cffcae6 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -245,6 +245,12 @@ void wpas_pr_deinit(struct wpa_supplicant *wpa_s)
 }
 
 
+void wpas_pr_update_dev_addr(struct wpa_supplicant *wpa_s)
+{
+	pr_set_dev_addr(wpa_s->global->pr, wpa_s->own_addr);
+}
+
+
 void wpas_pr_clear_dev_iks(struct wpa_supplicant *wpa_s)
 {
 	struct pr_data *pr = wpa_s->global->pr;
diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h
index 633de38b6..58b145788 100644
--- a/wpa_supplicant/pr_supplicant.h
+++ b/wpa_supplicant/pr_supplicant.h
@@ -15,6 +15,7 @@
 
 int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s);
 void wpas_pr_deinit(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,
 			const char *password, const u8 *pmk, bool own);
@@ -42,6 +43,9 @@ static inline void wpas_pr_deinit(struct wpa_supplicant *wpa_s)
 {
 }
 
+static inline void wpas_pr_update_dev_addr(struct wpa_supplicant *wpa_s)
+{
+}
 
 static inline void wpas_pr_clear_dev_iks(struct wpa_supplicant *wpa_s)
 {
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index 1524794aa..57ada0a30 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -2635,6 +2635,7 @@ int wpas_update_random_addr(struct wpa_supplicant *wpa_s,
 	}
 
 	wpas_p2p_update_dev_addr(wpa_s);
+	wpas_pr_update_dev_addr(wpa_s);
 
 	wpa_msg(wpa_s, MSG_DEBUG, "Using random MAC address " MACSTR,
 		MAC2STR(addr));
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:22 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:22 +0530
Subject: [PATCH 29/34] PR: Control iface support to initiate USD with PR elements
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-30-peddolla@qti.qualcomm.com>

Changes in initiate USD with Proximity Ranging capability IE. A new
attribute "pr" is added to the USD ctrl-iface command to add the PR
IE to the USD frames.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 wpa_supplicant/ctrl_iface.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index 8c81fd549..e4e21fa77 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -12572,6 +12572,11 @@ static int wpas_ctrl_nan_publish(struct wpa_supplicant *wpa_s, char *cmd,
 			continue;
 		}
 
+		if (os_strcmp(token, "pr=1") == 0) {
+			params.proximity_ranging = true;
+			continue;
+		}
+
 		wpa_printf(MSG_INFO, "CTRL: Invalid NAN_PUBLISH parameter: %s",
 			   token);
 		goto fail;
@@ -12729,6 +12734,11 @@ static int wpas_ctrl_nan_subscribe(struct wpa_supplicant *wpa_s, char *cmd,
 			continue;
 		}
 
+		if (os_strcmp(token, "pr=1") == 0) {
+			params.proximity_ranging = true;
+			continue;
+		}
+
 		wpa_printf(MSG_INFO,
 			   "CTRL: Invalid NAN_SUBSCRIBE parameter: %s",
 			   token);
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:23 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:23 +0530
Subject: [PATCH 30/34] PR: Control iface support to trigger PR PASN Authentication
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-31-peddolla@qti.qualcomm.com>

Changes to initiate PASN with Proximity Ranging negotiation.
Parameters to supplied with the command PR_PASN_START are
ranging role(ISTA/RSTA), ranging type(EDCA/NTB), frequency to
perform PASN on and such.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 wpa_supplicant/ctrl_iface.c | 58 +++++++++++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index e4e21fa77..ca05ce040 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -60,6 +60,8 @@
 #include "dpp_supplicant.h"
 #include "sme.h"
 #include "nan_usd.h"
+#include "pr_supplicant.h"
+#include "common/proximity_ranging.h"
 
 #ifdef __NetBSD__
 #include <net/if_ether.h>
@@ -11472,6 +11474,59 @@ static int wpas_ctrl_iface_pasn_deauthenticate(struct wpa_supplicant *wpa_s,
 	return wpas_pasn_deauthenticate(wpa_s, wpa_s->own_addr, bssid);
 }
 
+static int wpas_ctrl_iface_pr_pasn_start(struct wpa_supplicant *wpa_s,
+					 char *cmd)
+{
+	char *token, *context = NULL;
+	u8 addr[ETH_ALEN];
+	int update = 0;
+	int got_addr = 0, freq = 0, forced_pr_freq = 0;
+	u8 ranging_type = 0, role = 0, auth_mode = 0;
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (os_strncmp(token, "addr=", 5) == 0) {
+			if (hwaddr_aton(token + 5, addr))
+				return -1;
+			got_addr = 1;
+		} else if (os_strcmp(token, "role=ISTA") == 0) {
+			role |= PR_ISTA_SUPPORT;
+		} else if (os_strcmp(token, "role=RSTA") == 0) {
+			role |= PR_RSTA_SUPPORT;
+		} else if (os_strcmp(token, "ranging_type=EDCA") == 0) {
+			ranging_type |= PR_EDCA_BASED_RANGING;
+		} else if (os_strcmp(token, "ranging_type=NTB-OPEN-PHY") == 0) {
+			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;
+		} else if (os_strcmp(token, "update=1") == 0) {
+			update = 1;
+		} else if (os_strncmp(token, "freq=", 5) == 0) {
+			freq = atoi(token + 5);
+		} else if (os_strncmp(token, "auth=", 5) == 0) {
+			auth_mode = atoi(token + 5);
+		} else if (os_strncmp(token, "forced_pr_freq=", 15) == 0) {
+			forced_pr_freq = atoi(token + 15);
+		} else {
+			wpa_printf(MSG_DEBUG,
+				   "CTRL: PASN Invalid parameter: '%s'",
+				   token);
+			return -1;
+		}
+	}
+
+	if (!got_addr || ranging_type == 0 || role == 0 || freq == 0) {
+		wpa_printf(MSG_DEBUG,
+			   "CTRL: Proximity Ranging PASN missing parameter");
+		return -1;
+	}
+	wpa_printf(MSG_DEBUG,
+		   "CTRL: PR PASN params: ranging type=%d, role=%d, update=%d, forced pr freq= %d, addr=" MACSTR,
+		   ranging_type, role, update, forced_pr_freq, MAC2STR(addr));
+	wpas_pr_initiate_pasn_auth(wpa_s, addr, freq, auth_mode, role,
+				   ranging_type, forced_pr_freq);
+	return 0;
+}
+
 
 #ifdef CONFIG_TESTING_OPTIONS
 static int wpas_ctrl_iface_pasn_driver(struct wpa_supplicant *wpa_s,
@@ -13932,6 +13987,9 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
 	} else if (os_strncmp(buf, "PASN_DEAUTH ", 12) == 0) {
 		if (wpas_ctrl_iface_pasn_deauthenticate(wpa_s, buf + 12) < 0)
 			reply_len = -1;
+	} else if (os_strncmp(buf, "PR_PASN_START ", 14) == 0) {
+		if (wpas_ctrl_iface_pr_pasn_start(wpa_s, buf + 14) < 0)
+			reply_len = -1;
 #ifdef CONFIG_TESTING_OPTIONS
 	} else if (os_strncmp(buf, "PASN_DRIVER ", 12) == 0) {
 		if (wpas_ctrl_iface_pasn_driver(wpa_s, buf + 12) < 0)
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:24 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:24 +0530
Subject: [PATCH 31/34] PR: Control iface support to set and clear PR device identity context
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-32-peddolla@qti.qualcomm.com>

Changes to set or clear self or peer DIK along with PMK or password.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 wpa_supplicant/ctrl_iface.c | 55 +++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index ca05ce040..c48ceacce 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -11529,6 +11529,56 @@ static int wpas_ctrl_iface_pr_pasn_start(struct wpa_supplicant *wpa_s,
 
 
 #ifdef CONFIG_TESTING_OPTIONS
+static int wpas_ctrl_iface_pr_set_dik_ctx(struct wpa_supplicant *wpa_s,
+					    char *cmd)
+{
+	int ret = -1;
+	bool own = false;
+	char *token, *context = NULL;
+	const char *password = NULL;
+	const u8 *pmk = NULL, *dik = NULL;
+	struct wpabuf *pmk_buf = NULL, *dik_buf = NULL;
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (os_strncmp(token, "self", 4) == 0) {
+			own = 1;
+			continue;
+		}
+
+		if (os_strncmp(token, "dik=", 4) == 0) {
+			dik_buf = wpabuf_parse_bin(token + 4);
+			if (!dik_buf)
+				goto fail;
+			dik = wpabuf_head_u8(dik_buf);
+			continue;
+		}
+
+		if (os_strncmp(token, "password=", 9) == 0) {
+			password = token + 9;
+			continue;
+		}
+
+		if (os_strncmp(token, "pmk=", 4) == 0) {
+			pmk_buf = wpabuf_parse_bin(token + 4);
+			if (!pmk_buf)
+				goto fail;
+			pmk = wpabuf_head_u8(pmk_buf);
+			continue;
+		}
+	}
+
+	if (!dik)
+		goto fail;
+
+	wpas_pr_set_dev_ik(wpa_s, dik, password, pmk, own);
+	ret = 0;
+fail:
+	wpabuf_free(dik_buf);
+	wpabuf_free(pmk_buf);
+	return ret;
+}
+
+
 static int wpas_ctrl_iface_pasn_driver(struct wpa_supplicant *wpa_s,
 				       const char *cmd)
 {
@@ -13991,6 +14041,11 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
 		if (wpas_ctrl_iface_pr_pasn_start(wpa_s, buf + 14) < 0)
 			reply_len = -1;
 #ifdef CONFIG_TESTING_OPTIONS
+	} else if (os_strncmp(buf, "PR_SET_DIK_CONTEXT ", 19) == 0) {
+		if (wpas_ctrl_iface_pr_set_dik_ctx(wpa_s, buf + 19) < 0)
+			reply_len = -1;
+	} else if (os_strcmp(buf, "PR_CLEAR_DIK_CONTEXT") == 0) {
+		wpas_pr_clear_dev_iks(wpa_s);
 	} else if (os_strncmp(buf, "PASN_DRIVER ", 12) == 0) {
 		if (wpas_ctrl_iface_pasn_driver(wpa_s, buf + 12) < 0)
 			reply_len = -1;
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:25 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:25 +0530
Subject: [PATCH 32/34] PR: wpa_cli support for PR discovery and PR secure negotiation
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-33-peddolla@qti.qualcomm.com>

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 wpa_supplicant/wpa_cli.c | 37 +++++++++++++++++++++++++++++++++++--
 1 file changed, 35 insertions(+), 2 deletions(-)

diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c
index b13bf4992..715d7b3d2 100644
--- a/wpa_supplicant/wpa_cli.c
+++ b/wpa_supplicant/wpa_cli.c
@@ -3284,8 +3284,31 @@ static int wpa_cli_cmd_pasn_deauth(struct wpa_ctrl *ctrl, int argc,
 	return wpa_cli_cmd(ctrl, "PASN_DEAUTH", 1, argc, argv);
 }
 
+static int wpa_cli_cmd_pr_pasn_start(struct wpa_ctrl *ctrl, int argc,
+				     char *argv[])
+{
+	return wpa_cli_cmd(ctrl, "PR_PASN_START", 4, argc, argv);
+}
+
+static int wpa_cli_cmd_pr_set_dik_context(struct wpa_ctrl *ctrl, int argc,
+					  char *argv[])
+{
+	return wpa_cli_cmd(ctrl, "PR_SET_DIK_CONTEXT", 1, argc, argv);
+}
+
+static int wpa_cli_cmd_pr_clear_dik_context(struct wpa_ctrl *ctrl, int argc,
+					    char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "PR_CLEAR_DIK_CONTEXT");
+}
+
 #endif /* CONFIG_PASN */
 
+static int wpa_cli_cmd_generate_new_mac(struct wpa_ctrl *ctrl, int argc,
+				 char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "NEW_RANDOM_MAC_ADDRESS");
+}
 
 static int wpa_cli_cmd_mscs(struct wpa_ctrl *ctrl, int argc, char *argv[])
 {
@@ -4086,7 +4109,17 @@ static const struct wpa_cli_cmd wpa_cli_commands[] = {
 	{ "pasn_deauth", wpa_cli_cmd_pasn_deauth, NULL,
 	  cli_cmd_flag_none,
 	  "bssid=<BSSID> = Remove PASN PTKSA state" },
+	{ "pr_pasn_start", wpa_cli_cmd_pr_pasn_start, NULL,
+	  cli_cmd_flag_none,
+	  "bssid=<BSSID> role=<ISTA/RSTA> ranging_type=<edca/ntb/ntb-open/ntb-secure> update=<allow for role update?>" },
+	{ "pr_set_dik_context", wpa_cli_cmd_pr_set_dik_context, NULL,
+	  cli_cmd_flag_none,
+	  "self dik=<own_dik/peerdik> password=<global_pw/unique_pw> pmk=<pmk>" },
+	{ "pr_clear_dik_context", wpa_cli_cmd_pr_clear_dik_context, NULL,
+	  cli_cmd_flag_none, "= Clear all DIK contexts" },
 #endif /* CONFIG_PASN */
+	{ "new_random_mac_address", wpa_cli_cmd_generate_new_mac, NULL,
+	  cli_cmd_flag_none, "= Generate new random mac addr" },
 	{ "mscs", wpa_cli_cmd_mscs, NULL,
 	  cli_cmd_flag_none,
 	  "<add|remove|change> [up_bitmap=<hex byte>] [up_limit=<integer>] [stream_timeout=<in TUs>] [frame_classifier=<hex bytes>] = Configure MSCS request" },
@@ -4111,14 +4144,14 @@ static const struct wpa_cli_cmd wpa_cli_commands[] = {
 #ifdef CONFIG_NAN_USD
 	{ "nan_publish", wpa_cli_cmd_nan_publish, NULL,
 	  cli_cmd_flag_none,
-	  "service_name=<name> [ttl=<time-to-live-in-sec>] [freq=<in MHz>] [freq_list=<comma separate list of MHz>] [srv_proto_type=<type>] [ssi=<service specific information (hexdump)>] [solicited=0] [unsolicited=0] [fsd=0] [p2p=1] = Publish NAN service" },
+	  "service_name=<name> [ttl=<time-to-live-in-sec>] [freq=<in MHz>] [freq_list=<comma separate list of MHz>] [srv_proto_type=<type>] [ssi=<service specific information (hexdump)>] [solicited=0] [unsolicited=0] [fsd=0] [p2p=1] [pr=1] = Publish NAN service" },
 	{ "nan_cancel_publish", wpa_cli_cmd_nan_cancel_publish, NULL,
 	  cli_cmd_flag_none, "publish_id=<id from NAN_PUBLISH> = Cancel NAN USD publish instance" },
 	{ "nan_update_publish", wpa_cli_cmd_nan_update_publish, NULL,
 	  cli_cmd_flag_none, "publish_id=<id from NAN_PUBLISH> [ssi=<service specific information (hexdump)> = Update publish" },
 	{ "nan_subscribe", wpa_cli_cmd_nan_subscribe, NULL,
 	  cli_cmd_flag_none,
-	  "service_name=<name> [active=1] [ttl=<time-to-live-in-sec>] [freq=<in MHz>] [srv_proto_type=<type>] [ssi=<service specific information (hexdump)>] [p2p=1] = Subscribe to NAN service" },
+	  "service_name=<name> [active=1] [ttl=<time-to-live-in-sec>] [freq=<in MHz>] [srv_proto_type=<type>] [ssi=<service specific information (hexdump)>] [p2p=1] [pr=1] = Subscribe to NAN service" },
 	{ "nan_cancel_subscribe", wpa_cli_cmd_nan_cancel_subscribe, NULL,
 	  cli_cmd_flag_none, "subscribe_id=<id from NAN_PUBLISH> = Cancel NAN USD subscribe instance" },
 	{ "nan_transmit", wpa_cli_cmd_nan_transmit, NULL,
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:26 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:26 +0530
Subject: [PATCH 33/34] PR tests: Proximity Ranging test cases
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-34-peddolla@qti.qualcomm.com>

These changes are done to add test cases for proximity ranging.
The test cases cover USD with Proximity Ranging and PASN with
Proximity Ranging negotiation.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 tests/hwsim/test_pr.py | 246 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 246 insertions(+)
 create mode 100644 tests/hwsim/test_pr.py

diff --git a/tests/hwsim/test_pr.py b/tests/hwsim/test_pr.py
new file mode 100644
index 000000000..0816bee4d
--- /dev/null
+++ b/tests/hwsim/test_pr.py
@@ -0,0 +1,246 @@
+# Test cases for Proximity Ranging features like unsynchronized service
+# discovery (PR USD), PASN Authentication for Ranging negotiation and security.
+# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#
+# This software may be distributed under the terms of the BSD license.
+# See README for more details.
+
+import binascii
+import logging
+
+from hwsim import HWSimRadio
+
+logger = logging.getLogger()
+import os
+import hwsim_utils
+
+from wpasupplicant import WpaSupplicant
+from test_nan_usd import check_nan_usd_capab
+from test_pasn import check_pasn_capab
+
+def split_pr_nan_event(ev):
+    vals = dict()
+    for p in ev.split(' ')[1:]:
+        name, val = p.split('=')
+        vals[name] = val
+    return vals
+
+def check_pr_capab(dev):
+    check_nan_usd_capab(dev)
+    check_pasn_capab(dev)
+
+def test_pr_usd_publish_invalid_param(dev):
+    """PR USD Publish with invalid parameters"""
+    check_pr_capab(dev[0])
+
+    # Both solicited and unsolicited disabled is invalid
+    cmd = "NAN_PUBLISH service_name=_test solicited=0 unsolicited=0 pr=1"
+    id0 = dev[0].global_request(cmd)
+    if "FAIL" not in id0:
+        raise Exception("NAN_PUBLISH accepts both solicited=0 and unsolicited=0 with pr=1")
+
+def test_pr_usd_publish(dev, apdev):
+    """PR USD Publish"""
+    check_pr_capab(dev[0])
+    cmd = "NAN_PUBLISH unsolicited=0 srv_proto_type=2 ssi=6677 pr=1"
+    id0 = dev[0].global_request(cmd)
+    if "FAIL" in id0:
+        raise Exception("NAN_PUBLISH for PR failed")
+
+    cmd = "NAN_UPDATE_PUBLISH publish_id=" + id0 + " ssi=1122334455"
+    if "FAIL" in dev[0].global_request(cmd):
+        raise Exception("NAN_UPDATE_PUBLISH for PR failed")
+
+    cmd = "NAN_CANCEL_PUBLISH publish_id=" + id0
+    if "FAIL" in dev[0].global_request(cmd):
+        raise Exception("NAN_CANCEL_PUBLISH for PR failed")
+
+    ev = dev[0].wait_global_event(["NAN-PUBLISH-TERMINATED"], timeout=1)
+    if ev is None:
+        raise Exception("PublishTerminated event not seen")
+    if "publish_id=" + id0 not in ev:
+        raise Exception("Unexpected publish_id: " + ev)
+    if "reason=user-request" not in ev:
+        raise Exception("Unexpected reason: " + ev)
+
+    cmd = "NAN_PUBLISH pr=1"
+    count = 0
+    for i in range(256):
+        if "FAIL" in dev[0].global_request(cmd):
+            break
+        count += 1
+    logger.info("Maximum services: %d" % count)
+    for i in range(count):
+        cmd = "NAN_CANCEL_PUBLISH publish_id=%s" % (i + 1)
+        if "FAIL" in dev[0].global_request(cmd):
+            raise Exception("NAN_CANCEL_PUBLISH failed")
+
+        ev = dev[0].wait_global_event(["NAN-PUBLISH-TERMINATED"], timeout=1)
+        if ev is None:
+            raise Exception("PublishTerminated event not seen")
+
+def test_pr_usd_subscribe(dev, apdev):
+    """PR USD Subscribe"""
+    check_pr_capab(dev[0])
+    cmd = "NAN_SUBSCRIBE active=1 srv_proto_type=2 ssi=1122334455 pr=1"
+    id0 = dev[0].global_request(cmd)
+    if "FAIL" in id0:
+        raise Exception("NAN_SUBSCRIBE for PR failed")
+
+    cmd = "NAN_CANCEL_SUBSCRIBE subscribe_id=" + id0
+    if "FAIL" in dev[0].global_request(cmd):
+        raise Exception("NAN_CANCEL_SUBSCRIBE for PR failed")
+
+    ev = dev[0].wait_global_event(["NAN-SUBSCRIBE-TERMINATED"], timeout=1)
+    if ev is None:
+        raise Exception("SubscribeTerminated event not seen")
+    if "subscribe_id=" + id0 not in ev:
+        raise Exception("Unexpected subscribe_id: " + ev)
+    if "reason=user-request" not in ev:
+        raise Exception("Unexpected reason: " + ev)
+
+def test_pr_usd_match(dev, apdev):
+    """PR USD Publish/Subscribe match"""
+    check_pr_capab(dev[0])
+    check_pr_capab(dev[1])
+    cmd = "NAN_SUBSCRIBE active=1 srv_proto_type=2 ssi=1122334455 pr=1"
+    id0 = dev[0].global_request(cmd)
+    if "FAIL" in id0:
+        raise Exception("NAN_SUBSCRIBE for PR failed")
+
+    cmd = "NAN_PUBLISH unsolicited=0 srv_proto_type=2 ssi=6677 ttl=5 pr=1"
+    id1 = dev[1].global_request(cmd)
+    if "FAIL" in id1:
+        raise Exception("NAN_PUBLISH for PR failed")
+
+    ev = dev[0].wait_global_event(["NAN-DISCOVERY-RESULT"], timeout=5)
+    if ev is None:
+        raise Exception("DiscoveryResult event not seen")
+    if "srv_proto_type=2" not in ev.split(' '):
+        raise Exception("Unexpected srv_proto_type: " + ev)
+    if "ssi=6677" not in ev.split(' '):
+        raise Exception("Unexpected ssi: " + ev)
+
+    dev[0].global_request("NAN_CANCEL_SUBSCRIBE subscribe_id=" + id0)
+    dev[1].global_request("NAN_CANCEL_PUBLISH publish_id=" + id1)
+
+
+
+def clear_all_identities(dev):
+    cmd = "PR_CLEAR_DIK_CONTEXT"
+    dev[0].request(cmd)
+
+    cmd = "PR_CLEAR_DIK_CONTEXT"
+    dev[1].request(cmd)
+
+
+def run_pr_secure_negotiation(dev, auth_mode=0):
+    check_pr_capab(dev[0])
+    check_pr_capab(dev[1])
+
+    cmd = "NAN_SUBSCRIBE active=1 srv_proto_type=2 ssi=1122334455 ttl=10 pr=1 freq=2437"
+    id0 = dev[0].global_request(cmd)
+    if "FAIL" in id0:
+        raise Exception("NAN_SUBSCRIBE for PR failed")
+
+    cmd = "NAN_PUBLISH unsolicited=0 srv_proto_type=2 ssi=6677 ttl=10 pr=1 freq=2437"
+    id1 = dev[1].global_request(cmd)
+    if "FAIL" in id1:
+        raise Exception("NAN_PUBLISH for PR failed")
+
+    ev = dev[0].wait_global_event(["NAN-DISCOVERY-RESULT"], timeout=5)
+    if ev is None:
+        raise Exception("DiscoveryResult event not seen")
+    if "srv_proto_type=2" not in ev.split(' '):
+        raise Exception("Unexpected srv_proto_type: " + ev)
+    if "ssi=6677" not in ev.split(' '):
+        raise Exception("Unexpected ssi: " + ev)
+
+    ev = dev[1].wait_event(["NAN-REPLIED"], timeout=5)
+    if ev is None:
+        raise Exception("Replied event not seen")
+    vals = split_pr_nan_event(ev)
+    if vals['publish_id'] != id1:
+        raise Exception("Unexpected publish_id: " + ev)
+    if vals['subscribe_id'] != id0:
+        raise Exception("Unexpected subscribe_id: " + ev)
+    if vals['address'] != dev[0].own_addr():
+        raise Exception("Unexpected address: " + ev)
+    if vals['srv_proto_type'] != "2":
+        raise Exception("Unexpected ssi: " + ev)
+    if vals['ssi'] != "1122334455":
+        raise Exception("Unexpected ssi: " + ev)
+
+    cmd = "NAN_CANCEL_SUBSCRIBE subscribe_id=" + id0
+    if "FAIL" in dev[0].global_request(cmd):
+        raise Exception("NAN_CANCEL_SUBSCRIBE for PR failed")
+    cmd = "NAN_CANCEL_PUBLISH publish_id=" + id1
+    if "FAIL" in dev[1].global_request(cmd):
+        raise Exception("NAN_CANCEL_PUBLISH for PR failed")
+
+    peer_addr = dev[1].own_addr()
+    cmd = f"PR_PASN_START addr={peer_addr} role=ISTA auth={auth_mode} ranging_type=NTB-OPEN-PHY freq=2437"
+    id0 = dev[0].request(cmd)
+    if "FAIL" in id0:
+        raise Exception("PR_PASN_START Failed")
+
+    ev = dev[0].wait_event(["PR-PASN-RESULT"], timeout=5)
+    if ev is None:
+        raise Exception("PASN result not seen on subscriber")
+
+    ev = dev[1].wait_global_event(["PR-PASN-RESULT"], timeout=10)
+    if ev is None:
+        raise Exception("PASN result not seen on publisher")
+
+def test_pr_pasn_unauth(dev, apdev):
+    """PR Secure ranging negotiation with PASN-UNAUTH"""
+    clear_all_identities(dev)
+    run_pr_secure_negotiation(dev, auth_mode=0)
+
+def test_pr_pasn_sae_global_pw(dev, apdev):
+    """PR Secure ranging negotiation with PASN-SAE with Advertiser global password"""
+
+    clear_all_identities(dev)
+    cmd = "PR_SET_DIK_CONTEXT self dik=D216C41154D2187E422F1B0F193D0AAD password=asdfghjkl"
+    dev[1].request(cmd)
+
+    cmd = "PR_SET_DIK_CONTEXT dik=D216C41154D2187E422F1B0F193D0AAD password=asdfghjkl"
+    dev[0].request(cmd)
+
+    run_pr_secure_negotiation(dev, auth_mode=1)
+
+def test_pr_pasn_sae_unique_pw(dev, apdev):
+    """PR Secure ranging negotiation with PASN-SAE with unique password"""
+
+    clear_all_identities(dev)
+    cmd = "PR_SET_DIK_CONTEXT self dik=D216C41154D2187E422F1B0F193D0BBB"
+    dev[1].request(cmd)
+
+    cmd = "PR_SET_DIK_CONTEXT self dik=D216C41154D2187E422F1B0F193D0AAA"
+    dev[0].request(cmd)
+
+    cmd = "PR_SET_DIK_CONTEXT dik=D216C41154D2187E422F1B0F193D0AAA password=qwertyuiop"
+    dev[1].request(cmd)
+
+    cmd = "PR_SET_DIK_CONTEXT dik=D216C41154D2187E422F1B0F193D0BBB password=qwertyuiop"
+    dev[0].request(cmd)
+
+    run_pr_secure_negotiation(dev, auth_mode=1)
+
+def test_pr_pasn_pmk(dev, apdev):
+    """PR Secure ranging negotiation with PASN-PMK"""
+
+    clear_all_identities(dev)
+    cmd = "PR_SET_DIK_CONTEXT self dik=D216C41154D2187E422F1B0F193D0111"
+    dev[1].request(cmd)
+
+    cmd = "PR_SET_DIK_CONTEXT self dik=D216C41154D2187E422F1B0F193D0222"
+    dev[0].request(cmd)
+
+    cmd = "PR_SET_DIK_CONTEXT dik=D216C41154D2187E422F1B0F193D0222 pmk=7a55426d410b409780d3f2f3e9cb0cf667745c880816ccee5701a0b3bb1ee64d"
+    dev[1].request(cmd)
+
+    cmd = "PR_SET_DIK_CONTEXT dik=D216C41154D2187E422F1B0F193D0111 pmk=7a55426d410b409780d3f2f3e9cb0cf667745c880816ccee5701a0b3bb1ee64d"
+    dev[0].request(cmd)
+
+    run_pr_secure_negotiation(dev, auth_mode=2)
-- 
2.34.1



From peddolla at qti.qualcomm.com  Thu Jun 19 08:37:27 2025
From: peddolla at qti.qualcomm.com (Peddolla Harshavardhan Reddy)
Date: Thu, 19 Jun 2025 21:07:27 +0530
Subject: [PATCH 34/34] PR tests: Enable Proximity Ranging hwsim tests
In-Reply-To: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
References: <20250619153727.998806-1-peddolla@qti.qualcomm.com>
Message-ID: <20250619153727.998806-35-peddolla@qti.qualcomm.com>

Changes to enable Proximity Ranging hwsim tests

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla at qti.qualcomm.com>
---
 tests/hwsim/example-wpa_supplicant.config | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/tests/hwsim/example-wpa_supplicant.config b/tests/hwsim/example-wpa_supplicant.config
index a43bdc5ce..c6b32eaba 100644
--- a/tests/hwsim/example-wpa_supplicant.config
+++ b/tests/hwsim/example-wpa_supplicant.config
@@ -169,3 +169,5 @@ CONFIG_PASN=y
 CONFIG_NAN_USD=y
 
 CONFIG_IEEE80211BE=y
+
+CONFIG_PR=y
-- 
2.34.1



From j at w1.fi  Sat Jun 21 02:03:12 2025
From: j at w1.fi (Jouni Malinen)
Date: Sat, 21 Jun 2025 12:03:12 +0300
Subject: [PATCH 0/7] Various test fixes
In-Reply-To: <20250611084729.1640913-1-benjamin@sipsolutions.net>
References: <20250611084729.1640913-1-benjamin@sipsolutions.net>
Message-ID: <aFZ1UKvBsgQIOb6w@w1.fi>

On Wed, Jun 11, 2025 at 10:47:22AM +0200, Benjamin Berg wrote:
> This set contains various test fixes that are hopefully all reasonably
> straight forward fixes for minor issues.

> Benjamin Berg (7):
>   tests: use clear_regdom helper in ap_vht tests
>   tests: Ensure property change notifications happen in VM
>   tests: Add sleep to ensure wlantest processed packets
>   tests: improve tshark boolean output support
>   tests: generate proper dependencies for all tests
>   tests: ensure the RoC is setup when the NAN frame arrives
>   tests: permit encryption of DEAUTH frame

Thanks, applied.
 
-- 
Jouni Malinen                                            PGP id EFC895FA


From j at w1.fi  Sat Jun 21 02:03:37 2025
From: j at w1.fi (Jouni Malinen)
Date: Sat, 21 Jun 2025 12:03:37 +0300
Subject: [PATCH 1/3] WPS: fix leak of dh_ctx in error cases
In-Reply-To: <20250612081021.130450-1-benjamin@sipsolutions.net>
References: <20250612081021.130450-1-benjamin@sipsolutions.net>
Message-ID: <aFZ1aflMqn3pUcYc@w1.fi>

On Thu, Jun 12, 2025 at 10:10:19AM +0200, Benjamin Berg wrote:
> Usually the dh_ctx is passed into the wps state and is free'ed in that
> case. However, under some circumstances this may not happen and then a
> leak can occur.

Thanks, all three patches applied.
 
-- 
Jouni Malinen                                            PGP id EFC895FA


From j at w1.fi  Sat Jun 21 02:03:59 2025
From: j at w1.fi (Jouni Malinen)
Date: Sat, 21 Jun 2025 12:03:59 +0300
Subject: [PATCH] tests: use logger.exception to dump full traceback
In-Reply-To: <20250618122204.957868-1-benjamin@sipsolutions.net>
References: <20250618122204.957868-1-benjamin@sipsolutions.net>
Message-ID: <aFZ1fwnBDNSjzPyB@w1.fi>

On Wed, Jun 18, 2025 at 02:22:04PM +0200, Benjamin Berg wrote:
> The log sometimes does not contain relevant information. Switch to use
> logger.exception which dumps the full exception information. Note that
> the logging happens with level ERROR, so it will be dumped to the
> stdout/stderr by default.

Thanks, applied.
 
-- 
Jouni Malinen                                            PGP id EFC895FA


From benjamin at sipsolutions.net  Mon Jun 23 04:28:11 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Mon, 23 Jun 2025 13:28:11 +0200
Subject: [PATCH v2] AP MLD: never keep a per-link wpa_sm
Message-ID: <20250623112811.1249412-1-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

The __checK_assoc_ies function needs to create a per-link wpa_sm
temporarily. However, later on sta->wpa_sm should just be a reference to
the main wpa_sm.

Change the code so that the temporary state is self-contained to
__check_assoc_ies. This fixes a memory leak should __check_assoc_ies
return an error.

Also change the function to pass the wpa_sm for the assoc_link instead
of just a link boolean. The internal lookup appears to not have worked
and this also simplifies the code.

Fixes: 5f5db9366cde ("AP: MLO: Process Multi-Link element from (Re)Association Request frame")
Fixes: 84d2a36da02a ("AP MLD: Require same AKM and pairwise cipher for all links")

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>

---

v2:
- rebased

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 src/ap/ieee802_11.c | 135 ++++++++++++++++++++++++++------------------
 1 file changed, 81 insertions(+), 54 deletions(-)

diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 866a1c1c89..7c37d6a66a 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -4220,56 +4220,56 @@ static bool check_sa_query(struct hostapd_data *hapd, struct sta_info *sta,
 static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			     const u8 *ies, size_t ies_len,
 			     struct ieee802_11_elems *elems,
-			     enum link_parse_type type, bool link)
+			     enum link_parse_type type,
+			     struct wpa_state_machine *assoc_wpa_sm)
 {
 	int resp;
 	const u8 *wpa_ie;
 	size_t wpa_ie_len;
 	const u8 *p2p_dev_addr = NULL;
-	struct hostapd_data *assoc_hapd;
-	struct sta_info *assoc_sta = NULL;
 
 	if (type != LINK_PARSE_RECONF) {
 		resp = check_ssid(hapd, sta, elems->ssid, elems->ssid_len);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 	}
 
 	resp = check_wmm(hapd, sta, elems->wmm, elems->wmm_len);
 	if (resp != WLAN_STATUS_SUCCESS)
-		return resp;
+		goto out;
 	resp = check_ext_capab(hapd, sta, elems->ext_capab,
 			       elems->ext_capab_len);
 	if (resp != WLAN_STATUS_SUCCESS)
-		return resp;
+		goto out;
 	resp = copy_supp_rates(hapd, sta, elems);
 	if (resp != WLAN_STATUS_SUCCESS)
-		return resp;
+		goto out;
 
 	resp = check_multi_ap(hapd, sta, elems->multi_ap, elems->multi_ap_len);
 	if (resp != WLAN_STATUS_SUCCESS)
-		return resp;
+		goto out;
 
 	resp = copy_sta_ht_capab(hapd, sta, elems->ht_capabilities);
 	if (resp != WLAN_STATUS_SUCCESS)
-		return resp;
+		goto out;
 	if (hapd->iconf->ieee80211n && hapd->iconf->require_ht &&
 	    !(sta->flags & WLAN_STA_HT)) {
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_INFO, "Station does not support "
 			       "mandatory HT PHY - reject association");
-		return WLAN_STATUS_ASSOC_DENIED_NO_HT;
+		resp = WLAN_STATUS_ASSOC_DENIED_NO_HT;
+		goto out;
 	}
 
 #ifdef CONFIG_IEEE80211AC
 	if (hapd->iconf->ieee80211ac) {
 		resp = copy_sta_vht_capab(hapd, sta, elems->vht_capabilities);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 
 		resp = set_sta_vht_opmode(hapd, sta, elems->opmode_notif);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 	}
 
 	if (hapd->iconf->ieee80211ac && hapd->iconf->require_vht &&
@@ -4277,14 +4277,15 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_INFO, "Station does not support "
 			       "mandatory VHT PHY - reject association");
-		return WLAN_STATUS_ASSOC_DENIED_NO_VHT;
+		resp = WLAN_STATUS_ASSOC_DENIED_NO_VHT;
+		goto out;
 	}
 
 	if (hapd->conf->vendor_vht && !elems->vht_capabilities) {
 		resp = copy_sta_vendor_vht(hapd, sta, elems->vendor_vht,
 					   elems->vendor_vht_len);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 	}
 #endif /* CONFIG_IEEE80211AC */
 #ifdef CONFIG_IEEE80211AX
@@ -4293,14 +4294,15 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					 elems->he_capabilities,
 					 elems->he_capabilities_len);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 
 		if (hapd->iconf->require_he && !(sta->flags & WLAN_STA_HE)) {
 			hostapd_logger(hapd, sta->addr,
 				       HOSTAPD_MODULE_IEEE80211,
 				       HOSTAPD_LEVEL_INFO,
 				       "Station does not support mandatory HE PHY - reject association");
-			return WLAN_STATUS_DENIED_HE_NOT_SUPPORTED;
+			resp = WLAN_STATUS_DENIED_HE_NOT_SUPPORTED;
+			goto out;
 		}
 
 		if (is_6ghz_op_class(hapd->iconf->op_class)) {
@@ -4309,12 +4311,13 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					       HOSTAPD_MODULE_IEEE80211,
 					       HOSTAPD_LEVEL_INFO,
 					       "Station does not support mandatory HE PHY - reject association");
-				return WLAN_STATUS_DENIED_HE_NOT_SUPPORTED;
+				resp = WLAN_STATUS_DENIED_HE_NOT_SUPPORTED;
+				goto out;
 			}
 			resp = copy_sta_he_6ghz_capab(hapd, sta,
 						      elems->he_6ghz_band_cap);
 			if (resp != WLAN_STATUS_SUCCESS)
-				return resp;
+				goto out;
 		}
 	}
 #endif /* CONFIG_IEEE80211AX */
@@ -4326,12 +4329,12 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					  elems->eht_capabilities,
 					  elems->eht_capabilities_len);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 
-		if (!link) {
+		if (!assoc_wpa_sm) {
 			resp = hostapd_process_ml_assoc_req(hapd, elems, sta);
 			if (resp != WLAN_STATUS_SUCCESS)
-				return resp;
+				goto out;
 		}
 	}
 #endif /* CONFIG_IEEE80211BE */
@@ -4388,7 +4391,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		if (sta->wps_ie && wps_validate_assoc_req(sta->wps_ie) < 0) {
 			wpa_printf(MSG_DEBUG, "WPS: Invalid WPS IE in "
 				   "(Re)Association Request - reject");
-			return WLAN_STATUS_INVALID_IE;
+			resp = WLAN_STATUS_INVALID_IE;
+			goto out;
 		}
 	} else if (hapd->conf->wps_state && wpa_ie == NULL) {
 		wpa_printf(MSG_DEBUG, "STA did not include WPA/RSN IE in "
@@ -4400,7 +4404,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_INFO,
 			       "No WPA/RSN IE in association request");
-		return WLAN_STATUS_INVALID_IE;
+		resp = WLAN_STATUS_INVALID_IE;
+		goto out;
 	}
 
 	if (hapd->conf->wpa && wpa_ie) {
@@ -4414,10 +4419,10 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		wpa_ie_len += 2;
 
 		if (!sta->wpa_sm) {
-			if (!link)
-				assoc_sta = hostapd_ml_get_assoc_sta(
-					hapd, sta, &assoc_hapd);
-
+			/* NOTE: For links other than the assoc-link the
+			 * separate wpa_sm is only allocated internally to this
+			 * function.
+			 */
 			sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth,
 							sta->addr,
 							p2p_dev_addr);
@@ -4425,7 +4430,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			if (!sta->wpa_sm) {
 				wpa_printf(MSG_WARNING,
 					   "Failed to initialize RSN state machine");
-				return WLAN_STATUS_UNSPECIFIED_FAILURE;
+				resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+				goto out;
 			}
 		}
 
@@ -4452,11 +4458,11 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					  0,
 					  elems->mdie, elems->mdie_len,
 					  elems->owe_dh, elems->owe_dh_len,
-					  assoc_sta ? assoc_sta->wpa_sm : NULL,
+					  assoc_wpa_sm,
 					  ap_sta_is_mld(hapd, sta));
 		resp = wpa_res_to_status_code(res);
 		if (resp != WLAN_STATUS_SUCCESS)
-			return resp;
+			goto out;
 
 		if (wpa_auth_uses_mfp(sta->wpa_sm))
 			sta->flags |= WLAN_STA_MFP;
@@ -4475,17 +4481,18 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					   "to use association (not "
 					   "re-association) with FT auth_alg",
 					   MAC2STR(sta->addr));
-				return WLAN_STATUS_UNSPECIFIED_FAILURE;
+				resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+				goto out;
 			}
 
 			resp = wpa_ft_validate_reassoc(sta->wpa_sm, ies,
 						       ies_len);
 			if (resp != WLAN_STATUS_SUCCESS)
-				return resp;
+				goto out;
 		}
 #endif /* CONFIG_IEEE80211R_AP */
 
-		if (link)
+		if (assoc_wpa_sm)
 			goto skip_sae_owe;
 #ifdef CONFIG_SAE
 		if (wpa_auth_uses_sae(sta->wpa_sm) && sta->sae &&
@@ -4500,7 +4507,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 				wpa_printf(MSG_DEBUG,
 					   "SAE: No PMKSA cache entry found for "
 					   MACSTR, MAC2STR(sta->addr));
-				return WLAN_STATUS_INVALID_PMKID;
+				resp = WLAN_STATUS_INVALID_PMKID;
+				goto out;
 			}
 			wpa_printf(MSG_DEBUG, "SAE: " MACSTR
 				   " using PMKSA caching", MAC2STR(sta->addr));
@@ -4511,7 +4519,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			wpa_printf(MSG_DEBUG, "SAE: " MACSTR " tried to use "
 				   "SAE AKM after non-SAE auth_alg %u",
 				   MAC2STR(sta->addr), sta->auth_alg);
-			return WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG;
+			resp = WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG;
+			goto out;
 		}
 
 		if (hapd->conf->sae_pwe == SAE_PWE_BOTH &&
@@ -4522,7 +4531,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			wpa_printf(MSG_INFO, "SAE: " MACSTR
 				   " indicates support for SAE H2E, but did not use it",
 				   MAC2STR(sta->addr));
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 		}
 #endif /* CONFIG_SAE */
 
@@ -4533,7 +4543,7 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			resp = owe_process_assoc_req(hapd, sta, elems->owe_dh,
 						     elems->owe_dh_len);
 			if (resp != WLAN_STATUS_SUCCESS)
-				return resp;
+				goto out;
 		}
 #endif /* CONFIG_OWE */
 	skip_sae_owe:
@@ -4561,7 +4571,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 					    elems->owe_dh_len) < 0) {
 				dpp_pfs_free(sta->dpp_pfs);
 				sta->dpp_pfs = NULL;
-				return WLAN_STATUS_UNSPECIFIED_FAILURE;
+				resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+				goto out;
 			}
 		}
 
@@ -4577,7 +4588,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 				       HOSTAPD_LEVEL_INFO,
 				       "Station tried to use TKIP with HT "
 				       "association");
-			return WLAN_STATUS_CIPHER_REJECTED_PER_POLICY;
+			resp = WLAN_STATUS_CIPHER_REJECTED_PER_POLICY;
+			goto out;
 		}
 
 		wpa_auth_set_ssid_protection(
@@ -4609,7 +4621,8 @@ skip_wpa_ies:
 			wpa_printf(MSG_DEBUG,
 				   "HS 2.0: PMF not negotiated by release %d station "
 				   MACSTR, release, MAC2STR(sta->addr));
-			return WLAN_STATUS_ROBUST_MGMT_FRAME_POLICY_VIOLATION;
+			resp = WLAN_STATUS_ROBUST_MGMT_FRAME_POLICY_VIOLATION;
+			goto out;
 		}
 	} else {
 		sta->hs20_ie = NULL;
@@ -4640,7 +4653,8 @@ skip_wpa_ies:
 	    hapd->conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) {
 		wpa_printf(MSG_INFO,
 			   "MBO: Reject WPA2 association without PMF");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 #endif /* CONFIG_MBO */
 
@@ -4658,14 +4672,17 @@ skip_wpa_ies:
 		if (hostapd_drv_channel_info(hapd, &ci) != 0) {
 			wpa_printf(MSG_WARNING,
 				   "Failed to get channel info to validate received OCI in FILS (Re)Association Request frame");
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 		}
 
 		if (get_sta_tx_parameters(sta->wpa_sm,
 					  channel_width_to_int(ci.chanwidth),
 					  ci.seg1_idx, &tx_chanwidth,
-					  &tx_seg1_idx) < 0)
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+					  &tx_seg1_idx) < 0) {
+			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
+		}
 
 		res = ocv_verify_tx_params(elems->oci, elems->oci_len, &ci,
 					   tx_chanwidth, tx_seg1_idx);
@@ -4681,7 +4698,8 @@ skip_wpa_ies:
 			wpa_msg(hapd->msg_ctx, MSG_INFO, OCV_FAILURE "addr="
 				MACSTR " frame=fils-reassoc-req error=%s",
 				MAC2STR(sta->addr), ocv_errorstr);
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 		}
 	}
 #endif /* CONFIG_FILS && CONFIG_OCV */
@@ -4720,7 +4738,21 @@ skip_wpa_ies:
 		hostapd_wfa_capab(hapd, sta, elems->wfa_capab,
 				  elems->wfa_capab + elems->wfa_capab_len);
 
-	return WLAN_STATUS_SUCCESS;
+out:
+	if (resp != WLAN_STATUS_SUCCESS || assoc_wpa_sm) {
+		wpa_auth_sta_deinit(sta->wpa_sm);
+
+		/* Only keep a reference to the main wpa_sm and drop the
+		 * per-link instance.
+		 * This reference is needed during group rekey handling.
+		 */
+		if (resp == WLAN_STATUS_SUCCESS)
+			sta->wpa_sm = assoc_wpa_sm;
+		else
+			sta->wpa_sm = NULL;
+	}
+
+	return resp;
 }
 
 
@@ -4737,7 +4769,7 @@ static int check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 	}
 
-	return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, type, false);
+	return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, type, NULL);
 }
 
 
@@ -4860,7 +4892,8 @@ int ieee80211_ml_process_link(struct hostapd_data *hapd,
 	sta->mld_assoc_link_id = origin_sta->mld_assoc_link_id;
 	ap_sta_set_mld(sta, true);
 
-	status = __check_assoc_ies(hapd, sta, NULL, 0, &elems, type, true);
+	status = __check_assoc_ies(hapd, sta, NULL, 0, &elems, type,
+				   origin_sta->wpa_sm);
 	if (status != WLAN_STATUS_SUCCESS) {
 		wpa_printf(MSG_DEBUG, "MLD: link: Element check failed");
 		goto out;
@@ -4896,12 +4929,6 @@ int ieee80211_ml_process_link(struct hostapd_data *hapd,
 			ieee802_11_update_beacons(hapd->iface);
 	}
 
-	/* Maintain state machine reference on all link STAs, this is needed
-	 * during group rekey handling.
-	 */
-	wpa_auth_sta_deinit(sta->wpa_sm);
-	sta->wpa_sm = origin_sta->wpa_sm;
-
 	/*
 	 * Do not initialize the EAPOL state machine.
 	 * TODO: Maybe it is needed?
-- 
2.49.0



From christopher.soellinger at gmail.com  Mon Jun 23 12:11:42 2025
From: christopher.soellinger at gmail.com (=?UTF-8?Q?Christopher_S=C3=B6llinger?=)
Date: Mon, 23 Jun 2025 21:11:42 +0200
Subject: [PATCH] hostapd: safe call to hostapd_config_check_bss_6g()
Message-ID: <CA+riUNPQKYGZ9S91eU+MkBhF=9h0-eg_aW+aKRaQgxJ59ZZVSg@mail.gmail.com>

From: Christopher S?llinger <christopher.soellinger at gmail.com>
Subject: [PATCH] hostapd: fix incorrect call to hostapd_config_check_bss_6g()

In ap_drv_ops.c, the call to hostapd_config_check_bss_6g() is executed
unconditionally. However, it should only be used when the BSS is actually
configured on a 6 GHz operating class. This leads to false configuration
errors in setups where the 6 GHz BSS has proper settings but other bands
(2.4/5 GHz) share the same config structure.

This patch adds the missing is_6ghz_op_class() condition, mirroring how
it's already used elsewhere in the codebase (e.g. in ap_config.c).

Fix verified on OpenWRT 24.10-SNAPSHOT using Mediatek Wi-Fi 7 hardware.

Signed-off-by: Christopher S?llinger <christopher.soellinger at gmail.com>

---
 src/ap/ap_drv_ops.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index d7a0c7e..3cfe6ce 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -1220,7 +1220,8 @@ static int hostapd_prepare_rates(struct
hostapd_data *hapd,
 {
     int i;
     bool is_no_ir = false;
-    bool allow_6g_acs = hostapd_config_check_bss_6g(hapd->conf) &&
+    bool allow_6g_acs = is_6ghz_op_class(hapd->iconf->op_class) &&
+                        hostapd_config_check_bss_6g(hapd->conf) &&
                         (hapd->iface->conf->ieee80211ax ||
                          hapd->iface->conf->ieee80211be);


From j at w1.fi  Wed Jun 25 02:27:23 2025
From: j at w1.fi (Jouni Malinen)
Date: Wed, 25 Jun 2025 12:27:23 +0300
Subject: [PATCH] hostapd: safe call to hostapd_config_check_bss_6g()
In-Reply-To: <CA+riUNPQKYGZ9S91eU+MkBhF=9h0-eg_aW+aKRaQgxJ59ZZVSg@mail.gmail.com>
References: <CA+riUNPQKYGZ9S91eU+MkBhF=9h0-eg_aW+aKRaQgxJ59ZZVSg@mail.gmail.com>
Message-ID: <aFvA-yIOWg7le9Oe@w1.fi>

On Mon, Jun 23, 2025 at 09:11:42PM +0200, Christopher S?llinger wrote:
> In ap_drv_ops.c, the call to hostapd_config_check_bss_6g() is executed
> unconditionally. However, it should only be used when the BSS is actually
> configured on a 6 GHz operating class. This leads to false configuration
> errors in setups where the 6 GHz BSS has proper settings but other bands
> (2.4/5 GHz) share the same config structure.
> 
> This patch adds the missing is_6ghz_op_class() condition, mirroring how
> it's already used elsewhere in the codebase (e.g. in ap_config.c).
> 
> Fix verified on OpenWRT 24.10-SNAPSHOT using Mediatek Wi-Fi 7 hardware.

Thanks, applied.

-- 
Jouni Malinen                                            PGP id EFC895FA


From jithu.jance at broadcom.com  Wed Jun 25 17:30:57 2025
From: jithu.jance at broadcom.com (Jithu Jance (Syna))
Date: Thu, 26 Jun 2025 06:00:57 +0530
Subject: [PATCH 1/1] hostapd: Allow roam from OWE transition to OWE only AP
In-Reply-To: <CA+gyVQ0oBX9k38gNvN-3W9AsUefPNCHbGBsTmcEHjUw90MPt-g@mail.gmail.com>
References: <058bf3a1ee0d92f92a9d8e61fbd893470bee1829.1734079421.git.vinayak.yadawad@broadcom.com>
 <Z22orSyNnqeVay54@w1.fi> <CAMLe8U8R2CJhMcFKNJcpG_um21D-dVzg2NQMTwQRaopgF2p7fw@mail.gmail.com>
 <CAMLe8U_bM23mZXQ_bn5dqZonxEqiR0J-F5mOzaAQpRSn8s9mnA@mail.gmail.com> <CA+gyVQ0oBX9k38gNvN-3W9AsUefPNCHbGBsTmcEHjUw90MPt-g@mail.gmail.com>
Message-ID: <CA+gyVQ3c+wYon3kzHDQooaAQhf7nUapGoMd5AukPMa8z29ZRRw@mail.gmail.com>

Hi Jouni,

Gentle reminder.

 This patch is for a scenario where a user roams from OWE transition
mode AP to OWE only AP with both APs having the same SSID for OWE BSS.
 Since the current_ssid profile holds open BSS SSID and drv_ssid holds
OWE BSS SSID, the below condition check doesn't satisfy.
 The OWE BSS SSID information is only part of previous bss and hence
we referred to the current_bss of previous AP to confirm SSID remains
the same.
 Please advise whether the approach attempted is fine or not. Does it
make sense to store the owe BSS ssid also in the current_ssid
structure for comparison?

 ```
if (drv_ssid_len == wpa_s->current_ssid->ssid_len &&
   os_memcmp(drv_ssid, wpa_s->current_ssid->ssid,
     drv_ssid_len) == 0)
return 0; /* current profile still in use */
```


Jithu Jance



Jithu Jance


On Wed, Jun 25, 2025 at 2:33?PM Jithu Jance (Syna)
<jithu.jance at broadcom.com> wrote:
>
> Hi Jouni,
>
> Gentle reminder.
>
>  This patch is for a scenario where a user roams from OWE transition mode AP to OWE only AP with both APs having the same SSID for OWE BSS.
>  Since the current_ssid profile holds open BSS SSID and drv_ssid holds OWE BSS SSID, the below condition check doesn't satisfy.
>  The OWE BSS SSID information is only part of previous bss and hence we referred to the current_bss of previous AP to confirm SSID remains the same.
>  Please advise whether the approach attempted is fine or not. Does it make sense to store the owe BSS ssid also in the current_ssid structure for comparison?
>
>  ```
> if (drv_ssid_len == wpa_s->current_ssid->ssid_len &&
>    os_memcmp(drv_ssid, wpa_s->current_ssid->ssid,
>      drv_ssid_len) == 0)
> return 0; /* current profile still in use */
> ```
>
>
>
> Jithu Jance
>
>
> On Wed, Apr 16, 2025 at 10:01?AM Vinayak Yadawad <vinayak.yadawad at broadcom.com> wrote:
>>
>> Hi Jouni,
>>
>> Could you please review our update and provide any suggestions.
>>
>> Regards,
>> Vinayak
>>
>>
>> On Thu, Jan 2, 2025 at 2:28?PM Vinayak Yadawad
>> <vinayak.yadawad at broadcom.com> wrote:
>> >
>> > Hi Jouni,
>> >
>> > The scenario is of multiple APs containing both OWE-transition and OWE
>> > only mode APs in same network. The OWE BSS of transition mode and OWE
>> > only AP
>> > is configured with same SSID and roam is attempted between them.
>> > Initially the STA is connected to OWE-transition AP in OWE mode.
>> > For e.g: open mode SSID is "SSID_open" and transition mode SSID is
>> > "SSID1_owe". Now when a roam is triggered to an OWE-only AP with name
>> > "SSID1_owe", the SSID comparison fails as ssid profile stores open BSS
>> > SSID (SSID_open). The roam fails with error "No network configuration
>> > found for the current AP".
>> >  So the current change would help to compare the previous connection
>> > SSID instead of conf->SSID.
>> >
>> > Logs for roam failure from OWE-transition to OWE-only AP:
>> >    689.959  2125  2125 D wpa_supplicant: wlan0: Associated to a new
>> > BSS: BSSID=00:90:4c:4c:84:99
>> >    689.959  2125  2125 D wpa_supplicant: wlan0: Driver-initiated BSS
>> > selection changed the SSID to G3_Tb_58_AP_ROAM_2G_6_19_owe
>> >    689.959  2125  2125 D wpa_supplicant: wlan0: Select network based
>> > on association information
>> >    689.959  2125  2125 I wpa_supplicant: wlan0: No network
>> > configuration found for the current AP
>> >    689.959  2125  2125 D wpa_supplicant: wlan0: Request to
>> > deauthenticate - bssid=00:90:4c:4c:84:99
>> > pending_bssid=00:00:00:00:00:00 reason=3 (DEAUTH_LEAVING)
>> > state=ASSOCIATED valid_links=0x0 ap_mld_addr=00:00:00:00:00:00
>> >    689.959  2125  2125 D wpa_supplicant: TDLS: Tear down peers
>> >    689.959  2125  2125 D wpa_supplicant:
>> > wpa_driver_nl80211_disconnect(reason_code=3)
>> >    690.388  2125  2125 I wpa_supplicant: nl80211: send_event_marker
>> > failed: Source based routing not supported
>> >    690.388  2125  2125 D wpa_supplicant: wlan0: Event DEAUTH (11) received
>> >    690.388  2125  2125 D wpa_supplicant: wlan0: Deauthentication notification
>> >    690.388  2125  2125 D wpa_supplicant: wlan0:  * reason 3
>> > (DEAUTH_LEAVING) locally_generated=1
>> >    690.388  2125  2125 D wpa_supplicant: Deauthentication frame IE(s)
>> > - hexdump(len=0): [NULL]
>> >    690.388  2125  2125 I wpa_supplicant: wlan0:
>> > CTRL-EVENT-DISCONNECTED bssid=00:90:4c:4c:84:99 reason=3
>> > locally_generated=1
>> >
>> > Regards,
>> > Vinayak
>> >
>> >
>> > On Fri, Dec 27, 2024 at 12:34?AM Jouni Malinen <j at w1.fi> wrote:
>> > >
>> > > On Fri, Dec 13, 2024 at 02:15:27PM +0530, Vinayak Yadawad wrote:
>> > > > Currently STA roam works fine in case of OWE only AP to
>> > > > OWE-transition mode AP. The roam fails when STA is connected
>> > > > in OWE mode with OWE transition AP to an OWE only AP. In the
>> > > > OWE transition case, the current_ssid stores only open network
>> > > > SSID, specific check is required to compare the OWE BSS ssid
>> > > > from the previous target and new target.
>> > >
>> > > The change in owe_trans_ssid() is a clear fix to a recent regression, so
>> > > I applied that separately. As far as the changes in
>> > > wpa_supplicant_select_config() are concerned, I'm not completely sure I
>> > > understood them since the comment about the added OWE transition mode AP
>> > > to OWE-only AP comparison of the SSID does not seem to match what I
>> > > would have expected from the actual memcmp(). The SSID of the previous
>> > > BSS (i.e., the transition mode AP) is a random value that does not match
>> > > the network profile whereas the SSID of the selected BSS (i.e., OWE-only
>> > > AP) is the SSID of the network profile. Those are different and as such,
>> > > the new memcmp() == 0 case would not be met..
>> > >
>> > > Would you be able to share a debug log showing what fails without this
>> > > patch?
>> > >
>> > > I did some cleanup while reviewing and trying to understand this and
>> > > this is what I came up with while still not fully understanding what
>> > > this trying to do:
>> > >
>> > > From: Vinayak Yadawad <vinayak.yadawad at broadcom.com>
>> > > Date: Fri, 13 Dec 2024 14:15:27 +0530
>> > > Subject: [PATCH] OWE: Allow roam from OWE transition mode AP to OWE-only AP
>> > >
>> > > Currently STA roam works fine in case of OWE only AP to
>> > > OWE-transition mode AP. The roam fails when STA is connected
>> > > in OWE mode with OWE transition AP to an OWE only AP. In the
>> > > OWE transition case, the current_ssid stores only open network
>> > > SSID, specific check is required to compare the OWE BSS ssid
>> > > from the previous target and new target.
>> > >
>> > > Signed-off-by: Vinayak Yadawad <vinayak.yadawad at broadcom.com>
>> > > ---
>> > >  wpa_supplicant/events.c | 38 ++++++++++++++++++++++++++++++++------
>> > >  1 file changed, 32 insertions(+), 6 deletions(-)
>> > >
>> > > diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
>> > > index 279838afa2bf..e7285fcb5cea 100644
>> > > --- a/wpa_supplicant/events.c
>> > > +++ b/wpa_supplicant/events.c
>> > > @@ -226,6 +226,15 @@ static int wpa_supplicant_select_config(struct wpa_supplicant *wpa_s,
>> > >         int res;
>> > >
>> > >         if (wpa_s->conf->ap_scan == 1 && wpa_s->current_ssid) {
>> > > +#ifdef CONFIG_OWE
>> > > +               struct wpa_bss *prev_bss;
>> > > +
>> > > +               /* Remember the current BSS entry before update to a new one to
>> > > +                * allow appropriate comparison in OWE transition mode cases
>> > > +                * below. */
>> > > +               prev_bss = wpa_s->current_bss;
>> > > +#endif /* CONFIG_OWE */
>> > > +
>> > >                 wpa_supplicant_update_current_bss(wpa_s, wpa_s->bssid);
>> > >
>> > >                 if (wpa_s->current_ssid->ssid_len == 0)
>> > > @@ -245,12 +254,29 @@ static int wpa_supplicant_select_config(struct wpa_supplicant *wpa_s,
>> > >
>> > >  #ifdef CONFIG_OWE
>> > >                 if ((wpa_s->current_ssid->key_mgmt & WPA_KEY_MGMT_OWE) &&
>> > > -                   wpa_s->current_bss &&
>> > > -                   (wpa_s->current_bss->flags & WPA_BSS_OWE_TRANSITION) &&
>> > > -                   drv_ssid_len == wpa_s->current_bss->ssid_len &&
>> > > -                   os_memcmp(drv_ssid, wpa_s->current_bss->ssid,
>> > > -                             drv_ssid_len) == 0)
>> > > -                       return 0; /* current profile still in use */
>> > > +                   wpa_s->current_bss) {
>> > > +                       /* Handle the case where the selected BSS uses OWE
>> > > +                        * transition mode */
>> > > +                       if ((wpa_s->current_bss->flags &
>> > > +                            WPA_BSS_OWE_TRANSITION) &&
>> > > +                           drv_ssid_len == wpa_s->current_bss->ssid_len &&
>> > > +                           os_memcmp(drv_ssid, wpa_s->current_bss->ssid,
>> > > +                                     drv_ssid_len) == 0)
>> > > +                               return 0; /* current profile still in use */
>> > > +
>> > > +                       /* Handle the case where the selected BSS uses OWE-only
>> > > +                        * mode and the previous one uses OWE transition mode.
>> > > +                        * The connected SSID would be that of the OWE network
>> > > +                        * where as current_ssid stores the open network SSID.
>> > > +                        */
>> > > +                       if (prev_bss &&
>> > > +                           (prev_bss->flags & WPA_BSS_OWE_TRANSITION) &&
>> > > +                           (prev_bss->ssid_len ==
>> > > +                            wpa_s->current_bss->ssid_len) &&
>> > > +                           os_memcmp(prev_bss->ssid, wpa_s->current_bss->ssid,
>> > > +                                     wpa_s->current_bss->ssid_len) == 0)
>> > > +                               return 0; /* current profile still in use */
>> > > +               }
>> > >  #endif /* CONFIG_OWE */
>> > >
>> > >                 wpa_msg(wpa_s, MSG_DEBUG,
>> > > --
>> > > 2.43.0
>> > >
>> > > --
>> > > Jouni Malinen                                            PGP id EFC895FA

-- 
This electronic communication and the information and any files transmitted 
with it, or attached to it, are confidential and are intended solely for 
the use of the individual or entity to whom it is addressed and may contain 
information that is confidential, legally privileged, protected by privacy 
laws, or otherwise restricted from disclosure to anyone else. If you are 
not the intended recipient or the person responsible for delivering the 
e-mail to the intended recipient, you are hereby notified that any use, 
copying, distributing, dissemination, forwarding, printing, or copying of 
this e-mail is strictly prohibited. If you received this e-mail in error, 
please return the e-mail to the sender, delete it from your computer, and 
destroy any printed copy of it.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 4193 bytes
Desc: S/MIME Cryptographic Signature
URL: <http://lists.infradead.org/pipermail/hostap/attachments/20250626/cd871734/attachment.p7s>

From benjamin at sipsolutions.net  Thu Jun 26 01:51:24 2025
From: benjamin at sipsolutions.net (Benjamin Berg)
Date: Thu, 26 Jun 2025 10:51:24 +0200
Subject: [PATCH] tests: disable IPv6 in protected keep-alive test
Message-ID: <20250626085124.1706887-1-benjamin@sipsolutions.net>

From: Benjamin Berg <benjamin.berg at intel.com>

When the new protected_keep_alive test was added, the @disable_ipv6
decorator was not moved. Disable IPv6 for both keep alive tests by
moving it to the run_ function.

Also remove the @remote_compatible decorator as it does not work with
IPv6 disablement. It was not removed in the patch to disable IPv6 in the
original test.

Fixes: fdf23679f823 ("tests: More coverage for WNM BSS max idle period management")
Fixes: 1bd5e35b8733 ("tests: Disable IPv6 in WNM keep-alive test")
Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 tests/hwsim/test_wnm.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/tests/hwsim/test_wnm.py b/tests/hwsim/test_wnm.py
index cfe813e40b..9d8a91382b 100644
--- a/tests/hwsim/test_wnm.py
+++ b/tests/hwsim/test_wnm.py
@@ -786,8 +786,6 @@ def test_wnm_bss_tm_req(dev, apdev):
     resp = rx_bss_tm_resp(hapd, expect_dialog=8, expect_status=7)
     dev[0].dump_monitor()
 
- at remote_compatible
- at disable_ipv6
 def test_wnm_bss_keep_alive(dev, apdev):
     """WNM keep-alive"""
     run_wnm_bss_keep_alive(dev, apdev, False)
@@ -796,6 +794,7 @@ def test_wnm_bss_protected_keep_alive(dev, apdev):
     """WNM protected keep-alive"""
     run_wnm_bss_keep_alive(dev, apdev, True)
 
+ at disable_ipv6
 def run_wnm_bss_keep_alive(dev, apdev, protected):
     hapd = start_wnm_ap(apdev[0], bss_transition=False, ap_max_inactivity=1,
                         bss_max_idle=2 if protected else 1, rsn=True)
-- 
2.50.0



From bgalvani at redhat.com  Thu Jun 26 01:57:51 2025
From: bgalvani at redhat.com (Beniamino Galvani)
Date: Thu, 26 Jun 2025 10:57:51 +0200
Subject: [PATCH 2/2] dbus: emit the PskMismatch signal also for SAE
In-Reply-To: <20250626085751.58881-1-bgalvani@redhat.com>
References: <20250626085751.58881-1-bgalvani@redhat.com>
Message-ID: <20250626085751.58881-2-bgalvani@redhat.com>

Commit a678a510fb20 ("dbus: Add D-Bus signal for PSK mismatch
heuristics") introduced the "PskMismatch" signal. D-Bus clients can
use this signal to detect a wrong WPA-PSK password and prompt the user
for a new one.

This commit adds the same functionality to SAE authentication,
emitting the signal when a failure occurs that is likely caused by a
wrong password.

Signed-off-by: Beniamino Galvani <bgalvani at redhat.com>
---
 wpa_supplicant/sme.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index 47c965bfd..b7c916d08 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -2056,6 +2056,9 @@ void sme_event_auth(struct wpa_supplicant *wpa_s, union wpa_event_data *data)
 				   data->auth.ies_len, 0, data->auth.peer,
 				   &ie_offset);
 		if (res < 0) {
+			if (res == -2)
+				wpas_notify_psk_mismatch(wpa_s);
+
 			wpas_connection_failed(wpa_s, wpa_s->pending_bssid,
 					       NULL);
 			wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
-- 
2.49.0



From bgalvani at redhat.com  Thu Jun 26 01:57:50 2025
From: bgalvani at redhat.com (Beniamino Galvani)
Date: Thu, 26 Jun 2025 10:57:50 +0200
Subject: [PATCH 1/2] wpa_supplicant: improve PSK mismatch detection
Message-ID: <20250626085751.58881-1-bgalvani@redhat.com>

As the comment in wpa_supplicant_event_disassoc_finish() says,
mac80211-based drivers use the WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY
reason code in locally generated disconnection events for cases where
the AP does not reply anymore. The code does not indicate that the PSK
is wrong.

Signed-off-by: Beniamino Galvani <bgalvani at redhat.com>
---
 wpa_supplicant/events.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index d388f31ff..1c1894f87 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -4681,6 +4681,8 @@ static int could_be_psk_mismatch(struct wpa_supplicant *wpa_s, u16 reason_code,
 	if (locally_generated) {
 		if (reason_code == WLAN_REASON_IE_IN_4WAY_DIFFERS)
 			return 0;
+		if (reason_code == WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY)
+			return 0;
 	}
 
 	return 1;
-- 
2.49.0



From gubertoli at gmail.com  Thu Jun 26 13:34:03 2025
From: gubertoli at gmail.com (Gustavo Bertoli)
Date: Thu, 26 Jun 2025 22:34:03 +0200
Subject: [PATCH v2] hostapd_cli: Add newline to command output
Message-ID: <20250626203403.236500-1-gubertoli@gmail.com>

Now follows wpa_cli logic for printing command output.

Signed-off-by: Gustavo Bertoli <gubertoli at gmail.com>
---
 hostapd/hostapd_cli.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c
index 57702d93b..395b8996f 100644
--- a/hostapd/hostapd_cli.c
+++ b/hostapd/hostapd_cli.c
@@ -221,6 +221,8 @@ static int _wpa_ctrl_command(struct wpa_ctrl *ctrl, const char *cmd, int print)
 	if (print) {
 		buf[len] = '\0';
 		printf("%s", buf);
+		if (interactive && len > 0 && buf[len - 1] != '\n')
+			printf("\n");
 	}
 	return 0;
 }
-- 
2.34.1



From nefi.guclu at airties.com  Fri Jun 27 00:27:21 2025
From: nefi.guclu at airties.com (Nefi Guclu)
Date: Fri, 27 Jun 2025 07:27:21 +0000
Subject: PATCH 1/1:  AP: Add status/reason codes to PSK mismatch logs
Message-ID: <AS8PR10MB8018A99B5FD66FBA1596C45F8645A@AS8PR10MB8018.EURPRD10.PROD.OUTLOOK.COM>

>From a9dc464c5988ee89a348d67e890ac4556c0759c7 Mon Sep 17 00:00:00 2001
From: Nefi Guclu <nefi.guclu at airties.com>
Date: Fri, 27 Jun 2025 10:14:13 +0300
Subject: [PATCH] AP: Add status/reason codes to PSK mismatch logs

Extend PSK mismatch logging to include status codes for SAE failures
and reason codes for WPA-PSK failures.

Changes:
- SAE authentication failure paths now log the status code.
- WPA-PSK 4-way handshake failures now
  trigger a log with the corresponding disconnect reason code.
- Disconnect reason codes adjusted to better reflect failure causes.

This improves the ability to distinguish between different types of
authentication failures in logs.

Signed-off-by: Nefi Guclu <nefi.guclu at airties.com>
---
 src/ap/ieee802_11.c    |  4 ++++
 src/ap/wpa_auth.c      | 13 ++++++++-----
 src/ap/wpa_auth.h      |  2 +-
 src/ap/wpa_auth_glue.c |  6 +++---
 4 files changed, 16 insertions(+), 9 deletions(-)

diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 866a1c1c8..cdf9c42ba 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -1767,9 +1767,13 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
                                                sta->sae->tmp->
                                                        try_other_password = 1;
                                        resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+                                       wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_POSSIBLE_PSK_MISMATCH MACSTR " status=%d",
+                                              MAC2STR(sta->addr), resp);
                                        goto reply;
                                }
                                resp = WLAN_STATUS_CHALLENGE_FAIL;
+                               wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_POSSIBLE_PSK_MISMATCH MACSTR " status=%d",
+                                   MAC2STR(sta->addr), resp);
                                goto reply;
                        }
                        sae_password_track_success(hapd, sta);
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 333467ce8..0f30b97c2 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -237,10 +237,10 @@ static inline int wpa_auth_mic_failure_report(


 static inline void wpa_auth_psk_failure_report(
-       struct wpa_authenticator *wpa_auth, const u8 *addr)
+       struct wpa_authenticator *wpa_auth, const u8 *addr, u16 disconnect_reason)
 {
        if (wpa_auth->cb->psk_failure_report)
-               wpa_auth->cb->psk_failure_report(wpa_auth->cb_ctx, addr);
+               wpa_auth->cb->psk_failure_report(wpa_auth->cb_ctx, addr, disconnect_reason);
 }


@@ -3867,7 +3867,8 @@ SM_STATE(WPA_PTK, PTKCALCNEGOTIATING)
                                LOGGER_DEBUG,
                                "invalid MIC in msg 2/4 of 4-Way Handshake");
                if (psk_found)
-                       wpa_auth_psk_failure_report(sm->wpa_auth, sm->addr);
+                       sm->disconnect_reason = WLAN_REASON_MICHAEL_MIC_FAILURE;
+                       wpa_auth_psk_failure_report(sm->wpa_auth, sm->addr, sm->disconnect_reason);
                goto out;
        }

@@ -5360,8 +5361,10 @@ SM_STEP(WPA_PTK)
                                         LOGGER_DEBUG,
                                         "PTKSTART: Retry limit %u reached",
                                         conf->wpa_pairwise_update_count);
-                       sm->disconnect_reason =
-                               WLAN_REASON_4WAY_HANDSHAKE_TIMEOUT;
+                       if (sm->disconnect_reason != WLAN_REASON_MICHAEL_MIC_FAILURE) {
+                               sm->disconnect_reason = WLAN_REASON_4WAY_HANDSHAKE_TIMEOUT;
+                               wpa_auth_psk_failure_report(sm->wpa_auth, sm->addr, sm->disconnect_reason);
+                       }
                        SM_ENTER(WPA_PTK, DISCONNECT);
                } else if (sm->TimeoutEvt)
                        SM_ENTER(WPA_PTK, PTKSTART);
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 176ed2c44..02a246e18 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -362,7 +362,7 @@ struct wpa_auth_callbacks {
                       const char *txt);
        void (*disconnect)(void *ctx, const u8 *addr, u16 reason);
        int (*mic_failure_report)(void *ctx, const u8 *addr);
-       void (*psk_failure_report)(void *ctx, const u8 *addr);
+       void (*psk_failure_report)(void *ctx, const u8 *addr, u16 disconnect_reason);
        void (*set_eapol)(void *ctx, const u8 *addr, wpa_eapol_variable var,
                          int value);
        int (*get_eapol)(void *ctx, const u8 *addr, wpa_eapol_variable var);
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index 519948a51..f8e7e78e9 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -315,11 +315,11 @@ static int hostapd_wpa_auth_mic_failure_report(void *ctx, const u8 *addr)
 }


-static void hostapd_wpa_auth_psk_failure_report(void *ctx, const u8 *addr)
+static void hostapd_wpa_auth_psk_failure_report(void *ctx, const u8 *addr, u16 disconnect_reason)
 {
        struct hostapd_data *hapd = ctx;
-       wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_POSSIBLE_PSK_MISMATCH MACSTR,
-               MAC2STR(addr));
+       wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_POSSIBLE_PSK_MISMATCH MACSTR " reason=%d",
+               MAC2STR(addr), disconnect_reason);
 }


--
2.25.1
Information in this email including any attachments may be privileged, confidential and is intended exclusively for the addressee. The views expressed may not be official policy, but the personal views of the originator. If you have received it in error, please notify the sender by return e-mail and delete it from your system. You should not reproduce, distribute, store, retransmit, use or disclose its contents to anyone.


From greearb at candelatech.com  Sat Jun 28 08:41:39 2025
From: greearb at candelatech.com (Ben Greear)
Date: Sat, 28 Jun 2025 08:41:39 -0700
Subject: Example MLD AP config for hostapd?
Message-ID: <a1a9b6b2-d896-4141-97ef-fd846836e269@candelatech.com>

Hello,

I'm trying to set up hostapd to run a 3-link MLD AP.  I found some config
files from an openwrt AP to help get me started, but they do not appear to be directly given to
hostapd on the command line, so I'm not sure they are really what I need.

Does anyone have an example config file and/or set of config files with
hostapd command to use them they could share?

Thanks,
Ben

-- 
Ben Greear <greearb at candelatech.com>
Candela Technologies Inc  http://www.candelatech.com


