diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 3e0a7ec23..d9641bb05 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -270,6 +270,12 @@ extern "C" { /* BSS Transition Management Response frame received */ #define BSS_TM_RESP "BSS-TM-RESP " +/* MBO IE with cellular data connection preference received */ +#define MBO_CELL_PREFERENCE "MBO-CELL-PREFERENCE " + +/* BSS Transition Management Request received with MBO transition reason */ +#define MBO_TRANSITION_REASON "MBO-TRANSITION-REASON " + /* BSS command information masks */ #define WPA_BSS_MASK_ALL 0xFFFDFFFF diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 76835dec3..00e4c047b 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -1076,6 +1076,12 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, assoc_disallow[2]); continue; } + + if (wpa_is_bss_tmp_disallowed(wpa_s, bss->bssid)) { + wpa_dbg(wpa_s, MSG_DEBUG, + " skip - MBO retry delay has not passed yet"); + continue; + } #endif /* CONFIG_MBO */ /* Matching configuration found */ @@ -2541,7 +2547,8 @@ static void wpa_supplicant_event_disassoc_finish(struct wpa_supplicant *wpa_s, !disallowed_bssid(wpa_s, fast_reconnect->bssid) && !disallowed_ssid(wpa_s, fast_reconnect->ssid, fast_reconnect->ssid_len) && - !wpas_temp_disabled(wpa_s, fast_reconnect_ssid)) { + !wpas_temp_disabled(wpa_s, fast_reconnect_ssid) && + !wpa_is_bss_tmp_disallowed(wpa_s, fast_reconnect->bssid)) { #ifndef CONFIG_NO_SCAN_PROCESSING wpa_dbg(wpa_s, MSG_DEBUG, "Try to reconnect to the same BSS"); if (wpa_supplicant_connect(wpa_s, fast_reconnect, diff --git a/wpa_supplicant/mbo.c b/wpa_supplicant/mbo.c index 4663dd211..e0976c57c 100644 --- a/wpa_supplicant/mbo.c +++ b/wpa_supplicant/mbo.c @@ -620,3 +620,99 @@ int wpas_mbo_supp_op_class_ie(struct wpa_supplicant *wpa_s, int freq, u8 *pos, wpabuf_free(buf); return res; } + + +void wpas_mbo_ie_trans_req(struct wpa_supplicant *wpa_s, const u8 *mbo_ie, + size_t len) +{ + const u8 *pos, *cell_pref = NULL, *reason = NULL; + u8 id, elen; + u16 disallowed_sec = 0; + + if (len <= 4 || WPA_GET_BE24(mbo_ie) != OUI_WFA || + mbo_ie[3] != MBO_OUI_TYPE) + return; + + pos = mbo_ie + 4; + len -= 4; + + while (len >= 2) { + id = *pos++; + elen = *pos++; + len -= 2; + + if (elen > len) + goto fail; + + switch (id) { + case MBO_ATTR_ID_CELL_DATA_PREF: + if (elen != 1) + goto fail; + + if (wpa_s->conf->mbo_cell_capa == + MBO_CELL_CAPA_AVAILABLE) + cell_pref = pos; + else + wpa_printf(MSG_DEBUG, + "MBO: Station does not support Cellular data connection"); + break; + case MBO_ATTR_ID_TRANSITION_REASON: + if (elen != 1) + goto fail; + + reason = pos; + break; + case MBO_ATTR_ID_ASSOC_RETRY_DELAY: + if (elen != 2) + goto fail; + + if (wpa_s->wnm_mode & + WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) { + wpa_printf(MSG_DEBUG, + "MBO: Unexpected association retry delay, BSS is terminating"); + goto fail; + } else if (wpa_s->wnm_mode & + WNM_BSS_TM_REQ_DISASSOC_IMMINENT) { + disallowed_sec = WPA_GET_LE16(pos); + } else { + wpa_printf(MSG_DEBUG, + "MBO: Association retry delay attribute not in disassoc imminent mode"); + } + + break; + case MBO_ATTR_ID_AP_CAPA_IND: + case MBO_ATTR_ID_NON_PREF_CHAN_REPORT: + case MBO_ATTR_ID_CELL_DATA_CAPA: + case MBO_ATTR_ID_ASSOC_DISALLOW: + case MBO_ATTR_ID_TRANSITION_REJECT_REASON: + wpa_printf(MSG_DEBUG, + "MBO: Attribute %d should not be included in BTM Request frame", + id); + break; + default: + wpa_printf(MSG_DEBUG, "MBO: Unknown attribute id %u", + id); + return; + } + + pos += elen; + len -= elen; + } + + if (cell_pref) + wpa_msg(wpa_s, MSG_INFO, MBO_CELL_PREFERENCE "preference=%u", + *cell_pref); + + if (reason) + wpa_msg(wpa_s, MSG_INFO, MBO_TRANSITION_REASON "reason=%u", + *reason); + + if (disallowed_sec && wpa_s->current_bss) + wpa_bss_tmp_disallow(wpa_s, wpa_s->current_bss->bssid, + disallowed_sec); + + return; +fail: + wpa_printf(MSG_DEBUG, "MBO IE parsing failed (id=%u len=%u left=%zu)", + id, elen, len); +} diff --git a/wpa_supplicant/wnm_sta.c b/wpa_supplicant/wnm_sta.c index ffc9052f4..31411e3ac 100644 --- a/wpa_supplicant/wnm_sta.c +++ b/wpa_supplicant/wnm_sta.c @@ -546,6 +546,14 @@ compare_scan_neighbor_results(struct wpa_supplicant *wpa_s) continue; } + if (wpa_is_bss_tmp_disallowed(wpa_s, target->bssid)) { + wpa_printf(MSG_DEBUG, + "MBO: Candidate BSS " MACSTR + " retry delay is not over yet", + MAC2STR(nei->bssid)); + continue; + } + if (target->level < bss->level && target->level < -80) { wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR " (pref %d) does not have sufficient signal level (%d)", @@ -821,6 +829,9 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, { unsigned int beacon_int; u8 valid_int; +#ifdef CONFIG_MBO + const u8 *vendor; +#endif /* CONFIG_MBO */ if (end - pos < 5) return; @@ -881,6 +892,12 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, } } +#ifdef CONFIG_MBO + vendor = get_ie(pos, end - pos, WLAN_EID_VENDOR_SPECIFIC); + if (vendor) + wpas_mbo_ie_trans_req(wpa_s, vendor + 2, vendor[1]); +#endif /* CONFIG_MBO */ + if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_PREF_CAND_LIST_INCLUDED) { unsigned int valid_ms; diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index e2a48fbeb..2ffc23807 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -397,6 +397,18 @@ void free_hw_features(struct wpa_supplicant *wpa_s) } +static void free_bss_tmp_disallowed(struct wpa_supplicant *wpa_s) +{ + struct wpa_bss_tmp_disallowed *bss, *prev; + + dl_list_for_each_safe(bss, prev, &wpa_s->bss_tmp_disallowed, + struct wpa_bss_tmp_disallowed, list) { + dl_list_del(&bss->list); + os_free(bss); + } +} + + static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s) { int i; @@ -555,6 +567,8 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s) os_free(wpa_s->non_pref_chan); wpa_s->non_pref_chan = NULL; #endif /* CONFIG_MBO */ + + free_bss_tmp_disallowed(wpa_s); } @@ -3497,6 +3511,8 @@ wpa_supplicant_alloc(struct wpa_supplicant *parent) wpa_s->parent = parent ? parent : wpa_s; wpa_s->sched_scanning = 0; + dl_list_init(&wpa_s->bss_tmp_disallowed); + return wpa_s; } @@ -6308,3 +6324,73 @@ struct hostapd_hw_modes * get_mode(struct hostapd_hw_modes *modes, return NULL; } + + +static struct +wpa_bss_tmp_disallowed * wpas_get_disallowed_bss(struct wpa_supplicant *wpa_s, + const u8 *bssid) +{ + struct wpa_bss_tmp_disallowed *bss; + + dl_list_for_each(bss, &wpa_s->bss_tmp_disallowed, + struct wpa_bss_tmp_disallowed, list) { + if (os_memcmp(bssid, bss->bssid, ETH_ALEN) == 0) + return bss; + } + + return NULL; +} + + +void wpa_bss_tmp_disallow(struct wpa_supplicant *wpa_s, const u8 *bssid, + unsigned int sec) +{ + struct wpa_bss_tmp_disallowed *bss; + struct os_reltime until; + + os_get_reltime(&until); + until.sec += sec; + + bss = wpas_get_disallowed_bss(wpa_s, bssid); + if (bss) { + bss->disallowed_until = until; + return; + } + + bss = os_malloc(sizeof(*bss)); + if (!bss) { + wpa_printf(MSG_DEBUG, + "Failed to allocate memory for temp disallow BSS"); + return; + } + + bss->disallowed_until = until; + os_memcpy(bss->bssid, bssid, ETH_ALEN); + dl_list_add(&wpa_s->bss_tmp_disallowed, &bss->list); +} + + +int wpa_is_bss_tmp_disallowed(struct wpa_supplicant *wpa_s, const u8 *bssid) +{ + struct wpa_bss_tmp_disallowed *bss; + struct os_reltime now, age; + + os_get_reltime(&now); + + bss = wpas_get_disallowed_bss(wpa_s, bssid); + if (!bss) + return 0; + + if (os_reltime_before(&now, &bss->disallowed_until)) { + os_reltime_sub(&bss->disallowed_until, &now, &age); + wpa_printf(MSG_DEBUG, + "BSS " MACSTR " disabled for %ld.%0ld seconds", + MAC2STR(bss->bssid), age.sec, age.usec); + return 1; + } + + /* This BSS is not disallowed anymore */ + dl_list_del(&bss->list); + os_free(bss); + return 0; +} diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 202b9e616..07318fa68 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -434,6 +434,12 @@ struct icon_entry { size_t image_len; }; +struct wpa_bss_tmp_disallowed { + struct dl_list list; + u8 bssid[ETH_ALEN]; + struct os_reltime disallowed_until; +}; + /** * struct wpa_supplicant - Internal data for wpa_supplicant interface * @@ -1029,6 +1035,12 @@ struct wpa_supplicant { size_t non_pref_chan_num; u8 mbo_wnm_token; #endif /* CONFIG_MBO */ + + /* + * This should be under CONFIG_MBO, but it is left out to allow using + * the bss_temp_disallowed list for other purposes as well. + */ + struct dl_list bss_tmp_disallowed; }; @@ -1150,6 +1162,8 @@ int wpas_mbo_update_non_pref_chan(struct wpa_supplicant *wpa_s, void wpas_mbo_scan_ie(struct wpa_supplicant *wpa_s, struct wpabuf *ie); int wpas_mbo_supp_op_class_ie(struct wpa_supplicant *wpa_s, int freq, u8 *pos, size_t len); +void wpas_mbo_ie_trans_req(struct wpa_supplicant *wpa_s, const u8 *ie, + size_t len); /** * wpa_supplicant_ctrl_iface_ctrl_rsp_handle - Handle a control response @@ -1232,4 +1246,8 @@ int wpas_sched_scan_plans_set(struct wpa_supplicant *wpa_s, const char *cmd); struct hostapd_hw_modes * get_mode(struct hostapd_hw_modes *modes, u16 num_modes, enum hostapd_hw_mode mode); +void wpa_bss_tmp_disallow(struct wpa_supplicant *wpa_s, const u8 *bssid, + unsigned int sec); +int wpa_is_bss_tmp_disallowed(struct wpa_supplicant *wpa_s, const u8 *bssid); + #endif /* WPA_SUPPLICANT_I_H */