]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
Wi-Fi Generational Capabilities Indication transmission on STA
authorJouni Malinen <quic_jouni@quicinc.com>
Tue, 5 Nov 2024 16:45:10 +0000 (18:45 +0200)
committerJouni Malinen <j@w1.fi>
Tue, 5 Nov 2024 17:04:19 +0000 (19:04 +0200)
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 <quic_jouni@quicinc.com>
wpa_supplicant/config.c
wpa_supplicant/config.h
wpa_supplicant/config_file.c
wpa_supplicant/sme.c
wpa_supplicant/wpa_supplicant.c
wpa_supplicant/wpa_supplicant.conf
wpa_supplicant/wpa_supplicant_i.h

index 3d1a87a403b157261ad8f3b4618c4bb64dc474a3..675559d493c0b7506943d91d8375908c3dc47a7d 100644 (file)
@@ -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. */
index ab671db3260b028084bcd95b38684c0892988a96..8b76ff7204ab6ca404ae5356d5e55976049a45c7 100644 (file)
@@ -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;
 };
 
 
index f260669d3f5504bb8c48daaa2c50bfbdca376f0d..b250eabb7556aaeec4bd723fc2df8f2211276554 100644 (file)
@@ -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)
index b7faf53e39187200ec8fdcefc5de2e1effbfe63a..eb7516e36ddd824c53f0b51b6650f7296dce59e9 100644 (file)
@@ -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 };
index 55feaedb2db7a9cfe384ab694986b92b0e6f277b..c8ab4485e0f4ec4dc3337144f1645725b7b37b6f 100644 (file)
@@ -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) {
index a976a5b6b426d58fa7c6e333a854a1e8a7af2bcc..210bd1a7f0f545f3df3a052d625031836312356f 100644 (file)
@@ -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
index f497644b715d8abb09843455a474ea49507a0d3a..2549c8b5cd22a02d4a0be3082dd233925ada3ac6 100644 (file)
@@ -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);