In 802.11w/MFP networks, the infrastructure implements SA teardown protection by rejecting an Association Request from an already-associated client. The AP responds with error 30 (Association request rejected temporarily) to instruct the (potentially spoofing) client to back off, while it issues an SA-Query procedure to the already-associated client. If the client can respond to it within the back-off period, it considers the new association to be a spoof attempt. However, there are cases where a legitimate client might need to handle this error response - consider if the STA has deauthenticated, but the AP cannot hear it (out of range). If the MFP STA has deleted its keys, it cannot respond to the SA-Query procedure. The current implementation interprets this association error as a true error, and will either add the BSS to the blacklist, or continue to try other BSSes - all of which will respond with the same error. This can cause the supplicant to back off trying to reconnect for progressively longer intervals, depending on the infrastructure's configured comeback timeout. This patch allows the supplicant to interpret the error, searching for the Timeout Interval IE in the Association response and starting a timer in the SME layer to re-associate after the timeout. This can be a long delay (1-4 seconds in my experience), but it is likely much shorter than bouncing between nearby BSSes. Signed-off-by: Harry Bock <hbock@xxxxxxxxx> --- src/common/ieee802_11_defs.h | 6 +++ wpa_supplicant/sme.c | 67 ++++++++++++++++++++++++++++++- wpa_supplicant/wpa_supplicant_i.h | 1 + 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index 12137dcf4..6192f2396 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -3007,6 +3007,12 @@ struct ieee80211_neighbor_ap_info { u8 data[0]; } STRUCT_PACKED; +struct ieee80211_timeout_interval { + u8 timeout_interval_type; + u32 timeout_interval_value; +} STRUCT_PACKED; + + #ifdef _MSC_VER #pragma pack(pop) #endif /* _MSC_VER */ diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index c791e116b..82ff82281 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -39,6 +39,8 @@ static void sme_auth_timer(void *eloop_ctx, void *timeout_ctx); static void sme_assoc_timer(void *eloop_ctx, void *timeout_ctx); static void sme_obss_scan_timeout(void *eloop_ctx, void *timeout_ctx); +static void sme_assoc_comeback_timer(void *eloop_ctx, void *timeout_ctx); + static void sme_stop_sa_query(struct wpa_supplicant *wpa_s); @@ -2195,6 +2197,9 @@ void sme_associate(struct wpa_supplicant *wpa_s, enum wpas_mode mode, os_memset(¶ms, 0, sizeof(params)); + /* save auth type, in case we need to retry after comeback timer. */ + wpa_s->sme.assoc_auth_type = auth_type; + #ifdef CONFIG_FILS if (auth_type == WLAN_AUTH_FILS_SK || auth_type == WLAN_AUTH_FILS_SK_PFS) { @@ -2701,6 +2706,47 @@ static void sme_deauth(struct wpa_supplicant *wpa_s, const u8 **link_bssids) } +static void sme_assoc_comeback_timer(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + wpa_msg(wpa_s, MSG_DEBUG, "SME: Comeback timeout expired; retry associating to " MACSTR "; mode=%d auth_type=%u", + MAC2STR(wpa_s->current_bss->bssid), + wpa_s->current_ssid->mode, + wpa_s->sme.assoc_auth_type); + + /* auth state was completed already; just try association again. */ + sme_associate(wpa_s, wpa_s->current_ssid->mode, wpa_s->current_bss->bssid, wpa_s->sme.assoc_auth_type); +} + +static int sme_try_assoc_comeback(struct wpa_supplicant *wpa_s, + union wpa_event_data *data) +{ + int try_comeback = 0; + struct ieee802_11_elems elems; + if (ParseOK == ieee802_11_parse_elems(data->assoc_reject.resp_ies, data->assoc_reject.resp_ies_len, &elems, 0)) { + if (elems.timeout_int) { + struct ieee80211_timeout_interval* timeout = (struct ieee80211_timeout_interval*)elems.timeout_int; + if (timeout->timeout_interval_type == WLAN_TIMEOUT_ASSOC_COMEBACK) { + wpa_msg(wpa_s, MSG_DEBUG, "SME: Association comeback interval: %u TUs", + timeout->timeout_interval_value); + + u64 comeback_usec = ((u64)timeout->timeout_interval_value * 1024uLL); + eloop_register_timeout((unsigned int)(comeback_usec / 1000000uLL), + (unsigned int)(comeback_usec % 1000000uLL), + sme_assoc_comeback_timer, wpa_s, NULL); + try_comeback = 1; + } + /* no timeout interval IE; can't come back. */ + } else { + wpa_msg(wpa_s, MSG_ERROR, "SME: Temporary assoc reject: missing timeout interval IE"); + } + } else { + wpa_msg(wpa_s, MSG_ERROR, "SME: Temporary assoc reject: fail to parse association resp IEs"); + } + + return try_comeback; +} + void sme_event_assoc_reject(struct wpa_supplicant *wpa_s, union wpa_event_data *data, const u8 **link_bssids) @@ -2710,6 +2756,22 @@ void sme_event_assoc_reject(struct wpa_supplicant *wpa_s, data->assoc_reject.status_code); eloop_cancel_timeout(sme_assoc_timer, wpa_s, NULL); + eloop_cancel_timeout(sme_assoc_comeback_timer, wpa_s, NULL); + + /* Auth phase completed, but AP thinks our previous authentication is still valid (MFP). + * Meaning our auth states are out of sync. + * Perhaps we went out of range and/or deauthenticated, but AP didn't hear our deauth. + * AP rejects us temporarily, issues SA-Query challenge (which we can't complete anymore). + * We must wait for the AP's association comeback timeout period before associating + * again. + */ + if (WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY == data->assoc_reject.status_code) { + wpa_msg(wpa_s, MSG_DEBUG, "SME: Temporary assoc reject from BSS " MACSTR, MAC2STR(wpa_s->current_bss->bssid)); + if (sme_try_assoc_comeback(wpa_s, data)) { + /* break out early; comeback error is not a failure. */ + return; + } + } #ifdef CONFIG_SAE if (wpa_s->sme.sae_pmksa_caching && wpa_s->current_ssid && @@ -2837,8 +2899,10 @@ static void sme_assoc_timer(void *eloop_ctx, void *timeout_ctx) void sme_state_changed(struct wpa_supplicant *wpa_s) { /* Make sure timers are cleaned up appropriately. */ - if (wpa_s->wpa_state != WPA_ASSOCIATING) + if (wpa_s->wpa_state != WPA_ASSOCIATING) { eloop_cancel_timeout(sme_assoc_timer, wpa_s, NULL); + eloop_cancel_timeout(sme_assoc_comeback_timer, wpa_s, NULL); + } if (wpa_s->wpa_state != WPA_AUTHENTICATING) eloop_cancel_timeout(sme_auth_timer, wpa_s, NULL); } @@ -2871,6 +2935,7 @@ void sme_deinit(struct wpa_supplicant *wpa_s) eloop_cancel_timeout(sme_assoc_timer, wpa_s, NULL); eloop_cancel_timeout(sme_auth_timer, wpa_s, NULL); eloop_cancel_timeout(sme_obss_scan_timeout, wpa_s, NULL); + eloop_cancel_timeout(sme_assoc_comeback_timer, wpa_s, NULL); } diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 44e4416b1..a5c1d4ca6 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -1033,6 +1033,7 @@ struct wpa_supplicant { bool ext_ml_auth; int *sae_rejected_groups; #endif /* CONFIG_SAE */ + u16 assoc_auth_type; } sme; #endif /* CONFIG_SME */ -- 2.34.1 - CONFIDENTIAL- This email and any files transmitted with it are confidential, and may also be legally privileged. If you are not the intended recipient, you may not review, use, copy, or distribute this message. If you receive this email in error, please notify the sender immediately by reply email and then delete this email. _______________________________________________ Hostap mailing list Hostap@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/hostap