diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 58ecf19fd..00c5ff0db 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -3742,6 +3742,8 @@ struct wpa_config * wpa_config_alloc_empty(const char *ctrl_interface, #ifdef CONFIG_MBO config->mbo_cell_capa = DEFAULT_MBO_CELL_CAPA; + config->disassoc_imminent_rssi_threshold = + DEFAULT_DISASSOC_IMMINENT_RSSI_THRESHOLD; #endif /* CONFIG_MBO */ if (ctrl_interface) @@ -4453,6 +4455,7 @@ static const struct global_parse_data global_fields[] = { { STR(non_pref_chan), 0 }, { INT_RANGE(mbo_cell_capa, MBO_CELL_CAPA_AVAILABLE, MBO_CELL_CAPA_NOT_SUPPORTED), 0 }, + { INT_RANGE(disassoc_imminent_rssi_threshold, -120, 0), 0 }, #endif /*CONFIG_MBO */ { INT(gas_address3), 0 }, { INT_RANGE(ftm_responder, 0, 1), 0 }, diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h index 2f2bb01c6..3f84b0e1c 100644 --- a/wpa_supplicant/config.h +++ b/wpa_supplicant/config.h @@ -41,6 +41,7 @@ #define DEFAULT_P2P_GO_CTWINDOW 0 #define DEFAULT_WPA_RSC_RELAXATION 1 #define DEFAULT_MBO_CELL_CAPA MBO_CELL_CAPA_NOT_SUPPORTED +#define DEFAULT_DISASSOC_IMMINENT_RSSI_THRESHOLD -75 #include "config_ssid.h" #include "wps/wps.h" @@ -1291,6 +1292,12 @@ struct wpa_config { * mbo_cell_capa - Cellular capabilities for MBO */ enum mbo_cellular_capa mbo_cell_capa; + + /** + * disassoc_imminent_rssi_threshold - RSSI threshold of candidate AP + * when disassociation imminent is set. + */ + int disassoc_imminent_rssi_threshold; #endif /* CONFIG_MBO */ /** diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 84a1ee945..160a5da2c 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -1394,6 +1394,10 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config) fprintf(f, "non_pref_chan=%s\n", config->non_pref_chan); if (config->mbo_cell_capa != DEFAULT_MBO_CELL_CAPA) fprintf(f, "mbo_cell_capa=%u\n", config->mbo_cell_capa); + if (config->disassoc_imminent_rssi_threshold != + DEFAULT_DISASSOC_IMMINENT_RSSI_THRESHOLD) + fprintf(f, "disassoc_imminent_rssi_threshold=%d\n", + config->disassoc_imminent_rssi_threshold); #endif /* CONFIG_MBO */ if (config->gas_address3) diff --git a/wpa_supplicant/mbo.c b/wpa_supplicant/mbo.c index 3edfd27c6..5488f446a 100644 --- a/wpa_supplicant/mbo.c +++ b/wpa_supplicant/mbo.c @@ -382,7 +382,7 @@ void wpas_mbo_scan_ie(struct wpa_supplicant *wpa_s, struct wpabuf *ie) 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; + const u8 *pos, *cell_pref = NULL; u8 id, elen; u16 disallowed_sec = 0; @@ -417,7 +417,8 @@ void wpas_mbo_ie_trans_req(struct wpa_supplicant *wpa_s, const u8 *mbo_ie, if (elen != 1) goto fail; - reason = pos; + wpa_s->wnm_mbo_trans_reason_present = 1; + wpa_s->wnm_mbo_transition_reason = *pos; break; case MBO_ATTR_ID_ASSOC_RETRY_DELAY: if (elen != 2) @@ -460,9 +461,9 @@ void wpas_mbo_ie_trans_req(struct wpa_supplicant *wpa_s, const u8 *mbo_ie, wpa_msg(wpa_s, MSG_INFO, MBO_CELL_PREFERENCE "preference=%u", *cell_pref); - if (reason) + if (wpa_s->wnm_mbo_trans_reason_present) wpa_msg(wpa_s, MSG_INFO, MBO_TRANSITION_REASON "reason=%u", - *reason); + wpa_s->wnm_mbo_transition_reason); if (disallowed_sec && wpa_s->current_bss) wpa_bss_tmp_disallow(wpa_s, wpa_s->current_bss->bssid, diff --git a/wpa_supplicant/wnm_sta.c b/wpa_supplicant/wnm_sta.c index 0b1a2db8e..9bcb039a4 100644 --- a/wpa_supplicant/wnm_sta.c +++ b/wpa_supplicant/wnm_sta.c @@ -13,6 +13,7 @@ #include "common/ieee802_11_common.h" #include "common/wpa_ctrl.h" #include "rsn_supp/wpa.h" +#include "config.h" #include "wpa_supplicant_i.h" #include "driver_i.h" #include "scan.h" @@ -499,10 +500,127 @@ static void wnm_parse_neighbor_report(struct wpa_supplicant *wpa_s, } -static struct wpa_bss * -compare_scan_neighbor_results(struct wpa_supplicant *wpa_s, os_time_t age_secs) +static void wnm_clear_acceptable(struct wpa_supplicant *wpa_s) { + unsigned int i; + for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) + wpa_s->wnm_neighbor_report_elements[i].acceptable = 0; +} + + +static struct wpa_bss * get_first_acceptable(struct wpa_supplicant *wpa_s) +{ + unsigned int i; + struct neighbor_report *nei; + + for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { + nei = &wpa_s->wnm_neighbor_report_elements[i]; + if (nei->acceptable) + return wpa_bss_get_bssid(wpa_s, nei->bssid); + } + + return NULL; +} + + +#ifdef CONFIG_MBO +static struct wpa_bss * +get_mbo_transition_candidate(struct wpa_supplicant *wpa_s, + enum mbo_transition_reject_reason *reason) +{ + struct wpa_bss *target = NULL; + struct wpa_bss_trans_info params; + struct wpa_bss_candidate_info *info = NULL; + struct neighbor_report *nei = wpa_s->wnm_neighbor_report_elements; + u8 *first_candidate_bssid = NULL, *pos; + unsigned int i; + + params.mbo_transition_reason = wpa_s->wnm_mbo_transition_reason; + params.n_candidates = 0; + params.bssid = os_calloc(wpa_s->wnm_num_neighbor_report, ETH_ALEN); + if (!params.bssid) + return NULL; + + pos = params.bssid; + for (i = 0; i < wpa_s->wnm_num_neighbor_report; nei++, i++) { + if (nei->is_first) + first_candidate_bssid = nei->bssid; + if (!nei->acceptable) + continue; + os_memcpy(pos, nei->bssid, ETH_ALEN); + pos += ETH_ALEN; + params.n_candidates++; + } + + if (!params.n_candidates) + goto end; + + info = wpa_drv_get_bss_trans_status(wpa_s, ¶ms); + if (!info) { + /* If failed to get candidate BSS transition status from driver, + * get the first acceptable candidate from wpa_supplicant. + */ + target = wpa_bss_get_bssid(wpa_s, params.bssid); + goto end; + } + + /* Get the first acceptable candidate from driver */ + for (i = 0; i < info->num; i++) { + if (info->candidates[i].is_accept) { + target = wpa_bss_get_bssid(wpa_s, + info->candidates[i].bssid); + goto end; + } + } + + /* If Disassociation Imminent is set and driver rejects all the + * candidate select first acceptable candidate which has + * rssi > disassoc_imminent_rssi_threshold + */ + if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT) { + for (i = 0; i < info->num; i++) { + target = wpa_bss_get_bssid(wpa_s, + info->candidates[i].bssid); + if (target->level < + wpa_s->conf->disassoc_imminent_rssi_threshold) + continue; + goto end; + } + } + + /* While sending BTM reject use reason code of the first candidate + * received in BTM request frame + */ + if (reason) { + for (i = 0; i < info->num; i++) { + if (first_candidate_bssid && + os_memcmp(first_candidate_bssid, + info->candidates[i].bssid, ETH_ALEN) == 0) + { + *reason = info->candidates[i].reject_reason; + break; + } + } + } + + target = NULL; + +end: + os_free(params.bssid); + if (info) { + os_free(info->candidates); + os_free(info); + } + return target; +} +#endif /* CONFIG_MBO */ + + +static struct wpa_bss * +compare_scan_neighbor_results(struct wpa_supplicant *wpa_s, os_time_t age_secs, + enum mbo_transition_reject_reason *reason) +{ u8 i; struct wpa_bss *bss = wpa_s->current_bss; struct wpa_bss *target; @@ -513,6 +631,8 @@ compare_scan_neighbor_results(struct wpa_supplicant *wpa_s, os_time_t age_secs) wpa_printf(MSG_DEBUG, "WNM: Current BSS " MACSTR " RSSI %d", MAC2STR(wpa_s->bssid), bss->level); + wnm_clear_acceptable(wpa_s); + for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { struct neighbor_report *nei; @@ -589,14 +709,26 @@ compare_scan_neighbor_results(struct wpa_supplicant *wpa_s, os_time_t age_secs) continue; } + nei->acceptable = 1; + } + +#ifdef CONFIG_MBO + if (wpa_s->wnm_mbo_trans_reason_present) + target = get_mbo_transition_candidate(wpa_s, reason); + else + target = get_first_acceptable(wpa_s); +#else /* CONFIG_MBO */ + target = get_first_acceptable(wpa_s); +#endif /* CONFIG_MBO */ + + if (target) { wpa_printf(MSG_DEBUG, "WNM: Found an acceptable preferred transition candidate BSS " MACSTR " (RSSI %d)", - MAC2STR(nei->bssid), target->level); - return target; + MAC2STR(target->bssid), target->level); } - return NULL; + return target; } @@ -780,17 +912,19 @@ static int wnm_add_cand_list(struct wpa_supplicant *wpa_s, u8 *buf, size_t len) static void wnm_send_bss_transition_mgmt_resp( struct wpa_supplicant *wpa_s, u8 dialog_token, - enum bss_trans_mgmt_status_code status, u8 delay, - const u8 *target_bssid) + enum bss_trans_mgmt_status_code status, + enum mbo_transition_reject_reason reason, + u8 delay, const u8 *target_bssid) { u8 buf[2000], *pos; struct ieee80211_mgmt *mgmt; size_t len; int res; - wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Response " - "to " MACSTR " dialog_token=%u status=%u delay=%d", - MAC2STR(wpa_s->bssid), dialog_token, status, delay); + wpa_printf(MSG_DEBUG, + "WNM: Send BSS Transition Management Response to " MACSTR + " dialog_token=%u status=%u reason=%u delay=%d", + MAC2STR(wpa_s->bssid), dialog_token, status, reason, delay); if (!wpa_s->current_bss) { wpa_printf(MSG_DEBUG, "WNM: Current BSS not known - drop response"); @@ -827,10 +961,10 @@ static void wnm_send_bss_transition_mgmt_resp( pos += wnm_add_cand_list(wpa_s, pos, buf + sizeof(buf) - pos); #ifdef CONFIG_MBO - if (status != WNM_BSS_TM_ACCEPT) { + if (status != WNM_BSS_TM_ACCEPT && + wpa_bss_get_vendor_ie(wpa_s->current_bss, MBO_IE_VENDOR_TYPE)) { pos += wpas_mbo_ie_bss_trans_reject( - wpa_s, pos, buf + sizeof(buf) - pos, - MBO_TRANSITION_REJECT_REASON_UNSPECIFIED); + wpa_s, pos, buf + sizeof(buf) - pos, reason); } #endif /* CONFIG_MBO */ @@ -861,10 +995,9 @@ static void wnm_bss_tm_connect(struct wpa_supplicant *wpa_s, wpa_s->wnm_reply = 0; wpa_printf(MSG_DEBUG, "WNM: Sending successful BSS Transition Management Response"); - wnm_send_bss_transition_mgmt_resp(wpa_s, - wpa_s->wnm_dialog_token, - WNM_BSS_TM_ACCEPT, - 0, bss->bssid); + wnm_send_bss_transition_mgmt_resp( + wpa_s, wpa_s->wnm_dialog_token, WNM_BSS_TM_ACCEPT, + MBO_TRANSITION_REASON_UNSPECIFIED, 0, bss->bssid); } if (bss == wpa_s->current_bss) { @@ -886,6 +1019,8 @@ int wnm_scan_process(struct wpa_supplicant *wpa_s, int reply_on_fail) struct wpa_bss *bss; struct wpa_ssid *ssid = wpa_s->current_ssid; enum bss_trans_mgmt_status_code status = WNM_BSS_TM_REJECT_UNSPECIFIED; + enum mbo_transition_reject_reason reason = + MBO_TRANSITION_REJECT_REASON_UNSPECIFIED; if (!wpa_s->wnm_neighbor_report_elements) return 0; @@ -907,7 +1042,7 @@ int wnm_scan_process(struct wpa_supplicant *wpa_s, int reply_on_fail) } /* Compare the Neighbor Report and scan results */ - bss = compare_scan_neighbor_results(wpa_s, 0); + bss = compare_scan_neighbor_results(wpa_s, 0, &reason); if (!bss) { wpa_printf(MSG_DEBUG, "WNM: No BSS transition candidate match found"); status = WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES; @@ -928,7 +1063,7 @@ send_bss_resp_fail: wpa_s->wnm_reply = 0; wnm_send_bss_transition_mgmt_resp(wpa_s, wpa_s->wnm_dialog_token, - status, 0, NULL); + status, reason, 0, NULL); } wnm_deallocate_memory(wpa_s); @@ -1116,7 +1251,7 @@ static int wnm_fetch_scan_results(struct wpa_supplicant *wpa_s) return 0; } - bss = compare_scan_neighbor_results(wpa_s, WNM_SCAN_RESULT_AGE); + bss = compare_scan_neighbor_results(wpa_s, WNM_SCAN_RESULT_AGE, NULL); if (!bss) { wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Comparison of scan results against transition candidates did not find matches"); @@ -1142,6 +1277,11 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, if (end - pos < 5) return; +#ifdef CONFIG_MBO + wpa_s->wnm_mbo_trans_reason_present = 0; + wpa_s->wnm_mbo_transition_reason = 0; +#endif /* CONFIG_MBO */ + if (wpa_s->current_bss) beacon_int = wpa_s->current_bss->beacon_int; else @@ -1164,10 +1304,10 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, wpa_printf(MSG_INFO, "WNM: Testing - reject BSS Transition Management Request: reject_btm_req_reason=%d", wpa_s->reject_btm_req_reason); - wnm_send_bss_transition_mgmt_resp(wpa_s, - wpa_s->wnm_dialog_token, - wpa_s->reject_btm_req_reason, - 0, NULL); + wnm_send_bss_transition_mgmt_resp( + wpa_s, wpa_s->wnm_dialog_token, + wpa_s->reject_btm_req_reason, + MBO_TRANSITION_REASON_UNSPECIFIED, 0, NULL); return; } #endif /* CONFIG_MBO && CONFIG_TESTING_OPTIONS */ @@ -1246,6 +1386,15 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, wpa_s->wnm_num_neighbor_report]; wnm_parse_neighbor_report(wpa_s, pos, len, rep); wpa_s->wnm_num_neighbor_report++; +#ifdef CONFIG_MBO + if (wpa_s->wnm_mbo_trans_reason_present && + wpa_s->wnm_num_neighbor_report == 1) { + rep->is_first = 1; + wpa_printf(MSG_DEBUG, + "WNM: First transition candidate is " + MACSTR, MAC2STR(rep->bssid)); + } +#endif /* CONFIG_MBO */ } pos += len; @@ -1257,7 +1406,7 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, wnm_send_bss_transition_mgmt_resp( wpa_s, wpa_s->wnm_dialog_token, WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES, - 0, NULL); + MBO_TRANSITION_REASON_UNSPECIFIED, 0, NULL); return; } @@ -1320,9 +1469,9 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, wpa_msg(wpa_s, MSG_INFO, "WNM: BSS Transition Management Request did not include candidates"); status = WNM_BSS_TM_REJECT_UNSPECIFIED; } - wnm_send_bss_transition_mgmt_resp(wpa_s, - wpa_s->wnm_dialog_token, - status, 0, NULL); + wnm_send_bss_transition_mgmt_resp( + wpa_s, wpa_s->wnm_dialog_token, status, + MBO_TRANSITION_REASON_UNSPECIFIED, 0, NULL); } } diff --git a/wpa_supplicant/wnm_sta.h b/wpa_supplicant/wnm_sta.h index 81d815359..78b87d055 100644 --- a/wpa_supplicant/wnm_sta.h +++ b/wpa_supplicant/wnm_sta.h @@ -43,6 +43,10 @@ struct neighbor_report { unsigned int rm_capab_present:1; unsigned int bearing_present:1; unsigned int bss_term_present:1; + unsigned int acceptable:1; +#ifdef CONFIG_MBO + unsigned int is_first:1; +#endif /* CONFIG_MBO */ struct measurement_pilot *meas_pilot; struct multiple_bssid *mul_bssid; int freq; diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 95e19b8fe..87065cf9e 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -1044,6 +1044,10 @@ struct wpa_supplicant { struct neighbor_report *wnm_neighbor_report_elements; struct os_reltime wnm_cand_valid_until; u8 wnm_cand_from_bss[ETH_ALEN]; +#ifdef CONFIG_MBO + unsigned int wnm_mbo_trans_reason_present:1; + u8 wnm_mbo_transition_reason; +#endif /* CONFIG_MBO */ #endif /* CONFIG_WNM */ #ifdef CONFIG_TESTING_GET_GTK