[PATCH] P2P: Cap client bandwidth to GO BSS operating bandwidth at association

Junyu Lai junyulai at google.com
Thu Sep 3 23:33:00 PDT 2026


When a P2P client associates with the GO, the GO's wpa_supplicant
historically parsed the client's VHT/HE capabilities to determine the
link bandwidth, but failed to cap it by the actual operating bandwidth
of the GO BSS. This caused wpa_supplicant to report the client's maximum
supported bandwidth even if the GO was operating at a lower bandwidth
(e.g., 20 MHz).

Fix this by:
1. Adding chan_width_to_mhz() in src/common/defs.h to convert enum
   chan_width to bandwidth in MHz for proper magnitude comparisons,
   since enum chan_width is not numerically ordered (60 GHz EDMG values
   were placed before 320 MHz).
2. Adding get_oper_chan_width callback to struct p2p_group_config and
   implementing wpas_p2p_get_oper_chan_width() to query the GO BSS
   operating channel width, taking into account HT/VHT/HE/EHT channel
   widths as well as legacy non-HT mode (CHAN_WIDTH_20_NOHT).
3. Capping the client's bandwidth in p2p_group_notif_assoc() against the
   GO BSS operating bandwidth using chan_width_to_mhz().

Signed-off-by: Junyu Lai <junyulai at google.com>
---
 src/common/defs.h               | 39 ++++++++++++++++++++
 src/p2p/p2p.h                   |  7 ++++
 src/p2p/p2p_group.c             | 20 ++++++++++-
 wpa_supplicant/p2p_supplicant.c | 64 +++++++++++++++++++++++++++++++++
 4 files changed, 129 insertions(+), 1 deletion(-)

diff --git a/src/common/defs.h b/src/common/defs.h
index f28106cc..e98aca0f 100644
--- a/src/common/defs.h
+++ b/src/common/defs.h
@@ -496,6 +496,45 @@ enum chan_width {
 	CHAN_WIDTH_UNKNOWN
 };

+/**
+ * chan_width_to_mhz - Convert enum chan_width to bandwidth in MHz
+ * @width: Channel width enum value
+ * Returns: Bandwidth in MHz, or 0 for unknown/non-comparable widths
+ *
+ * Note: enum chan_width is NOT numerically ordered by bandwidth (60 GHz EDMG
+ * values 2160/4320/6480/8640 MHz were inserted before 320 MHz), so direct
+ * integer comparison of enum values must not be used to compare bandwidths.
+ * Use this function whenever bandwidth magnitude comparisons are needed.
+ */
+static inline unsigned int chan_width_to_mhz(enum chan_width width)
+{
+	switch (width) {
+	case CHAN_WIDTH_20_NOHT:
+	case CHAN_WIDTH_20:
+		return 20;
+	case CHAN_WIDTH_40:
+		return 40;
+	case CHAN_WIDTH_80:
+	case CHAN_WIDTH_80P80:
+		return 80;
+	case CHAN_WIDTH_160:
+		return 160;
+	case CHAN_WIDTH_320:
+		return 320;
+	case CHAN_WIDTH_2160:
+		return 2160;
+	case CHAN_WIDTH_4320:
+		return 4320;
+	case CHAN_WIDTH_6480:
+		return 6480;
+	case CHAN_WIDTH_8640:
+		return 8640;
+	case CHAN_WIDTH_UNKNOWN:
+	default:
+		return 0;
+	}
+}
+
 /* VHT/EDMG/etc. channel widths
  * Note: The first four values are used in hostapd.conf and as such, must
  * maintain their defined values. Other values are used internally. */
diff --git a/src/p2p/p2p.h b/src/p2p/p2p.h
index 83476be0..b711b0a4 100644
--- a/src/p2p/p2p.h
+++ b/src/p2p/p2p.h
@@ -2113,6 +2113,13 @@ struct p2p_group_config {
 	 * @idle: Whether the group is idle (no associated stations)
 	 */
 	void (*idle_update)(void *ctx, int idle);
+
+	/**
+	 * get_oper_chan_width - Query GO BSS operating channel width
+	 * @ctx: Callback context from cb_ctx
+	 * Returns: Operating channel width of the BSS
+	 */
+	enum chan_width (*get_oper_chan_width)(void *ctx);
 };

 /**
diff --git a/src/p2p/p2p_group.c b/src/p2p/p2p_group.c
index 4ba9096a..b6b9def9 100644
--- a/src/p2p/p2p_group.c
+++ b/src/p2p/p2p_group.c
@@ -653,7 +653,7 @@ int p2p_group_notif_assoc(struct p2p_group *group,
struct sta_info *sta,
 	int ht = sta->ht_capabilities ? 1 : 0;
 	int vht = sta->vht_capabilities ? 1 : 0;
 	int he = sta->he_capab ? 1 : 0;
-	int bw = CHAN_WIDTH_20;
+	enum chan_width bw = CHAN_WIDTH_20;

 	if (ht && (sta->ht_capabilities->ht_capabilities_info &
HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET)) {
 		bw = CHAN_WIDTH_40;
@@ -670,6 +670,24 @@ int p2p_group_notif_assoc(struct p2p_group
*group, struct sta_info *sta,
 	}
 #endif

+	/*
+	 * Cap the client's bandwidth by the GO BSS operating bandwidth.
+	 * This prevents reporting client capability bandwidth when it exceeds
+	 * the actual channel width of the BSS.
+	 *
+	 * Use chan_width_to_mhz() for the comparison rather than comparing enum
+	 * values directly: enum chan_width is not numerically ordered by
+	 * bandwidth (60 GHz EDMG values 2160/4320/6480/8640 MHz were inserted
+	 * before CHAN_WIDTH_320 = 320 MHz), so a raw integer comparison would
+	 * produce incorrect results for those widths.
+	 */
+	if (group->cfg && group->cfg->get_oper_chan_width) {
+		enum chan_width max_bw =
+			group->cfg->get_oper_chan_width(group->cfg->cb_ctx);
+		if (chan_width_to_mhz(bw) > chan_width_to_mhz(max_bw))
+			bw = max_bw;
+	}
+
 	p2p_add_device(group->p2p, sta->addr, 0, NULL, 0, ie, len, 0);

 	m = os_zalloc(sizeof(*m));
diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c
index cb4f821b..6e761a3b 100644
--- a/wpa_supplicant/p2p_supplicant.c
+++ b/wpa_supplicant/p2p_supplicant.c
@@ -8636,6 +8636,69 @@ static void wpas_p2p_idle_update(void *ctx, int idle)
 }


+/*
+ * Query the GO BSS operating channel width.
+ * Maps hostapd's enum oper_chan_width to supplicant's enum chan_width.
+ *
+ * For CONF_OPER_CHWIDTH_USE_HT (802.11n HT mode), the actual channel width
+ * (HT40 vs HT20) is determined by secondary_channel != 0 rather than
+ * oper_chan_width itself.  When ieee80211n is not set at all (legacy non-HT
+ * BSS), CHAN_WIDTH_20_NOHT is returned to accurately reflect the absence of
+ * HT.  This distinction matters for the capping comparison in
+ * p2p_group_notif_assoc(): chan_width_to_mhz(CHAN_WIDTH_20_NOHT) == 20 MHz,
+ * so the cap still works correctly, and the returned value faithfully
+ * represents the BSS mode.
+ */
+static enum chan_width wpas_p2p_get_oper_chan_width(void *ctx)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+	enum oper_chan_width oper_width;
+
+	if (!wpa_s || !wpa_s->ap_iface || !wpa_s->ap_iface->bss ||
+	    !wpa_s->ap_iface->bss[0])
+		return CHAN_WIDTH_20;
+
+	oper_width = hostapd_get_oper_chan_width_of_bss(wpa_s->ap_iface->bss[0]);
+
+	switch (oper_width) {
+	case CONF_OPER_CHWIDTH_80MHZ:
+		return CHAN_WIDTH_80;
+	case CONF_OPER_CHWIDTH_80P80MHZ:
+		return CHAN_WIDTH_80P80;
+	case CONF_OPER_CHWIDTH_160MHZ:
+		return CHAN_WIDTH_160;
+	case CONF_OPER_CHWIDTH_320MHZ:
+		return CHAN_WIDTH_320;
+	case CONF_OPER_CHWIDTH_2160MHZ:
+		return CHAN_WIDTH_2160;
+	case CONF_OPER_CHWIDTH_4320MHZ:
+		return CHAN_WIDTH_4320;
+	case CONF_OPER_CHWIDTH_6480MHZ:
+		return CHAN_WIDTH_6480;
+	case CONF_OPER_CHWIDTH_8640MHZ:
+		return CHAN_WIDTH_8640;
+	case CONF_OPER_CHWIDTH_40MHZ_6GHZ:
+		return CHAN_WIDTH_40;
+	case CONF_OPER_CHWIDTH_USE_HT:
+	default:
+		/*
+		 * For 802.11n HT mode, channel width (HT40 vs HT20) is
+		 * determined by secondary_channel != 0 rather than
+		 * oper_chan_width.  When ieee80211n is not set (legacy non-HT
+		 * BSS), return CHAN_WIDTH_20_NOHT to accurately reflect the
+		 * absence of HT.
+		 */
+		if (wpa_s->ap_iface->conf &&
+		    wpa_s->ap_iface->conf->ieee80211n) {
+			if (wpa_s->ap_iface->conf->secondary_channel != 0)
+				return CHAN_WIDTH_40;
+			return CHAN_WIDTH_20;
+		}
+		return CHAN_WIDTH_20_NOHT;
+	}
+}
+
+
 struct p2p_group * wpas_p2p_group_init(struct wpa_supplicant *wpa_s,
 				       struct wpa_ssid *ssid)
 {
@@ -8666,6 +8729,7 @@ struct p2p_group * wpas_p2p_group_init(struct
wpa_supplicant *wpa_s,
 	cfg->cb_ctx = wpa_s;
 	cfg->ie_update = wpas_p2p_ie_update;
 	cfg->idle_update = wpas_p2p_idle_update;
+	cfg->get_oper_chan_width = wpas_p2p_get_oper_chan_width;
 	cfg->ip_addr_alloc = WPA_GET_BE32(wpa_s->p2pdev->conf->ip_addr_start)
 		!= 0;
 	cfg->p2p2 = wpa_s->p2p2;
-- 
2.55.0.979.g7e5102b832-goog



More information about the Hostap mailing list