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=4f8d895ae8a1c6e70e55717660876e5180cf4182;hb=e8afaad7fd9a7fc9a761ed2f8a117bbb6ac9c730;hpb=ae8535b6e1a98ca40ce87650a4179851e7cd13a7 diff --git a/wpa_supplicant/wnm_sta.c b/wpa_supplicant/wnm_sta.c index 4f8d895..7674825 100644 --- a/wpa_supplicant/wnm_sta.c +++ b/wpa_supplicant/wnm_sta.c @@ -10,6 +10,7 @@ #include "utils/common.h" #include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" #include "common/wpa_ctrl.h" #include "rsn_supp/wpa.h" #include "wpa_supplicant_i.h" @@ -18,10 +19,12 @@ #include "ctrl_iface.h" #include "bss.h" #include "wnm_sta.h" +#include "hs20_supplicant.h" #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, @@ -35,12 +38,14 @@ static int ieee80211_11_get_tfs_ie(struct wpa_supplicant *wpa_s, u8 *buf, /* set the TFS IE to driver */ static int ieee80211_11_set_tfs_ie(struct wpa_supplicant *wpa_s, - const u8 *addr, u8 *buf, u16 *buf_len, + const u8 *addr, const u8 *buf, u16 buf_len, enum wnm_oper oper) { + u16 len = buf_len; + wpa_printf(MSG_DEBUG, "%s: TFS set operation %d", __func__, oper); - return wpa_drv_wnm_oper(wpa_s, oper, addr, buf, buf_len); + return wpa_drv_wnm_oper(wpa_s, oper, addr, (u8 *) buf, &len); } @@ -135,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); @@ -145,8 +152,8 @@ int ieee802_11_send_wnmsleep_req(struct wpa_supplicant *wpa_s, static void wnm_sleep_mode_enter_success(struct wpa_supplicant *wpa_s, - u8 *tfsresp_ie_start, - u8 *tfsresp_ie_end) + const u8 *tfsresp_ie_start, + const u8 *tfsresp_ie_end) { wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_ENTER_CONFIRM, wpa_s->bssid, NULL, NULL); @@ -162,7 +169,7 @@ static void wnm_sleep_mode_enter_success(struct wpa_supplicant *wpa_s, /* pass the TFS Resp IE(s) to driver for processing */ if (ieee80211_11_set_tfs_ie(wpa_s, wpa_s->bssid, tfsresp_ie_start, - &tfsresp_ie_len, + tfsresp_ie_len, WNM_SLEEP_TFS_RESP_IE_SET)) wpa_printf(MSG_DEBUG, "WNM: Fail to set TFS Resp IE"); } @@ -181,12 +188,18 @@ static void wnm_sleep_mode_exit_success(struct wpa_supplicant *wpa_s, /* Install GTK/IGTK */ /* point to key data field */ - ptr = (u8 *) frm + 1 + 1 + 2; + ptr = (u8 *) frm + 1 + 2; end = ptr + key_len_total; wpa_hexdump_key(MSG_DEBUG, "WNM: Key Data", ptr, key_len_total); - while (ptr + 1 < end) { - if (ptr + 2 + ptr[1] > end) { + 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 " "length"); if (end > ptr) { @@ -234,31 +247,43 @@ static void ieee802_11_rx_wnmsleep_resp(struct wpa_supplicant *wpa_s, const u8 *frm, int len) { /* - * Action [1] | Diaglog Token [1] | Key Data Len [2] | Key Data | + * Action [1] | Dialog Token [1] | Key Data Len [2] | Key Data | * WNM-Sleep Mode IE | TFS Response IE */ - u8 *pos = (u8 *) frm; /* point to action field */ - u16 key_len_total = le_to_host16(*((u16 *)(frm+2))); + const u8 *pos = frm; /* point to payload after the action field */ + u16 key_len_total; struct wnm_sleep_element *wnmsleep_ie = NULL; /* multiple TFS Resp IE (assuming consecutive) */ - u8 *tfsresp_ie_start = NULL; - u8 *tfsresp_ie_end = NULL; + const u8 *tfsresp_ie_start = NULL; + 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); - wpa_printf(MSG_DEBUG, "action=%d token = %d key_len_total = %d", - frm[0], frm[1], key_len_total); - pos += 4 + key_len_total; - if (pos > frm + len) { + wpa_printf(MSG_DEBUG, "WNM-Sleep Mode Response token=%u key_len_total=%d", + frm[0], key_len_total); + left = len - 3; + if (key_len_total > left) { wpa_printf(MSG_INFO, "WNM: Too short frame for Key Data field"); return; } - while (pos - frm < len) { + pos += 3 + key_len_total; + while (pos - frm + 1 < len) { u8 ie_len = *(pos + 1); - if (pos + 2 + ie_len > frm + len) { + if (2 + ie_len > frm + len - pos) { wpa_printf(MSG_INFO, "WNM: Invalid IE len %u", ie_len); break; } wpa_hexdump(MSG_DEBUG, "WNM: Element", pos, 2 + ie_len); - if (*pos == WLAN_EID_WNMSLEEP) + if (*pos == WLAN_EID_WNMSLEEP && ie_len >= 4) wnmsleep_ie = (struct wnm_sleep_element *) pos; else if (*pos == WLAN_EID_TFS_RESP) { if (!tfsresp_ie_start) @@ -304,16 +329,11 @@ void wnm_deallocate_memory(struct wpa_supplicant *wpa_s) int i; for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { - os_free(wpa_s->wnm_neighbor_report_elements[i].tsf_info); - os_free(wpa_s->wnm_neighbor_report_elements[i].con_coun_str); - os_free(wpa_s->wnm_neighbor_report_elements[i].bss_tran_can); - os_free(wpa_s->wnm_neighbor_report_elements[i].bss_term_dur); - os_free(wpa_s->wnm_neighbor_report_elements[i].bearing); os_free(wpa_s->wnm_neighbor_report_elements[i].meas_pilot); - os_free(wpa_s->wnm_neighbor_report_elements[i].rrm_cap); os_free(wpa_s->wnm_neighbor_report_elements[i].mul_bssid); } + wpa_s->wnm_num_neighbor_report = 0; os_free(wpa_s->wnm_neighbor_report_elements); wpa_s->wnm_neighbor_report_elements = NULL; } @@ -328,12 +348,9 @@ static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep, wpa_printf(MSG_DEBUG, "WNM: Too short TSF"); break; } - rep->tsf_info = os_zalloc(sizeof(struct tsf_info)); - if (rep->tsf_info == NULL) - break; - rep->tsf_info->present = 1; - os_memcpy(rep->tsf_info->tsf_offset, pos, 2); - os_memcpy(rep->tsf_info->beacon_interval, pos + 2, 2); + rep->tsf_offset = WPA_GET_LE16(pos); + rep->beacon_int = WPA_GET_LE16(pos + 2); + rep->tsf_present = 1; break; case WNM_NEIGHBOR_CONDENSED_COUNTRY_STRING: if (elen < 2) { @@ -341,12 +358,8 @@ static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep, "country string"); break; } - rep->con_coun_str = - os_zalloc(sizeof(struct condensed_country_string)); - if (rep->con_coun_str == NULL) - break; - rep->con_coun_str->present = 1; - os_memcpy(rep->con_coun_str->country_string, pos, 2); + os_memcpy(rep->country, pos, 2); + rep->country_present = 1; break; case WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE: if (elen < 1) { @@ -354,25 +367,13 @@ static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep, "candidate"); break; } - rep->bss_tran_can = - os_zalloc(sizeof(struct bss_transition_candidate)); - if (rep->bss_tran_can == NULL) - break; - rep->bss_tran_can->present = 1; - rep->bss_tran_can->preference = pos[0]; + rep->preference = pos[0]; + rep->preference_present = 1; break; case WNM_NEIGHBOR_BSS_TERMINATION_DURATION: - if (elen < 12) { - wpa_printf(MSG_DEBUG, "WNM: Too short BSS termination " - "duration"); - break; - } - rep->bss_term_dur = - os_zalloc(sizeof(struct bss_termination_duration)); - if (rep->bss_term_dur == NULL) - break; - rep->bss_term_dur->present = 1; - os_memcpy(rep->bss_term_dur->duration, pos, 12); + rep->bss_term_tsf = WPA_GET_LE64(pos); + rep->bss_term_dur = WPA_GET_LE16(pos + 8); + rep->bss_term_present = 1; break; case WNM_NEIGHBOR_BEARING: if (elen < 8) { @@ -380,56 +381,82 @@ static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep, "bearing"); break; } - rep->bearing = os_zalloc(sizeof(struct bearing)); - if (rep->bearing == NULL) - break; - rep->bearing->present = 1; - os_memcpy(rep->bearing->bearing, pos, 8); + rep->bearing = WPA_GET_LE16(pos); + rep->distance = WPA_GET_LE32(pos + 2); + rep->rel_height = WPA_GET_LE16(pos + 2 + 4); + rep->bearing_present = 1; break; case WNM_NEIGHBOR_MEASUREMENT_PILOT: - if (elen < 2) { + if (elen < 1) { wpa_printf(MSG_DEBUG, "WNM: Too short measurement " "pilot"); break; } + os_free(rep->meas_pilot); rep->meas_pilot = os_zalloc(sizeof(struct measurement_pilot)); if (rep->meas_pilot == NULL) break; - rep->meas_pilot->present = 1; rep->meas_pilot->measurement_pilot = pos[0]; - rep->meas_pilot->num_vendor_specific = pos[1]; - os_memcpy(rep->meas_pilot->vendor_specific, pos + 2, elen - 2); + rep->meas_pilot->subelem_len = elen - 1; + os_memcpy(rep->meas_pilot->subelems, pos + 1, elen - 1); break; case WNM_NEIGHBOR_RRM_ENABLED_CAPABILITIES: - if (elen < 4) { + if (elen < 5) { wpa_printf(MSG_DEBUG, "WNM: Too short RRM enabled " "capabilities"); break; } - rep->rrm_cap = - os_zalloc(sizeof(struct rrm_enabled_capabilities)); - if (rep->rrm_cap == NULL) - break; - rep->rrm_cap->present = 1; - os_memcpy(rep->rrm_cap->capabilities, pos, 4); + os_memcpy(rep->rm_capab, pos, 5); + rep->rm_capab_present = 1; break; case WNM_NEIGHBOR_MULTIPLE_BSSID: - if (elen < 2) { + if (elen < 1) { wpa_printf(MSG_DEBUG, "WNM: Too short multiple BSSID"); break; } + os_free(rep->mul_bssid); rep->mul_bssid = os_zalloc(sizeof(struct multiple_bssid)); if (rep->mul_bssid == NULL) break; - rep->mul_bssid->present = 1; rep->mul_bssid->max_bssid_indicator = pos[0]; - rep->mul_bssid->num_vendor_specific = pos[1]; - os_memcpy(rep->mul_bssid->vendor_specific, pos + 2, elen - 2); + rep->mul_bssid->subelem_len = elen - 1; + os_memcpy(rep->mul_bssid->subelems, pos + 1, elen - 1); break; } } +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); + + if (elem && elem[1] >= 2) + country = (const char *) (elem + 2); + } + + 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; +} + + static void wnm_parse_neighbor_report(struct wpa_supplicant *wpa_s, const u8 *pos, u8 len, struct neighbor_report *rep) @@ -442,7 +469,7 @@ static void wnm_parse_neighbor_report(struct wpa_supplicant *wpa_s, } os_memcpy(rep->bssid, pos, ETH_ALEN); - os_memcpy(rep->bssid_information, pos + ETH_ALEN, 4); + rep->bssid_info = WPA_GET_LE32(pos + ETH_ALEN); rep->regulatory_class = *(pos + 10); rep->channel_number = *(pos + 11); rep->phy_type = *(pos + 12); @@ -455,40 +482,299 @@ static void wnm_parse_neighbor_report(struct wpa_supplicant *wpa_s, id = *pos++; elen = *pos++; + wpa_printf(MSG_DEBUG, "WNM: Subelement id=%u len=%u", id, elen); + left -= 2; + if (elen > left) { + wpa_printf(MSG_DEBUG, + "WNM: Truncated neighbor report subelement"); + break; + } wnm_parse_neighbor_report_elem(rep, id, elen, pos); - left -= 2 + elen; + left -= elen; pos += elen; } + + rep->freq = wnm_nei_get_chan(wpa_s, rep->regulatory_class, + rep->channel_number); } -static int compare_scan_neighbor_results(struct wpa_supplicant *wpa_s, - struct wpa_scan_results *scan_res, - struct neighbor_report *neigh_rep, - u8 num_neigh_rep, u8 *bssid_to_connect) +static struct wpa_bss * +compare_scan_neighbor_results(struct wpa_supplicant *wpa_s, os_time_t age_secs) { - u8 i, j; + u8 i; + struct wpa_bss *bss = wpa_s->current_bss; + struct wpa_bss *target; - if (scan_res == NULL || num_neigh_rep == 0) - return 0; + if (!bss) + return NULL; - for (i = 0; i < num_neigh_rep; i++) { - for (j = 0; j < scan_res->num; j++) { - /* Check for a better RSSI AP */ - if (os_memcmp(scan_res->res[j]->bssid, - neigh_rep[i].bssid, ETH_ALEN) == 0 && - scan_res->res[j]->level > - wpa_s->current_bss->level) { - /* Got a BSSID with better RSSI value */ - os_memcpy(bssid_to_connect, neigh_rep[i].bssid, - ETH_ALEN); - return 1; + wpa_printf(MSG_DEBUG, "WNM: Current BSS " MACSTR " RSSI %d", + MAC2STR(wpa_s->bssid), bss->level); + + 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) { + wpa_printf(MSG_DEBUG, "Skip excluded BSS " MACSTR, + MAC2STR(nei->bssid)); + continue; + } + + target = wpa_bss_get_bssid(wpa_s, nei->bssid); + if (!target) { + wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR + " (pref %d) not found in scan results", + MAC2STR(nei->bssid), + nei->preference_present ? nei->preference : + -1); + 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) { + /* + * TODO: Could consider allowing transition to another + * ESS if PMF was enabled for the association. + */ + wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR + " (pref %d) in different ESS", + MAC2STR(nei->bssid), + nei->preference_present ? nei->preference : + -1); + 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)", + MAC2STR(nei->bssid), + nei->preference_present ? nei->preference : + -1, + target->level); + continue; + } + + wpa_printf(MSG_DEBUG, + "WNM: Found an acceptable preferred transition candidate BSS " + MACSTR " (RSSI %d)", + MAC2STR(nei->bssid), target->level); + return target; } - return 0; + return NULL; +} + + +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; } @@ -497,13 +783,19 @@ static void wnm_send_bss_transition_mgmt_resp( 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; 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); + if (!wpa_s->current_bss) { + wpa_printf(MSG_DEBUG, + "WNM: Current BSS not known - drop response"); + return; + } mgmt = (struct ieee80211_mgmt *) buf; os_memset(&buf, 0, sizeof(buf)); @@ -521,66 +813,319 @@ static void wnm_send_bss_transition_mgmt_resp( if (target_bssid) { os_memcpy(pos, target_bssid, ETH_ALEN); pos += ETH_ALEN; + } else if (status == WNM_BSS_TM_ACCEPT) { + /* + * P802.11-REVmc clarifies that the Target BSSID field is always + * present when status code is zero, so use a fake value here if + * no BSSID is yet known. + */ + os_memset(pos, 0, ETH_ALEN); + 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; - wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, - wpa_s->own_addr, wpa_s->bssid, - &mgmt->u.action.category, len, 0); + res = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, + wpa_s->own_addr, wpa_s->bssid, + &mgmt->u.action.category, len, 0); + if (res < 0) { + wpa_printf(MSG_DEBUG, + "WNM: Failed to send BSS Transition Management Response"); + } } -void wnm_scan_response(struct wpa_supplicant *wpa_s, - struct wpa_scan_results *scan_res) +static void wnm_bss_tm_connect(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss, struct wpa_ssid *ssid, + int after_new_scan) { - u8 bssid[ETH_ALEN]; - - if (scan_res == NULL) { - wpa_printf(MSG_ERROR, "Scan result is NULL"); - goto send_bss_resp_fail; - } + 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); - /* Compare the Neighbor Report and scan results */ - if (compare_scan_neighbor_results(wpa_s, scan_res, - wpa_s->wnm_neighbor_report_elements, - wpa_s->wnm_num_neighbor_report, - bssid) == 1) { - /* Associate to the network */ - struct wpa_bss *bss; - struct wpa_ssid *ssid = wpa_s->current_ssid; - - bss = wpa_bss_get_bssid(wpa_s, bssid); - if (!bss) { - wpa_printf(MSG_DEBUG, "WNM: Target AP not found from " - "BSS table"); - goto send_bss_resp_fail; - } - - /* Send the BSS Management Response - Accept */ - if (wpa_s->wnm_reply) { - wnm_send_bss_transition_mgmt_resp(wpa_s, + /* 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, NULL); - } + 0, bss->bssid); + } - wpa_s->reassociate = 1; - wpa_supplicant_connect(wpa_s, bss, ssid); + if (bss == wpa_s->current_bss) { + wpa_printf(MSG_DEBUG, + "WNM: Already associated with the preferred candidate"); wnm_deallocate_memory(wpa_s); return; } - /* Send reject response for all the failures */ -send_bss_resp_fail: + 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; + struct wpa_ssid *ssid = wpa_s->current_ssid; + enum bss_trans_mgmt_status_code status = WNM_BSS_TM_REJECT_UNSPECIFIED; + + 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"); + wnm_deallocate_memory(wpa_s); + return 0; + } + + if (!wpa_s->current_bss || + os_memcmp(wpa_s->wnm_cand_from_bss, wpa_s->current_bss->bssid, + ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, "WNM: Stored BSS transition candidate list not from the current BSS - ignore it"); + return 0; + } + + /* Compare the Neighbor Report and scan results */ + 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; + goto send_bss_resp_fail; + } + + /* Associate to the network */ + wnm_bss_tm_connect(wpa_s, bss, ssid, 1); + return 1; + +send_bss_resp_fail: + if (!reply_on_fail) + return 0; + + /* Send reject response for all the failures */ + 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_REJECT_UNSPECIFIED, - 0, NULL); + status, 0, NULL); + } + wnm_deallocate_memory(wpa_s); + + return 0; +} + + +static int cand_pref_compar(const void *a, const void *b) +{ + const struct neighbor_report *aa = a; + const struct neighbor_report *bb = b; + + if (!aa->preference_present && !bb->preference_present) + return 0; + if (!aa->preference_present) + return 1; + if (!bb->preference_present) + return -1; + if (bb->preference > aa->preference) + return 1; + if (bb->preference < aa->preference) + return -1; + return 0; +} + + +static void wnm_sort_cand_list(struct wpa_supplicant *wpa_s) +{ + if (!wpa_s->wnm_neighbor_report_elements) + return; + qsort(wpa_s->wnm_neighbor_report_elements, + wpa_s->wnm_num_neighbor_report, sizeof(struct neighbor_report), + cand_pref_compar); +} + + +static void wnm_dump_cand_list(struct wpa_supplicant *wpa_s) +{ + unsigned int i; + + wpa_printf(MSG_DEBUG, "WNM: BSS Transition Candidate List"); + if (!wpa_s->wnm_neighbor_report_elements) + return; + for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { + struct neighbor_report *nei; + + nei = &wpa_s->wnm_neighbor_report_elements[i]; + wpa_printf(MSG_DEBUG, "%u: " MACSTR + " info=0x%x op_class=%u chan=%u phy=%u pref=%d freq=%d", + i, MAC2STR(nei->bssid), nei->bssid_info, + nei->regulatory_class, + nei->channel_number, nei->phy_type, + nei->preference_present ? nei->preference : -1, + nei->freq); + } +} + + +static int chan_supported(struct wpa_supplicant *wpa_s, int freq) +{ + unsigned int i; + + for (i = 0; i < wpa_s->hw.num_modes; i++) { + struct hostapd_hw_modes *mode = &wpa_s->hw.modes[i]; + int j; + + for (j = 0; j < mode->num_channels; j++) { + struct hostapd_channel_data *chan; + + chan = &mode->channels[j]; + if (chan->freq == freq && + !(chan->flag & HOSTAPD_CHAN_DISABLED)) + return 1; + } + } + + return 0; +} + + +static void wnm_set_scan_freqs(struct wpa_supplicant *wpa_s) +{ + int *freqs; + int num_freqs = 0; + unsigned int i; + + if (!wpa_s->wnm_neighbor_report_elements) + return; + + if (wpa_s->hw.modes == NULL) + return; + + os_free(wpa_s->next_scan_freqs); + wpa_s->next_scan_freqs = NULL; + + freqs = os_calloc(wpa_s->wnm_num_neighbor_report + 1, sizeof(int)); + if (freqs == NULL) + return; + + 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->freq <= 0) { + wpa_printf(MSG_DEBUG, + "WNM: Unknown neighbor operating frequency for " + MACSTR " - scan all channels", + MAC2STR(nei->bssid)); + os_free(freqs); + return; + } + if (chan_supported(wpa_s, nei->freq)) + add_freq(freqs, &num_freqs, nei->freq); + } + + if (num_freqs == 0) { + os_free(freqs); + return; } - return; + + wpa_printf(MSG_DEBUG, + "WNM: Scan %d frequencies based on transition candidate list", + num_freqs); + wpa_s->next_scan_freqs = freqs; +} + + +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; } @@ -588,25 +1133,36 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, const u8 *pos, const u8 *end, int reply) { - if (pos + 5 > end) + unsigned int beacon_int; + u8 valid_int; +#ifdef CONFIG_MBO + const u8 *vendor; +#endif /* CONFIG_MBO */ + + if (end - pos < 5) return; + if (wpa_s->current_bss) + beacon_int = wpa_s->current_bss->beacon_int; + else + beacon_int = 100; /* best guess */ + wpa_s->wnm_dialog_token = pos[0]; wpa_s->wnm_mode = pos[1]; wpa_s->wnm_dissoc_timer = WPA_GET_LE16(pos + 2); - wpa_s->wnm_validity_interval = pos[4]; + valid_int = pos[4]; wpa_s->wnm_reply = reply; wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Request: " "dialog_token=%u request_mode=0x%x " "disassoc_timer=%u validity_interval=%u", wpa_s->wnm_dialog_token, wpa_s->wnm_mode, - wpa_s->wnm_dissoc_timer, wpa_s->wnm_validity_interval); + wpa_s->wnm_dissoc_timer, valid_int); pos += 5; if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) { - if (pos + 12 > end) { + if (end - pos < 12) { wpa_printf(MSG_DEBUG, "WNM: Too short BSS TM Request"); return; } @@ -616,9 +1172,8 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT) { char url[256]; - unsigned int beacon_int; - if (pos + 1 > end || pos + 1 + pos[0] > end) { + if (end - pos < 1 || 1 + pos[0] > end - pos) { wpa_printf(MSG_DEBUG, "WNM: Invalid BSS Transition " "Management Request (URL)"); return; @@ -627,11 +1182,6 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, url[pos[0]] = '\0'; pos += 1 + pos[0]; - if (wpa_s->current_bss) - beacon_int = wpa_s->current_bss->beacon_int; - else - beacon_int = 100; /* best guess */ - wpa_msg(wpa_s, MSG_INFO, ESS_DISASSOC_IMMINENT "%d %u %s", wpa_sm_pmf_enabled(wpa_s->wpa), wpa_s->wnm_dissoc_timer * beacon_int * 128 / 125, url); @@ -648,17 +1198,24 @@ 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; + wpa_msg(wpa_s, MSG_INFO, "WNM: Preferred List Available"); - wpa_s->wnm_num_neighbor_report = 0; - os_free(wpa_s->wnm_neighbor_report_elements); - wpa_s->wnm_neighbor_report_elements = os_zalloc( - WNM_MAX_NEIGHBOR_REPORT * + wnm_deallocate_memory(wpa_s); + wpa_s->wnm_neighbor_report_elements = os_calloc( + WNM_MAX_NEIGHBOR_REPORT, sizeof(struct neighbor_report)); if (wpa_s->wnm_neighbor_report_elements == NULL) return; - while (pos + 2 <= end && + while (end - pos >= 2 && wpa_s->wnm_num_neighbor_report < WNM_MAX_NEIGHBOR_REPORT) { u8 tag = *pos++; @@ -666,20 +1223,81 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, wpa_printf(MSG_DEBUG, "WNM: Neighbor report tag %u", tag); - if (pos + len > end) { + if (len > end - pos) { wpa_printf(MSG_DEBUG, "WNM: Truncated request"); return; } - wnm_parse_neighbor_report( - wpa_s, pos, len, - &wpa_s->wnm_neighbor_report_elements[ - wpa_s->wnm_num_neighbor_report]); + if (tag == WLAN_EID_NEIGHBOR_REPORT) { + struct neighbor_report *rep; + 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++; } - wpa_s->scan_res_handler = wnm_scan_response; + 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; + wpa_printf(MSG_DEBUG, "WNM: Candidate list valid for %u ms", + valid_ms); + os_get_reltime(&wpa_s->wnm_cand_valid_until); + wpa_s->wnm_cand_valid_until.sec += valid_ms / 1000; + wpa_s->wnm_cand_valid_until.usec += (valid_ms % 1000) * 1000; + wpa_s->wnm_cand_valid_until.sec += + wpa_s->wnm_cand_valid_until.usec / 1000000; + 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; + + os_get_reltime(&now); + if (!os_reltime_expired(&now, &wpa_s->last_scan, 10)) { + wpa_printf(MSG_DEBUG, + "WNM: Try to use recent scan results"); + if (wnm_scan_process(wpa_s, 0) > 0) + return; + wpa_printf(MSG_DEBUG, + "WNM: No match in previous scan results - try a new scan"); + } + } + + 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; @@ -697,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)); @@ -717,10 +1336,13 @@ int wnm_send_bss_transition_mgmt_query(struct wpa_supplicant *wpa_s, WLAN_FC_STYPE_ACTION); mgmt->u.action.category = WLAN_ACTION_WNM; mgmt->u.action.u.bss_tm_query.action = WNM_BSS_TRANS_MGMT_QUERY; - mgmt->u.action.u.bss_tm_query.dialog_token = 0; + mgmt->u.action.u.bss_tm_query.dialog_token = 1; 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, @@ -731,23 +1353,170 @@ int wnm_send_bss_transition_mgmt_query(struct wpa_supplicant *wpa_s, } +static void ieee802_11_rx_wnm_notif_req_wfa(struct wpa_supplicant *wpa_s, + const u8 *sa, const u8 *data, + int len) +{ + const u8 *pos, *end, *next; + u8 ie, ie_len; + + pos = data; + end = data + len; + + while (end - pos > 1) { + ie = *pos++; + ie_len = *pos++; + wpa_printf(MSG_DEBUG, "WNM: WFA subelement %u len %u", + ie, ie_len); + if (ie_len > end - pos) { + wpa_printf(MSG_DEBUG, "WNM: Not enough room for " + "subelement"); + break; + } + next = pos + ie_len; + if (ie_len < 4) { + pos = next; + continue; + } + wpa_printf(MSG_DEBUG, "WNM: Subelement OUI %06x type %u", + WPA_GET_BE24(pos), pos[3]); + +#ifdef CONFIG_HS20 + if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 5 && + WPA_GET_BE24(pos) == OUI_WFA && + pos[3] == HS20_WNM_SUB_REM_NEEDED) { + /* Subscription Remediation subelement */ + const u8 *ie_end; + u8 url_len; + char *url; + u8 osu_method; + + wpa_printf(MSG_DEBUG, "WNM: Subscription Remediation " + "subelement"); + ie_end = pos + ie_len; + pos += 4; + url_len = *pos++; + if (url_len == 0) { + wpa_printf(MSG_DEBUG, "WNM: No Server URL included"); + url = NULL; + osu_method = 1; + } else { + if (url_len + 1 > ie_end - pos) { + wpa_printf(MSG_DEBUG, "WNM: Not enough room for Server URL (len=%u) and Server Method (left %d)", + url_len, + (int) (ie_end - pos)); + break; + } + url = os_malloc(url_len + 1); + if (url == NULL) + break; + os_memcpy(url, pos, url_len); + url[url_len] = '\0'; + osu_method = pos[url_len]; + } + hs20_rx_subscription_remediation(wpa_s, url, + osu_method); + os_free(url); + pos = next; + continue; + } + + if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 8 && + WPA_GET_BE24(pos) == OUI_WFA && + pos[3] == HS20_WNM_DEAUTH_IMMINENT_NOTICE) { + const u8 *ie_end; + u8 url_len; + char *url; + u8 code; + u16 reauth_delay; + + ie_end = pos + ie_len; + pos += 4; + code = *pos++; + reauth_delay = WPA_GET_LE16(pos); + pos += 2; + url_len = *pos++; + wpa_printf(MSG_DEBUG, "WNM: HS 2.0 Deauthentication " + "Imminent - Reason Code %u " + "Re-Auth Delay %u URL Length %u", + code, reauth_delay, url_len); + if (url_len > ie_end - pos) + break; + url = os_malloc(url_len + 1); + if (url == NULL) + break; + os_memcpy(url, pos, url_len); + url[url_len] = '\0'; + hs20_rx_deauth_imminent_notice(wpa_s, code, + reauth_delay, url); + os_free(url); + pos = next; + continue; + } +#endif /* CONFIG_HS20 */ + + pos = next; + } +} + + +static void ieee802_11_rx_wnm_notif_req(struct wpa_supplicant *wpa_s, + const u8 *sa, const u8 *frm, int len) +{ + const u8 *pos, *end; + u8 dialog_token, type; + + /* Dialog Token [1] | Type [1] | Subelements */ + + if (len < 2 || sa == NULL) + return; + end = frm + len; + pos = frm; + dialog_token = *pos++; + type = *pos++; + + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Received WNM-Notification Request " + "(dialog_token %u type %u sa " MACSTR ")", + dialog_token, type, MAC2STR(sa)); + wpa_hexdump(MSG_DEBUG, "WNM-Notification Request subelements", + pos, end - pos); + + if (wpa_s->wpa_state != WPA_COMPLETED || + os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0) { + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: WNM-Notification frame not " + "from our AP - ignore it"); + return; + } + + switch (type) { + case 1: + ieee802_11_rx_wnm_notif_req_wfa(wpa_s, sa, pos, end - pos); + break; + default: + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Ignore unknown " + "WNM-Notification type %u", type); + break; + } +} + + void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s, - struct rx_action *action) + const struct ieee80211_mgmt *mgmt, size_t len) { const u8 *pos, *end; u8 act; - if (action->data == NULL || action->len == 0) + if (len < IEEE80211_HDRLEN + 2) return; - pos = action->data; - end = pos + action->len; + pos = ((const u8 *) mgmt) + IEEE80211_HDRLEN + 1; act = *pos++; + end = ((const u8 *) mgmt) + len; wpa_printf(MSG_DEBUG, "WNM: RX action %u from " MACSTR, - act, MAC2STR(action->sa)); + act, MAC2STR(mgmt->sa)); if (wpa_s->wpa_state < WPA_ASSOCIATED || - os_memcmp(action->sa, wpa_s->bssid, ETH_ALEN) != 0) { + os_memcmp(mgmt->sa, wpa_s->bssid, ETH_ALEN) != 0) { wpa_printf(MSG_DEBUG, "WNM: Ignore unexpected WNM Action " "frame"); return; @@ -756,10 +1525,13 @@ void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s, switch (act) { case WNM_BSS_TRANS_MGMT_REQ: ieee802_11_rx_bss_trans_mgmt_req(wpa_s, pos, end, - !(action->da[0] & 0x01)); + !(mgmt->da[0] & 0x01)); break; case WNM_SLEEP_MODE_RESP: - ieee802_11_rx_wnmsleep_resp(wpa_s, action->data, action->len); + ieee802_11_rx_wnmsleep_resp(wpa_s, pos, end - pos); + break; + case WNM_NOTIFICATION_REQ: + ieee802_11_rx_wnm_notif_req(wpa_s, mgmt->sa, pos, end - pos); break; default: wpa_printf(MSG_ERROR, "WNM: Unknown request");