From: Jouni Malinen Date: Tue, 5 Nov 2024 16:45:10 +0000 (+0200) Subject: Wi-Fi Generational Capabilities Indication transmission on STA X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8f5ae880157253e709643f6070de0736d6bfabba;p=thirdparty%2Fhostap.git Wi-Fi Generational Capabilities Indication transmission on STA Add support to send generational capabilities indication to the associated AP. This includes generation of the Generational Capabilities Indication attribute and sending it in either the (Re)Association Request frame or the W-Fi Capabilities frame. By default, this functionality is disabled. It can be enabled by setting the global wpa_supplicant configuration parameter wfa_gen_capa to either 1 (protected) or 2 (unprotected) and setting the supported (and optionally also certified) generational capabilities in wfa_gen_capa_supp (and wfa_gen_capa_cert). Signed-off-by: Jouni Malinen --- diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 3d1a87a40..675559d49 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -3105,6 +3105,8 @@ void wpa_config_free(struct wpa_config *config) os_free(config->dpp_extra_conf_req_name); os_free(config->dpp_extra_conf_req_value); wpabuf_free(config->dik); + wpabuf_free(config->wfa_gen_capa_supp); + wpabuf_free(config->wfa_gen_capa_cert); os_free(config); } @@ -5614,6 +5616,9 @@ static const struct global_parse_data global_fields[] = { { FUNC(mld_connect_bssid_pref), 0 }, #endif /* CONFIG_TESTING_OPTIONS */ { INT_RANGE(ft_prepend_pmkid, 0, 1), CFG_CHANGED_FT_PREPEND_PMKID }, + { INT_RANGE(wfa_gen_capa, 0, 2), 0}, + { BIN(wfa_gen_capa_supp), 0 }, + { BIN(wfa_gen_capa_cert), 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 ab671db32..8b76ff720 100644 --- a/wpa_supplicant/config.h +++ b/wpa_supplicant/config.h @@ -1884,6 +1884,41 @@ struct wpa_config { * This is for setting the bit 77 of the Extended Capabilities element. */ bool twt_requester; + + /** + * wfa_gen_capa: Whether to indicate Wi-Fi generational capability to + * the AP + * + * 0 = do not indicate (default) + * 1 = indicate in protected Action frame + * 2 = indicate in unprotected (Re)Association Request frame + */ + enum { + WFA_GEN_CAPA_DISABLED = 0, + WFA_GEN_CAPA_PROTECTED = 1, + WFA_GEN_CAPA_UNPROTECTED = 2, + } wfa_gen_capa; + + /** + * wfa_gen_capa_supp: Supported Generations (hexdump of a bit field) + * + * A bit field of supported Wi-Fi generations. This is encoded as an + * little endian octt string. + * bit 0: Wi-Fi 4 + * bit 1: Wi-Fi 5 + * bit 2: Wi-Fi 6 + * bit 3: Wi-Fi 7 + */ + struct wpabuf *wfa_gen_capa_supp; + + /** + * wfa_gen_capa_cert: Certified Generations (hexdump of a bit field) + * + * This has the same format as wfa_gen_capa_supp. This is an optional + * field, but if included, shall have the same length as + * wfa_gen_capa_supp. + */ + struct wpabuf *wfa_gen_capa_cert; }; diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index f260669d3..b250eabb7 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -1741,6 +1741,10 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config) fprintf(f, "dik_cipher=%d\n", config->dik_cipher); write_global_bin(f, "dik", config->dik); } + if (config->wfa_gen_capa) + 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); } static void wpa_config_write_identity(FILE *f, struct wpa_dev_ik *dev_ik) diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index b7faf53e3..eb7516e36 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -2475,6 +2475,12 @@ pfs_fail: mscs_fail: #endif /* CONFIG_NO_ROBUST_AV */ + wpa_s->sme.assoc_req_ie_len = + wpas_populate_wfa_capa(wpa_s, wpa_s->current_bss, + wpa_s->sme.assoc_req_ie, + wpa_s->sme.assoc_req_ie_len, + sizeof(wpa_s->sme.assoc_req_ie)); + if (ssid && ssid->multi_ap_backhaul_sta) { size_t multi_ap_ie_len; struct multi_ap_params multi_ap = { 0 }; diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 55feaedb2..c8ab4485e 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -503,6 +503,87 @@ void wpas_flush_fils_hlp_req(struct wpa_supplicant *wpa_s) } +static struct wpabuf * wpas_wfa_gen_capab_attr(struct wpa_supplicant *wpa_s) +{ + struct wpabuf *attr; + size_t gen_len; + bool add_cert; + + if (wpa_s->conf->wfa_gen_capa == WFA_GEN_CAPA_DISABLED || + !wpa_s->conf->wfa_gen_capa_supp || + wpabuf_len(wpa_s->conf->wfa_gen_capa_supp) == 0) + return NULL; + + add_cert = wpa_s->conf->wfa_gen_capa_cert && + wpabuf_len(wpa_s->conf->wfa_gen_capa_cert) == + wpabuf_len(wpa_s->conf->wfa_gen_capa_supp); + + gen_len = 1 + wpabuf_len(wpa_s->conf->wfa_gen_capa_supp); + if (add_cert) { + gen_len++; + gen_len += wpabuf_len(wpa_s->conf->wfa_gen_capa_cert); + } + + attr = wpabuf_alloc(2 + gen_len); + if (!attr) + return NULL; + + wpabuf_put_u8(attr, WFA_CAPA_ATTR_GENERATIONAL_CAPAB); + wpabuf_put_u8(attr, gen_len); + wpabuf_put_u8(attr, wpabuf_len(wpa_s->conf->wfa_gen_capa_supp)); + wpabuf_put_buf(attr, wpa_s->conf->wfa_gen_capa_supp); + if (add_cert) { + wpabuf_put_u8(attr, + wpabuf_len(wpa_s->conf->wfa_gen_capa_cert)); + wpabuf_put_buf(attr, wpa_s->conf->wfa_gen_capa_cert); + } + + return attr; +} + + + +static void wpas_wfa_capab_tx(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + struct wpabuf *attr, *buf; + size_t buf_len; + + if (wpa_s->conf->wfa_gen_capa != WFA_GEN_CAPA_PROTECTED || + !wpa_s->conf->wfa_gen_capa_supp || + wpabuf_len(wpa_s->conf->wfa_gen_capa_supp) == 0 || + wpa_s->wpa_state != WPA_COMPLETED || + !pmf_in_use(wpa_s, wpa_s->bssid)) + return; + + attr = wpas_wfa_gen_capab_attr(wpa_s); + if (!attr) + return; + + buf_len = 1 + 3 + 1 + 1 + wpabuf_len(attr); + buf = wpabuf_alloc(buf_len); + if (!buf) { + wpabuf_free(attr); + return; + } + + wpabuf_put_u8(buf, WLAN_ACTION_VENDOR_SPECIFIC_PROTECTED); + wpabuf_put_be32(buf, WFA_CAPAB_VENDOR_TYPE); + wpabuf_put_u8(buf, 0); /* Capabilities Length */ + wpabuf_put_buf(buf, attr); + wpabuf_free(attr); + + wpa_printf(MSG_DEBUG, "WFA: Send WFA Capabilities frame"); + if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, + wpa_s->own_addr, wpa_s->bssid, + wpabuf_head(buf), wpabuf_len(buf), 0) < 0) + wpa_printf(MSG_DEBUG, + "WFA: Failed to send WFA Capabilities frame"); + + wpabuf_free(buf); +} + + void wpas_clear_disabled_interface(void *eloop_ctx, void *timeout_ctx) { struct wpa_supplicant *wpa_s = eloop_ctx; @@ -616,6 +697,7 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s) eloop_cancel_timeout(wpas_network_reenabled, wpa_s, NULL); eloop_cancel_timeout(wpas_clear_disabled_interface, wpa_s, NULL); eloop_cancel_timeout(wpas_verify_ssid_beacon, wpa_s, NULL); + eloop_cancel_timeout(wpas_wfa_capab_tx, wpa_s, NULL); wpas_wps_deinit(wpa_s); @@ -1141,6 +1223,14 @@ void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s, if (ssid && (ssid->key_mgmt & WPA_KEY_MGMT_OWE)) wpas_update_owe_connect_params(wpa_s); #endif /* CONFIG_OWE */ + if (wpa_s->conf->wfa_gen_capa == WFA_GEN_CAPA_PROTECTED && + wpa_s->conf->wfa_gen_capa_supp && + wpabuf_len(wpa_s->conf->wfa_gen_capa_supp) > 0 && + pmf_in_use(wpa_s, wpa_s->bssid)) { + eloop_cancel_timeout(wpas_wfa_capab_tx, wpa_s, NULL); + eloop_register_timeout(0, 100000, wpas_wfa_capab_tx, + wpa_s, NULL); + } } else if (state == WPA_DISCONNECTED || state == WPA_ASSOCIATING || state == WPA_ASSOCIATED) { wpa_s->new_connection = 1; @@ -3457,13 +3547,12 @@ bool wpa_is_non_eht_scs_traffic_desc_supported(struct wpa_bss *bss) } -static int wpas_populate_wfa_capa(struct wpa_supplicant *wpa_s, - struct wpa_bss *bss, - u8 *wpa_ie, size_t wpa_ie_len, - size_t max_wpa_ie_len) +int wpas_populate_wfa_capa(struct wpa_supplicant *wpa_s, struct wpa_bss *bss, + u8 *wpa_ie, size_t wpa_ie_len, size_t max_wpa_ie_len) { - struct wpabuf *wfa_ie = NULL; + struct wpabuf *wfa_ie = NULL, *attr = NULL; u8 wfa_capa[1]; + u8 capab_len = 0; size_t wfa_ie_len, buf_len; os_memset(wfa_capa, 0, sizeof(wfa_capa)); @@ -3475,7 +3564,13 @@ static int wpas_populate_wfa_capa(struct wpa_supplicant *wpa_s, if (wpa_is_non_eht_scs_traffic_desc_supported(bss)) wfa_capa[0] |= WFA_CAPA_QM_NON_EHT_SCS_TRAFFIC_DESC; - if (!wfa_capa[0]) + if (wfa_capa[0]) + capab_len = 1; + + if (wpa_s->conf->wfa_gen_capa == WFA_GEN_CAPA_UNPROTECTED) + attr = wpas_wfa_gen_capab_attr(wpa_s); + + if (capab_len == 0 && !attr) return wpa_ie_len; /* Wi-Fi Alliance element */ @@ -3484,17 +3579,23 @@ static int wpas_populate_wfa_capa(struct wpa_supplicant *wpa_s, 3 + /* OUI */ 1 + /* OUI Type */ 1 + /* Capabilities Length */ - sizeof(wfa_capa); /* Capabilities */ + capab_len + /* Capabilities */ + (attr ? wpabuf_len(attr) : 0) /* Attributes */; wfa_ie = wpabuf_alloc(buf_len); - if (!wfa_ie) + if (!wfa_ie) { + wpabuf_free(attr); return wpa_ie_len; + } wpabuf_put_u8(wfa_ie, WLAN_EID_VENDOR_SPECIFIC); wpabuf_put_u8(wfa_ie, buf_len - 2); wpabuf_put_be24(wfa_ie, OUI_WFA); wpabuf_put_u8(wfa_ie, WFA_CAPA_OUI_TYPE); - wpabuf_put_u8(wfa_ie, sizeof(wfa_capa)); - wpabuf_put_data(wfa_ie, wfa_capa, sizeof(wfa_capa)); + wpabuf_put_u8(wfa_ie, capab_len); + wpabuf_put_data(wfa_ie, wfa_capa, capab_len); + if (attr) + wpabuf_put_buf(wfa_ie, attr); + wpabuf_free(attr); wfa_ie_len = wpabuf_len(wfa_ie); if (wpa_ie_len + wfa_ie_len <= max_wpa_ie_len) { diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index a976a5b6b..210bd1a7f 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -657,6 +657,28 @@ fast_reauth=1 # 1 = enabled if supported by the driver #twt_requester=0 +# Wi-Fi Alliance generational capabilities indication +# +# wfa_gen_capa: Whether to indicate Wi-Fi generational capability to the AP +# 0 = do not indicate (default) +# 1 = indicate in protected Action frame +# 2 = indicate in unprotected (Re)Association Request frame +#wfa_gen_capa=0 +# +# wfa_gen_capa_supp: Supported Generations (hexdump of a bit field) +# A bit field of supported Wi-Fi generations. This is encoded as an little +# endian octt string. +# bit 0: Wi-Fi 4 +# bit 1: Wi-Fi 5 +# bit 2: Wi-Fi 6 +# bit 3: Wi-Fi 7 +#wfa_gen_capa_supp=07 +# +# wfa_gen_capa_cert: Certified Generations (hexdump of a bit field) +# This has the same format as wfa_gen_capa_supp. This is an optional field, but +# if included, shall have the same length as wfa_gen_capa_supp. +#wfa_gen_capa_cert=07 + # credential block # # Each credential used for automatic network selection is configured as a set diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index f497644b7..2549c8b5c 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -1752,6 +1752,9 @@ void wpas_request_connection(struct wpa_supplicant *wpa_s); void wpas_request_disconnection(struct wpa_supplicant *wpa_s); int wpas_build_ext_capab(struct wpa_supplicant *wpa_s, u8 *buf, size_t buflen, struct wpa_bss *bss); +int wpas_populate_wfa_capa(struct wpa_supplicant *wpa_s, struct wpa_bss *bss, + u8 *wpa_ie, size_t wpa_ie_len, + size_t max_wpa_ie_len); int wpas_update_random_addr(struct wpa_supplicant *wpa_s, enum wpas_mac_addr_style style, struct wpa_ssid *ssid);