1252190Srpaulo/* 2252190Srpaulo * wpa_supplicant - WNM 3281806Srpaulo * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. 4252190Srpaulo * 5252190Srpaulo * This software may be distributed under the terms of the BSD license. 6252190Srpaulo * See README for more details. 7252190Srpaulo */ 8252190Srpaulo 9252190Srpaulo#include "utils/includes.h" 10252190Srpaulo 11252190Srpaulo#include "utils/common.h" 12252190Srpaulo#include "common/ieee802_11_defs.h" 13281806Srpaulo#include "common/ieee802_11_common.h" 14281806Srpaulo#include "common/wpa_ctrl.h" 15346981Scy#include "common/ocv.h" 16252190Srpaulo#include "rsn_supp/wpa.h" 17346981Scy#include "config.h" 18252190Srpaulo#include "wpa_supplicant_i.h" 19252190Srpaulo#include "driver_i.h" 20252190Srpaulo#include "scan.h" 21281806Srpaulo#include "ctrl_iface.h" 22281806Srpaulo#include "bss.h" 23281806Srpaulo#include "wnm_sta.h" 24346981Scy#include "notify.h" 25281806Srpaulo#include "hs20_supplicant.h" 26252190Srpaulo 27252190Srpaulo#define MAX_TFS_IE_LEN 1024 28281806Srpaulo#define WNM_MAX_NEIGHBOR_REPORT 10 29252190Srpaulo 30337817Scy#define WNM_SCAN_RESULT_AGE 2 /* 2 seconds */ 31252190Srpaulo 32252190Srpaulo/* get the TFS IE from driver */ 33252190Srpaulostatic int ieee80211_11_get_tfs_ie(struct wpa_supplicant *wpa_s, u8 *buf, 34252190Srpaulo u16 *buf_len, enum wnm_oper oper) 35252190Srpaulo{ 36252190Srpaulo wpa_printf(MSG_DEBUG, "%s: TFS get operation %d", __func__, oper); 37252190Srpaulo 38252190Srpaulo return wpa_drv_wnm_oper(wpa_s, oper, wpa_s->bssid, buf, buf_len); 39252190Srpaulo} 40252190Srpaulo 41252190Srpaulo 42252190Srpaulo/* set the TFS IE to driver */ 43252190Srpaulostatic int ieee80211_11_set_tfs_ie(struct wpa_supplicant *wpa_s, 44337817Scy const u8 *addr, const u8 *buf, u16 buf_len, 45252190Srpaulo enum wnm_oper oper) 46252190Srpaulo{ 47337817Scy u16 len = buf_len; 48337817Scy 49252190Srpaulo wpa_printf(MSG_DEBUG, "%s: TFS set operation %d", __func__, oper); 50252190Srpaulo 51337817Scy return wpa_drv_wnm_oper(wpa_s, oper, addr, (u8 *) buf, &len); 52252190Srpaulo} 53252190Srpaulo 54252190Srpaulo 55252190Srpaulo/* MLME-SLEEPMODE.request */ 56252190Srpauloint ieee802_11_send_wnmsleep_req(struct wpa_supplicant *wpa_s, 57252190Srpaulo u8 action, u16 intval, struct wpabuf *tfs_req) 58252190Srpaulo{ 59252190Srpaulo struct ieee80211_mgmt *mgmt; 60252190Srpaulo int res; 61252190Srpaulo size_t len; 62252190Srpaulo struct wnm_sleep_element *wnmsleep_ie; 63346981Scy u8 *wnmtfs_ie, *oci_ie; 64346981Scy u8 wnmsleep_ie_len, oci_ie_len; 65252190Srpaulo u16 wnmtfs_ie_len; /* possibly multiple IE(s) */ 66252190Srpaulo enum wnm_oper tfs_oper = action == 0 ? WNM_SLEEP_TFS_REQ_IE_ADD : 67252190Srpaulo WNM_SLEEP_TFS_REQ_IE_NONE; 68252190Srpaulo 69252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: Request to send WNM-Sleep Mode Request " 70252190Srpaulo "action=%s to " MACSTR, 71252190Srpaulo action == 0 ? "enter" : "exit", 72252190Srpaulo MAC2STR(wpa_s->bssid)); 73252190Srpaulo 74252190Srpaulo /* WNM-Sleep Mode IE */ 75252190Srpaulo wnmsleep_ie_len = sizeof(struct wnm_sleep_element); 76252190Srpaulo wnmsleep_ie = os_zalloc(sizeof(struct wnm_sleep_element)); 77252190Srpaulo if (wnmsleep_ie == NULL) 78252190Srpaulo return -1; 79252190Srpaulo wnmsleep_ie->eid = WLAN_EID_WNMSLEEP; 80252190Srpaulo wnmsleep_ie->len = wnmsleep_ie_len - 2; 81252190Srpaulo wnmsleep_ie->action_type = action; 82252190Srpaulo wnmsleep_ie->status = WNM_STATUS_SLEEP_ACCEPT; 83252190Srpaulo wnmsleep_ie->intval = host_to_le16(intval); 84252190Srpaulo wpa_hexdump(MSG_DEBUG, "WNM: WNM-Sleep Mode element", 85252190Srpaulo (u8 *) wnmsleep_ie, wnmsleep_ie_len); 86252190Srpaulo 87252190Srpaulo /* TFS IE(s) */ 88252190Srpaulo if (tfs_req) { 89252190Srpaulo wnmtfs_ie_len = wpabuf_len(tfs_req); 90346981Scy wnmtfs_ie = os_memdup(wpabuf_head(tfs_req), wnmtfs_ie_len); 91252190Srpaulo if (wnmtfs_ie == NULL) { 92252190Srpaulo os_free(wnmsleep_ie); 93252190Srpaulo return -1; 94252190Srpaulo } 95252190Srpaulo } else { 96252190Srpaulo wnmtfs_ie = os_zalloc(MAX_TFS_IE_LEN); 97252190Srpaulo if (wnmtfs_ie == NULL) { 98252190Srpaulo os_free(wnmsleep_ie); 99252190Srpaulo return -1; 100252190Srpaulo } 101252190Srpaulo if (ieee80211_11_get_tfs_ie(wpa_s, wnmtfs_ie, &wnmtfs_ie_len, 102252190Srpaulo tfs_oper)) { 103252190Srpaulo wnmtfs_ie_len = 0; 104252190Srpaulo os_free(wnmtfs_ie); 105252190Srpaulo wnmtfs_ie = NULL; 106252190Srpaulo } 107252190Srpaulo } 108252190Srpaulo wpa_hexdump(MSG_DEBUG, "WNM: TFS Request element", 109252190Srpaulo (u8 *) wnmtfs_ie, wnmtfs_ie_len); 110252190Srpaulo 111346981Scy oci_ie = NULL; 112346981Scy oci_ie_len = 0; 113346981Scy#ifdef CONFIG_OCV 114346981Scy if (action == WNM_SLEEP_MODE_EXIT && wpa_sm_ocv_enabled(wpa_s->wpa)) { 115346981Scy struct wpa_channel_info ci; 116346981Scy 117346981Scy if (wpa_drv_channel_info(wpa_s, &ci) != 0) { 118346981Scy wpa_printf(MSG_WARNING, 119346981Scy "Failed to get channel info for OCI element in WNM-Sleep Mode frame"); 120346981Scy os_free(wnmsleep_ie); 121346981Scy os_free(wnmtfs_ie); 122346981Scy return -1; 123346981Scy } 124346981Scy 125346981Scy oci_ie_len = OCV_OCI_EXTENDED_LEN; 126346981Scy oci_ie = os_zalloc(oci_ie_len); 127346981Scy if (!oci_ie) { 128346981Scy wpa_printf(MSG_WARNING, 129346981Scy "Failed to allocate buffer for for OCI element in WNM-Sleep Mode frame"); 130346981Scy os_free(wnmsleep_ie); 131346981Scy os_free(wnmtfs_ie); 132346981Scy return -1; 133346981Scy } 134346981Scy 135346981Scy if (ocv_insert_extended_oci(&ci, oci_ie) < 0) { 136346981Scy os_free(wnmsleep_ie); 137346981Scy os_free(wnmtfs_ie); 138346981Scy os_free(oci_ie); 139346981Scy return -1; 140346981Scy } 141346981Scy } 142346981Scy#endif /* CONFIG_OCV */ 143346981Scy 144346981Scy mgmt = os_zalloc(sizeof(*mgmt) + wnmsleep_ie_len + wnmtfs_ie_len + 145346981Scy oci_ie_len); 146252190Srpaulo if (mgmt == NULL) { 147252190Srpaulo wpa_printf(MSG_DEBUG, "MLME: Failed to allocate buffer for " 148252190Srpaulo "WNM-Sleep Request action frame"); 149252190Srpaulo os_free(wnmsleep_ie); 150252190Srpaulo os_free(wnmtfs_ie); 151252190Srpaulo return -1; 152252190Srpaulo } 153252190Srpaulo 154252190Srpaulo os_memcpy(mgmt->da, wpa_s->bssid, ETH_ALEN); 155252190Srpaulo os_memcpy(mgmt->sa, wpa_s->own_addr, ETH_ALEN); 156252190Srpaulo os_memcpy(mgmt->bssid, wpa_s->bssid, ETH_ALEN); 157252190Srpaulo mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, 158252190Srpaulo WLAN_FC_STYPE_ACTION); 159252190Srpaulo mgmt->u.action.category = WLAN_ACTION_WNM; 160252190Srpaulo mgmt->u.action.u.wnm_sleep_req.action = WNM_SLEEP_MODE_REQ; 161252190Srpaulo mgmt->u.action.u.wnm_sleep_req.dialogtoken = 1; 162252190Srpaulo os_memcpy(mgmt->u.action.u.wnm_sleep_req.variable, wnmsleep_ie, 163252190Srpaulo wnmsleep_ie_len); 164252190Srpaulo /* copy TFS IE here */ 165252190Srpaulo if (wnmtfs_ie_len > 0) { 166252190Srpaulo os_memcpy(mgmt->u.action.u.wnm_sleep_req.variable + 167252190Srpaulo wnmsleep_ie_len, wnmtfs_ie, wnmtfs_ie_len); 168252190Srpaulo } 169252190Srpaulo 170346981Scy#ifdef CONFIG_OCV 171346981Scy /* copy OCV OCI here */ 172346981Scy if (oci_ie_len > 0) { 173346981Scy os_memcpy(mgmt->u.action.u.wnm_sleep_req.variable + 174346981Scy wnmsleep_ie_len + wnmtfs_ie_len, oci_ie, oci_ie_len); 175346981Scy } 176346981Scy#endif /* CONFIG_OCV */ 177346981Scy 178252190Srpaulo len = 1 + sizeof(mgmt->u.action.u.wnm_sleep_req) + wnmsleep_ie_len + 179346981Scy wnmtfs_ie_len + oci_ie_len; 180252190Srpaulo 181252190Srpaulo res = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, 182252190Srpaulo wpa_s->own_addr, wpa_s->bssid, 183252190Srpaulo &mgmt->u.action.category, len, 0); 184252190Srpaulo if (res < 0) 185252190Srpaulo wpa_printf(MSG_DEBUG, "Failed to send WNM-Sleep Request " 186252190Srpaulo "(action=%d, intval=%d)", action, intval); 187324697Sgordon else 188324697Sgordon wpa_s->wnmsleep_used = 1; 189252190Srpaulo 190252190Srpaulo os_free(wnmsleep_ie); 191252190Srpaulo os_free(wnmtfs_ie); 192346981Scy os_free(oci_ie); 193252190Srpaulo os_free(mgmt); 194252190Srpaulo 195252190Srpaulo return res; 196252190Srpaulo} 197252190Srpaulo 198252190Srpaulo 199252190Srpaulostatic void wnm_sleep_mode_enter_success(struct wpa_supplicant *wpa_s, 200337817Scy const u8 *tfsresp_ie_start, 201337817Scy const u8 *tfsresp_ie_end) 202252190Srpaulo{ 203252190Srpaulo wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_ENTER_CONFIRM, 204252190Srpaulo wpa_s->bssid, NULL, NULL); 205252190Srpaulo /* remove GTK/IGTK ?? */ 206252190Srpaulo 207252190Srpaulo /* set the TFS Resp IE(s) */ 208252190Srpaulo if (tfsresp_ie_start && tfsresp_ie_end && 209252190Srpaulo tfsresp_ie_end - tfsresp_ie_start >= 0) { 210252190Srpaulo u16 tfsresp_ie_len; 211252190Srpaulo tfsresp_ie_len = (tfsresp_ie_end + tfsresp_ie_end[1] + 2) - 212252190Srpaulo tfsresp_ie_start; 213252190Srpaulo wpa_printf(MSG_DEBUG, "TFS Resp IE(s) found"); 214252190Srpaulo /* pass the TFS Resp IE(s) to driver for processing */ 215252190Srpaulo if (ieee80211_11_set_tfs_ie(wpa_s, wpa_s->bssid, 216252190Srpaulo tfsresp_ie_start, 217337817Scy tfsresp_ie_len, 218252190Srpaulo WNM_SLEEP_TFS_RESP_IE_SET)) 219252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: Fail to set TFS Resp IE"); 220252190Srpaulo } 221252190Srpaulo} 222252190Srpaulo 223252190Srpaulo 224252190Srpaulostatic void wnm_sleep_mode_exit_success(struct wpa_supplicant *wpa_s, 225252190Srpaulo const u8 *frm, u16 key_len_total) 226252190Srpaulo{ 227252190Srpaulo u8 *ptr, *end; 228252190Srpaulo u8 gtk_len; 229252190Srpaulo 230252190Srpaulo wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_EXIT_CONFIRM, wpa_s->bssid, 231252190Srpaulo NULL, NULL); 232252190Srpaulo 233252190Srpaulo /* Install GTK/IGTK */ 234252190Srpaulo 235252190Srpaulo /* point to key data field */ 236281806Srpaulo ptr = (u8 *) frm + 1 + 2; 237252190Srpaulo end = ptr + key_len_total; 238252190Srpaulo wpa_hexdump_key(MSG_DEBUG, "WNM: Key Data", ptr, key_len_total); 239252190Srpaulo 240324697Sgordon if (key_len_total && !wpa_sm_pmf_enabled(wpa_s->wpa)) { 241324697Sgordon wpa_msg(wpa_s, MSG_INFO, 242324697Sgordon "WNM: Ignore Key Data in WNM-Sleep Mode Response - PMF not enabled"); 243324697Sgordon return; 244324697Sgordon } 245324697Sgordon 246337817Scy while (end - ptr > 1) { 247337817Scy if (2 + ptr[1] > end - ptr) { 248252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: Invalid Key Data element " 249252190Srpaulo "length"); 250252190Srpaulo if (end > ptr) { 251252190Srpaulo wpa_hexdump(MSG_DEBUG, "WNM: Remaining data", 252252190Srpaulo ptr, end - ptr); 253252190Srpaulo } 254252190Srpaulo break; 255252190Srpaulo } 256252190Srpaulo if (*ptr == WNM_SLEEP_SUBELEM_GTK) { 257252190Srpaulo if (ptr[1] < 11 + 5) { 258252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: Too short GTK " 259252190Srpaulo "subelem"); 260252190Srpaulo break; 261252190Srpaulo } 262252190Srpaulo gtk_len = *(ptr + 4); 263252190Srpaulo if (ptr[1] < 11 + gtk_len || 264252190Srpaulo gtk_len < 5 || gtk_len > 32) { 265252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: Invalid GTK " 266252190Srpaulo "subelem"); 267252190Srpaulo break; 268252190Srpaulo } 269252190Srpaulo wpa_wnmsleep_install_key( 270252190Srpaulo wpa_s->wpa, 271252190Srpaulo WNM_SLEEP_SUBELEM_GTK, 272252190Srpaulo ptr); 273252190Srpaulo ptr += 13 + gtk_len; 274252190Srpaulo#ifdef CONFIG_IEEE80211W 275252190Srpaulo } else if (*ptr == WNM_SLEEP_SUBELEM_IGTK) { 276252190Srpaulo if (ptr[1] < 2 + 6 + WPA_IGTK_LEN) { 277252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: Too short IGTK " 278252190Srpaulo "subelem"); 279252190Srpaulo break; 280252190Srpaulo } 281252190Srpaulo wpa_wnmsleep_install_key(wpa_s->wpa, 282252190Srpaulo WNM_SLEEP_SUBELEM_IGTK, ptr); 283252190Srpaulo ptr += 10 + WPA_IGTK_LEN; 284252190Srpaulo#endif /* CONFIG_IEEE80211W */ 285252190Srpaulo } else 286252190Srpaulo break; /* skip the loop */ 287252190Srpaulo } 288252190Srpaulo} 289252190Srpaulo 290252190Srpaulo 291252190Srpaulostatic void ieee802_11_rx_wnmsleep_resp(struct wpa_supplicant *wpa_s, 292252190Srpaulo const u8 *frm, int len) 293252190Srpaulo{ 294252190Srpaulo /* 295281806Srpaulo * Action [1] | Dialog Token [1] | Key Data Len [2] | Key Data | 296252190Srpaulo * WNM-Sleep Mode IE | TFS Response IE 297252190Srpaulo */ 298337817Scy const u8 *pos = frm; /* point to payload after the action field */ 299281806Srpaulo u16 key_len_total; 300252190Srpaulo struct wnm_sleep_element *wnmsleep_ie = NULL; 301252190Srpaulo /* multiple TFS Resp IE (assuming consecutive) */ 302337817Scy const u8 *tfsresp_ie_start = NULL; 303337817Scy const u8 *tfsresp_ie_end = NULL; 304346981Scy#ifdef CONFIG_OCV 305346981Scy const u8 *oci_ie = NULL; 306346981Scy u8 oci_ie_len = 0; 307346981Scy#endif /* CONFIG_OCV */ 308281806Srpaulo size_t left; 309252190Srpaulo 310324697Sgordon if (!wpa_s->wnmsleep_used) { 311324697Sgordon wpa_printf(MSG_DEBUG, 312324697Sgordon "WNM: Ignore WNM-Sleep Mode Response frame since WNM-Sleep Mode operation has not been requested"); 313324697Sgordon return; 314324697Sgordon } 315324697Sgordon 316281806Srpaulo if (len < 3) 317281806Srpaulo return; 318281806Srpaulo key_len_total = WPA_GET_LE16(frm + 1); 319281806Srpaulo 320281806Srpaulo wpa_printf(MSG_DEBUG, "WNM-Sleep Mode Response token=%u key_len_total=%d", 321281806Srpaulo frm[0], key_len_total); 322281806Srpaulo left = len - 3; 323281806Srpaulo if (key_len_total > left) { 324252190Srpaulo wpa_printf(MSG_INFO, "WNM: Too short frame for Key Data field"); 325252190Srpaulo return; 326252190Srpaulo } 327281806Srpaulo pos += 3 + key_len_total; 328337817Scy while (pos - frm + 1 < len) { 329252190Srpaulo u8 ie_len = *(pos + 1); 330337817Scy if (2 + ie_len > frm + len - pos) { 331252190Srpaulo wpa_printf(MSG_INFO, "WNM: Invalid IE len %u", ie_len); 332252190Srpaulo break; 333252190Srpaulo } 334252190Srpaulo wpa_hexdump(MSG_DEBUG, "WNM: Element", pos, 2 + ie_len); 335337817Scy if (*pos == WLAN_EID_WNMSLEEP && ie_len >= 4) 336252190Srpaulo wnmsleep_ie = (struct wnm_sleep_element *) pos; 337252190Srpaulo else if (*pos == WLAN_EID_TFS_RESP) { 338252190Srpaulo if (!tfsresp_ie_start) 339252190Srpaulo tfsresp_ie_start = pos; 340252190Srpaulo tfsresp_ie_end = pos; 341346981Scy#ifdef CONFIG_OCV 342346981Scy } else if (*pos == WLAN_EID_EXTENSION && ie_len >= 1 && 343346981Scy pos[2] == WLAN_EID_EXT_OCV_OCI) { 344346981Scy oci_ie = pos + 3; 345346981Scy oci_ie_len = ie_len - 1; 346346981Scy#endif /* CONFIG_OCV */ 347252190Srpaulo } else 348252190Srpaulo wpa_printf(MSG_DEBUG, "EID %d not recognized", *pos); 349252190Srpaulo pos += ie_len + 2; 350252190Srpaulo } 351252190Srpaulo 352252190Srpaulo if (!wnmsleep_ie) { 353252190Srpaulo wpa_printf(MSG_DEBUG, "No WNM-Sleep IE found"); 354252190Srpaulo return; 355252190Srpaulo } 356252190Srpaulo 357346981Scy#ifdef CONFIG_OCV 358346981Scy if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT && 359346981Scy wpa_sm_ocv_enabled(wpa_s->wpa)) { 360346981Scy struct wpa_channel_info ci; 361346981Scy 362346981Scy if (wpa_drv_channel_info(wpa_s, &ci) != 0) { 363346981Scy wpa_msg(wpa_s, MSG_WARNING, 364346981Scy "Failed to get channel info to validate received OCI in WNM-Sleep Mode frame"); 365346981Scy return; 366346981Scy } 367346981Scy 368346981Scy if (ocv_verify_tx_params(oci_ie, oci_ie_len, &ci, 369346981Scy channel_width_to_int(ci.chanwidth), 370346981Scy ci.seg1_idx) != 0) { 371346981Scy wpa_msg(wpa_s, MSG_WARNING, "WNM: %s", ocv_errorstr); 372346981Scy return; 373346981Scy } 374346981Scy } 375346981Scy#endif /* CONFIG_OCV */ 376346981Scy 377324697Sgordon wpa_s->wnmsleep_used = 0; 378324697Sgordon 379252190Srpaulo if (wnmsleep_ie->status == WNM_STATUS_SLEEP_ACCEPT || 380252190Srpaulo wnmsleep_ie->status == WNM_STATUS_SLEEP_EXIT_ACCEPT_GTK_UPDATE) { 381252190Srpaulo wpa_printf(MSG_DEBUG, "Successfully recv WNM-Sleep Response " 382252190Srpaulo "frame (action=%d, intval=%d)", 383252190Srpaulo wnmsleep_ie->action_type, wnmsleep_ie->intval); 384252190Srpaulo if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_ENTER) { 385252190Srpaulo wnm_sleep_mode_enter_success(wpa_s, tfsresp_ie_start, 386252190Srpaulo tfsresp_ie_end); 387252190Srpaulo } else if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT) { 388252190Srpaulo wnm_sleep_mode_exit_success(wpa_s, frm, key_len_total); 389252190Srpaulo } 390252190Srpaulo } else { 391252190Srpaulo wpa_printf(MSG_DEBUG, "Reject recv WNM-Sleep Response frame " 392252190Srpaulo "(action=%d, intval=%d)", 393252190Srpaulo wnmsleep_ie->action_type, wnmsleep_ie->intval); 394252190Srpaulo if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_ENTER) 395252190Srpaulo wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_ENTER_FAIL, 396252190Srpaulo wpa_s->bssid, NULL, NULL); 397252190Srpaulo else if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT) 398252190Srpaulo wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_EXIT_FAIL, 399252190Srpaulo wpa_s->bssid, NULL, NULL); 400252190Srpaulo } 401252190Srpaulo} 402252190Srpaulo 403252190Srpaulo 404281806Srpaulovoid wnm_deallocate_memory(struct wpa_supplicant *wpa_s) 405252190Srpaulo{ 406281806Srpaulo int i; 407281806Srpaulo 408281806Srpaulo for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { 409281806Srpaulo os_free(wpa_s->wnm_neighbor_report_elements[i].meas_pilot); 410281806Srpaulo os_free(wpa_s->wnm_neighbor_report_elements[i].mul_bssid); 411281806Srpaulo } 412281806Srpaulo 413281806Srpaulo wpa_s->wnm_num_neighbor_report = 0; 414281806Srpaulo os_free(wpa_s->wnm_neighbor_report_elements); 415281806Srpaulo wpa_s->wnm_neighbor_report_elements = NULL; 416346981Scy 417346981Scy wpabuf_free(wpa_s->coloc_intf_elems); 418346981Scy wpa_s->coloc_intf_elems = NULL; 419281806Srpaulo} 420281806Srpaulo 421281806Srpaulo 422281806Srpaulostatic void wnm_parse_neighbor_report_elem(struct neighbor_report *rep, 423281806Srpaulo u8 id, u8 elen, const u8 *pos) 424281806Srpaulo{ 425281806Srpaulo switch (id) { 426281806Srpaulo case WNM_NEIGHBOR_TSF: 427281806Srpaulo if (elen < 2 + 2) { 428281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Too short TSF"); 429281806Srpaulo break; 430281806Srpaulo } 431281806Srpaulo rep->tsf_offset = WPA_GET_LE16(pos); 432281806Srpaulo rep->beacon_int = WPA_GET_LE16(pos + 2); 433281806Srpaulo rep->tsf_present = 1; 434281806Srpaulo break; 435281806Srpaulo case WNM_NEIGHBOR_CONDENSED_COUNTRY_STRING: 436281806Srpaulo if (elen < 2) { 437281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Too short condensed " 438281806Srpaulo "country string"); 439281806Srpaulo break; 440281806Srpaulo } 441281806Srpaulo os_memcpy(rep->country, pos, 2); 442281806Srpaulo rep->country_present = 1; 443281806Srpaulo break; 444281806Srpaulo case WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE: 445281806Srpaulo if (elen < 1) { 446281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Too short BSS transition " 447281806Srpaulo "candidate"); 448281806Srpaulo break; 449281806Srpaulo } 450281806Srpaulo rep->preference = pos[0]; 451281806Srpaulo rep->preference_present = 1; 452281806Srpaulo break; 453281806Srpaulo case WNM_NEIGHBOR_BSS_TERMINATION_DURATION: 454351611Scy if (elen < 10) { 455351611Scy wpa_printf(MSG_DEBUG, 456351611Scy "WNM: Too short BSS termination duration"); 457351611Scy break; 458351611Scy } 459281806Srpaulo rep->bss_term_tsf = WPA_GET_LE64(pos); 460281806Srpaulo rep->bss_term_dur = WPA_GET_LE16(pos + 8); 461281806Srpaulo rep->bss_term_present = 1; 462281806Srpaulo break; 463281806Srpaulo case WNM_NEIGHBOR_BEARING: 464281806Srpaulo if (elen < 8) { 465281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Too short neighbor " 466281806Srpaulo "bearing"); 467281806Srpaulo break; 468281806Srpaulo } 469281806Srpaulo rep->bearing = WPA_GET_LE16(pos); 470281806Srpaulo rep->distance = WPA_GET_LE32(pos + 2); 471281806Srpaulo rep->rel_height = WPA_GET_LE16(pos + 2 + 4); 472281806Srpaulo rep->bearing_present = 1; 473281806Srpaulo break; 474281806Srpaulo case WNM_NEIGHBOR_MEASUREMENT_PILOT: 475281806Srpaulo if (elen < 1) { 476281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Too short measurement " 477281806Srpaulo "pilot"); 478281806Srpaulo break; 479281806Srpaulo } 480281806Srpaulo os_free(rep->meas_pilot); 481281806Srpaulo rep->meas_pilot = os_zalloc(sizeof(struct measurement_pilot)); 482281806Srpaulo if (rep->meas_pilot == NULL) 483281806Srpaulo break; 484281806Srpaulo rep->meas_pilot->measurement_pilot = pos[0]; 485281806Srpaulo rep->meas_pilot->subelem_len = elen - 1; 486281806Srpaulo os_memcpy(rep->meas_pilot->subelems, pos + 1, elen - 1); 487281806Srpaulo break; 488281806Srpaulo case WNM_NEIGHBOR_RRM_ENABLED_CAPABILITIES: 489281806Srpaulo if (elen < 5) { 490281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Too short RRM enabled " 491281806Srpaulo "capabilities"); 492281806Srpaulo break; 493281806Srpaulo } 494281806Srpaulo os_memcpy(rep->rm_capab, pos, 5); 495281806Srpaulo rep->rm_capab_present = 1; 496281806Srpaulo break; 497281806Srpaulo case WNM_NEIGHBOR_MULTIPLE_BSSID: 498281806Srpaulo if (elen < 1) { 499281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Too short multiple BSSID"); 500281806Srpaulo break; 501281806Srpaulo } 502281806Srpaulo os_free(rep->mul_bssid); 503281806Srpaulo rep->mul_bssid = os_zalloc(sizeof(struct multiple_bssid)); 504281806Srpaulo if (rep->mul_bssid == NULL) 505281806Srpaulo break; 506281806Srpaulo rep->mul_bssid->max_bssid_indicator = pos[0]; 507281806Srpaulo rep->mul_bssid->subelem_len = elen - 1; 508281806Srpaulo os_memcpy(rep->mul_bssid->subelems, pos + 1, elen - 1); 509281806Srpaulo break; 510281806Srpaulo } 511281806Srpaulo} 512281806Srpaulo 513281806Srpaulo 514281806Srpaulostatic int wnm_nei_get_chan(struct wpa_supplicant *wpa_s, u8 op_class, u8 chan) 515281806Srpaulo{ 516281806Srpaulo struct wpa_bss *bss = wpa_s->current_bss; 517281806Srpaulo const char *country = NULL; 518337817Scy int freq; 519281806Srpaulo 520281806Srpaulo if (bss) { 521281806Srpaulo const u8 *elem = wpa_bss_get_ie(bss, WLAN_EID_COUNTRY); 522281806Srpaulo 523281806Srpaulo if (elem && elem[1] >= 2) 524281806Srpaulo country = (const char *) (elem + 2); 525281806Srpaulo } 526281806Srpaulo 527337817Scy freq = ieee80211_chan_to_freq(country, op_class, chan); 528337817Scy if (freq <= 0 && op_class == 0) { 529337817Scy /* 530337817Scy * Some APs do not advertise correct operating class 531337817Scy * information. Try to determine the most likely operating 532337817Scy * frequency based on the channel number. 533337817Scy */ 534337817Scy if (chan >= 1 && chan <= 13) 535337817Scy freq = 2407 + chan * 5; 536337817Scy else if (chan == 14) 537337817Scy freq = 2484; 538337817Scy else if (chan >= 36 && chan <= 169) 539337817Scy freq = 5000 + chan * 5; 540337817Scy } 541337817Scy return freq; 542281806Srpaulo} 543281806Srpaulo 544281806Srpaulo 545281806Srpaulostatic void wnm_parse_neighbor_report(struct wpa_supplicant *wpa_s, 546281806Srpaulo const u8 *pos, u8 len, 547281806Srpaulo struct neighbor_report *rep) 548281806Srpaulo{ 549281806Srpaulo u8 left = len; 550281806Srpaulo 551281806Srpaulo if (left < 13) { 552281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Too short neighbor report"); 553281806Srpaulo return; 554281806Srpaulo } 555281806Srpaulo 556281806Srpaulo os_memcpy(rep->bssid, pos, ETH_ALEN); 557281806Srpaulo rep->bssid_info = WPA_GET_LE32(pos + ETH_ALEN); 558281806Srpaulo rep->regulatory_class = *(pos + 10); 559281806Srpaulo rep->channel_number = *(pos + 11); 560281806Srpaulo rep->phy_type = *(pos + 12); 561281806Srpaulo 562281806Srpaulo pos += 13; 563281806Srpaulo left -= 13; 564281806Srpaulo 565281806Srpaulo while (left >= 2) { 566281806Srpaulo u8 id, elen; 567281806Srpaulo 568281806Srpaulo id = *pos++; 569281806Srpaulo elen = *pos++; 570281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Subelement id=%u len=%u", id, elen); 571281806Srpaulo left -= 2; 572281806Srpaulo if (elen > left) { 573281806Srpaulo wpa_printf(MSG_DEBUG, 574281806Srpaulo "WNM: Truncated neighbor report subelement"); 575281806Srpaulo break; 576281806Srpaulo } 577281806Srpaulo wnm_parse_neighbor_report_elem(rep, id, elen, pos); 578281806Srpaulo left -= elen; 579281806Srpaulo pos += elen; 580281806Srpaulo } 581281806Srpaulo 582281806Srpaulo rep->freq = wnm_nei_get_chan(wpa_s, rep->regulatory_class, 583281806Srpaulo rep->channel_number); 584281806Srpaulo} 585281806Srpaulo 586281806Srpaulo 587346981Scystatic void wnm_clear_acceptable(struct wpa_supplicant *wpa_s) 588346981Scy{ 589346981Scy unsigned int i; 590346981Scy 591346981Scy for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) 592346981Scy wpa_s->wnm_neighbor_report_elements[i].acceptable = 0; 593346981Scy} 594346981Scy 595346981Scy 596346981Scystatic struct wpa_bss * get_first_acceptable(struct wpa_supplicant *wpa_s) 597346981Scy{ 598346981Scy unsigned int i; 599346981Scy struct neighbor_report *nei; 600346981Scy 601346981Scy for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { 602346981Scy nei = &wpa_s->wnm_neighbor_report_elements[i]; 603346981Scy if (nei->acceptable) 604346981Scy return wpa_bss_get_bssid(wpa_s, nei->bssid); 605346981Scy } 606346981Scy 607346981Scy return NULL; 608346981Scy} 609346981Scy 610346981Scy 611346981Scy#ifdef CONFIG_MBO 612281806Srpaulostatic struct wpa_bss * 613346981Scyget_mbo_transition_candidate(struct wpa_supplicant *wpa_s, 614346981Scy enum mbo_transition_reject_reason *reason) 615281806Srpaulo{ 616346981Scy struct wpa_bss *target = NULL; 617346981Scy struct wpa_bss_trans_info params; 618346981Scy struct wpa_bss_candidate_info *info = NULL; 619346981Scy struct neighbor_report *nei = wpa_s->wnm_neighbor_report_elements; 620346981Scy u8 *first_candidate_bssid = NULL, *pos; 621346981Scy unsigned int i; 622281806Srpaulo 623346981Scy params.mbo_transition_reason = wpa_s->wnm_mbo_transition_reason; 624346981Scy params.n_candidates = 0; 625346981Scy params.bssid = os_calloc(wpa_s->wnm_num_neighbor_report, ETH_ALEN); 626346981Scy if (!params.bssid) 627346981Scy return NULL; 628346981Scy 629346981Scy pos = params.bssid; 630346981Scy for (i = 0; i < wpa_s->wnm_num_neighbor_report; nei++, i++) { 631346981Scy if (nei->is_first) 632346981Scy first_candidate_bssid = nei->bssid; 633346981Scy if (!nei->acceptable) 634346981Scy continue; 635346981Scy os_memcpy(pos, nei->bssid, ETH_ALEN); 636346981Scy pos += ETH_ALEN; 637346981Scy params.n_candidates++; 638346981Scy } 639346981Scy 640346981Scy if (!params.n_candidates) 641346981Scy goto end; 642346981Scy 643346981Scy info = wpa_drv_get_bss_trans_status(wpa_s, ¶ms); 644346981Scy if (!info) { 645346981Scy /* If failed to get candidate BSS transition status from driver, 646346981Scy * get the first acceptable candidate from wpa_supplicant. 647346981Scy */ 648346981Scy target = wpa_bss_get_bssid(wpa_s, params.bssid); 649346981Scy goto end; 650346981Scy } 651346981Scy 652346981Scy /* Get the first acceptable candidate from driver */ 653346981Scy for (i = 0; i < info->num; i++) { 654346981Scy if (info->candidates[i].is_accept) { 655346981Scy target = wpa_bss_get_bssid(wpa_s, 656346981Scy info->candidates[i].bssid); 657346981Scy goto end; 658346981Scy } 659346981Scy } 660346981Scy 661346981Scy /* If Disassociation Imminent is set and driver rejects all the 662346981Scy * candidate select first acceptable candidate which has 663346981Scy * rssi > disassoc_imminent_rssi_threshold 664346981Scy */ 665346981Scy if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT) { 666346981Scy for (i = 0; i < info->num; i++) { 667346981Scy target = wpa_bss_get_bssid(wpa_s, 668346981Scy info->candidates[i].bssid); 669346981Scy if (target && 670346981Scy (target->level < 671346981Scy wpa_s->conf->disassoc_imminent_rssi_threshold)) 672346981Scy continue; 673346981Scy goto end; 674346981Scy } 675346981Scy } 676346981Scy 677346981Scy /* While sending BTM reject use reason code of the first candidate 678346981Scy * received in BTM request frame 679346981Scy */ 680346981Scy if (reason) { 681346981Scy for (i = 0; i < info->num; i++) { 682346981Scy if (first_candidate_bssid && 683346981Scy os_memcmp(first_candidate_bssid, 684346981Scy info->candidates[i].bssid, ETH_ALEN) == 0) 685346981Scy { 686346981Scy *reason = info->candidates[i].reject_reason; 687346981Scy break; 688346981Scy } 689346981Scy } 690346981Scy } 691346981Scy 692346981Scy target = NULL; 693346981Scy 694346981Scyend: 695346981Scy os_free(params.bssid); 696346981Scy if (info) { 697346981Scy os_free(info->candidates); 698346981Scy os_free(info); 699346981Scy } 700346981Scy return target; 701346981Scy} 702346981Scy#endif /* CONFIG_MBO */ 703346981Scy 704346981Scy 705346981Scystatic struct wpa_bss * 706346981Scycompare_scan_neighbor_results(struct wpa_supplicant *wpa_s, os_time_t age_secs, 707346981Scy enum mbo_transition_reject_reason *reason) 708346981Scy{ 709281806Srpaulo u8 i; 710281806Srpaulo struct wpa_bss *bss = wpa_s->current_bss; 711281806Srpaulo struct wpa_bss *target; 712281806Srpaulo 713281806Srpaulo if (!bss) 714337817Scy return NULL; 715281806Srpaulo 716281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Current BSS " MACSTR " RSSI %d", 717281806Srpaulo MAC2STR(wpa_s->bssid), bss->level); 718281806Srpaulo 719346981Scy wnm_clear_acceptable(wpa_s); 720346981Scy 721281806Srpaulo for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { 722281806Srpaulo struct neighbor_report *nei; 723281806Srpaulo 724281806Srpaulo nei = &wpa_s->wnm_neighbor_report_elements[i]; 725281806Srpaulo if (nei->preference_present && nei->preference == 0) { 726281806Srpaulo wpa_printf(MSG_DEBUG, "Skip excluded BSS " MACSTR, 727281806Srpaulo MAC2STR(nei->bssid)); 728281806Srpaulo continue; 729281806Srpaulo } 730281806Srpaulo 731281806Srpaulo target = wpa_bss_get_bssid(wpa_s, nei->bssid); 732281806Srpaulo if (!target) { 733281806Srpaulo wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR 734281806Srpaulo " (pref %d) not found in scan results", 735281806Srpaulo MAC2STR(nei->bssid), 736281806Srpaulo nei->preference_present ? nei->preference : 737281806Srpaulo -1); 738281806Srpaulo continue; 739281806Srpaulo } 740281806Srpaulo 741337817Scy if (age_secs) { 742337817Scy struct os_reltime now; 743337817Scy 744337817Scy if (os_get_reltime(&now) == 0 && 745337817Scy os_reltime_expired(&now, &target->last_update, 746337817Scy age_secs)) { 747337817Scy wpa_printf(MSG_DEBUG, 748337817Scy "Candidate BSS is more than %ld seconds old", 749337817Scy age_secs); 750337817Scy continue; 751337817Scy } 752337817Scy } 753337817Scy 754281806Srpaulo if (bss->ssid_len != target->ssid_len || 755281806Srpaulo os_memcmp(bss->ssid, target->ssid, bss->ssid_len) != 0) { 756281806Srpaulo /* 757281806Srpaulo * TODO: Could consider allowing transition to another 758281806Srpaulo * ESS if PMF was enabled for the association. 759281806Srpaulo */ 760281806Srpaulo wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR 761281806Srpaulo " (pref %d) in different ESS", 762281806Srpaulo MAC2STR(nei->bssid), 763281806Srpaulo nei->preference_present ? nei->preference : 764281806Srpaulo -1); 765281806Srpaulo continue; 766281806Srpaulo } 767281806Srpaulo 768337817Scy if (wpa_s->current_ssid && 769337817Scy !wpa_scan_res_match(wpa_s, 0, target, wpa_s->current_ssid, 770346981Scy 1, 0)) { 771337817Scy wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR 772337817Scy " (pref %d) does not match the current network profile", 773337817Scy MAC2STR(nei->bssid), 774337817Scy nei->preference_present ? nei->preference : 775337817Scy -1); 776337817Scy continue; 777337817Scy } 778337817Scy 779346981Scy if (wpa_is_bss_tmp_disallowed(wpa_s, target)) { 780337817Scy wpa_printf(MSG_DEBUG, 781337817Scy "MBO: Candidate BSS " MACSTR 782337817Scy " retry delay is not over yet", 783337817Scy MAC2STR(nei->bssid)); 784337817Scy continue; 785337817Scy } 786337817Scy 787281806Srpaulo if (target->level < bss->level && target->level < -80) { 788281806Srpaulo wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR 789281806Srpaulo " (pref %d) does not have sufficient signal level (%d)", 790281806Srpaulo MAC2STR(nei->bssid), 791281806Srpaulo nei->preference_present ? nei->preference : 792281806Srpaulo -1, 793281806Srpaulo target->level); 794281806Srpaulo continue; 795281806Srpaulo } 796281806Srpaulo 797346981Scy nei->acceptable = 1; 798346981Scy } 799346981Scy 800346981Scy#ifdef CONFIG_MBO 801346981Scy if (wpa_s->wnm_mbo_trans_reason_present) 802346981Scy target = get_mbo_transition_candidate(wpa_s, reason); 803346981Scy else 804346981Scy target = get_first_acceptable(wpa_s); 805346981Scy#else /* CONFIG_MBO */ 806346981Scy target = get_first_acceptable(wpa_s); 807346981Scy#endif /* CONFIG_MBO */ 808346981Scy 809346981Scy if (target) { 810281806Srpaulo wpa_printf(MSG_DEBUG, 811281806Srpaulo "WNM: Found an acceptable preferred transition candidate BSS " 812281806Srpaulo MACSTR " (RSSI %d)", 813346981Scy MAC2STR(target->bssid), target->level); 814281806Srpaulo } 815281806Srpaulo 816346981Scy return target; 817281806Srpaulo} 818281806Srpaulo 819281806Srpaulo 820337817Scystatic int wpa_bss_ies_eq(struct wpa_bss *a, struct wpa_bss *b, u8 eid) 821337817Scy{ 822337817Scy const u8 *ie_a, *ie_b; 823337817Scy 824337817Scy if (!a || !b) 825337817Scy return 0; 826337817Scy 827337817Scy ie_a = wpa_bss_get_ie(a, eid); 828337817Scy ie_b = wpa_bss_get_ie(b, eid); 829337817Scy 830337817Scy if (!ie_a || !ie_b || ie_a[1] != ie_b[1]) 831337817Scy return 0; 832337817Scy 833337817Scy return os_memcmp(ie_a, ie_b, ie_a[1]) == 0; 834337817Scy} 835337817Scy 836337817Scy 837337817Scystatic u32 wnm_get_bss_info(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) 838337817Scy{ 839337817Scy u32 info = 0; 840337817Scy 841337817Scy info |= NEI_REP_BSSID_INFO_AP_UNKNOWN_REACH; 842337817Scy 843337817Scy /* 844337817Scy * Leave the security and key scope bits unset to indicate that the 845337817Scy * security information is not available. 846337817Scy */ 847337817Scy 848337817Scy if (bss->caps & WLAN_CAPABILITY_SPECTRUM_MGMT) 849337817Scy info |= NEI_REP_BSSID_INFO_SPECTRUM_MGMT; 850337817Scy if (bss->caps & WLAN_CAPABILITY_QOS) 851337817Scy info |= NEI_REP_BSSID_INFO_QOS; 852337817Scy if (bss->caps & WLAN_CAPABILITY_APSD) 853337817Scy info |= NEI_REP_BSSID_INFO_APSD; 854337817Scy if (bss->caps & WLAN_CAPABILITY_RADIO_MEASUREMENT) 855337817Scy info |= NEI_REP_BSSID_INFO_RM; 856337817Scy if (bss->caps & WLAN_CAPABILITY_DELAYED_BLOCK_ACK) 857337817Scy info |= NEI_REP_BSSID_INFO_DELAYED_BA; 858337817Scy if (bss->caps & WLAN_CAPABILITY_IMM_BLOCK_ACK) 859337817Scy info |= NEI_REP_BSSID_INFO_IMM_BA; 860337817Scy if (wpa_bss_ies_eq(bss, wpa_s->current_bss, WLAN_EID_MOBILITY_DOMAIN)) 861337817Scy info |= NEI_REP_BSSID_INFO_MOBILITY_DOMAIN; 862337817Scy if (wpa_bss_ies_eq(bss, wpa_s->current_bss, WLAN_EID_HT_CAP)) 863337817Scy info |= NEI_REP_BSSID_INFO_HT; 864337817Scy 865337817Scy return info; 866337817Scy} 867337817Scy 868337817Scy 869346981Scystatic int wnm_add_nei_rep(struct wpabuf **buf, const u8 *bssid, 870346981Scy u32 bss_info, u8 op_class, u8 chan, u8 phy_type, 871346981Scy u8 pref) 872337817Scy{ 873346981Scy if (wpabuf_len(*buf) + 18 > 874346981Scy IEEE80211_MAX_MMPDU_SIZE - IEEE80211_HDRLEN) { 875346981Scy wpa_printf(MSG_DEBUG, 876346981Scy "WNM: No room in frame for Neighbor Report element"); 877346981Scy return -1; 878346981Scy } 879337817Scy 880346981Scy if (wpabuf_resize(buf, 18) < 0) { 881337817Scy wpa_printf(MSG_DEBUG, 882346981Scy "WNM: Failed to allocate memory for Neighbor Report element"); 883337817Scy return -1; 884337817Scy } 885337817Scy 886346981Scy wpabuf_put_u8(*buf, WLAN_EID_NEIGHBOR_REPORT); 887337817Scy /* length: 13 for basic neighbor report + 3 for preference subelement */ 888346981Scy wpabuf_put_u8(*buf, 16); 889346981Scy wpabuf_put_data(*buf, bssid, ETH_ALEN); 890346981Scy wpabuf_put_le32(*buf, bss_info); 891346981Scy wpabuf_put_u8(*buf, op_class); 892346981Scy wpabuf_put_u8(*buf, chan); 893346981Scy wpabuf_put_u8(*buf, phy_type); 894346981Scy wpabuf_put_u8(*buf, WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE); 895346981Scy wpabuf_put_u8(*buf, 1); 896346981Scy wpabuf_put_u8(*buf, pref); 897346981Scy return 0; 898337817Scy} 899337817Scy 900337817Scy 901337817Scystatic int wnm_nei_rep_add_bss(struct wpa_supplicant *wpa_s, 902346981Scy struct wpa_bss *bss, struct wpabuf **buf, 903337817Scy u8 pref) 904337817Scy{ 905337817Scy const u8 *ie; 906337817Scy u8 op_class, chan; 907337817Scy int sec_chan = 0, vht = 0; 908337817Scy enum phy_type phy_type; 909337817Scy u32 info; 910337817Scy struct ieee80211_ht_operation *ht_oper = NULL; 911337817Scy struct ieee80211_vht_operation *vht_oper = NULL; 912337817Scy 913337817Scy ie = wpa_bss_get_ie(bss, WLAN_EID_HT_OPERATION); 914337817Scy if (ie && ie[1] >= 2) { 915337817Scy ht_oper = (struct ieee80211_ht_operation *) (ie + 2); 916337817Scy 917337817Scy if (ht_oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE) 918337817Scy sec_chan = 1; 919337817Scy else if (ht_oper->ht_param & 920337817Scy HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW) 921337817Scy sec_chan = -1; 922337817Scy } 923337817Scy 924337817Scy ie = wpa_bss_get_ie(bss, WLAN_EID_VHT_OPERATION); 925337817Scy if (ie && ie[1] >= 1) { 926337817Scy vht_oper = (struct ieee80211_vht_operation *) (ie + 2); 927337817Scy 928351611Scy if (vht_oper->vht_op_info_chwidth == CHANWIDTH_80MHZ || 929351611Scy vht_oper->vht_op_info_chwidth == CHANWIDTH_160MHZ || 930351611Scy vht_oper->vht_op_info_chwidth == CHANWIDTH_80P80MHZ) 931337817Scy vht = vht_oper->vht_op_info_chwidth; 932337817Scy } 933337817Scy 934337817Scy if (ieee80211_freq_to_channel_ext(bss->freq, sec_chan, vht, &op_class, 935337817Scy &chan) == NUM_HOSTAPD_MODES) { 936337817Scy wpa_printf(MSG_DEBUG, 937337817Scy "WNM: Cannot determine operating class and channel"); 938337817Scy return -2; 939337817Scy } 940337817Scy 941337817Scy phy_type = ieee80211_get_phy_type(bss->freq, (ht_oper != NULL), 942337817Scy (vht_oper != NULL)); 943337817Scy if (phy_type == PHY_TYPE_UNSPECIFIED) { 944337817Scy wpa_printf(MSG_DEBUG, 945337817Scy "WNM: Cannot determine BSS phy type for Neighbor Report"); 946337817Scy return -2; 947337817Scy } 948337817Scy 949337817Scy info = wnm_get_bss_info(wpa_s, bss); 950337817Scy 951346981Scy return wnm_add_nei_rep(buf, bss->bssid, info, op_class, chan, phy_type, 952346981Scy pref); 953337817Scy} 954337817Scy 955337817Scy 956346981Scystatic void wnm_add_cand_list(struct wpa_supplicant *wpa_s, struct wpabuf **buf) 957337817Scy{ 958337817Scy unsigned int i, pref = 255; 959337817Scy struct os_reltime now; 960337817Scy struct wpa_ssid *ssid = wpa_s->current_ssid; 961337817Scy 962337817Scy if (!ssid) 963346981Scy return; 964337817Scy 965337817Scy /* 966337817Scy * TODO: Define when scan results are no longer valid for the candidate 967337817Scy * list. 968337817Scy */ 969337817Scy os_get_reltime(&now); 970337817Scy if (os_reltime_expired(&now, &wpa_s->last_scan, 10)) 971346981Scy return; 972337817Scy 973337817Scy wpa_printf(MSG_DEBUG, 974337817Scy "WNM: Add candidate list to BSS Transition Management Response frame"); 975337817Scy for (i = 0; i < wpa_s->last_scan_res_used && pref; i++) { 976337817Scy struct wpa_bss *bss = wpa_s->last_scan_res[i]; 977337817Scy int res; 978337817Scy 979346981Scy if (wpa_scan_res_match(wpa_s, i, bss, ssid, 1, 0)) { 980346981Scy res = wnm_nei_rep_add_bss(wpa_s, bss, buf, pref--); 981337817Scy if (res == -2) 982337817Scy continue; /* could not build entry for BSS */ 983337817Scy if (res < 0) 984337817Scy break; /* no more room for candidates */ 985337817Scy if (pref == 1) 986337817Scy break; 987337817Scy } 988337817Scy } 989337817Scy 990346981Scy wpa_hexdump_buf(MSG_DEBUG, 991346981Scy "WNM: BSS Transition Management Response candidate list", 992346981Scy *buf); 993337817Scy} 994337817Scy 995337817Scy 996346981Scy#define BTM_RESP_MIN_SIZE 5 + ETH_ALEN 997346981Scy 998281806Srpaulostatic void wnm_send_bss_transition_mgmt_resp( 999281806Srpaulo struct wpa_supplicant *wpa_s, u8 dialog_token, 1000346981Scy enum bss_trans_mgmt_status_code status, 1001346981Scy enum mbo_transition_reject_reason reason, 1002346981Scy u8 delay, const u8 *target_bssid) 1003281806Srpaulo{ 1004346981Scy struct wpabuf *buf; 1005281806Srpaulo int res; 1006252190Srpaulo 1007346981Scy wpa_printf(MSG_DEBUG, 1008346981Scy "WNM: Send BSS Transition Management Response to " MACSTR 1009346981Scy " dialog_token=%u status=%u reason=%u delay=%d", 1010346981Scy MAC2STR(wpa_s->bssid), dialog_token, status, reason, delay); 1011281806Srpaulo if (!wpa_s->current_bss) { 1012281806Srpaulo wpa_printf(MSG_DEBUG, 1013281806Srpaulo "WNM: Current BSS not known - drop response"); 1014281806Srpaulo return; 1015281806Srpaulo } 1016252190Srpaulo 1017346981Scy buf = wpabuf_alloc(BTM_RESP_MIN_SIZE); 1018346981Scy if (!buf) { 1019346981Scy wpa_printf(MSG_DEBUG, 1020346981Scy "WNM: Failed to allocate memory for BTM response"); 1021346981Scy return; 1022346981Scy } 1023346981Scy 1024346981Scy wpa_s->bss_tm_status = status; 1025346981Scy wpas_notify_bss_tm_status(wpa_s); 1026346981Scy 1027346981Scy wpabuf_put_u8(buf, WLAN_ACTION_WNM); 1028346981Scy wpabuf_put_u8(buf, WNM_BSS_TRANS_MGMT_RESP); 1029346981Scy wpabuf_put_u8(buf, dialog_token); 1030346981Scy wpabuf_put_u8(buf, status); 1031346981Scy wpabuf_put_u8(buf, delay); 1032252190Srpaulo if (target_bssid) { 1033346981Scy wpabuf_put_data(buf, target_bssid, ETH_ALEN); 1034281806Srpaulo } else if (status == WNM_BSS_TM_ACCEPT) { 1035281806Srpaulo /* 1036281806Srpaulo * P802.11-REVmc clarifies that the Target BSSID field is always 1037281806Srpaulo * present when status code is zero, so use a fake value here if 1038281806Srpaulo * no BSSID is yet known. 1039281806Srpaulo */ 1040346981Scy wpabuf_put_data(buf, "\0\0\0\0\0\0", ETH_ALEN); 1041252190Srpaulo } 1042252190Srpaulo 1043337817Scy if (status == WNM_BSS_TM_ACCEPT) 1044346981Scy wnm_add_cand_list(wpa_s, &buf); 1045337817Scy 1046337817Scy#ifdef CONFIG_MBO 1047346981Scy if (status != WNM_BSS_TM_ACCEPT && 1048346981Scy wpa_bss_get_vendor_ie(wpa_s->current_bss, MBO_IE_VENDOR_TYPE)) { 1049346981Scy u8 mbo[10]; 1050346981Scy size_t ret; 1051346981Scy 1052346981Scy ret = wpas_mbo_ie_bss_trans_reject(wpa_s, mbo, sizeof(mbo), 1053346981Scy reason); 1054346981Scy if (ret) { 1055346981Scy if (wpabuf_resize(&buf, ret) < 0) { 1056346981Scy wpabuf_free(buf); 1057346981Scy wpa_printf(MSG_DEBUG, 1058346981Scy "WNM: Failed to allocate memory for MBO IE"); 1059346981Scy return; 1060346981Scy } 1061346981Scy 1062346981Scy wpabuf_put_data(buf, mbo, ret); 1063346981Scy } 1064337817Scy } 1065337817Scy#endif /* CONFIG_MBO */ 1066337817Scy 1067281806Srpaulo res = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, 1068281806Srpaulo wpa_s->own_addr, wpa_s->bssid, 1069346981Scy wpabuf_head_u8(buf), wpabuf_len(buf), 0); 1070281806Srpaulo if (res < 0) { 1071281806Srpaulo wpa_printf(MSG_DEBUG, 1072281806Srpaulo "WNM: Failed to send BSS Transition Management Response"); 1073281806Srpaulo } 1074346981Scy 1075346981Scy wpabuf_free(buf); 1076252190Srpaulo} 1077252190Srpaulo 1078252190Srpaulo 1079337817Scystatic void wnm_bss_tm_connect(struct wpa_supplicant *wpa_s, 1080337817Scy struct wpa_bss *bss, struct wpa_ssid *ssid, 1081337817Scy int after_new_scan) 1082337817Scy{ 1083337817Scy wpa_dbg(wpa_s, MSG_DEBUG, 1084337817Scy "WNM: Transition to BSS " MACSTR 1085337817Scy " based on BSS Transition Management Request (old BSSID " 1086337817Scy MACSTR " after_new_scan=%d)", 1087337817Scy MAC2STR(bss->bssid), MAC2STR(wpa_s->bssid), after_new_scan); 1088337817Scy 1089337817Scy /* Send the BSS Management Response - Accept */ 1090337817Scy if (wpa_s->wnm_reply) { 1091337817Scy wpa_s->wnm_reply = 0; 1092337817Scy wpa_printf(MSG_DEBUG, 1093337817Scy "WNM: Sending successful BSS Transition Management Response"); 1094346981Scy wnm_send_bss_transition_mgmt_resp( 1095346981Scy wpa_s, wpa_s->wnm_dialog_token, WNM_BSS_TM_ACCEPT, 1096346981Scy MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0, 1097346981Scy bss->bssid); 1098337817Scy } 1099337817Scy 1100337817Scy if (bss == wpa_s->current_bss) { 1101337817Scy wpa_printf(MSG_DEBUG, 1102337817Scy "WNM: Already associated with the preferred candidate"); 1103337817Scy wnm_deallocate_memory(wpa_s); 1104337817Scy return; 1105337817Scy } 1106337817Scy 1107337817Scy wpa_s->reassociate = 1; 1108337817Scy wpa_printf(MSG_DEBUG, "WNM: Issuing connect"); 1109337817Scy wpa_supplicant_connect(wpa_s, bss, ssid); 1110337817Scy wnm_deallocate_memory(wpa_s); 1111337817Scy} 1112337817Scy 1113337817Scy 1114281806Srpauloint wnm_scan_process(struct wpa_supplicant *wpa_s, int reply_on_fail) 1115281806Srpaulo{ 1116281806Srpaulo struct wpa_bss *bss; 1117281806Srpaulo struct wpa_ssid *ssid = wpa_s->current_ssid; 1118281806Srpaulo enum bss_trans_mgmt_status_code status = WNM_BSS_TM_REJECT_UNSPECIFIED; 1119346981Scy enum mbo_transition_reject_reason reason = 1120346981Scy MBO_TRANSITION_REJECT_REASON_UNSPECIFIED; 1121281806Srpaulo 1122281806Srpaulo if (!wpa_s->wnm_neighbor_report_elements) 1123281806Srpaulo return 0; 1124281806Srpaulo 1125337817Scy wpa_dbg(wpa_s, MSG_DEBUG, 1126337817Scy "WNM: Process scan results for BSS Transition Management"); 1127281806Srpaulo if (os_reltime_before(&wpa_s->wnm_cand_valid_until, 1128281806Srpaulo &wpa_s->scan_trigger_time)) { 1129281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Previously stored BSS transition candidate list is not valid anymore - drop it"); 1130281806Srpaulo wnm_deallocate_memory(wpa_s); 1131281806Srpaulo return 0; 1132281806Srpaulo } 1133281806Srpaulo 1134281806Srpaulo if (!wpa_s->current_bss || 1135281806Srpaulo os_memcmp(wpa_s->wnm_cand_from_bss, wpa_s->current_bss->bssid, 1136281806Srpaulo ETH_ALEN) != 0) { 1137281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Stored BSS transition candidate list not from the current BSS - ignore it"); 1138281806Srpaulo return 0; 1139281806Srpaulo } 1140281806Srpaulo 1141281806Srpaulo /* Compare the Neighbor Report and scan results */ 1142346981Scy bss = compare_scan_neighbor_results(wpa_s, 0, &reason); 1143281806Srpaulo if (!bss) { 1144281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: No BSS transition candidate match found"); 1145281806Srpaulo status = WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES; 1146281806Srpaulo goto send_bss_resp_fail; 1147281806Srpaulo } 1148281806Srpaulo 1149281806Srpaulo /* Associate to the network */ 1150337817Scy wnm_bss_tm_connect(wpa_s, bss, ssid, 1); 1151281806Srpaulo return 1; 1152281806Srpaulo 1153281806Srpaulosend_bss_resp_fail: 1154281806Srpaulo if (!reply_on_fail) 1155281806Srpaulo return 0; 1156281806Srpaulo 1157281806Srpaulo /* Send reject response for all the failures */ 1158281806Srpaulo 1159281806Srpaulo if (wpa_s->wnm_reply) { 1160281806Srpaulo wpa_s->wnm_reply = 0; 1161281806Srpaulo wnm_send_bss_transition_mgmt_resp(wpa_s, 1162281806Srpaulo wpa_s->wnm_dialog_token, 1163346981Scy status, reason, 0, NULL); 1164281806Srpaulo } 1165281806Srpaulo wnm_deallocate_memory(wpa_s); 1166281806Srpaulo 1167281806Srpaulo return 0; 1168281806Srpaulo} 1169281806Srpaulo 1170281806Srpaulo 1171281806Srpaulostatic int cand_pref_compar(const void *a, const void *b) 1172281806Srpaulo{ 1173281806Srpaulo const struct neighbor_report *aa = a; 1174281806Srpaulo const struct neighbor_report *bb = b; 1175281806Srpaulo 1176281806Srpaulo if (!aa->preference_present && !bb->preference_present) 1177281806Srpaulo return 0; 1178281806Srpaulo if (!aa->preference_present) 1179281806Srpaulo return 1; 1180281806Srpaulo if (!bb->preference_present) 1181281806Srpaulo return -1; 1182281806Srpaulo if (bb->preference > aa->preference) 1183281806Srpaulo return 1; 1184281806Srpaulo if (bb->preference < aa->preference) 1185281806Srpaulo return -1; 1186281806Srpaulo return 0; 1187281806Srpaulo} 1188281806Srpaulo 1189281806Srpaulo 1190281806Srpaulostatic void wnm_sort_cand_list(struct wpa_supplicant *wpa_s) 1191281806Srpaulo{ 1192281806Srpaulo if (!wpa_s->wnm_neighbor_report_elements) 1193281806Srpaulo return; 1194281806Srpaulo qsort(wpa_s->wnm_neighbor_report_elements, 1195281806Srpaulo wpa_s->wnm_num_neighbor_report, sizeof(struct neighbor_report), 1196281806Srpaulo cand_pref_compar); 1197281806Srpaulo} 1198281806Srpaulo 1199281806Srpaulo 1200281806Srpaulostatic void wnm_dump_cand_list(struct wpa_supplicant *wpa_s) 1201281806Srpaulo{ 1202281806Srpaulo unsigned int i; 1203281806Srpaulo 1204281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: BSS Transition Candidate List"); 1205281806Srpaulo if (!wpa_s->wnm_neighbor_report_elements) 1206281806Srpaulo return; 1207281806Srpaulo for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { 1208281806Srpaulo struct neighbor_report *nei; 1209281806Srpaulo 1210281806Srpaulo nei = &wpa_s->wnm_neighbor_report_elements[i]; 1211281806Srpaulo wpa_printf(MSG_DEBUG, "%u: " MACSTR 1212281806Srpaulo " info=0x%x op_class=%u chan=%u phy=%u pref=%d freq=%d", 1213281806Srpaulo i, MAC2STR(nei->bssid), nei->bssid_info, 1214281806Srpaulo nei->regulatory_class, 1215281806Srpaulo nei->channel_number, nei->phy_type, 1216281806Srpaulo nei->preference_present ? nei->preference : -1, 1217281806Srpaulo nei->freq); 1218281806Srpaulo } 1219281806Srpaulo} 1220281806Srpaulo 1221281806Srpaulo 1222281806Srpaulostatic int chan_supported(struct wpa_supplicant *wpa_s, int freq) 1223281806Srpaulo{ 1224281806Srpaulo unsigned int i; 1225281806Srpaulo 1226281806Srpaulo for (i = 0; i < wpa_s->hw.num_modes; i++) { 1227281806Srpaulo struct hostapd_hw_modes *mode = &wpa_s->hw.modes[i]; 1228281806Srpaulo int j; 1229281806Srpaulo 1230281806Srpaulo for (j = 0; j < mode->num_channels; j++) { 1231281806Srpaulo struct hostapd_channel_data *chan; 1232281806Srpaulo 1233281806Srpaulo chan = &mode->channels[j]; 1234281806Srpaulo if (chan->freq == freq && 1235281806Srpaulo !(chan->flag & HOSTAPD_CHAN_DISABLED)) 1236281806Srpaulo return 1; 1237281806Srpaulo } 1238281806Srpaulo } 1239281806Srpaulo 1240281806Srpaulo return 0; 1241281806Srpaulo} 1242281806Srpaulo 1243281806Srpaulo 1244281806Srpaulostatic void wnm_set_scan_freqs(struct wpa_supplicant *wpa_s) 1245281806Srpaulo{ 1246281806Srpaulo int *freqs; 1247281806Srpaulo int num_freqs = 0; 1248281806Srpaulo unsigned int i; 1249281806Srpaulo 1250281806Srpaulo if (!wpa_s->wnm_neighbor_report_elements) 1251281806Srpaulo return; 1252281806Srpaulo 1253281806Srpaulo if (wpa_s->hw.modes == NULL) 1254281806Srpaulo return; 1255281806Srpaulo 1256281806Srpaulo os_free(wpa_s->next_scan_freqs); 1257281806Srpaulo wpa_s->next_scan_freqs = NULL; 1258281806Srpaulo 1259281806Srpaulo freqs = os_calloc(wpa_s->wnm_num_neighbor_report + 1, sizeof(int)); 1260281806Srpaulo if (freqs == NULL) 1261281806Srpaulo return; 1262281806Srpaulo 1263281806Srpaulo for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { 1264281806Srpaulo struct neighbor_report *nei; 1265281806Srpaulo 1266281806Srpaulo nei = &wpa_s->wnm_neighbor_report_elements[i]; 1267281806Srpaulo if (nei->freq <= 0) { 1268281806Srpaulo wpa_printf(MSG_DEBUG, 1269281806Srpaulo "WNM: Unknown neighbor operating frequency for " 1270281806Srpaulo MACSTR " - scan all channels", 1271281806Srpaulo MAC2STR(nei->bssid)); 1272281806Srpaulo os_free(freqs); 1273281806Srpaulo return; 1274281806Srpaulo } 1275281806Srpaulo if (chan_supported(wpa_s, nei->freq)) 1276281806Srpaulo add_freq(freqs, &num_freqs, nei->freq); 1277281806Srpaulo } 1278281806Srpaulo 1279281806Srpaulo if (num_freqs == 0) { 1280281806Srpaulo os_free(freqs); 1281281806Srpaulo return; 1282281806Srpaulo } 1283281806Srpaulo 1284281806Srpaulo wpa_printf(MSG_DEBUG, 1285281806Srpaulo "WNM: Scan %d frequencies based on transition candidate list", 1286281806Srpaulo num_freqs); 1287281806Srpaulo wpa_s->next_scan_freqs = freqs; 1288281806Srpaulo} 1289281806Srpaulo 1290281806Srpaulo 1291337817Scystatic int wnm_fetch_scan_results(struct wpa_supplicant *wpa_s) 1292337817Scy{ 1293337817Scy struct wpa_scan_results *scan_res; 1294337817Scy struct wpa_bss *bss; 1295337817Scy struct wpa_ssid *ssid = wpa_s->current_ssid; 1296337817Scy u8 i, found = 0; 1297337817Scy size_t j; 1298337817Scy 1299337817Scy wpa_dbg(wpa_s, MSG_DEBUG, 1300337817Scy "WNM: Fetch current scan results from the driver for checking transition candidates"); 1301337817Scy scan_res = wpa_drv_get_scan_results2(wpa_s); 1302337817Scy if (!scan_res) { 1303337817Scy wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Failed to get scan results"); 1304337817Scy return 0; 1305337817Scy } 1306337817Scy 1307337817Scy if (scan_res->fetch_time.sec == 0) 1308337817Scy os_get_reltime(&scan_res->fetch_time); 1309337817Scy 1310337817Scy filter_scan_res(wpa_s, scan_res); 1311337817Scy 1312337817Scy for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) { 1313337817Scy struct neighbor_report *nei; 1314337817Scy 1315337817Scy nei = &wpa_s->wnm_neighbor_report_elements[i]; 1316337817Scy if (nei->preference_present && nei->preference == 0) 1317337817Scy continue; 1318337817Scy 1319337817Scy for (j = 0; j < scan_res->num; j++) { 1320337817Scy struct wpa_scan_res *res; 1321337817Scy const u8 *ssid_ie; 1322337817Scy 1323337817Scy res = scan_res->res[j]; 1324337817Scy if (os_memcmp(nei->bssid, res->bssid, ETH_ALEN) != 0 || 1325337817Scy res->age > WNM_SCAN_RESULT_AGE * 1000) 1326337817Scy continue; 1327337817Scy bss = wpa_s->current_bss; 1328337817Scy ssid_ie = wpa_scan_get_ie(res, WLAN_EID_SSID); 1329337817Scy if (bss && ssid_ie && 1330337817Scy (bss->ssid_len != ssid_ie[1] || 1331337817Scy os_memcmp(bss->ssid, ssid_ie + 2, 1332337817Scy bss->ssid_len) != 0)) 1333337817Scy continue; 1334337817Scy 1335337817Scy /* Potential candidate found */ 1336337817Scy found = 1; 1337337817Scy scan_snr(res); 1338337817Scy scan_est_throughput(wpa_s, res); 1339337817Scy wpa_bss_update_scan_res(wpa_s, res, 1340337817Scy &scan_res->fetch_time); 1341337817Scy } 1342337817Scy } 1343337817Scy 1344337817Scy wpa_scan_results_free(scan_res); 1345337817Scy if (!found) { 1346337817Scy wpa_dbg(wpa_s, MSG_DEBUG, 1347337817Scy "WNM: No transition candidate matches existing scan results"); 1348337817Scy return 0; 1349337817Scy } 1350337817Scy 1351346981Scy bss = compare_scan_neighbor_results(wpa_s, WNM_SCAN_RESULT_AGE, NULL); 1352337817Scy if (!bss) { 1353337817Scy wpa_dbg(wpa_s, MSG_DEBUG, 1354337817Scy "WNM: Comparison of scan results against transition candidates did not find matches"); 1355337817Scy return 0; 1356337817Scy } 1357337817Scy 1358337817Scy /* Associate to the network */ 1359337817Scy wnm_bss_tm_connect(wpa_s, bss, ssid, 0); 1360337817Scy return 1; 1361337817Scy} 1362337817Scy 1363337817Scy 1364252190Srpaulostatic void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, 1365252190Srpaulo const u8 *pos, const u8 *end, 1366252190Srpaulo int reply) 1367252190Srpaulo{ 1368281806Srpaulo unsigned int beacon_int; 1369281806Srpaulo u8 valid_int; 1370337817Scy#ifdef CONFIG_MBO 1371337817Scy const u8 *vendor; 1372337817Scy#endif /* CONFIG_MBO */ 1373252190Srpaulo 1374351611Scy if (wpa_s->conf->disable_btm) 1375351611Scy return; 1376351611Scy 1377337817Scy if (end - pos < 5) 1378252190Srpaulo return; 1379252190Srpaulo 1380346981Scy#ifdef CONFIG_MBO 1381346981Scy wpa_s->wnm_mbo_trans_reason_present = 0; 1382346981Scy wpa_s->wnm_mbo_transition_reason = 0; 1383346981Scy#endif /* CONFIG_MBO */ 1384346981Scy 1385281806Srpaulo if (wpa_s->current_bss) 1386281806Srpaulo beacon_int = wpa_s->current_bss->beacon_int; 1387281806Srpaulo else 1388281806Srpaulo beacon_int = 100; /* best guess */ 1389252190Srpaulo 1390281806Srpaulo wpa_s->wnm_dialog_token = pos[0]; 1391281806Srpaulo wpa_s->wnm_mode = pos[1]; 1392281806Srpaulo wpa_s->wnm_dissoc_timer = WPA_GET_LE16(pos + 2); 1393281806Srpaulo valid_int = pos[4]; 1394281806Srpaulo wpa_s->wnm_reply = reply; 1395281806Srpaulo 1396252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Request: " 1397252190Srpaulo "dialog_token=%u request_mode=0x%x " 1398252190Srpaulo "disassoc_timer=%u validity_interval=%u", 1399281806Srpaulo wpa_s->wnm_dialog_token, wpa_s->wnm_mode, 1400281806Srpaulo wpa_s->wnm_dissoc_timer, valid_int); 1401281806Srpaulo 1402337817Scy#if defined(CONFIG_MBO) && defined(CONFIG_TESTING_OPTIONS) 1403337817Scy if (wpa_s->reject_btm_req_reason) { 1404337817Scy wpa_printf(MSG_INFO, 1405337817Scy "WNM: Testing - reject BSS Transition Management Request: reject_btm_req_reason=%d", 1406337817Scy wpa_s->reject_btm_req_reason); 1407346981Scy wnm_send_bss_transition_mgmt_resp( 1408346981Scy wpa_s, wpa_s->wnm_dialog_token, 1409346981Scy wpa_s->reject_btm_req_reason, 1410346981Scy MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0, NULL); 1411337817Scy return; 1412337817Scy } 1413337817Scy#endif /* CONFIG_MBO && CONFIG_TESTING_OPTIONS */ 1414337817Scy 1415252190Srpaulo pos += 5; 1416281806Srpaulo 1417281806Srpaulo if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) { 1418337817Scy if (end - pos < 12) { 1419281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Too short BSS TM Request"); 1420281806Srpaulo return; 1421281806Srpaulo } 1422281806Srpaulo os_memcpy(wpa_s->wnm_bss_termination_duration, pos, 12); 1423252190Srpaulo pos += 12; /* BSS Termination Duration */ 1424281806Srpaulo } 1425281806Srpaulo 1426281806Srpaulo if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT) { 1427252190Srpaulo char url[256]; 1428281806Srpaulo 1429337817Scy if (end - pos < 1 || 1 + pos[0] > end - pos) { 1430252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: Invalid BSS Transition " 1431252190Srpaulo "Management Request (URL)"); 1432252190Srpaulo return; 1433252190Srpaulo } 1434252190Srpaulo os_memcpy(url, pos + 1, pos[0]); 1435252190Srpaulo url[pos[0]] = '\0'; 1436281806Srpaulo pos += 1 + pos[0]; 1437281806Srpaulo 1438281806Srpaulo wpa_msg(wpa_s, MSG_INFO, ESS_DISASSOC_IMMINENT "%d %u %s", 1439281806Srpaulo wpa_sm_pmf_enabled(wpa_s->wpa), 1440281806Srpaulo wpa_s->wnm_dissoc_timer * beacon_int * 128 / 125, url); 1441252190Srpaulo } 1442252190Srpaulo 1443281806Srpaulo if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT) { 1444252190Srpaulo wpa_msg(wpa_s, MSG_INFO, "WNM: Disassociation Imminent - " 1445281806Srpaulo "Disassociation Timer %u", wpa_s->wnm_dissoc_timer); 1446281806Srpaulo if (wpa_s->wnm_dissoc_timer && !wpa_s->scanning) { 1447252190Srpaulo /* TODO: mark current BSS less preferred for 1448252190Srpaulo * selection */ 1449252190Srpaulo wpa_printf(MSG_DEBUG, "Trying to find another BSS"); 1450252190Srpaulo wpa_supplicant_req_scan(wpa_s, 0, 0); 1451252190Srpaulo } 1452252190Srpaulo } 1453252190Srpaulo 1454337817Scy#ifdef CONFIG_MBO 1455337817Scy vendor = get_ie(pos, end - pos, WLAN_EID_VENDOR_SPECIFIC); 1456337817Scy if (vendor) 1457337817Scy wpas_mbo_ie_trans_req(wpa_s, vendor + 2, vendor[1]); 1458337817Scy#endif /* CONFIG_MBO */ 1459337817Scy 1460281806Srpaulo if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_PREF_CAND_LIST_INCLUDED) { 1461281806Srpaulo unsigned int valid_ms; 1462281806Srpaulo 1463281806Srpaulo wpa_msg(wpa_s, MSG_INFO, "WNM: Preferred List Available"); 1464281806Srpaulo wnm_deallocate_memory(wpa_s); 1465281806Srpaulo wpa_s->wnm_neighbor_report_elements = os_calloc( 1466281806Srpaulo WNM_MAX_NEIGHBOR_REPORT, 1467281806Srpaulo sizeof(struct neighbor_report)); 1468281806Srpaulo if (wpa_s->wnm_neighbor_report_elements == NULL) 1469281806Srpaulo return; 1470281806Srpaulo 1471337817Scy while (end - pos >= 2 && 1472281806Srpaulo wpa_s->wnm_num_neighbor_report < WNM_MAX_NEIGHBOR_REPORT) 1473281806Srpaulo { 1474281806Srpaulo u8 tag = *pos++; 1475281806Srpaulo u8 len = *pos++; 1476281806Srpaulo 1477281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Neighbor report tag %u", 1478281806Srpaulo tag); 1479337817Scy if (len > end - pos) { 1480281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Truncated request"); 1481281806Srpaulo return; 1482281806Srpaulo } 1483281806Srpaulo if (tag == WLAN_EID_NEIGHBOR_REPORT) { 1484281806Srpaulo struct neighbor_report *rep; 1485281806Srpaulo rep = &wpa_s->wnm_neighbor_report_elements[ 1486281806Srpaulo wpa_s->wnm_num_neighbor_report]; 1487281806Srpaulo wnm_parse_neighbor_report(wpa_s, pos, len, rep); 1488337817Scy wpa_s->wnm_num_neighbor_report++; 1489346981Scy#ifdef CONFIG_MBO 1490346981Scy if (wpa_s->wnm_mbo_trans_reason_present && 1491346981Scy wpa_s->wnm_num_neighbor_report == 1) { 1492346981Scy rep->is_first = 1; 1493346981Scy wpa_printf(MSG_DEBUG, 1494346981Scy "WNM: First transition candidate is " 1495346981Scy MACSTR, MAC2STR(rep->bssid)); 1496346981Scy } 1497346981Scy#endif /* CONFIG_MBO */ 1498281806Srpaulo } 1499281806Srpaulo 1500281806Srpaulo pos += len; 1501281806Srpaulo } 1502337817Scy 1503337817Scy if (!wpa_s->wnm_num_neighbor_report) { 1504337817Scy wpa_printf(MSG_DEBUG, 1505337817Scy "WNM: Candidate list included bit is set, but no candidates found"); 1506337817Scy wnm_send_bss_transition_mgmt_resp( 1507337817Scy wpa_s, wpa_s->wnm_dialog_token, 1508337817Scy WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES, 1509346981Scy MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0, 1510346981Scy NULL); 1511337817Scy return; 1512337817Scy } 1513337817Scy 1514281806Srpaulo wnm_sort_cand_list(wpa_s); 1515281806Srpaulo wnm_dump_cand_list(wpa_s); 1516281806Srpaulo valid_ms = valid_int * beacon_int * 128 / 125; 1517281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Candidate list valid for %u ms", 1518281806Srpaulo valid_ms); 1519281806Srpaulo os_get_reltime(&wpa_s->wnm_cand_valid_until); 1520281806Srpaulo wpa_s->wnm_cand_valid_until.sec += valid_ms / 1000; 1521281806Srpaulo wpa_s->wnm_cand_valid_until.usec += (valid_ms % 1000) * 1000; 1522281806Srpaulo wpa_s->wnm_cand_valid_until.sec += 1523281806Srpaulo wpa_s->wnm_cand_valid_until.usec / 1000000; 1524281806Srpaulo wpa_s->wnm_cand_valid_until.usec %= 1000000; 1525281806Srpaulo os_memcpy(wpa_s->wnm_cand_from_bss, wpa_s->bssid, ETH_ALEN); 1526281806Srpaulo 1527337817Scy /* 1528337817Scy * Fetch the latest scan results from the kernel and check for 1529337817Scy * candidates based on those results first. This can help in 1530337817Scy * finding more up-to-date information should the driver has 1531337817Scy * done some internal scanning operations after the last scan 1532337817Scy * result update in wpa_supplicant. 1533337817Scy */ 1534337817Scy if (wnm_fetch_scan_results(wpa_s) > 0) 1535337817Scy return; 1536337817Scy 1537337817Scy /* 1538337817Scy * Try to use previously received scan results, if they are 1539337817Scy * recent enough to use for a connection. 1540337817Scy */ 1541281806Srpaulo if (wpa_s->last_scan_res_used > 0) { 1542281806Srpaulo struct os_reltime now; 1543281806Srpaulo 1544281806Srpaulo os_get_reltime(&now); 1545281806Srpaulo if (!os_reltime_expired(&now, &wpa_s->last_scan, 10)) { 1546281806Srpaulo wpa_printf(MSG_DEBUG, 1547281806Srpaulo "WNM: Try to use recent scan results"); 1548281806Srpaulo if (wnm_scan_process(wpa_s, 0) > 0) 1549281806Srpaulo return; 1550281806Srpaulo wpa_printf(MSG_DEBUG, 1551281806Srpaulo "WNM: No match in previous scan results - try a new scan"); 1552281806Srpaulo } 1553281806Srpaulo } 1554281806Srpaulo 1555281806Srpaulo wnm_set_scan_freqs(wpa_s); 1556337817Scy if (wpa_s->wnm_num_neighbor_report == 1) { 1557337817Scy os_memcpy(wpa_s->next_scan_bssid, 1558337817Scy wpa_s->wnm_neighbor_report_elements[0].bssid, 1559337817Scy ETH_ALEN); 1560337817Scy wpa_printf(MSG_DEBUG, 1561337817Scy "WNM: Scan only for a specific BSSID since there is only a single candidate " 1562337817Scy MACSTR, MAC2STR(wpa_s->next_scan_bssid)); 1563337817Scy } 1564281806Srpaulo wpa_supplicant_req_scan(wpa_s, 0, 0); 1565281806Srpaulo } else if (reply) { 1566281806Srpaulo enum bss_trans_mgmt_status_code status; 1567281806Srpaulo if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT) 1568281806Srpaulo status = WNM_BSS_TM_ACCEPT; 1569281806Srpaulo else { 1570281806Srpaulo wpa_msg(wpa_s, MSG_INFO, "WNM: BSS Transition Management Request did not include candidates"); 1571281806Srpaulo status = WNM_BSS_TM_REJECT_UNSPECIFIED; 1572281806Srpaulo } 1573346981Scy wnm_send_bss_transition_mgmt_resp( 1574346981Scy wpa_s, wpa_s->wnm_dialog_token, status, 1575346981Scy MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0, NULL); 1576252190Srpaulo } 1577252190Srpaulo} 1578252190Srpaulo 1579252190Srpaulo 1580346981Scy#define BTM_QUERY_MIN_SIZE 4 1581346981Scy 1582281806Srpauloint wnm_send_bss_transition_mgmt_query(struct wpa_supplicant *wpa_s, 1583346981Scy u8 query_reason, 1584346981Scy const char *btm_candidates, 1585346981Scy int cand_list) 1586281806Srpaulo{ 1587346981Scy struct wpabuf *buf; 1588281806Srpaulo int ret; 1589281806Srpaulo 1590281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Query to " 1591337817Scy MACSTR " query_reason=%u%s", 1592337817Scy MAC2STR(wpa_s->bssid), query_reason, 1593337817Scy cand_list ? " candidate list" : ""); 1594281806Srpaulo 1595346981Scy buf = wpabuf_alloc(BTM_QUERY_MIN_SIZE); 1596346981Scy if (!buf) 1597346981Scy return -1; 1598281806Srpaulo 1599346981Scy wpabuf_put_u8(buf, WLAN_ACTION_WNM); 1600346981Scy wpabuf_put_u8(buf, WNM_BSS_TRANS_MGMT_QUERY); 1601346981Scy wpabuf_put_u8(buf, 1); 1602346981Scy wpabuf_put_u8(buf, query_reason); 1603346981Scy 1604337817Scy if (cand_list) 1605346981Scy wnm_add_cand_list(wpa_s, &buf); 1606337817Scy 1607346981Scy if (btm_candidates) { 1608346981Scy const size_t max_len = 1000; 1609281806Srpaulo 1610346981Scy ret = wpabuf_resize(&buf, max_len); 1611346981Scy if (ret < 0) { 1612346981Scy wpabuf_free(buf); 1613346981Scy return ret; 1614346981Scy } 1615346981Scy 1616346981Scy ret = ieee802_11_parse_candidate_list(btm_candidates, 1617346981Scy wpabuf_put(buf, 0), 1618346981Scy max_len); 1619346981Scy if (ret < 0) { 1620346981Scy wpabuf_free(buf); 1621346981Scy return ret; 1622346981Scy } 1623346981Scy 1624346981Scy wpabuf_put(buf, ret); 1625346981Scy } 1626346981Scy 1627281806Srpaulo ret = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, 1628281806Srpaulo wpa_s->own_addr, wpa_s->bssid, 1629346981Scy wpabuf_head_u8(buf), wpabuf_len(buf), 0); 1630281806Srpaulo 1631346981Scy wpabuf_free(buf); 1632281806Srpaulo return ret; 1633281806Srpaulo} 1634281806Srpaulo 1635281806Srpaulo 1636281806Srpaulostatic void ieee802_11_rx_wnm_notif_req_wfa(struct wpa_supplicant *wpa_s, 1637281806Srpaulo const u8 *sa, const u8 *data, 1638281806Srpaulo int len) 1639281806Srpaulo{ 1640281806Srpaulo const u8 *pos, *end, *next; 1641281806Srpaulo u8 ie, ie_len; 1642281806Srpaulo 1643281806Srpaulo pos = data; 1644281806Srpaulo end = data + len; 1645281806Srpaulo 1646337817Scy while (end - pos > 1) { 1647281806Srpaulo ie = *pos++; 1648281806Srpaulo ie_len = *pos++; 1649281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: WFA subelement %u len %u", 1650281806Srpaulo ie, ie_len); 1651281806Srpaulo if (ie_len > end - pos) { 1652281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Not enough room for " 1653281806Srpaulo "subelement"); 1654281806Srpaulo break; 1655281806Srpaulo } 1656281806Srpaulo next = pos + ie_len; 1657281806Srpaulo if (ie_len < 4) { 1658281806Srpaulo pos = next; 1659281806Srpaulo continue; 1660281806Srpaulo } 1661281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Subelement OUI %06x type %u", 1662281806Srpaulo WPA_GET_BE24(pos), pos[3]); 1663281806Srpaulo 1664281806Srpaulo#ifdef CONFIG_HS20 1665281806Srpaulo if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 5 && 1666281806Srpaulo WPA_GET_BE24(pos) == OUI_WFA && 1667281806Srpaulo pos[3] == HS20_WNM_SUB_REM_NEEDED) { 1668281806Srpaulo /* Subscription Remediation subelement */ 1669281806Srpaulo const u8 *ie_end; 1670281806Srpaulo u8 url_len; 1671281806Srpaulo char *url; 1672281806Srpaulo u8 osu_method; 1673281806Srpaulo 1674281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Subscription Remediation " 1675281806Srpaulo "subelement"); 1676281806Srpaulo ie_end = pos + ie_len; 1677281806Srpaulo pos += 4; 1678281806Srpaulo url_len = *pos++; 1679281806Srpaulo if (url_len == 0) { 1680281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: No Server URL included"); 1681281806Srpaulo url = NULL; 1682281806Srpaulo osu_method = 1; 1683281806Srpaulo } else { 1684337817Scy if (url_len + 1 > ie_end - pos) { 1685281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Not enough room for Server URL (len=%u) and Server Method (left %d)", 1686281806Srpaulo url_len, 1687281806Srpaulo (int) (ie_end - pos)); 1688281806Srpaulo break; 1689281806Srpaulo } 1690281806Srpaulo url = os_malloc(url_len + 1); 1691281806Srpaulo if (url == NULL) 1692281806Srpaulo break; 1693281806Srpaulo os_memcpy(url, pos, url_len); 1694281806Srpaulo url[url_len] = '\0'; 1695281806Srpaulo osu_method = pos[url_len]; 1696281806Srpaulo } 1697281806Srpaulo hs20_rx_subscription_remediation(wpa_s, url, 1698281806Srpaulo osu_method); 1699281806Srpaulo os_free(url); 1700281806Srpaulo pos = next; 1701281806Srpaulo continue; 1702281806Srpaulo } 1703281806Srpaulo 1704281806Srpaulo if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 8 && 1705281806Srpaulo WPA_GET_BE24(pos) == OUI_WFA && 1706281806Srpaulo pos[3] == HS20_WNM_DEAUTH_IMMINENT_NOTICE) { 1707281806Srpaulo const u8 *ie_end; 1708281806Srpaulo u8 url_len; 1709281806Srpaulo char *url; 1710281806Srpaulo u8 code; 1711281806Srpaulo u16 reauth_delay; 1712281806Srpaulo 1713281806Srpaulo ie_end = pos + ie_len; 1714281806Srpaulo pos += 4; 1715281806Srpaulo code = *pos++; 1716281806Srpaulo reauth_delay = WPA_GET_LE16(pos); 1717281806Srpaulo pos += 2; 1718281806Srpaulo url_len = *pos++; 1719281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: HS 2.0 Deauthentication " 1720281806Srpaulo "Imminent - Reason Code %u " 1721281806Srpaulo "Re-Auth Delay %u URL Length %u", 1722281806Srpaulo code, reauth_delay, url_len); 1723337817Scy if (url_len > ie_end - pos) 1724281806Srpaulo break; 1725281806Srpaulo url = os_malloc(url_len + 1); 1726281806Srpaulo if (url == NULL) 1727281806Srpaulo break; 1728281806Srpaulo os_memcpy(url, pos, url_len); 1729281806Srpaulo url[url_len] = '\0'; 1730281806Srpaulo hs20_rx_deauth_imminent_notice(wpa_s, code, 1731281806Srpaulo reauth_delay, url); 1732281806Srpaulo os_free(url); 1733281806Srpaulo pos = next; 1734281806Srpaulo continue; 1735281806Srpaulo } 1736346981Scy 1737346981Scy if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 5 && 1738346981Scy WPA_GET_BE24(pos) == OUI_WFA && 1739346981Scy pos[3] == HS20_WNM_T_C_ACCEPTANCE) { 1740346981Scy const u8 *ie_end; 1741346981Scy u8 url_len; 1742346981Scy char *url; 1743346981Scy 1744346981Scy ie_end = pos + ie_len; 1745346981Scy pos += 4; 1746346981Scy url_len = *pos++; 1747346981Scy wpa_printf(MSG_DEBUG, 1748346981Scy "WNM: HS 2.0 Terms and Conditions Acceptance (URL Length %u)", 1749346981Scy url_len); 1750346981Scy if (url_len > ie_end - pos) 1751346981Scy break; 1752346981Scy url = os_malloc(url_len + 1); 1753346981Scy if (!url) 1754346981Scy break; 1755346981Scy os_memcpy(url, pos, url_len); 1756346981Scy url[url_len] = '\0'; 1757346981Scy hs20_rx_t_c_acceptance(wpa_s, url); 1758346981Scy os_free(url); 1759346981Scy pos = next; 1760346981Scy continue; 1761346981Scy } 1762281806Srpaulo#endif /* CONFIG_HS20 */ 1763281806Srpaulo 1764281806Srpaulo pos = next; 1765281806Srpaulo } 1766281806Srpaulo} 1767281806Srpaulo 1768281806Srpaulo 1769281806Srpaulostatic void ieee802_11_rx_wnm_notif_req(struct wpa_supplicant *wpa_s, 1770281806Srpaulo const u8 *sa, const u8 *frm, int len) 1771281806Srpaulo{ 1772281806Srpaulo const u8 *pos, *end; 1773281806Srpaulo u8 dialog_token, type; 1774281806Srpaulo 1775281806Srpaulo /* Dialog Token [1] | Type [1] | Subelements */ 1776281806Srpaulo 1777281806Srpaulo if (len < 2 || sa == NULL) 1778281806Srpaulo return; 1779281806Srpaulo end = frm + len; 1780281806Srpaulo pos = frm; 1781281806Srpaulo dialog_token = *pos++; 1782281806Srpaulo type = *pos++; 1783281806Srpaulo 1784281806Srpaulo wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Received WNM-Notification Request " 1785281806Srpaulo "(dialog_token %u type %u sa " MACSTR ")", 1786281806Srpaulo dialog_token, type, MAC2STR(sa)); 1787281806Srpaulo wpa_hexdump(MSG_DEBUG, "WNM-Notification Request subelements", 1788281806Srpaulo pos, end - pos); 1789281806Srpaulo 1790281806Srpaulo if (wpa_s->wpa_state != WPA_COMPLETED || 1791281806Srpaulo os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0) { 1792281806Srpaulo wpa_dbg(wpa_s, MSG_DEBUG, "WNM: WNM-Notification frame not " 1793281806Srpaulo "from our AP - ignore it"); 1794281806Srpaulo return; 1795281806Srpaulo } 1796281806Srpaulo 1797281806Srpaulo switch (type) { 1798281806Srpaulo case 1: 1799281806Srpaulo ieee802_11_rx_wnm_notif_req_wfa(wpa_s, sa, pos, end - pos); 1800281806Srpaulo break; 1801281806Srpaulo default: 1802281806Srpaulo wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Ignore unknown " 1803281806Srpaulo "WNM-Notification type %u", type); 1804281806Srpaulo break; 1805281806Srpaulo } 1806281806Srpaulo} 1807281806Srpaulo 1808281806Srpaulo 1809346981Scystatic void ieee802_11_rx_wnm_coloc_intf_req(struct wpa_supplicant *wpa_s, 1810346981Scy const u8 *sa, const u8 *frm, 1811346981Scy int len) 1812346981Scy{ 1813346981Scy u8 dialog_token, req_info, auto_report, timeout; 1814346981Scy 1815346981Scy if (!wpa_s->conf->coloc_intf_reporting) 1816346981Scy return; 1817346981Scy 1818346981Scy /* Dialog Token [1] | Request Info [1] */ 1819346981Scy 1820346981Scy if (len < 2) 1821346981Scy return; 1822346981Scy dialog_token = frm[0]; 1823346981Scy req_info = frm[1]; 1824346981Scy auto_report = req_info & 0x03; 1825346981Scy timeout = req_info >> 2; 1826346981Scy 1827346981Scy wpa_dbg(wpa_s, MSG_DEBUG, 1828346981Scy "WNM: Received Collocated Interference Request (dialog_token %u auto_report %u timeout %u sa " MACSTR ")", 1829346981Scy dialog_token, auto_report, timeout, MAC2STR(sa)); 1830346981Scy 1831346981Scy if (dialog_token == 0) 1832346981Scy return; /* only nonzero values are used for request */ 1833346981Scy 1834346981Scy if (wpa_s->wpa_state != WPA_COMPLETED || 1835346981Scy os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0) { 1836346981Scy wpa_dbg(wpa_s, MSG_DEBUG, 1837346981Scy "WNM: Collocated Interference Request frame not from current AP - ignore it"); 1838346981Scy return; 1839346981Scy } 1840346981Scy 1841346981Scy wpa_msg(wpa_s, MSG_INFO, COLOC_INTF_REQ "%u %u %u", 1842346981Scy dialog_token, auto_report, timeout); 1843346981Scy wpa_s->coloc_intf_dialog_token = dialog_token; 1844346981Scy wpa_s->coloc_intf_auto_report = auto_report; 1845346981Scy wpa_s->coloc_intf_timeout = timeout; 1846346981Scy} 1847346981Scy 1848346981Scy 1849252190Srpaulovoid ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s, 1850281806Srpaulo const struct ieee80211_mgmt *mgmt, size_t len) 1851252190Srpaulo{ 1852252190Srpaulo const u8 *pos, *end; 1853252190Srpaulo u8 act; 1854252190Srpaulo 1855281806Srpaulo if (len < IEEE80211_HDRLEN + 2) 1856252190Srpaulo return; 1857252190Srpaulo 1858281806Srpaulo pos = ((const u8 *) mgmt) + IEEE80211_HDRLEN + 1; 1859252190Srpaulo act = *pos++; 1860281806Srpaulo end = ((const u8 *) mgmt) + len; 1861252190Srpaulo 1862252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: RX action %u from " MACSTR, 1863281806Srpaulo act, MAC2STR(mgmt->sa)); 1864252190Srpaulo if (wpa_s->wpa_state < WPA_ASSOCIATED || 1865281806Srpaulo os_memcmp(mgmt->sa, wpa_s->bssid, ETH_ALEN) != 0) { 1866252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: Ignore unexpected WNM Action " 1867252190Srpaulo "frame"); 1868252190Srpaulo return; 1869252190Srpaulo } 1870252190Srpaulo 1871252190Srpaulo switch (act) { 1872252190Srpaulo case WNM_BSS_TRANS_MGMT_REQ: 1873252190Srpaulo ieee802_11_rx_bss_trans_mgmt_req(wpa_s, pos, end, 1874281806Srpaulo !(mgmt->da[0] & 0x01)); 1875252190Srpaulo break; 1876252190Srpaulo case WNM_SLEEP_MODE_RESP: 1877281806Srpaulo ieee802_11_rx_wnmsleep_resp(wpa_s, pos, end - pos); 1878252190Srpaulo break; 1879281806Srpaulo case WNM_NOTIFICATION_REQ: 1880281806Srpaulo ieee802_11_rx_wnm_notif_req(wpa_s, mgmt->sa, pos, end - pos); 1881281806Srpaulo break; 1882346981Scy case WNM_COLLOCATED_INTERFERENCE_REQ: 1883346981Scy ieee802_11_rx_wnm_coloc_intf_req(wpa_s, mgmt->sa, pos, 1884346981Scy end - pos); 1885346981Scy break; 1886252190Srpaulo default: 1887281806Srpaulo wpa_printf(MSG_ERROR, "WNM: Unknown request"); 1888252190Srpaulo break; 1889252190Srpaulo } 1890252190Srpaulo} 1891346981Scy 1892346981Scy 1893346981Scyint wnm_send_coloc_intf_report(struct wpa_supplicant *wpa_s, u8 dialog_token, 1894346981Scy const struct wpabuf *elems) 1895346981Scy{ 1896346981Scy struct wpabuf *buf; 1897346981Scy int ret; 1898346981Scy 1899346981Scy if (wpa_s->wpa_state < WPA_ASSOCIATED || !elems) 1900346981Scy return -1; 1901346981Scy 1902346981Scy wpa_printf(MSG_DEBUG, "WNM: Send Collocated Interference Report to " 1903346981Scy MACSTR " (dialog token %u)", 1904346981Scy MAC2STR(wpa_s->bssid), dialog_token); 1905346981Scy 1906346981Scy buf = wpabuf_alloc(3 + wpabuf_len(elems)); 1907346981Scy if (!buf) 1908346981Scy return -1; 1909346981Scy 1910346981Scy wpabuf_put_u8(buf, WLAN_ACTION_WNM); 1911346981Scy wpabuf_put_u8(buf, WNM_COLLOCATED_INTERFERENCE_REPORT); 1912346981Scy wpabuf_put_u8(buf, dialog_token); 1913346981Scy wpabuf_put_buf(buf, elems); 1914346981Scy 1915346981Scy ret = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, 1916346981Scy wpa_s->own_addr, wpa_s->bssid, 1917346981Scy wpabuf_head_u8(buf), wpabuf_len(buf), 0); 1918346981Scy wpabuf_free(buf); 1919346981Scy return ret; 1920346981Scy} 1921346981Scy 1922346981Scy 1923346981Scyvoid wnm_set_coloc_intf_elems(struct wpa_supplicant *wpa_s, 1924346981Scy struct wpabuf *elems) 1925346981Scy{ 1926346981Scy wpabuf_free(wpa_s->coloc_intf_elems); 1927346981Scy if (elems && wpabuf_len(elems) == 0) { 1928346981Scy wpabuf_free(elems); 1929346981Scy elems = NULL; 1930346981Scy } 1931346981Scy wpa_s->coloc_intf_elems = elems; 1932346981Scy 1933346981Scy if (wpa_s->conf->coloc_intf_reporting && wpa_s->coloc_intf_elems && 1934346981Scy wpa_s->coloc_intf_dialog_token && 1935346981Scy (wpa_s->coloc_intf_auto_report == 1 || 1936346981Scy wpa_s->coloc_intf_auto_report == 3)) { 1937346981Scy /* TODO: Check that there has not been less than 1938346981Scy * wpa_s->coloc_intf_timeout * 200 TU from the last report. 1939346981Scy */ 1940346981Scy wnm_send_coloc_intf_report(wpa_s, 1941346981Scy wpa_s->coloc_intf_dialog_token, 1942346981Scy wpa_s->coloc_intf_elems); 1943346981Scy } 1944346981Scy} 1945346981Scy 1946346981Scy 1947346981Scyvoid wnm_clear_coloc_intf_reporting(struct wpa_supplicant *wpa_s) 1948346981Scy{ 1949346981Scy#ifdef CONFIG_WNM 1950346981Scy wpa_s->coloc_intf_dialog_token = 0; 1951346981Scy wpa_s->coloc_intf_auto_report = 0; 1952346981Scy#endif /* CONFIG_WNM */ 1953346981Scy} 1954