From frankth3tank79 at protonmail.com Sun Nov 1 22:36:35 2026 From: frankth3tank79 at protonmail.com (frankth3tank79) Date: Mon, 02 Nov 2026 06:36:35 +0000 Subject: Upload ~15 Mb/s to Technicolor XB7 (CGM4331COM) unless PMF is disabled on the client (iwlwifi AX210 and rtw89 RTL8922AE) Message-ID: <3GdfZzytiGgjtiV7JOT7DqKDOB7dAjgMEx9515JX73L9c7oqqSQ3Ol7qThrLYXiSjl9IuraXd8mRVU9rxZRwfez9iKY63dFqaLyssw02Kkk=@protonmail.com> Hi, I found this bug while setting up my laptop. Would it be possible to take a look at it? Upload (STA -> AP) throughput is about 15 Mb/s when this laptop associates with a Comcast/Technicolor XB7 gateway, model CGM4331COM (Broadcom radio, ISP-managed). Setting PMF to "disable" in NetworkManager on the client raises it to about 90-100 Mb/s. The same laptop gets about 700 Mb/s upload to a different AP. Other (non-Linux) clients on the XB7 are not affected. Hardware / software Laptop: HP OmniBook 5 16-cd0xxx, BIOS F.07 Card 1: Intel AX210, iwlmvm, fw 89.b20dcd1f.0 (ty-a0-gf-a0-89.ucode) Card 2: Realtek RTL8922AE (rtw89), the laptop's original card: same symptom Kernels: 7.2.11-200.fc44 (Fedora 44), 7.3.0-9-generic (Ubuntu 26.04 live) wpa_supplicant 2.11, NetworkManager 1.56.1, linux-firmware 20261019 Link 5 GHz ch 157, 80 MHz, HE 2x2, signal about -60 dBm tx bitrate HE-MCS 5-7 NSS 2 (576-720 Mbit/s), so the PHY rate is fine Results (iperf3 -P4, STA sending, TCP, to a host behind the AP) PMF default (optional), run 1 15.8 Mbit/s PMF disabled, run 1 101 Mbit/s PMF default, run 2 14.0 Mbit/s PMF disabled, run 2 87.1 Mbit/s Different AP (phone hotspot, HE80 2x2, PMF default) 706-747 Mbit/s With PMF at default, the upload looks like this: - only ~130 frames/s are handed to the radio (station dump tx packets), with ~35% tx retries and 0 tx failed - ping to the AP rises from 6 ms idle to 85 ms avg / 331 ms max under load - UDP upload tops out at ~2,300 frames/s, one datagram per frame - forcing VHT (no HE) gives the same result, so TWT and UL OFDMA are not involved Download from the AP is also limited (~40-50 Mbit/s). Relevant AP IEs (from iw scan dump) RSN capabilities: 0x000c (MFPC=0, MFPR=0: the AP does not advertise PMF) HT capabilities: Max AMSDU 3839, RX AMPDU 65535, spacing 4 usec VHT operation: basic MCS set 0x0000 (demands MCS 0-7 on all 8 streams) HE operation: basic HE-MCS NSS set 0xfffc, BSS color disabled WMM BE: CW 15-1023, AIFSN 3, TXOP 0 Kernel 6.19.10 logged "required MCSes not supported, disabling HT" with this AP and fell back to 54 Mbit/s legacy. 7.2/7.3 associate with HE. So the client advertises MFPC (PMF optional), the AP doesn't, PMF should not be negotiated, yet upload is ~6x slower than with PMF disabled. My guess is that the AP mishandles a STA whose RSNE has MFPC set, maybe by treating some frames (e.g. action frames for BA setup) as protected. A capture can be provided if useful. Is this a known XB7 interop issue? Would a quirk make sense, e.g. not advertising MFPC when the AP's RSNE has MFPC=0? Thank you, Frank From devnull+christophergriffin05.gmail.com at kernel.org Sun Nov 1 10:59:28 2026 From: devnull+christophergriffin05.gmail.com at kernel.org (Christopher Griffin via B4 Relay) Date: Sun, 01 Nov 2026 12:59:28 -0600 Subject: [PATCH] FT: Answer a repeated FT Authentication Request with the same response Message-ID: <20261101-b95-ft-repeated-auth-v1-1-27e1a0d4e36d@gmail.com> From: Christopher Griffin A STA that gets no FT Authentication Response in time sends its request again, as a new frame: mac80211 retransmits it 100 ms after the AP acknowledged it without answering. hostapd takes the repeat for a new exchange: it draws a new ANonce, derives a new PTK and installs it. If the STA goes on with the response to its first request, its Reassociation Request carries the first ANonce while hostapd has the second, and hostapd refuses it with status 55 (Invalid FTE, ANonce mismatch). The STA gets on only after a new exchange. An AP that is slow to answer makes this common. One such case is a STA that roams back to an AP that still has it as associated without PMF: the response waits until hostapd has removed the STA from the driver and added it again. On OpenWrt APs about 5% of FT roams back to the AP a STA had just left were refused with status 55, after an Authentication frame retransmission, on FT-SAE and FT-EAP. Reproduced with two FT-PSK BSSes on one ath10k radio and a mac80211 STA, with hostapd stopped for 400 ms as the STA started the roam: every roam where the STA retransmitted its request was refused with status 55 (5 of 5). With this change, 10 of 10 such roams completed, with one PTK installation each. Keep the last FT Authentication Request and its successful response per STA, and answer an identical request with the same response, without deriving or installing anything. Forget them on any WPA state machine event except the FT reassociation that completes the exchange, so that a late copy of a completed request does not install a new PTK either. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Christopher Griffin --- Seen on OpenWrt APs (ath11k, mt76) running hostapd with FT-SAE and FT-EAP; reproduced and tested on an Arista W-118 (ath10k). --- src/ap/wpa_auth.c | 9 +++++++++ src/ap/wpa_auth_ft.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- src/ap/wpa_auth_i.h | 3 +++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c index 2c8c743..5b10a62 100644 --- a/src/ap/wpa_auth.c +++ b/src/ap/wpa_auth.c @@ -1202,6 +1202,7 @@ static void wpa_free_sta_sm(struct wpa_state_machine *sm) #ifdef CONFIG_IEEE80211R_AP os_free(sm->assoc_resp_ftie); wpabuf_free(sm->ft_pending_req_ies); + wpa_ft_auth_forget(sm); #endif /* CONFIG_IEEE80211R_AP */ os_free(sm->last_rx_eapol_key); os_free(sm->wpa_ie); @@ -2484,6 +2485,14 @@ int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event) wpa_auth_vlogger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, "event %d notification", event); +#ifdef CONFIG_IEEE80211R_AP + /* After anything but the reassociation that completes it, a later FT + * Authentication Request starts a new exchange. A late copy of the + * completed one still gets its response and changes nothing. */ + if (event != WPA_ASSOC_FT) + wpa_ft_auth_forget(sm); +#endif /* CONFIG_IEEE80211R_AP */ + switch (event) { case WPA_AUTH: #ifdef CONFIG_MESH diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c index e12f2ad..551af5b 100644 --- a/src/ap/wpa_auth_ft.c +++ b/src/ap/wpa_auth_ft.c @@ -3426,6 +3426,29 @@ out: } +void wpa_ft_auth_forget(struct wpa_state_machine *sm) +{ + wpabuf_free(sm->ft_auth_req); + sm->ft_auth_req = NULL; + wpabuf_free(sm->ft_auth_resp); + sm->ft_auth_resp = NULL; +} + + +static void wpa_ft_auth_remember(struct wpa_state_machine *sm, + const u8 *req, size_t req_len, u16 status, + const u8 *resp, size_t resp_len) +{ + wpa_ft_auth_forget(sm); + if (status != WLAN_STATUS_SUCCESS) + return; + sm->ft_auth_req = wpabuf_alloc_copy(req, req_len); + sm->ft_auth_resp = wpabuf_alloc_copy(resp, resp_len); + if (!sm->ft_auth_req || !sm->ft_auth_resp) + wpa_ft_auth_forget(sm); +} + + 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, @@ -3452,6 +3475,26 @@ void wpa_ft_process_auth(struct wpa_state_machine *sm, sm->ft_pending_cb_ctx = ctx; sm->ft_pending_auth_transaction = auth_transaction; sm->ft_pending_pull_left_retries = sm->wpa_auth->conf.rkh_pull_retries; + + /* + * A STA that got no response in time sends the same request again, + * as a new frame. It may then go on with the response to either, so + * answer a repeat with the same response: a new ANonce and PTK would + * make the Reassociation Request that follows the first response fail + * with an ANonce mismatch, and would replace the PTK in the driver. + */ + if (sm->ft_auth_req && wpabuf_len(sm->ft_auth_req) == ies_len && + os_memcmp(wpabuf_head(sm->ft_auth_req), ies, ies_len) == 0) { + wpa_printf(MSG_DEBUG, + "FT: Repeated authentication request from " MACSTR + " - send the same response again", + MAC2STR(sm->addr)); + cb(ctx, sm->addr, auth_transaction + 1, WLAN_STATUS_SUCCESS, + wpabuf_head(sm->ft_auth_resp), + wpabuf_len(sm->ft_auth_resp)); + return; + } + res = wpa_ft_process_auth_req(sm, ies, ies_len, &resp_ies, &resp_ies_len); if (res < 0) { @@ -3459,6 +3502,7 @@ void wpa_ft_process_auth(struct wpa_state_machine *sm, return; } status = res; + wpa_ft_auth_remember(sm, ies, ies_len, status, resp_ies, resp_ies_len); wpa_printf(MSG_DEBUG, "FT: FT authentication response: dst=" MACSTR " auth_transaction=%d status=%u (%s)", @@ -4317,9 +4361,12 @@ static void ft_finish_pull(struct wpa_state_machine *sm) "FT: Callback postponed until response is available"); return; } + status = res; + wpa_ft_auth_remember(sm, wpabuf_head(sm->ft_pending_req_ies), + wpabuf_len(sm->ft_pending_req_ies), status, + resp_ies, resp_ies_len); wpabuf_free(sm->ft_pending_req_ies); sm->ft_pending_req_ies = NULL; - status = res; wpa_printf(MSG_DEBUG, "FT: Postponed auth callback result for " MACSTR " - status %u", MAC2STR(sm->addr), status); diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h index b80e9bd..52d1d08 100644 --- a/src/ap/wpa_auth_i.h +++ b/src/ap/wpa_auth_i.h @@ -149,6 +149,8 @@ struct wpa_state_machine { const u8 *ies, size_t ies_len); void *ft_pending_cb_ctx; struct wpabuf *ft_pending_req_ies; + struct wpabuf *ft_auth_req; /* last FT Authentication Request */ + struct wpabuf *ft_auth_resp; /* and its successful response */ u8 ft_pending_pull_nonce[FT_RRB_NONCE_LEN]; u8 ft_pending_auth_transaction; u8 ft_pending_current_ap[ETH_ALEN]; @@ -356,6 +358,7 @@ void wpa_auth_ft_store_keys(struct wpa_state_machine *sm, const u8 *pmk_r0, struct wpa_ft_pmk_cache * wpa_ft_pmk_cache_init(void); void wpa_ft_pmk_cache_deinit(struct wpa_ft_pmk_cache *cache); void wpa_ft_install_ptk(struct wpa_state_machine *sm, int retry); +void wpa_ft_auth_forget(struct wpa_state_machine *sm); int wpa_ft_store_pmk_fils(struct wpa_state_machine *sm, const u8 *pmk_r0, const u8 *pmk_r0_name); #endif /* CONFIG_IEEE80211R_AP */ --- base-commit: 1f2231a88a742befbea44bc19ffee94bed616505 change-id: 20261101-b95-ft-repeated-auth-0b27075cece4 Best regards, -- Christopher Griffin From devnull+christophergriffin05.gmail.com at kernel.org Sun Nov 1 18:22:08 2026 From: devnull+christophergriffin05.gmail.com at kernel.org (Christopher Griffin via B4 Relay) Date: Sun, 01 Nov 2026 20:22:08 -0600 Subject: [PATCH] FT: Set RSNXE Used in Reassociation Response only with an RSNXE Message-ID: <20261101-b128-ft-rsnxe-used-v1-1-080f12ecf18a@gmail.com> From: Christopher Griffin The AP set RSNXE Used in the FTE of an FT protocol Reassociation Response frame whenever sae_pwe was 1 or 2, for any AKM, while the MIC covers an RSNXE only when one is written. A BSS with FT-EAP or FT-PSK and no SAE AKM has no RSNXE, so with sae_pwe set (OpenWrt 25.12 sets it on such a BSS) the frame claimed an RSNXE that the AP does not advertise. wpa_supplicant rejects that ("FT: FTE indicated that AP uses RSNXE, but RSNXE was not included in Beacon/Probe Response frames") and deauthenticates with reason 13 (invalid element), so every FT roam to such a BSS failed. Write the RSNXE before the FTE and set RSNXE Used only when the response includes one, which is what the MIC covers. Responses that include an RSNXE, as with SAE H2E, are unchanged. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Christopher Griffin --- Seen with OpenWrt 25.12.2 APs (mt76) whose FT-EAP BSS has sae_pwe=2: every FT roam to them failed with reason 13. Reproduced with FT-PSK and sae_pwe=2 on an Arista W-118 (ath10k), wpa_supplicant as the station: 0 of 6 FT roams completed without this patch, each deauthenticated with reason 13 right after the Reassociation Response; 6 of 6 with it. --- src/ap/wpa_auth_ft.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c index e12f2ad..02e9402 100644 --- a/src/ap/wpa_auth_ft.c +++ b/src/ap/wpa_auth_ft.c @@ -2714,7 +2714,21 @@ u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos, anonce = NULL; snonce = NULL; } - rsnxe_used = (auth_alg == WLAN_AUTH_FT) && + if (omit_rsnxe) { + rsnxe_len = 0; + } else { + res = wpa_write_rsnxe(&sm->wpa_auth->conf, rsnxe, + sizeof(rsnxe_buf)); + if (res < 0) { + os_free(subelem); + return NULL; + } + rsnxe_len = res; + } + /* RSNXE Used tells the STA that the MIC covers an RSNXE. Without one + * (e.g., FT-EAP or FT-PSK with sae_pwe set, but no SAE), the STA would + * reject the response for an RSNXE the AP does not advertise. */ + rsnxe_used = (auth_alg == WLAN_AUTH_FT) && rsnxe_len && (conf->sae_pwe == SAE_PWE_HASH_TO_ELEMENT || conf->sae_pwe == SAE_PWE_BOTH); #ifdef CONFIG_TESTING_OPTIONS @@ -2782,17 +2796,6 @@ u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos, if (ric_start == pos) ric_start = NULL; - if (omit_rsnxe) { - rsnxe_len = 0; - } else { - res = wpa_write_rsnxe(&sm->wpa_auth->conf, rsnxe, - sizeof(rsnxe_buf)); - if (res < 0) { - pos = NULL; - goto fail; - } - rsnxe_len = res; - } #ifdef CONFIG_TESTING_OPTIONS if (auth_alg == WLAN_AUTH_FT && sm->wpa_auth->conf.rsnxe_override_ft_set) { --- base-commit: 1f2231a88a742befbea44bc19ffee94bed616505 change-id: 20261101-b128-ft-rsnxe-used-71f9af438205 Best regards, -- Christopher Griffin From devnull+christophergriffin05.gmail.com at kernel.org Sun Nov 1 20:29:14 2026 From: devnull+christophergriffin05.gmail.com at kernel.org (Christopher Griffin via B4 Relay) Date: Sun, 01 Nov 2026 22:29:14 -0600 Subject: [PATCH v2] FT: Set RSNXE Used only when the AP advertises an RSNXE Message-ID: <20261101-b128-ft-rsnxe-used-v2-1-871e5a75a9f8@gmail.com> From: Christopher Griffin RSNXE Used in the FTE of an FT Reassociation Response indicates that the AP uses the RSNXE in other frames (commit 51d1924bd), and wpa_supplicant checks it against the RSNXE of the Beacon and Probe Response frames. The AP set it whenever sae_pwe was 1 or 2. A BSS without an SAE AKM, for example FT-EAP or FT-PSK, usually has no extended RSN capability to advertise and thus no RSNXE, but with sae_pwe set its responses still announced one. wpa_supplicant then rejected every FT Reassociation Response ("FT: FTE indicated that AP uses RSNXE, but RSNXE was not included in Beacon/Probe Response frames") and deauthenticated with reason 13 (invalid element). OpenWrt 25.12.2 sets sae_pwe=2 on WPA3 and WPA2/WPA3 Enterprise BSSs, so FT-EAP roams to such APs failed. Set RSNXE Used only when the Beacon and Probe Response frames carry an RSNXE: it is not omitted with rsn_override_omit_rsnxe, and the AP has an extended RSN capability to advertise. The response to a STA that sent no RSNXE is unchanged. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Christopher Griffin --- Seen with OpenWrt 25.12.2 APs (mt76) whose FT-EAP BSS has sae_pwe=2: every FT roam to them failed with reason 13. Changes in v2: - v1 cleared RSNXE Used whenever the response itself carried no RSNXE. Per 51d1924bd the subfield reflects the RSNXE of the Beacon and Probe Response frames, as Koral Ilgun's series "FT: RSNXE Used without the RSNXE" points out, so a response to a STA that sent no RSNXE keeps it. v2 clears the subfield only when the AP advertises no RSNXE. That also covers the rsn_override_omit_rsnxe case of patch 1 of that series. - v1: "[PATCH] FT: Set RSNXE Used in Reassociation Response only with an RSNXE", 2026-11-01. Tested on an Arista W-118 (ath10k, OpenWrt main), wpa_supplicant as the STA, six FT roams between two BSSes each: - FT-PSK, AP sae_pwe=2 (no SAE AKM): 6 of 6 roams completed; 0 of 6 without this patch, each deauthenticated with reason 13. - FT-SAE, AP sae_pwe=2, STA sae_pwe=0 (no RSNXE in its request): 5 of 6; the first ROAM came before the target was in the scan results and was not attempted. - FT-SAE, AP sae_pwe=2, STA sae_pwe=2: 6 of 6. The hwsim tests were not run. --- src/ap/wpa_auth_ft.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c index e12f2ad..7046e34 100644 --- a/src/ap/wpa_auth_ft.c +++ b/src/ap/wpa_auth_ft.c @@ -2714,7 +2714,12 @@ u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos, anonce = NULL; snonce = NULL; } + /* RSNXE Used indicates that the AP uses the RSNXE in other frames, so + * only an AP whose Beacon and Probe Response frames carry an RSNXE + * may set it. */ rsnxe_used = (auth_alg == WLAN_AUTH_FT) && + !conf->rsn_override_omit_rsnxe && + wpa_write_rsnxe(conf, rsnxe_buf, sizeof(rsnxe_buf)) > 0 && (conf->sae_pwe == SAE_PWE_HASH_TO_ELEMENT || conf->sae_pwe == SAE_PWE_BOTH); #ifdef CONFIG_TESTING_OPTIONS --- base-commit: 1f2231a88a742befbea44bc19ffee94bed616505 change-id: 20261101-b128-ft-rsnxe-used-71f9af438205 Best regards, -- Christopher Griffin