mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
ecc87cfc8d
Update WPS code with upstream. Also moved ESP specific code in another esp code folder.
1134 lines
29 KiB
C
1134 lines
29 KiB
C
/*
|
|
* wpa_supplicant - WNM
|
|
* Copyright (c) 2011-2013, Qualcomm Atheros, Inc.
|
|
*
|
|
* This software may be distributed under the terms of the BSD license.
|
|
* See README for more details.
|
|
*/
|
|
|
|
#include "utils/includes.h"
|
|
|
|
#include "utils/common.h"
|
|
#include "common/ieee802_11_defs.h"
|
|
#include "common/ieee802_11_common.h"
|
|
#include "rsn_supp/wpa.h"
|
|
#include "wpa_supplicant_i.h"
|
|
#include "drivers/driver.h"
|
|
#include "scan.h"
|
|
#include "bss.h"
|
|
#include "wnm_sta.h"
|
|
|
|
#define MAX_TFS_IE_LEN 1024
|
|
#define WNM_MAX_NEIGHBOR_REPORT 10
|
|
|
|
#define WNM_SCAN_RESULT_AGE 2 /* 2 seconds */
|
|
|
|
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].meas_pilot);
|
|
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;
|
|
}
|
|
|
|
|
|
static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep,
|
|
u8 id, u8 elen, const u8 *pos)
|
|
{
|
|
switch (id) {
|
|
case WNM_NEIGHBOR_TSF:
|
|
if (elen < 2 + 2) {
|
|
wpa_printf(MSG_DEBUG, "WNM: Too short TSF");
|
|
break;
|
|
}
|
|
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) {
|
|
wpa_printf(MSG_DEBUG, "WNM: Too short condensed "
|
|
"country string");
|
|
break;
|
|
}
|
|
os_memcpy(rep->country, pos, 2);
|
|
rep->country_present = 1;
|
|
break;
|
|
case WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE:
|
|
if (elen < 1) {
|
|
wpa_printf(MSG_DEBUG, "WNM: Too short BSS transition "
|
|
"candidate");
|
|
break;
|
|
}
|
|
rep->preference = pos[0];
|
|
rep->preference_present = 1;
|
|
break;
|
|
case WNM_NEIGHBOR_BSS_TERMINATION_DURATION:
|
|
if (elen < 10) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"WNM: Too short BSS termination duration");
|
|
break;
|
|
}
|
|
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) {
|
|
wpa_printf(MSG_DEBUG, "WNM: Too short neighbor "
|
|
"bearing");
|
|
break;
|
|
}
|
|
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 < 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->measurement_pilot = pos[0];
|
|
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 < 5) {
|
|
wpa_printf(MSG_DEBUG, "WNM: Too short RRM enabled "
|
|
"capabilities");
|
|
break;
|
|
}
|
|
os_memcpy(rep->rm_capab, pos, 5);
|
|
rep->rm_capab_present = 1;
|
|
break;
|
|
case WNM_NEIGHBOR_MULTIPLE_BSSID:
|
|
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->max_bssid_indicator = pos[0];
|
|
rep->mul_bssid->subelem_len = elen - 1;
|
|
os_memcpy(rep->mul_bssid->subelems, pos + 1, elen - 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void wnm_parse_neighbor_report(struct wpa_supplicant *wpa_s,
|
|
const u8 *pos, u8 len,
|
|
struct neighbor_report *rep)
|
|
{
|
|
u8 left = len;
|
|
|
|
if (left < 13) {
|
|
wpa_printf(MSG_DEBUG, "WNM: Too short neighbor report");
|
|
return;
|
|
}
|
|
|
|
os_memcpy(rep->bssid, pos, ETH_ALEN);
|
|
rep->bssid_info = WPA_GET_LE32(pos + ETH_ALEN);
|
|
rep->regulatory_class = *(pos + 10);
|
|
rep->channel_number = *(pos + 11);
|
|
rep->phy_type = *(pos + 12);
|
|
|
|
pos += 13;
|
|
left -= 13;
|
|
|
|
while (left >= 2) {
|
|
u8 id, elen;
|
|
|
|
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 -= elen;
|
|
pos += elen;
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
#ifndef ESP_SUPPLICANT
|
|
info = wpa_drv_get_bss_trans_status(wpa_s, ¶ms);
|
|
#endif
|
|
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);
|
|
#ifndef ESP_SUPPLICANT
|
|
if (target &&
|
|
(target->level <
|
|
wpa_s->conf->disassoc_imminent_rssi_threshold))
|
|
continue;
|
|
#else
|
|
if (target)
|
|
continue;
|
|
#endif
|
|
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 */
|
|
|
|
|
|
|
|
/* basic function to match candidate profile with current bss */
|
|
bool wpa_scan_res_match(struct wpa_supplicant *wpa_s,
|
|
struct wpa_bss *current_bss,
|
|
struct wpa_bss *target_bss)
|
|
{
|
|
if (current_bss->ssid_len != target_bss->ssid_len) {
|
|
wpa_printf(MSG_DEBUG, "WNM: ssid didn't match");
|
|
return false;
|
|
}
|
|
if (os_memcmp(current_bss->ssid, target_bss->ssid, current_bss->ssid_len) != 0) {
|
|
wpa_printf(MSG_DEBUG, "WNM: ssid didn't match");
|
|
return false;
|
|
}
|
|
|
|
/* Just check for Open/secure mode */
|
|
if ((current_bss->caps & WLAN_CAPABILITY_PRIVACY) != (target_bss->caps & WLAN_CAPABILITY_PRIVACY)) {
|
|
wpa_printf(MSG_DEBUG, "WNM: Security didn't match");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
|
|
if (!bss)
|
|
return NULL;
|
|
|
|
wnm_clear_acceptable(wpa_s);
|
|
|
|
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 %jd seconds old",
|
|
(intmax_t)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_bss &&
|
|
!wpa_scan_res_match(wpa_s, wpa_s->current_bss, target)) {
|
|
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 (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;
|
|
}
|
|
|
|
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(target->bssid), target->level);
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
|
|
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(struct wpabuf **buf, const u8 *bssid,
|
|
u32 bss_info, u8 op_class, u8 chan, u8 phy_type,
|
|
u8 pref)
|
|
{
|
|
if (wpabuf_len(*buf) + 18 >
|
|
IEEE80211_MAX_MMPDU_SIZE - IEEE80211_HDRLEN) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"WNM: No room in frame for Neighbor Report element");
|
|
return -1;
|
|
}
|
|
|
|
if (wpabuf_resize(buf, 18) < 0) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"WNM: Failed to allocate memory for Neighbor Report element");
|
|
return -1;
|
|
}
|
|
|
|
wpabuf_put_u8(*buf, WLAN_EID_NEIGHBOR_REPORT);
|
|
/* length: 13 for basic neighbor report + 3 for preference subelement */
|
|
wpabuf_put_u8(*buf, 16);
|
|
wpabuf_put_data(*buf, bssid, ETH_ALEN);
|
|
wpabuf_put_le32(*buf, bss_info);
|
|
wpabuf_put_u8(*buf, op_class);
|
|
wpabuf_put_u8(*buf, chan);
|
|
wpabuf_put_u8(*buf, phy_type);
|
|
wpabuf_put_u8(*buf, WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE);
|
|
wpabuf_put_u8(*buf, 1);
|
|
wpabuf_put_u8(*buf, pref);
|
|
return 0;
|
|
}
|
|
|
|
static int wnm_nei_rep_add_bss(struct wpa_supplicant *wpa_s,
|
|
struct wpa_bss *bss, struct wpabuf **buf,
|
|
u8 pref)
|
|
{
|
|
const u8 *ie;
|
|
u8 op_class;
|
|
int sec_chan = 0;
|
|
enum phy_type phy_type;
|
|
u32 info;
|
|
struct ieee80211_ht_operation *ht_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;
|
|
}
|
|
|
|
op_class = get_operating_class(bss->channel, sec_chan);
|
|
|
|
phy_type = (sec_chan != 0) ? PHY_TYPE_HT : PHY_TYPE_ERP;
|
|
|
|
info = wnm_get_bss_info(wpa_s, bss);
|
|
|
|
return wnm_add_nei_rep(buf, bss->bssid, info, op_class, bss->channel, phy_type,
|
|
pref);
|
|
}
|
|
|
|
|
|
static void wnm_add_cand_list(struct wpa_supplicant *wpa_s, struct wpabuf **buf)
|
|
{
|
|
unsigned int i, pref = 255;
|
|
struct os_reltime now;
|
|
|
|
if (!wpa_s->current_bss)
|
|
return;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
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, wpa_s->current_bss, bss)) {
|
|
res = wnm_nei_rep_add_bss(wpa_s, bss, buf, pref--);
|
|
if (res == -2)
|
|
continue; /* could not build entry for BSS */
|
|
if (res < 0)
|
|
break; /* no more room for candidates */
|
|
if (pref == 1)
|
|
break;
|
|
}
|
|
}
|
|
|
|
wpa_hexdump_buf(MSG_DEBUG,
|
|
"WNM: BSS Transition Management Response candidate list",
|
|
*buf);
|
|
}
|
|
|
|
|
|
#define BTM_RESP_MIN_SIZE 5 + ETH_ALEN
|
|
|
|
static void wnm_send_bss_transition_mgmt_resp(
|
|
struct wpa_supplicant *wpa_s, u8 dialog_token,
|
|
enum bss_trans_mgmt_status_code status,
|
|
enum mbo_transition_reject_reason reason,
|
|
u8 delay, const u8 *target_bssid)
|
|
{
|
|
struct wpabuf *buf;
|
|
int res;
|
|
|
|
if (!wpa_s->current_bss) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"WNM: Current BSS not known - drop response");
|
|
return;
|
|
}
|
|
wpa_printf(MSG_DEBUG,
|
|
"WNM: Send BSS Transition Management Response to " MACSTR
|
|
" dialog_token=%u status=%u reason=%u delay=%d",
|
|
MAC2STR(wpa_s->current_bss->bssid), dialog_token, status, reason, delay);
|
|
|
|
buf = wpabuf_alloc(BTM_RESP_MIN_SIZE);
|
|
if (!buf) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"WNM: Failed to allocate memory for BTM response");
|
|
return;
|
|
}
|
|
|
|
wpabuf_put_u8(buf, WLAN_ACTION_WNM);
|
|
wpabuf_put_u8(buf, WNM_BSS_TRANS_MGMT_RESP);
|
|
wpabuf_put_u8(buf, dialog_token);
|
|
wpabuf_put_u8(buf, status);
|
|
wpabuf_put_u8(buf, delay);
|
|
if (target_bssid) {
|
|
wpabuf_put_data(buf, target_bssid, 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.
|
|
*/
|
|
wpabuf_put_data(buf, "\0\0\0\0\0\0", ETH_ALEN);
|
|
}
|
|
|
|
if (status == WNM_BSS_TM_ACCEPT)
|
|
wnm_add_cand_list(wpa_s, &buf);
|
|
|
|
#ifdef CONFIG_MBO
|
|
if (status != WNM_BSS_TM_ACCEPT &&
|
|
wpa_bss_get_vendor_ie(wpa_s->current_bss, MBO_IE_VENDOR_TYPE)) {
|
|
u8 mbo[10];
|
|
size_t ret;
|
|
|
|
ret = wpas_mbo_ie_bss_trans_reject(wpa_s, mbo, sizeof(mbo),
|
|
reason);
|
|
if (ret) {
|
|
if (wpabuf_resize(&buf, ret) < 0) {
|
|
wpabuf_free(buf);
|
|
wpa_printf(MSG_DEBUG,
|
|
"WNM: Failed to allocate memory for MBO IE");
|
|
return;
|
|
}
|
|
|
|
wpabuf_put_data(buf, mbo, ret);
|
|
}
|
|
}
|
|
#endif /* CONFIG_MBO */
|
|
|
|
res = wpa_drv_send_action(wpa_s, 0, 0,
|
|
wpabuf_head_u8(buf), wpabuf_len(buf), 0);
|
|
if (res < 0) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"WNM: Failed to send BSS Transition Management Response");
|
|
}
|
|
|
|
wpabuf_free(buf);
|
|
}
|
|
|
|
void wnm_bss_tm_connect(struct wpa_supplicant *wpa_s,
|
|
struct wpa_bss *bss, char *ssid,
|
|
int after_new_scan)
|
|
{
|
|
wpa_printf(MSG_DEBUG,
|
|
"WNM: Transition to BSS " MACSTR
|
|
" based on BSS Transition Management Request after_new_scan=%d)",
|
|
MAC2STR(bss->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,
|
|
MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 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_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;
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
/* Compare the Neighbor Report and scan results */
|
|
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;
|
|
goto send_bss_resp_fail;
|
|
}
|
|
|
|
/* Associate to the network */
|
|
wnm_bss_tm_connect(wpa_s, bss, NULL, 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,
|
|
status, reason, 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)
|
|
{
|
|
#ifdef DEBUG_PRINT
|
|
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",
|
|
i, MAC2STR(nei->bssid), nei->bssid_info,
|
|
nei->regulatory_class,
|
|
nei->channel_number, nei->phy_type,
|
|
nei->preference_present ? nei->preference : -1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void wnm_set_scan_freqs(struct wpa_supplicant *wpa_s)
|
|
{
|
|
unsigned int i;
|
|
int num_chan = 0;
|
|
u8 chan = 0;
|
|
|
|
wpa_s->next_scan_chan = 0;
|
|
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];
|
|
if (nei->channel_number <= 0) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"WNM: Unknown neighbor operating channel_number for "
|
|
MACSTR " - scan all channels",
|
|
MAC2STR(nei->bssid));
|
|
return;
|
|
}
|
|
if (nei->channel_number != chan) {
|
|
chan = nei->channel_number;
|
|
num_chan++;
|
|
}
|
|
}
|
|
|
|
if (num_chan == 1) {
|
|
wpa_s->next_scan_chan = chan;
|
|
}
|
|
}
|
|
|
|
static int wnm_fetch_scan_results(struct wpa_supplicant *wpa_s)
|
|
{
|
|
/* ESP doesn't support this */
|
|
return 0;
|
|
}
|
|
|
|
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 */
|
|
#ifdef ESP_SUPPLICANT
|
|
bool scan_required = false;
|
|
#endif
|
|
|
|
if (wpa_s->disable_mbo_oce || wpa_s->disable_btm)
|
|
return;
|
|
|
|
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
|
|
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);
|
|
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, valid_int);
|
|
|
|
#if defined(CONFIG_MBO) && defined(CONFIG_TESTING_OPTIONS)
|
|
if (wpa_s->reject_btm_req_reason) {
|
|
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,
|
|
MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0, NULL);
|
|
return;
|
|
}
|
|
#endif /* CONFIG_MBO && CONFIG_TESTING_OPTIONS */
|
|
|
|
pos += 5;
|
|
|
|
if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) {
|
|
if (end - pos < 12) {
|
|
wpa_printf(MSG_DEBUG, "WNM: Too short BSS TM Request");
|
|
return;
|
|
}
|
|
os_memcpy(wpa_s->wnm_bss_termination_duration, pos, 12);
|
|
pos += 12; /* BSS Termination Duration */
|
|
}
|
|
|
|
if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT) {
|
|
char url[256];
|
|
|
|
if (end - pos < 1 || 1 + pos[0] > end - pos) {
|
|
wpa_printf(MSG_DEBUG, "WNM: Invalid BSS Transition "
|
|
"Management Request (URL)");
|
|
return;
|
|
}
|
|
os_memcpy(url, pos + 1, pos[0]);
|
|
url[pos[0]] = '\0';
|
|
pos += 1 + pos[0];
|
|
|
|
wpa_msg(wpa_s, MSG_DEBUG, "ESS_DISASSOC_IMMINENT %u %s",
|
|
wpa_s->wnm_dissoc_timer * beacon_int * 128 / 125, url);
|
|
}
|
|
|
|
if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT) {
|
|
wpa_msg(wpa_s, MSG_DEBUG, "WNM: Disassociation Imminent - "
|
|
"Disassociation Timer %u", wpa_s->wnm_dissoc_timer);
|
|
if (wpa_s->wnm_dissoc_timer) {
|
|
/* TODO: mark current BSS less preferred for
|
|
* selection */
|
|
#ifdef ESP_SUPPLICANT
|
|
os_memset(wpa_s->next_scan_bssid, 0, ETH_ALEN);
|
|
wpa_s->next_scan_chan = 0;
|
|
scan_required = true;
|
|
#else
|
|
wpa_printf(MSG_DEBUG, "Trying to find another BSS");
|
|
wpa_supplicant_req_scan(wpa_s, 0, 0);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#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_DEBUG, "WNM: Preferred List Available");
|
|
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 (end - pos >= 2 &&
|
|
wpa_s->wnm_num_neighbor_report < WNM_MAX_NEIGHBOR_REPORT)
|
|
{
|
|
u8 tag = *pos++;
|
|
u8 len = *pos++;
|
|
|
|
wpa_printf(MSG_DEBUG, "WNM: Neighbor report tag %u",
|
|
tag);
|
|
if (len > end - pos) {
|
|
wpa_printf(MSG_DEBUG, "WNM: Truncated request");
|
|
return;
|
|
}
|
|
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++;
|
|
#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;
|
|
}
|
|
|
|
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,
|
|
MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 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;
|
|
|
|
/*
|
|
* 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));
|
|
}
|
|
#ifdef ESP_SUPPLICANT
|
|
scan_required = true;
|
|
}
|
|
if (scan_required) {
|
|
wpa_printf(MSG_DEBUG, "Trying to find another BSS");
|
|
#endif
|
|
wpa_supplicant_req_scan(wpa_s, 0, 0);
|
|
} else if (reply) {
|
|
enum bss_trans_mgmt_status_code status;
|
|
if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT)
|
|
status = WNM_BSS_TM_ACCEPT;
|
|
else {
|
|
wpa_msg(wpa_s, MSG_DEBUG, "WNM: BSS Transition Management Request did not include candidates");
|
|
status = WNM_BSS_TM_REJECT_UNSPECIFIED;
|
|
}
|
|
wpa_s->wnm_reply = 0;
|
|
wnm_send_bss_transition_mgmt_resp(
|
|
wpa_s, wpa_s->wnm_dialog_token, status,
|
|
MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
#define BTM_QUERY_MIN_SIZE 4
|
|
|
|
int wnm_send_bss_transition_mgmt_query(struct wpa_supplicant *wpa_s,
|
|
u8 query_reason,
|
|
const char *btm_candidates,
|
|
int cand_list)
|
|
{
|
|
struct wpabuf *buf;
|
|
int ret;
|
|
|
|
wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Query to "
|
|
MACSTR " query_reason=%u%s",
|
|
MAC2STR(wpa_s->current_bss->bssid), query_reason,
|
|
cand_list ? " candidate list" : "");
|
|
|
|
buf = wpabuf_alloc(BTM_QUERY_MIN_SIZE);
|
|
if (!buf)
|
|
return -1;
|
|
|
|
wpabuf_put_u8(buf, WLAN_ACTION_WNM);
|
|
wpabuf_put_u8(buf, WNM_BSS_TRANS_MGMT_QUERY);
|
|
wpabuf_put_u8(buf, 1);
|
|
wpabuf_put_u8(buf, query_reason);
|
|
|
|
if (cand_list)
|
|
wnm_add_cand_list(wpa_s, &buf);
|
|
|
|
if (btm_candidates) {
|
|
const size_t max_len = 1000;
|
|
|
|
ret = wpabuf_resize(&buf, max_len);
|
|
if (ret < 0) {
|
|
wpabuf_free(buf);
|
|
return ret;
|
|
}
|
|
|
|
ret = ieee802_11_parse_candidate_list(btm_candidates,
|
|
wpabuf_put(buf, 0),
|
|
max_len);
|
|
if (ret < 0) {
|
|
wpabuf_free(buf);
|
|
return ret;
|
|
}
|
|
|
|
wpabuf_put(buf, ret);
|
|
}
|
|
|
|
ret = wpa_drv_send_action(wpa_s, 0, 0,
|
|
wpabuf_head_u8(buf), wpabuf_len(buf), 0);
|
|
|
|
wpabuf_free(buf);
|
|
return ret;
|
|
}
|
|
|
|
void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s,
|
|
u8 *sender, u8 *payload, size_t len)
|
|
{
|
|
const u8 *pos, *end;
|
|
u8 act;
|
|
|
|
if (len < 2)
|
|
return;
|
|
|
|
pos = payload;
|
|
act = *pos++;
|
|
end = payload + len;
|
|
|
|
wpa_printf(MSG_DEBUG, "WNM: RX action %u from " MACSTR,
|
|
act, MAC2STR(sender));
|
|
|
|
switch (act) {
|
|
case WNM_BSS_TRANS_MGMT_REQ:
|
|
ieee802_11_rx_bss_trans_mgmt_req(wpa_s, pos, end,
|
|
0x01);
|
|
break;
|
|
default:
|
|
wpa_printf(MSG_ERROR, "WNM: Unknown request");
|
|
break;
|
|
}
|
|
}
|