[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