X-Git-Url: http://www.project-moonshot.org/gitweb/?p=mech_eap.git;a=blobdiff_plain;f=wpa_supplicant%2Fwnm_sta.c;h=7674825e691377217bf42e8465f66c5ec76af410;hp=3b45bf632fdbea8b29a941c53283733b52fb0742;hb=6013bbe04f138f7d5d750a3e1939732cbde0426a;hpb=1e74ae4de95f70718c1cc433ad1cb03e1a886c4e diff --git a/wpa_supplicant/wnm_sta.c b/wpa_supplicant/wnm_sta.c index 3b45bf6..7674825 100644 --- a/wpa_supplicant/wnm_sta.c +++ b/wpa_supplicant/wnm_sta.c @@ -24,6 +24,7 @@ #define MAX_TFS_IE_LEN 1024 #define WNM_MAX_NEIGHBOR_REPORT 10 +#define WNM_SCAN_RESULT_AGE 2 /* 2 seconds */ /* get the TFS IE from driver */ static int ieee80211_11_get_tfs_ie(struct wpa_supplicant *wpa_s, u8 *buf, @@ -139,6 +140,8 @@ int ieee802_11_send_wnmsleep_req(struct wpa_supplicant *wpa_s, if (res < 0) wpa_printf(MSG_DEBUG, "Failed to send WNM-Sleep Request " "(action=%d, intval=%d)", action, intval); + else + wpa_s->wnmsleep_used = 1; os_free(wnmsleep_ie); os_free(wnmtfs_ie); @@ -189,6 +192,12 @@ static void wnm_sleep_mode_exit_success(struct wpa_supplicant *wpa_s, end = ptr + key_len_total; wpa_hexdump_key(MSG_DEBUG, "WNM: Key Data", ptr, key_len_total); + if (key_len_total && !wpa_sm_pmf_enabled(wpa_s->wpa)) { + wpa_msg(wpa_s, MSG_INFO, + "WNM: Ignore Key Data in WNM-Sleep Mode Response - PMF not enabled"); + return; + } + while (end - ptr > 1) { if (2 + ptr[1] > end - ptr) { wpa_printf(MSG_DEBUG, "WNM: Invalid Key Data element " @@ -249,6 +258,12 @@ static void ieee802_11_rx_wnmsleep_resp(struct wpa_supplicant *wpa_s, const u8 *tfsresp_ie_end = NULL; size_t left; + if (!wpa_s->wnmsleep_used) { + wpa_printf(MSG_DEBUG, + "WNM: Ignore WNM-Sleep Mode Response frame since WNM-Sleep Mode has not been used in this association"); + return; + } + if (len < 3) return; key_len_total = WPA_GET_LE16(frm + 1); @@ -415,6 +430,7 @@ static int wnm_nei_get_chan(struct wpa_supplicant *wpa_s, u8 op_class, u8 chan) { struct wpa_bss *bss = wpa_s->current_bss; const char *country = NULL; + int freq; if (bss) { const u8 *elem = wpa_bss_get_ie(bss, WLAN_EID_COUNTRY); @@ -423,7 +439,21 @@ static int wnm_nei_get_chan(struct wpa_supplicant *wpa_s, u8 op_class, u8 chan) country = (const char *) (elem + 2); } - return ieee80211_chan_to_freq(country, op_class, chan); + freq = ieee80211_chan_to_freq(country, op_class, chan); + if (freq <= 0 && op_class == 0) { + /* + * Some APs do not advertise correct operating class + * information. Try to determine the most likely operating + * frequency based on the channel number. + */ + if (chan >= 1 && chan <= 13) + freq = 2407 + chan * 5; + else if (chan == 14) + freq = 2484; + else if (chan >= 36 && chan <= 169) + freq = 5000 + chan * 5; + } + return freq; } @@ -470,7 +500,7 @@ static void wnm_parse_neighbor_report(struct wpa_supplicant *wpa_s, static struct wpa_bss * -compare_scan_neighbor_results(struct wpa_supplicant *wpa_s) +compare_scan_neighbor_results(struct wpa_supplicant *wpa_s, os_time_t age_secs) { u8 i; @@ -478,7 +508,7 @@ compare_scan_neighbor_results(struct wpa_supplicant *wpa_s) struct wpa_bss *target; if (!bss) - return 0; + return NULL; wpa_printf(MSG_DEBUG, "WNM: Current BSS " MACSTR " RSSI %d", MAC2STR(wpa_s->bssid), bss->level); @@ -503,6 +533,19 @@ compare_scan_neighbor_results(struct wpa_supplicant *wpa_s) continue; } + if (age_secs) { + struct os_reltime now; + + if (os_get_reltime(&now) == 0 && + os_reltime_expired(&now, &target->last_update, + age_secs)) { + wpa_printf(MSG_DEBUG, + "Candidate BSS is more than %ld seconds old", + age_secs); + continue; + } + } + if (bss->ssid_len != target->ssid_len || os_memcmp(bss->ssid, target->ssid, bss->ssid_len) != 0) { /* @@ -517,6 +560,25 @@ compare_scan_neighbor_results(struct wpa_supplicant *wpa_s) continue; } + if (wpa_s->current_ssid && + !wpa_scan_res_match(wpa_s, 0, target, wpa_s->current_ssid, + 1)) { + wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR + " (pref %d) does not match the current network profile", + MAC2STR(nei->bssid), + nei->preference_present ? nei->preference : + -1); + 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)", @@ -538,12 +600,190 @@ compare_scan_neighbor_results(struct wpa_supplicant *wpa_s) } +static int wpa_bss_ies_eq(struct wpa_bss *a, struct wpa_bss *b, u8 eid) +{ + const u8 *ie_a, *ie_b; + + if (!a || !b) + return 0; + + ie_a = wpa_bss_get_ie(a, eid); + ie_b = wpa_bss_get_ie(b, eid); + + if (!ie_a || !ie_b || ie_a[1] != ie_b[1]) + return 0; + + return os_memcmp(ie_a, ie_b, ie_a[1]) == 0; +} + + +static u32 wnm_get_bss_info(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) +{ + u32 info = 0; + + info |= NEI_REP_BSSID_INFO_AP_UNKNOWN_REACH; + + /* + * Leave the security and key scope bits unset to indicate that the + * security information is not available. + */ + + if (bss->caps & WLAN_CAPABILITY_SPECTRUM_MGMT) + info |= NEI_REP_BSSID_INFO_SPECTRUM_MGMT; + if (bss->caps & WLAN_CAPABILITY_QOS) + info |= NEI_REP_BSSID_INFO_QOS; + if (bss->caps & WLAN_CAPABILITY_APSD) + info |= NEI_REP_BSSID_INFO_APSD; + if (bss->caps & WLAN_CAPABILITY_RADIO_MEASUREMENT) + info |= NEI_REP_BSSID_INFO_RM; + if (bss->caps & WLAN_CAPABILITY_DELAYED_BLOCK_ACK) + info |= NEI_REP_BSSID_INFO_DELAYED_BA; + if (bss->caps & WLAN_CAPABILITY_IMM_BLOCK_ACK) + info |= NEI_REP_BSSID_INFO_IMM_BA; + if (wpa_bss_ies_eq(bss, wpa_s->current_bss, WLAN_EID_MOBILITY_DOMAIN)) + info |= NEI_REP_BSSID_INFO_MOBILITY_DOMAIN; + if (wpa_bss_ies_eq(bss, wpa_s->current_bss, WLAN_EID_HT_CAP)) + info |= NEI_REP_BSSID_INFO_HT; + + return info; +} + + +static int wnm_add_nei_rep(u8 *buf, size_t len, const u8 *bssid, u32 bss_info, + u8 op_class, u8 chan, u8 phy_type, u8 pref) +{ + u8 *pos = buf; + + if (len < 18) { + wpa_printf(MSG_DEBUG, + "WNM: Not enough room for Neighbor Report element"); + return -1; + } + + *pos++ = WLAN_EID_NEIGHBOR_REPORT; + /* length: 13 for basic neighbor report + 3 for preference subelement */ + *pos++ = 16; + os_memcpy(pos, bssid, ETH_ALEN); + pos += ETH_ALEN; + WPA_PUT_LE32(pos, bss_info); + pos += 4; + *pos++ = op_class; + *pos++ = chan; + *pos++ = phy_type; + *pos++ = WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE; + *pos++ = 1; + *pos++ = pref; + return pos - buf; +} + + +static int wnm_nei_rep_add_bss(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss, u8 *buf, size_t len, + u8 pref) +{ + const u8 *ie; + u8 op_class, chan; + int sec_chan = 0, vht = 0; + enum phy_type phy_type; + u32 info; + struct ieee80211_ht_operation *ht_oper = NULL; + struct ieee80211_vht_operation *vht_oper = NULL; + + ie = wpa_bss_get_ie(bss, WLAN_EID_HT_OPERATION); + if (ie && ie[1] >= 2) { + ht_oper = (struct ieee80211_ht_operation *) (ie + 2); + + if (ht_oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE) + sec_chan = 1; + else if (ht_oper->ht_param & + HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW) + sec_chan = -1; + } + + ie = wpa_bss_get_ie(bss, WLAN_EID_VHT_OPERATION); + if (ie && ie[1] >= 1) { + vht_oper = (struct ieee80211_vht_operation *) (ie + 2); + + if (vht_oper->vht_op_info_chwidth == VHT_CHANWIDTH_80MHZ || + vht_oper->vht_op_info_chwidth == VHT_CHANWIDTH_160MHZ || + vht_oper->vht_op_info_chwidth == VHT_CHANWIDTH_80P80MHZ) + vht = vht_oper->vht_op_info_chwidth; + } + + if (ieee80211_freq_to_channel_ext(bss->freq, sec_chan, vht, &op_class, + &chan) == NUM_HOSTAPD_MODES) { + wpa_printf(MSG_DEBUG, + "WNM: Cannot determine operating class and channel"); + return -2; + } + + phy_type = ieee80211_get_phy_type(bss->freq, (ht_oper != NULL), + (vht_oper != NULL)); + if (phy_type == PHY_TYPE_UNSPECIFIED) { + wpa_printf(MSG_DEBUG, + "WNM: Cannot determine BSS phy type for Neighbor Report"); + return -2; + } + + info = wnm_get_bss_info(wpa_s, bss); + + return wnm_add_nei_rep(buf, len, bss->bssid, info, op_class, chan, + phy_type, pref); +} + + +static int wnm_add_cand_list(struct wpa_supplicant *wpa_s, u8 *buf, size_t len) +{ + u8 *pos = buf; + unsigned int i, pref = 255; + struct os_reltime now; + struct wpa_ssid *ssid = wpa_s->current_ssid; + + if (!ssid) + return 0; + + /* + * TODO: Define when scan results are no longer valid for the candidate + * list. + */ + os_get_reltime(&now); + if (os_reltime_expired(&now, &wpa_s->last_scan, 10)) + return 0; + + wpa_printf(MSG_DEBUG, + "WNM: Add candidate list to BSS Transition Management Response frame"); + for (i = 0; i < wpa_s->last_scan_res_used && pref; i++) { + struct wpa_bss *bss = wpa_s->last_scan_res[i]; + int res; + + if (wpa_scan_res_match(wpa_s, i, bss, ssid, 1)) { + res = wnm_nei_rep_add_bss(wpa_s, bss, pos, len, pref--); + if (res == -2) + continue; /* could not build entry for BSS */ + if (res < 0) + break; /* no more room for candidates */ + if (pref == 1) + break; + + pos += res; + len -= res; + } + } + + wpa_hexdump(MSG_DEBUG, + "WNM: BSS Transition Management Response candidate list", + buf, pos - buf); + + return pos - buf; +} + + 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) { - u8 buf[1000], *pos; + u8 buf[2000], *pos; struct ieee80211_mgmt *mgmt; size_t len; int res; @@ -583,6 +823,17 @@ static void wnm_send_bss_transition_mgmt_resp( pos += ETH_ALEN; } + if (status == WNM_BSS_TM_ACCEPT) + pos += wnm_add_cand_list(wpa_s, pos, buf + sizeof(buf) - pos); + +#ifdef CONFIG_MBO + if (status != WNM_BSS_TM_ACCEPT) { + pos += wpas_mbo_ie_bss_trans_reject( + wpa_s, pos, buf + sizeof(buf) - pos, + MBO_TRANSITION_REJECT_REASON_UNSPECIFIED); + } +#endif /* CONFIG_MBO */ + len = pos - (u8 *) &mgmt->u.action.category; res = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, @@ -595,6 +846,41 @@ static void wnm_send_bss_transition_mgmt_resp( } +static void wnm_bss_tm_connect(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss, struct wpa_ssid *ssid, + int after_new_scan) +{ + wpa_dbg(wpa_s, MSG_DEBUG, + "WNM: Transition to BSS " MACSTR + " based on BSS Transition Management Request (old BSSID " + MACSTR " after_new_scan=%d)", + MAC2STR(bss->bssid), MAC2STR(wpa_s->bssid), after_new_scan); + + /* Send the BSS Management Response - Accept */ + if (wpa_s->wnm_reply) { + 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); + } + + if (bss == wpa_s->current_bss) { + wpa_printf(MSG_DEBUG, + "WNM: Already associated with the preferred candidate"); + wnm_deallocate_memory(wpa_s); + return; + } + + wpa_s->reassociate = 1; + wpa_printf(MSG_DEBUG, "WNM: Issuing connect"); + wpa_supplicant_connect(wpa_s, bss, ssid); + wnm_deallocate_memory(wpa_s); +} + + int wnm_scan_process(struct wpa_supplicant *wpa_s, int reply_on_fail) { struct wpa_bss *bss; @@ -604,6 +890,8 @@ int wnm_scan_process(struct wpa_supplicant *wpa_s, int reply_on_fail) if (!wpa_s->wnm_neighbor_report_elements) return 0; + wpa_dbg(wpa_s, MSG_DEBUG, + "WNM: Process scan results for BSS Transition Management"); if (os_reltime_before(&wpa_s->wnm_cand_valid_until, &wpa_s->scan_trigger_time)) { wpa_printf(MSG_DEBUG, "WNM: Previously stored BSS transition candidate list is not valid anymore - drop it"); @@ -619,7 +907,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); + bss = compare_scan_neighbor_results(wpa_s, 0); if (!bss) { wpa_printf(MSG_DEBUG, "WNM: No BSS transition candidate match found"); status = WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES; @@ -627,25 +915,7 @@ int wnm_scan_process(struct wpa_supplicant *wpa_s, int reply_on_fail) } /* Associate to the network */ - /* Send the BSS Management Response - Accept */ - if (wpa_s->wnm_reply) { - wpa_s->wnm_reply = 0; - wnm_send_bss_transition_mgmt_resp(wpa_s, - wpa_s->wnm_dialog_token, - WNM_BSS_TM_ACCEPT, - 0, bss->bssid); - } - - if (bss == wpa_s->current_bss) { - wpa_printf(MSG_DEBUG, - "WNM: Already associated with the preferred candidate"); - wnm_deallocate_memory(wpa_s); - return 1; - } - - wpa_s->reassociate = 1; - wpa_supplicant_connect(wpa_s, bss, ssid); - wnm_deallocate_memory(wpa_s); + wnm_bss_tm_connect(wpa_s, bss, ssid, 1); return 1; send_bss_resp_fail: @@ -786,12 +1056,88 @@ static void wnm_set_scan_freqs(struct wpa_supplicant *wpa_s) } +static int wnm_fetch_scan_results(struct wpa_supplicant *wpa_s) +{ + struct wpa_scan_results *scan_res; + struct wpa_bss *bss; + struct wpa_ssid *ssid = wpa_s->current_ssid; + u8 i, found = 0; + size_t j; + + wpa_dbg(wpa_s, MSG_DEBUG, + "WNM: Fetch current scan results from the driver for checking transition candidates"); + scan_res = wpa_drv_get_scan_results2(wpa_s); + if (!scan_res) { + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Failed to get scan results"); + return 0; + } + + if (scan_res->fetch_time.sec == 0) + os_get_reltime(&scan_res->fetch_time); + + filter_scan_res(wpa_s, scan_res); + + for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { + struct neighbor_report *nei; + + nei = &wpa_s->wnm_neighbor_report_elements[i]; + if (nei->preference_present && nei->preference == 0) + continue; + + for (j = 0; j < scan_res->num; j++) { + struct wpa_scan_res *res; + const u8 *ssid_ie; + + res = scan_res->res[j]; + if (os_memcmp(nei->bssid, res->bssid, ETH_ALEN) != 0 || + res->age > WNM_SCAN_RESULT_AGE * 1000) + continue; + bss = wpa_s->current_bss; + ssid_ie = wpa_scan_get_ie(res, WLAN_EID_SSID); + if (bss && ssid_ie && + (bss->ssid_len != ssid_ie[1] || + os_memcmp(bss->ssid, ssid_ie + 2, + bss->ssid_len) != 0)) + continue; + + /* Potential candidate found */ + found = 1; + scan_snr(res); + scan_est_throughput(wpa_s, res); + wpa_bss_update_scan_res(wpa_s, res, + &scan_res->fetch_time); + } + } + + wpa_scan_results_free(scan_res); + if (!found) { + wpa_dbg(wpa_s, MSG_DEBUG, + "WNM: No transition candidate matches existing scan results"); + return 0; + } + + bss = compare_scan_neighbor_results(wpa_s, WNM_SCAN_RESULT_AGE); + if (!bss) { + wpa_dbg(wpa_s, MSG_DEBUG, + "WNM: Comparison of scan results against transition candidates did not find matches"); + return 0; + } + + /* Associate to the network */ + wnm_bss_tm_connect(wpa_s, bss, ssid, 0); + return 1; +} + + static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, const u8 *pos, const u8 *end, int reply) { unsigned int beacon_int; u8 valid_int; +#ifdef CONFIG_MBO + const u8 *vendor; +#endif /* CONFIG_MBO */ if (end - pos < 5) return; @@ -852,6 +1198,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; @@ -880,11 +1232,22 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, rep = &wpa_s->wnm_neighbor_report_elements[ wpa_s->wnm_num_neighbor_report]; wnm_parse_neighbor_report(wpa_s, pos, len, rep); + wpa_s->wnm_num_neighbor_report++; } pos += len; - wpa_s->wnm_num_neighbor_report++; } + + if (!wpa_s->wnm_num_neighbor_report) { + wpa_printf(MSG_DEBUG, + "WNM: Candidate list included bit is set, but no candidates found"); + wnm_send_bss_transition_mgmt_resp( + wpa_s, wpa_s->wnm_dialog_token, + WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES, + 0, NULL); + return; + } + wnm_sort_cand_list(wpa_s); wnm_dump_cand_list(wpa_s); valid_ms = valid_int * beacon_int * 128 / 125; @@ -898,6 +1261,20 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, wpa_s->wnm_cand_valid_until.usec %= 1000000; os_memcpy(wpa_s->wnm_cand_from_bss, wpa_s->bssid, ETH_ALEN); + /* + * Fetch the latest scan results from the kernel and check for + * candidates based on those results first. This can help in + * finding more up-to-date information should the driver has + * done some internal scanning operations after the last scan + * result update in wpa_supplicant. + */ + if (wnm_fetch_scan_results(wpa_s) > 0) + return; + + /* + * Try to use previously received scan results, if they are + * recent enough to use for a connection. + */ if (wpa_s->last_scan_res_used > 0) { struct os_reltime now; @@ -913,6 +1290,14 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, } wnm_set_scan_freqs(wpa_s); + if (wpa_s->wnm_num_neighbor_report == 1) { + os_memcpy(wpa_s->next_scan_bssid, + wpa_s->wnm_neighbor_report_elements[0].bssid, + ETH_ALEN); + wpa_printf(MSG_DEBUG, + "WNM: Scan only for a specific BSSID since there is only a single candidate " + MACSTR, MAC2STR(wpa_s->next_scan_bssid)); + } wpa_supplicant_req_scan(wpa_s, 0, 0); } else if (reply) { enum bss_trans_mgmt_status_code status; @@ -930,16 +1315,17 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, int wnm_send_bss_transition_mgmt_query(struct wpa_supplicant *wpa_s, - u8 query_reason) + u8 query_reason, int cand_list) { - u8 buf[1000], *pos; + u8 buf[2000], *pos; struct ieee80211_mgmt *mgmt; size_t len; int ret; wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Query to " - MACSTR " query_reason=%u", - MAC2STR(wpa_s->bssid), query_reason); + MACSTR " query_reason=%u%s", + MAC2STR(wpa_s->bssid), query_reason, + cand_list ? " candidate list" : ""); mgmt = (struct ieee80211_mgmt *) buf; os_memset(&buf, 0, sizeof(buf)); @@ -954,6 +1340,9 @@ int wnm_send_bss_transition_mgmt_query(struct wpa_supplicant *wpa_s, mgmt->u.action.u.bss_tm_query.query_reason = query_reason; pos = mgmt->u.action.u.bss_tm_query.variable; + if (cand_list) + pos += wnm_add_cand_list(wpa_s, pos, buf + sizeof(buf) - pos); + len = pos - (u8 *) &mgmt->u.action.category; ret = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,