1252190Srpaulo/* 2252190Srpaulo * hostapd - WNM 3281806Srpaulo * Copyright (c) 2011-2014, 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" 12281806Srpaulo#include "utils/eloop.h" 13252190Srpaulo#include "common/ieee802_11_defs.h" 14281806Srpaulo#include "common/wpa_ctrl.h" 15346981Scy#include "common/ocv.h" 16252190Srpaulo#include "ap/hostapd.h" 17252190Srpaulo#include "ap/sta_info.h" 18252190Srpaulo#include "ap/ap_config.h" 19252190Srpaulo#include "ap/ap_drv_ops.h" 20252190Srpaulo#include "ap/wpa_auth.h" 21337817Scy#include "mbo_ap.h" 22252190Srpaulo#include "wnm_ap.h" 23252190Srpaulo 24252190Srpaulo#define MAX_TFS_IE_LEN 1024 25252190Srpaulo 26252190Srpaulo 27252190Srpaulo/* get the TFS IE from driver */ 28252190Srpaulostatic int ieee80211_11_get_tfs_ie(struct hostapd_data *hapd, const u8 *addr, 29252190Srpaulo u8 *buf, u16 *buf_len, enum wnm_oper oper) 30252190Srpaulo{ 31252190Srpaulo wpa_printf(MSG_DEBUG, "%s: TFS get operation %d", __func__, oper); 32252190Srpaulo 33252190Srpaulo return hostapd_drv_wnm_oper(hapd, oper, addr, buf, buf_len); 34252190Srpaulo} 35252190Srpaulo 36252190Srpaulo 37252190Srpaulo/* set the TFS IE to driver */ 38252190Srpaulostatic int ieee80211_11_set_tfs_ie(struct hostapd_data *hapd, const u8 *addr, 39252190Srpaulo u8 *buf, u16 *buf_len, enum wnm_oper oper) 40252190Srpaulo{ 41252190Srpaulo wpa_printf(MSG_DEBUG, "%s: TFS set operation %d", __func__, oper); 42252190Srpaulo 43252190Srpaulo return hostapd_drv_wnm_oper(hapd, oper, addr, buf, buf_len); 44252190Srpaulo} 45252190Srpaulo 46252190Srpaulo 47252190Srpaulo/* MLME-SLEEPMODE.response */ 48252190Srpaulostatic int ieee802_11_send_wnmsleep_resp(struct hostapd_data *hapd, 49252190Srpaulo const u8 *addr, u8 dialog_token, 50252190Srpaulo u8 action_type, u16 intval) 51252190Srpaulo{ 52252190Srpaulo struct ieee80211_mgmt *mgmt; 53252190Srpaulo int res; 54252190Srpaulo size_t len; 55252190Srpaulo size_t gtk_elem_len = 0; 56252190Srpaulo size_t igtk_elem_len = 0; 57252190Srpaulo struct wnm_sleep_element wnmsleep_ie; 58346981Scy u8 *wnmtfs_ie, *oci_ie; 59346981Scy u8 wnmsleep_ie_len, oci_ie_len; 60252190Srpaulo u16 wnmtfs_ie_len; 61252190Srpaulo u8 *pos; 62252190Srpaulo struct sta_info *sta; 63252190Srpaulo enum wnm_oper tfs_oper = action_type == WNM_SLEEP_MODE_ENTER ? 64252190Srpaulo WNM_SLEEP_TFS_RESP_IE_ADD : WNM_SLEEP_TFS_RESP_IE_NONE; 65252190Srpaulo 66252190Srpaulo sta = ap_get_sta(hapd, addr); 67252190Srpaulo if (sta == NULL) { 68252190Srpaulo wpa_printf(MSG_DEBUG, "%s: station not found", __func__); 69252190Srpaulo return -EINVAL; 70252190Srpaulo } 71252190Srpaulo 72252190Srpaulo /* WNM-Sleep Mode IE */ 73252190Srpaulo os_memset(&wnmsleep_ie, 0, sizeof(struct wnm_sleep_element)); 74252190Srpaulo wnmsleep_ie_len = sizeof(struct wnm_sleep_element); 75252190Srpaulo wnmsleep_ie.eid = WLAN_EID_WNMSLEEP; 76252190Srpaulo wnmsleep_ie.len = wnmsleep_ie_len - 2; 77252190Srpaulo wnmsleep_ie.action_type = action_type; 78252190Srpaulo wnmsleep_ie.status = WNM_STATUS_SLEEP_ACCEPT; 79281806Srpaulo wnmsleep_ie.intval = host_to_le16(intval); 80252190Srpaulo 81252190Srpaulo /* TFS IE(s) */ 82252190Srpaulo wnmtfs_ie = os_zalloc(MAX_TFS_IE_LEN); 83252190Srpaulo if (wnmtfs_ie == NULL) 84252190Srpaulo return -1; 85252190Srpaulo if (ieee80211_11_get_tfs_ie(hapd, addr, wnmtfs_ie, &wnmtfs_ie_len, 86252190Srpaulo tfs_oper)) { 87252190Srpaulo wnmtfs_ie_len = 0; 88252190Srpaulo os_free(wnmtfs_ie); 89252190Srpaulo wnmtfs_ie = NULL; 90252190Srpaulo } 91252190Srpaulo 92346981Scy oci_ie = NULL; 93346981Scy oci_ie_len = 0; 94346981Scy#ifdef CONFIG_OCV 95346981Scy if (action_type == WNM_SLEEP_MODE_EXIT && 96346981Scy wpa_auth_uses_ocv(sta->wpa_sm)) { 97346981Scy struct wpa_channel_info ci; 98346981Scy 99346981Scy if (hostapd_drv_channel_info(hapd, &ci) != 0) { 100346981Scy wpa_printf(MSG_WARNING, 101346981Scy "Failed to get channel info for OCI element in WNM-Sleep Mode frame"); 102346981Scy os_free(wnmtfs_ie); 103346981Scy return -1; 104346981Scy } 105346981Scy 106346981Scy oci_ie_len = OCV_OCI_EXTENDED_LEN; 107346981Scy oci_ie = os_zalloc(oci_ie_len); 108346981Scy if (!oci_ie) { 109346981Scy wpa_printf(MSG_WARNING, 110346981Scy "Failed to allocate buffer for OCI element in WNM-Sleep Mode frame"); 111346981Scy os_free(wnmtfs_ie); 112346981Scy return -1; 113346981Scy } 114346981Scy 115346981Scy if (ocv_insert_extended_oci(&ci, oci_ie) < 0) { 116346981Scy os_free(wnmtfs_ie); 117346981Scy os_free(oci_ie); 118346981Scy return -1; 119346981Scy } 120346981Scy } 121346981Scy#endif /* CONFIG_OCV */ 122346981Scy 123252190Srpaulo#define MAX_GTK_SUBELEM_LEN 45 124252190Srpaulo#define MAX_IGTK_SUBELEM_LEN 26 125252190Srpaulo mgmt = os_zalloc(sizeof(*mgmt) + wnmsleep_ie_len + 126346981Scy MAX_GTK_SUBELEM_LEN + MAX_IGTK_SUBELEM_LEN + 127346981Scy oci_ie_len); 128252190Srpaulo if (mgmt == NULL) { 129252190Srpaulo wpa_printf(MSG_DEBUG, "MLME: Failed to allocate buffer for " 130252190Srpaulo "WNM-Sleep Response action frame"); 131346981Scy res = -1; 132346981Scy goto fail; 133252190Srpaulo } 134252190Srpaulo os_memcpy(mgmt->da, addr, ETH_ALEN); 135252190Srpaulo os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN); 136252190Srpaulo os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN); 137252190Srpaulo mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, 138252190Srpaulo WLAN_FC_STYPE_ACTION); 139252190Srpaulo mgmt->u.action.category = WLAN_ACTION_WNM; 140252190Srpaulo mgmt->u.action.u.wnm_sleep_resp.action = WNM_SLEEP_MODE_RESP; 141252190Srpaulo mgmt->u.action.u.wnm_sleep_resp.dialogtoken = dialog_token; 142252190Srpaulo pos = (u8 *)mgmt->u.action.u.wnm_sleep_resp.variable; 143252190Srpaulo /* add key data if MFP is enabled */ 144252190Srpaulo if (!wpa_auth_uses_mfp(sta->wpa_sm) || 145346981Scy hapd->conf->wnm_sleep_mode_no_keys || 146252190Srpaulo action_type != WNM_SLEEP_MODE_EXIT) { 147252190Srpaulo mgmt->u.action.u.wnm_sleep_resp.keydata_len = 0; 148252190Srpaulo } else { 149252190Srpaulo gtk_elem_len = wpa_wnmsleep_gtk_subelem(sta->wpa_sm, pos); 150252190Srpaulo pos += gtk_elem_len; 151252190Srpaulo wpa_printf(MSG_DEBUG, "Pass 4, gtk_len = %d", 152252190Srpaulo (int) gtk_elem_len); 153252190Srpaulo#ifdef CONFIG_IEEE80211W 154252190Srpaulo res = wpa_wnmsleep_igtk_subelem(sta->wpa_sm, pos); 155346981Scy if (res < 0) 156346981Scy goto fail; 157252190Srpaulo igtk_elem_len = res; 158252190Srpaulo pos += igtk_elem_len; 159252190Srpaulo wpa_printf(MSG_DEBUG, "Pass 4 igtk_len = %d", 160252190Srpaulo (int) igtk_elem_len); 161252190Srpaulo#endif /* CONFIG_IEEE80211W */ 162252190Srpaulo 163252190Srpaulo WPA_PUT_LE16((u8 *) 164252190Srpaulo &mgmt->u.action.u.wnm_sleep_resp.keydata_len, 165252190Srpaulo gtk_elem_len + igtk_elem_len); 166252190Srpaulo } 167252190Srpaulo os_memcpy(pos, &wnmsleep_ie, wnmsleep_ie_len); 168252190Srpaulo /* copy TFS IE here */ 169252190Srpaulo pos += wnmsleep_ie_len; 170346981Scy if (wnmtfs_ie) { 171252190Srpaulo os_memcpy(pos, wnmtfs_ie, wnmtfs_ie_len); 172346981Scy pos += wnmtfs_ie_len; 173346981Scy } 174346981Scy#ifdef CONFIG_OCV 175346981Scy /* copy OCV OCI here */ 176346981Scy if (oci_ie_len > 0) 177346981Scy os_memcpy(pos, oci_ie, oci_ie_len); 178346981Scy#endif /* CONFIG_OCV */ 179252190Srpaulo 180252190Srpaulo len = 1 + sizeof(mgmt->u.action.u.wnm_sleep_resp) + gtk_elem_len + 181346981Scy igtk_elem_len + wnmsleep_ie_len + wnmtfs_ie_len + oci_ie_len; 182252190Srpaulo 183252190Srpaulo /* In driver, response frame should be forced to sent when STA is in 184252190Srpaulo * PS mode */ 185252190Srpaulo res = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, 186252190Srpaulo mgmt->da, &mgmt->u.action.category, len); 187252190Srpaulo 188252190Srpaulo if (!res) { 189252190Srpaulo wpa_printf(MSG_DEBUG, "Successfully send WNM-Sleep Response " 190252190Srpaulo "frame"); 191252190Srpaulo 192252190Srpaulo /* when entering wnmsleep 193252190Srpaulo * 1. pause the node in driver 194252190Srpaulo * 2. mark the node so that AP won't update GTK/IGTK during 195252190Srpaulo * WNM Sleep 196252190Srpaulo */ 197252190Srpaulo if (wnmsleep_ie.status == WNM_STATUS_SLEEP_ACCEPT && 198252190Srpaulo wnmsleep_ie.action_type == WNM_SLEEP_MODE_ENTER) { 199281806Srpaulo sta->flags |= WLAN_STA_WNM_SLEEP_MODE; 200252190Srpaulo hostapd_drv_wnm_oper(hapd, WNM_SLEEP_ENTER_CONFIRM, 201252190Srpaulo addr, NULL, NULL); 202252190Srpaulo wpa_set_wnmsleep(sta->wpa_sm, 1); 203252190Srpaulo } 204252190Srpaulo /* when exiting wnmsleep 205252190Srpaulo * 1. unmark the node 206252190Srpaulo * 2. start GTK/IGTK update if MFP is not used 207252190Srpaulo * 3. unpause the node in driver 208252190Srpaulo */ 209252190Srpaulo if ((wnmsleep_ie.status == WNM_STATUS_SLEEP_ACCEPT || 210252190Srpaulo wnmsleep_ie.status == 211252190Srpaulo WNM_STATUS_SLEEP_EXIT_ACCEPT_GTK_UPDATE) && 212252190Srpaulo wnmsleep_ie.action_type == WNM_SLEEP_MODE_EXIT) { 213281806Srpaulo sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE; 214252190Srpaulo wpa_set_wnmsleep(sta->wpa_sm, 0); 215252190Srpaulo hostapd_drv_wnm_oper(hapd, WNM_SLEEP_EXIT_CONFIRM, 216252190Srpaulo addr, NULL, NULL); 217346981Scy if (!wpa_auth_uses_mfp(sta->wpa_sm) || 218346981Scy hapd->conf->wnm_sleep_mode_no_keys) 219252190Srpaulo wpa_wnmsleep_rekey_gtk(sta->wpa_sm); 220252190Srpaulo } 221252190Srpaulo } else 222252190Srpaulo wpa_printf(MSG_DEBUG, "Fail to send WNM-Sleep Response frame"); 223252190Srpaulo 224252190Srpaulo#undef MAX_GTK_SUBELEM_LEN 225252190Srpaulo#undef MAX_IGTK_SUBELEM_LEN 226346981Scyfail: 227252190Srpaulo os_free(wnmtfs_ie); 228346981Scy os_free(oci_ie); 229252190Srpaulo os_free(mgmt); 230252190Srpaulo return res; 231252190Srpaulo} 232252190Srpaulo 233252190Srpaulo 234252190Srpaulostatic void ieee802_11_rx_wnmsleep_req(struct hostapd_data *hapd, 235252190Srpaulo const u8 *addr, const u8 *frm, int len) 236252190Srpaulo{ 237252190Srpaulo /* Dialog Token [1] | WNM-Sleep Mode IE | TFS Response IE */ 238252190Srpaulo const u8 *pos = frm; 239252190Srpaulo u8 dialog_token; 240252190Srpaulo struct wnm_sleep_element *wnmsleep_ie = NULL; 241252190Srpaulo /* multiple TFS Req IE (assuming consecutive) */ 242252190Srpaulo u8 *tfsreq_ie_start = NULL; 243252190Srpaulo u8 *tfsreq_ie_end = NULL; 244252190Srpaulo u16 tfsreq_ie_len = 0; 245346981Scy#ifdef CONFIG_OCV 246346981Scy struct sta_info *sta; 247346981Scy const u8 *oci_ie = NULL; 248346981Scy u8 oci_ie_len = 0; 249346981Scy#endif /* CONFIG_OCV */ 250252190Srpaulo 251346981Scy if (!hapd->conf->wnm_sleep_mode) { 252346981Scy wpa_printf(MSG_DEBUG, "Ignore WNM-Sleep Mode Request from " 253346981Scy MACSTR " since WNM-Sleep Mode is disabled", 254346981Scy MAC2STR(addr)); 255346981Scy return; 256346981Scy } 257346981Scy 258346981Scy if (len < 1) { 259346981Scy wpa_printf(MSG_DEBUG, 260346981Scy "WNM: Ignore too short WNM-Sleep Mode Request from " 261346981Scy MACSTR, MAC2STR(addr)); 262346981Scy return; 263346981Scy } 264346981Scy 265252190Srpaulo dialog_token = *pos++; 266252190Srpaulo while (pos + 1 < frm + len) { 267252190Srpaulo u8 ie_len = pos[1]; 268252190Srpaulo if (pos + 2 + ie_len > frm + len) 269252190Srpaulo break; 270346981Scy if (*pos == WLAN_EID_WNMSLEEP && 271346981Scy ie_len >= (int) sizeof(*wnmsleep_ie) - 2) 272252190Srpaulo wnmsleep_ie = (struct wnm_sleep_element *) pos; 273252190Srpaulo else if (*pos == WLAN_EID_TFS_REQ) { 274252190Srpaulo if (!tfsreq_ie_start) 275252190Srpaulo tfsreq_ie_start = (u8 *) pos; 276252190Srpaulo tfsreq_ie_end = (u8 *) pos; 277346981Scy#ifdef CONFIG_OCV 278346981Scy } else if (*pos == WLAN_EID_EXTENSION && ie_len >= 1 && 279346981Scy pos[2] == WLAN_EID_EXT_OCV_OCI) { 280346981Scy oci_ie = pos + 3; 281346981Scy oci_ie_len = ie_len - 1; 282346981Scy#endif /* CONFIG_OCV */ 283252190Srpaulo } else 284252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: EID %d not recognized", 285252190Srpaulo *pos); 286252190Srpaulo pos += ie_len + 2; 287252190Srpaulo } 288252190Srpaulo 289252190Srpaulo if (!wnmsleep_ie) { 290252190Srpaulo wpa_printf(MSG_DEBUG, "No WNM-Sleep IE found"); 291252190Srpaulo return; 292252190Srpaulo } 293252190Srpaulo 294346981Scy#ifdef CONFIG_OCV 295346981Scy sta = ap_get_sta(hapd, addr); 296346981Scy if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT && 297346981Scy sta && wpa_auth_uses_ocv(sta->wpa_sm)) { 298346981Scy struct wpa_channel_info ci; 299346981Scy 300346981Scy if (hostapd_drv_channel_info(hapd, &ci) != 0) { 301346981Scy wpa_printf(MSG_WARNING, 302346981Scy "Failed to get channel info to validate received OCI in WNM-Sleep Mode frame"); 303346981Scy return; 304346981Scy } 305346981Scy 306346981Scy if (ocv_verify_tx_params(oci_ie, oci_ie_len, &ci, 307346981Scy channel_width_to_int(ci.chanwidth), 308346981Scy ci.seg1_idx) != 0) { 309346981Scy wpa_msg(hapd, MSG_WARNING, "WNM: %s", ocv_errorstr); 310346981Scy return; 311346981Scy } 312346981Scy } 313346981Scy#endif /* CONFIG_OCV */ 314346981Scy 315252190Srpaulo if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_ENTER && 316252190Srpaulo tfsreq_ie_start && tfsreq_ie_end && 317252190Srpaulo tfsreq_ie_end - tfsreq_ie_start >= 0) { 318252190Srpaulo tfsreq_ie_len = (tfsreq_ie_end + tfsreq_ie_end[1] + 2) - 319252190Srpaulo tfsreq_ie_start; 320252190Srpaulo wpa_printf(MSG_DEBUG, "TFS Req IE(s) found"); 321252190Srpaulo /* pass the TFS Req IE(s) to driver for processing */ 322252190Srpaulo if (ieee80211_11_set_tfs_ie(hapd, addr, tfsreq_ie_start, 323252190Srpaulo &tfsreq_ie_len, 324252190Srpaulo WNM_SLEEP_TFS_REQ_IE_SET)) 325252190Srpaulo wpa_printf(MSG_DEBUG, "Fail to set TFS Req IE"); 326252190Srpaulo } 327252190Srpaulo 328252190Srpaulo ieee802_11_send_wnmsleep_resp(hapd, addr, dialog_token, 329252190Srpaulo wnmsleep_ie->action_type, 330281806Srpaulo le_to_host16(wnmsleep_ie->intval)); 331252190Srpaulo 332252190Srpaulo if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT) { 333252190Srpaulo /* clear the tfs after sending the resp frame */ 334252190Srpaulo ieee80211_11_set_tfs_ie(hapd, addr, tfsreq_ie_start, 335252190Srpaulo &tfsreq_ie_len, WNM_SLEEP_TFS_IE_DEL); 336252190Srpaulo } 337252190Srpaulo} 338252190Srpaulo 339252190Srpaulo 340281806Srpaulostatic int ieee802_11_send_bss_trans_mgmt_request(struct hostapd_data *hapd, 341281806Srpaulo const u8 *addr, 342346981Scy u8 dialog_token) 343281806Srpaulo{ 344281806Srpaulo struct ieee80211_mgmt *mgmt; 345346981Scy size_t len; 346281806Srpaulo u8 *pos; 347281806Srpaulo int res; 348281806Srpaulo 349346981Scy mgmt = os_zalloc(sizeof(*mgmt)); 350281806Srpaulo if (mgmt == NULL) 351281806Srpaulo return -1; 352281806Srpaulo os_memcpy(mgmt->da, addr, ETH_ALEN); 353281806Srpaulo os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN); 354281806Srpaulo os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN); 355281806Srpaulo mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, 356281806Srpaulo WLAN_FC_STYPE_ACTION); 357281806Srpaulo mgmt->u.action.category = WLAN_ACTION_WNM; 358281806Srpaulo mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ; 359281806Srpaulo mgmt->u.action.u.bss_tm_req.dialog_token = dialog_token; 360281806Srpaulo mgmt->u.action.u.bss_tm_req.req_mode = 0; 361281806Srpaulo mgmt->u.action.u.bss_tm_req.disassoc_timer = host_to_le16(0); 362281806Srpaulo mgmt->u.action.u.bss_tm_req.validity_interval = 1; 363281806Srpaulo pos = mgmt->u.action.u.bss_tm_req.variable; 364281806Srpaulo 365281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request to " 366281806Srpaulo MACSTR " dialog_token=%u req_mode=0x%x disassoc_timer=%u " 367281806Srpaulo "validity_interval=%u", 368281806Srpaulo MAC2STR(addr), dialog_token, 369281806Srpaulo mgmt->u.action.u.bss_tm_req.req_mode, 370281806Srpaulo le_to_host16(mgmt->u.action.u.bss_tm_req.disassoc_timer), 371281806Srpaulo mgmt->u.action.u.bss_tm_req.validity_interval); 372281806Srpaulo 373281806Srpaulo len = pos - &mgmt->u.action.category; 374281806Srpaulo res = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, 375281806Srpaulo mgmt->da, &mgmt->u.action.category, len); 376281806Srpaulo os_free(mgmt); 377281806Srpaulo return res; 378281806Srpaulo} 379281806Srpaulo 380281806Srpaulo 381281806Srpaulostatic void ieee802_11_rx_bss_trans_mgmt_query(struct hostapd_data *hapd, 382281806Srpaulo const u8 *addr, const u8 *frm, 383281806Srpaulo size_t len) 384281806Srpaulo{ 385281806Srpaulo u8 dialog_token, reason; 386281806Srpaulo const u8 *pos, *end; 387346981Scy int enabled = hapd->conf->bss_transition; 388281806Srpaulo 389346981Scy#ifdef CONFIG_MBO 390346981Scy if (hapd->conf->mbo_enabled) 391346981Scy enabled = 1; 392346981Scy#endif /* CONFIG_MBO */ 393346981Scy if (!enabled) { 394346981Scy wpa_printf(MSG_DEBUG, 395346981Scy "Ignore BSS Transition Management Query from " 396346981Scy MACSTR 397346981Scy " since BSS Transition Management is disabled", 398346981Scy MAC2STR(addr)); 399346981Scy return; 400346981Scy } 401346981Scy 402281806Srpaulo if (len < 2) { 403281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Ignore too short BSS Transition Management Query from " 404281806Srpaulo MACSTR, MAC2STR(addr)); 405281806Srpaulo return; 406281806Srpaulo } 407281806Srpaulo 408281806Srpaulo pos = frm; 409281806Srpaulo end = pos + len; 410281806Srpaulo dialog_token = *pos++; 411281806Srpaulo reason = *pos++; 412281806Srpaulo 413281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Query from " 414281806Srpaulo MACSTR " dialog_token=%u reason=%u", 415281806Srpaulo MAC2STR(addr), dialog_token, reason); 416281806Srpaulo 417281806Srpaulo wpa_hexdump(MSG_DEBUG, "WNM: BSS Transition Candidate List Entries", 418281806Srpaulo pos, end - pos); 419281806Srpaulo 420346981Scy ieee802_11_send_bss_trans_mgmt_request(hapd, addr, dialog_token); 421281806Srpaulo} 422281806Srpaulo 423281806Srpaulo 424346981Scyvoid ap_sta_reset_steer_flag_timer(void *eloop_ctx, void *timeout_ctx) 425346981Scy{ 426346981Scy struct hostapd_data *hapd = eloop_ctx; 427346981Scy struct sta_info *sta = timeout_ctx; 428346981Scy 429346981Scy if (sta->agreed_to_steer) { 430346981Scy wpa_printf(MSG_DEBUG, "%s: Reset steering flag for STA " MACSTR, 431346981Scy hapd->conf->iface, MAC2STR(sta->addr)); 432346981Scy sta->agreed_to_steer = 0; 433346981Scy } 434346981Scy} 435346981Scy 436346981Scy 437281806Srpaulostatic void ieee802_11_rx_bss_trans_mgmt_resp(struct hostapd_data *hapd, 438281806Srpaulo const u8 *addr, const u8 *frm, 439281806Srpaulo size_t len) 440281806Srpaulo{ 441281806Srpaulo u8 dialog_token, status_code, bss_termination_delay; 442281806Srpaulo const u8 *pos, *end; 443346981Scy int enabled = hapd->conf->bss_transition; 444346981Scy struct sta_info *sta; 445281806Srpaulo 446346981Scy#ifdef CONFIG_MBO 447346981Scy if (hapd->conf->mbo_enabled) 448346981Scy enabled = 1; 449346981Scy#endif /* CONFIG_MBO */ 450346981Scy if (!enabled) { 451346981Scy wpa_printf(MSG_DEBUG, 452346981Scy "Ignore BSS Transition Management Response from " 453346981Scy MACSTR 454346981Scy " since BSS Transition Management is disabled", 455346981Scy MAC2STR(addr)); 456346981Scy return; 457346981Scy } 458346981Scy 459281806Srpaulo if (len < 3) { 460281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Ignore too short BSS Transition Management Response from " 461281806Srpaulo MACSTR, MAC2STR(addr)); 462281806Srpaulo return; 463281806Srpaulo } 464281806Srpaulo 465281806Srpaulo pos = frm; 466281806Srpaulo end = pos + len; 467281806Srpaulo dialog_token = *pos++; 468281806Srpaulo status_code = *pos++; 469281806Srpaulo bss_termination_delay = *pos++; 470281806Srpaulo 471281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Response from " 472281806Srpaulo MACSTR " dialog_token=%u status_code=%u " 473281806Srpaulo "bss_termination_delay=%u", MAC2STR(addr), dialog_token, 474281806Srpaulo status_code, bss_termination_delay); 475281806Srpaulo 476346981Scy sta = ap_get_sta(hapd, addr); 477346981Scy if (!sta) { 478346981Scy wpa_printf(MSG_DEBUG, "Station " MACSTR 479346981Scy " not found for received BSS TM Response", 480346981Scy MAC2STR(addr)); 481346981Scy return; 482346981Scy } 483346981Scy 484281806Srpaulo if (status_code == WNM_BSS_TM_ACCEPT) { 485281806Srpaulo if (end - pos < ETH_ALEN) { 486281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: not enough room for Target BSSID field"); 487281806Srpaulo return; 488281806Srpaulo } 489346981Scy sta->agreed_to_steer = 1; 490346981Scy eloop_cancel_timeout(ap_sta_reset_steer_flag_timer, hapd, sta); 491346981Scy eloop_register_timeout(2, 0, ap_sta_reset_steer_flag_timer, 492346981Scy hapd, sta); 493281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Target BSSID: " MACSTR, 494281806Srpaulo MAC2STR(pos)); 495281806Srpaulo wpa_msg(hapd->msg_ctx, MSG_INFO, BSS_TM_RESP MACSTR 496281806Srpaulo " status_code=%u bss_termination_delay=%u target_bssid=" 497281806Srpaulo MACSTR, 498281806Srpaulo MAC2STR(addr), status_code, bss_termination_delay, 499281806Srpaulo MAC2STR(pos)); 500281806Srpaulo pos += ETH_ALEN; 501281806Srpaulo } else { 502346981Scy sta->agreed_to_steer = 0; 503281806Srpaulo wpa_msg(hapd->msg_ctx, MSG_INFO, BSS_TM_RESP MACSTR 504281806Srpaulo " status_code=%u bss_termination_delay=%u", 505281806Srpaulo MAC2STR(addr), status_code, bss_termination_delay); 506281806Srpaulo } 507281806Srpaulo 508281806Srpaulo wpa_hexdump(MSG_DEBUG, "WNM: BSS Transition Candidate List Entries", 509281806Srpaulo pos, end - pos); 510281806Srpaulo} 511281806Srpaulo 512281806Srpaulo 513337817Scystatic void ieee802_11_rx_wnm_notification_req(struct hostapd_data *hapd, 514337817Scy const u8 *addr, const u8 *buf, 515337817Scy size_t len) 516337817Scy{ 517337817Scy u8 dialog_token, type; 518337817Scy 519337817Scy if (len < 2) 520337817Scy return; 521337817Scy dialog_token = *buf++; 522337817Scy type = *buf++; 523337817Scy len -= 2; 524337817Scy 525337817Scy wpa_printf(MSG_DEBUG, 526337817Scy "WNM: Received WNM Notification Request frame from " 527337817Scy MACSTR " (dialog_token=%u type=%u)", 528337817Scy MAC2STR(addr), dialog_token, type); 529337817Scy wpa_hexdump(MSG_MSGDUMP, "WNM: Notification Request subelements", 530337817Scy buf, len); 531337817Scy if (type == WLAN_EID_VENDOR_SPECIFIC) 532337817Scy mbo_ap_wnm_notification_req(hapd, addr, buf, len); 533337817Scy} 534337817Scy 535337817Scy 536346981Scystatic void ieee802_11_rx_wnm_coloc_intf_report(struct hostapd_data *hapd, 537346981Scy const u8 *addr, const u8 *buf, 538346981Scy size_t len) 539346981Scy{ 540346981Scy u8 dialog_token; 541346981Scy char *hex; 542346981Scy size_t hex_len; 543346981Scy 544346981Scy if (!hapd->conf->coloc_intf_reporting) { 545346981Scy wpa_printf(MSG_DEBUG, 546346981Scy "WNM: Ignore unexpected Collocated Interference Report from " 547346981Scy MACSTR, MAC2STR(addr)); 548346981Scy return; 549346981Scy } 550346981Scy 551346981Scy if (len < 1) { 552346981Scy wpa_printf(MSG_DEBUG, 553346981Scy "WNM: Ignore too short Collocated Interference Report from " 554346981Scy MACSTR, MAC2STR(addr)); 555346981Scy return; 556346981Scy } 557346981Scy dialog_token = *buf++; 558346981Scy len--; 559346981Scy 560346981Scy wpa_printf(MSG_DEBUG, 561346981Scy "WNM: Received Collocated Interference Report frame from " 562346981Scy MACSTR " (dialog_token=%u)", 563346981Scy MAC2STR(addr), dialog_token); 564346981Scy wpa_hexdump(MSG_MSGDUMP, "WNM: Collocated Interference Report Elements", 565346981Scy buf, len); 566346981Scy 567346981Scy hex_len = 2 * len + 1; 568346981Scy hex = os_malloc(hex_len); 569346981Scy if (!hex) 570346981Scy return; 571346981Scy wpa_snprintf_hex(hex, hex_len, buf, len); 572346981Scy wpa_msg_ctrl(hapd->msg_ctx, MSG_INFO, COLOC_INTF_REPORT MACSTR " %d %s", 573346981Scy MAC2STR(addr), dialog_token, hex); 574346981Scy os_free(hex); 575346981Scy} 576346981Scy 577346981Scy 578252190Srpauloint ieee802_11_rx_wnm_action_ap(struct hostapd_data *hapd, 579281806Srpaulo const struct ieee80211_mgmt *mgmt, size_t len) 580252190Srpaulo{ 581281806Srpaulo u8 action; 582281806Srpaulo const u8 *payload; 583281806Srpaulo size_t plen; 584281806Srpaulo 585281806Srpaulo if (len < IEEE80211_HDRLEN + 2) 586252190Srpaulo return -1; 587252190Srpaulo 588281806Srpaulo payload = ((const u8 *) mgmt) + IEEE80211_HDRLEN + 1; 589281806Srpaulo action = *payload++; 590281806Srpaulo plen = len - IEEE80211_HDRLEN - 2; 591281806Srpaulo 592281806Srpaulo switch (action) { 593252190Srpaulo case WNM_BSS_TRANS_MGMT_QUERY: 594281806Srpaulo ieee802_11_rx_bss_trans_mgmt_query(hapd, mgmt->sa, payload, 595281806Srpaulo plen); 596281806Srpaulo return 0; 597252190Srpaulo case WNM_BSS_TRANS_MGMT_RESP: 598281806Srpaulo ieee802_11_rx_bss_trans_mgmt_resp(hapd, mgmt->sa, payload, 599281806Srpaulo plen); 600281806Srpaulo return 0; 601252190Srpaulo case WNM_SLEEP_MODE_REQ: 602281806Srpaulo ieee802_11_rx_wnmsleep_req(hapd, mgmt->sa, payload, plen); 603252190Srpaulo return 0; 604337817Scy case WNM_NOTIFICATION_REQ: 605337817Scy ieee802_11_rx_wnm_notification_req(hapd, mgmt->sa, payload, 606337817Scy plen); 607337817Scy return 0; 608346981Scy case WNM_COLLOCATED_INTERFERENCE_REPORT: 609346981Scy ieee802_11_rx_wnm_coloc_intf_report(hapd, mgmt->sa, payload, 610346981Scy plen); 611346981Scy return 0; 612252190Srpaulo } 613252190Srpaulo 614252190Srpaulo wpa_printf(MSG_DEBUG, "WNM: Unsupported WNM Action %u from " MACSTR, 615281806Srpaulo action, MAC2STR(mgmt->sa)); 616252190Srpaulo return -1; 617252190Srpaulo} 618281806Srpaulo 619281806Srpaulo 620281806Srpauloint wnm_send_disassoc_imminent(struct hostapd_data *hapd, 621281806Srpaulo struct sta_info *sta, int disassoc_timer) 622281806Srpaulo{ 623281806Srpaulo u8 buf[1000], *pos; 624281806Srpaulo struct ieee80211_mgmt *mgmt; 625281806Srpaulo 626281806Srpaulo os_memset(buf, 0, sizeof(buf)); 627281806Srpaulo mgmt = (struct ieee80211_mgmt *) buf; 628281806Srpaulo mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, 629281806Srpaulo WLAN_FC_STYPE_ACTION); 630281806Srpaulo os_memcpy(mgmt->da, sta->addr, ETH_ALEN); 631281806Srpaulo os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN); 632281806Srpaulo os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN); 633281806Srpaulo mgmt->u.action.category = WLAN_ACTION_WNM; 634281806Srpaulo mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ; 635281806Srpaulo mgmt->u.action.u.bss_tm_req.dialog_token = 1; 636281806Srpaulo mgmt->u.action.u.bss_tm_req.req_mode = 637281806Srpaulo WNM_BSS_TM_REQ_DISASSOC_IMMINENT; 638281806Srpaulo mgmt->u.action.u.bss_tm_req.disassoc_timer = 639281806Srpaulo host_to_le16(disassoc_timer); 640281806Srpaulo mgmt->u.action.u.bss_tm_req.validity_interval = 0; 641281806Srpaulo 642281806Srpaulo pos = mgmt->u.action.u.bss_tm_req.variable; 643281806Srpaulo 644281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request frame to indicate imminent disassociation (disassoc_timer=%d) to " 645281806Srpaulo MACSTR, disassoc_timer, MAC2STR(sta->addr)); 646281806Srpaulo if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0) < 0) { 647281806Srpaulo wpa_printf(MSG_DEBUG, "Failed to send BSS Transition " 648281806Srpaulo "Management Request frame"); 649281806Srpaulo return -1; 650281806Srpaulo } 651281806Srpaulo 652281806Srpaulo return 0; 653281806Srpaulo} 654281806Srpaulo 655281806Srpaulo 656281806Srpaulostatic void set_disassoc_timer(struct hostapd_data *hapd, struct sta_info *sta, 657281806Srpaulo int disassoc_timer) 658281806Srpaulo{ 659281806Srpaulo int timeout, beacon_int; 660281806Srpaulo 661281806Srpaulo /* 662281806Srpaulo * Prevent STA from reconnecting using cached PMKSA to force 663281806Srpaulo * full authentication with the authentication server (which may 664281806Srpaulo * decide to reject the connection), 665281806Srpaulo */ 666281806Srpaulo wpa_auth_pmksa_remove(hapd->wpa_auth, sta->addr); 667281806Srpaulo 668281806Srpaulo beacon_int = hapd->iconf->beacon_int; 669281806Srpaulo if (beacon_int < 1) 670281806Srpaulo beacon_int = 100; /* best guess */ 671281806Srpaulo /* Calculate timeout in ms based on beacon_int in TU */ 672281806Srpaulo timeout = disassoc_timer * beacon_int * 128 / 125; 673281806Srpaulo wpa_printf(MSG_DEBUG, "Disassociation timer for " MACSTR 674281806Srpaulo " set to %d ms", MAC2STR(sta->addr), timeout); 675281806Srpaulo 676281806Srpaulo sta->timeout_next = STA_DISASSOC_FROM_CLI; 677281806Srpaulo eloop_cancel_timeout(ap_handle_timer, hapd, sta); 678281806Srpaulo eloop_register_timeout(timeout / 1000, 679281806Srpaulo timeout % 1000 * 1000, 680281806Srpaulo ap_handle_timer, hapd, sta); 681281806Srpaulo} 682281806Srpaulo 683281806Srpaulo 684281806Srpauloint wnm_send_ess_disassoc_imminent(struct hostapd_data *hapd, 685281806Srpaulo struct sta_info *sta, const char *url, 686281806Srpaulo int disassoc_timer) 687281806Srpaulo{ 688281806Srpaulo u8 buf[1000], *pos; 689281806Srpaulo struct ieee80211_mgmt *mgmt; 690281806Srpaulo size_t url_len; 691281806Srpaulo 692281806Srpaulo os_memset(buf, 0, sizeof(buf)); 693281806Srpaulo mgmt = (struct ieee80211_mgmt *) buf; 694281806Srpaulo mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, 695281806Srpaulo WLAN_FC_STYPE_ACTION); 696281806Srpaulo os_memcpy(mgmt->da, sta->addr, ETH_ALEN); 697281806Srpaulo os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN); 698281806Srpaulo os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN); 699281806Srpaulo mgmt->u.action.category = WLAN_ACTION_WNM; 700281806Srpaulo mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ; 701281806Srpaulo mgmt->u.action.u.bss_tm_req.dialog_token = 1; 702281806Srpaulo mgmt->u.action.u.bss_tm_req.req_mode = 703281806Srpaulo WNM_BSS_TM_REQ_DISASSOC_IMMINENT | 704281806Srpaulo WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT; 705281806Srpaulo mgmt->u.action.u.bss_tm_req.disassoc_timer = 706281806Srpaulo host_to_le16(disassoc_timer); 707281806Srpaulo mgmt->u.action.u.bss_tm_req.validity_interval = 0x01; 708281806Srpaulo 709281806Srpaulo pos = mgmt->u.action.u.bss_tm_req.variable; 710281806Srpaulo 711281806Srpaulo /* Session Information URL */ 712281806Srpaulo url_len = os_strlen(url); 713281806Srpaulo if (url_len > 255) 714281806Srpaulo return -1; 715281806Srpaulo *pos++ = url_len; 716281806Srpaulo os_memcpy(pos, url, url_len); 717281806Srpaulo pos += url_len; 718281806Srpaulo 719281806Srpaulo if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0) < 0) { 720281806Srpaulo wpa_printf(MSG_DEBUG, "Failed to send BSS Transition " 721281806Srpaulo "Management Request frame"); 722281806Srpaulo return -1; 723281806Srpaulo } 724281806Srpaulo 725281806Srpaulo if (disassoc_timer) { 726281806Srpaulo /* send disassociation frame after time-out */ 727281806Srpaulo set_disassoc_timer(hapd, sta, disassoc_timer); 728281806Srpaulo } 729281806Srpaulo 730281806Srpaulo return 0; 731281806Srpaulo} 732281806Srpaulo 733281806Srpaulo 734281806Srpauloint wnm_send_bss_tm_req(struct hostapd_data *hapd, struct sta_info *sta, 735281806Srpaulo u8 req_mode, int disassoc_timer, u8 valid_int, 736281806Srpaulo const u8 *bss_term_dur, const char *url, 737337817Scy const u8 *nei_rep, size_t nei_rep_len, 738337817Scy const u8 *mbo_attrs, size_t mbo_len) 739281806Srpaulo{ 740281806Srpaulo u8 *buf, *pos; 741281806Srpaulo struct ieee80211_mgmt *mgmt; 742281806Srpaulo size_t url_len; 743281806Srpaulo 744281806Srpaulo wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request to " 745281806Srpaulo MACSTR " req_mode=0x%x disassoc_timer=%d valid_int=0x%x", 746281806Srpaulo MAC2STR(sta->addr), req_mode, disassoc_timer, valid_int); 747337817Scy buf = os_zalloc(1000 + nei_rep_len + mbo_len); 748281806Srpaulo if (buf == NULL) 749281806Srpaulo return -1; 750281806Srpaulo mgmt = (struct ieee80211_mgmt *) buf; 751281806Srpaulo mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, 752281806Srpaulo WLAN_FC_STYPE_ACTION); 753281806Srpaulo os_memcpy(mgmt->da, sta->addr, ETH_ALEN); 754281806Srpaulo os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN); 755281806Srpaulo os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN); 756281806Srpaulo mgmt->u.action.category = WLAN_ACTION_WNM; 757281806Srpaulo mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ; 758281806Srpaulo mgmt->u.action.u.bss_tm_req.dialog_token = 1; 759281806Srpaulo mgmt->u.action.u.bss_tm_req.req_mode = req_mode; 760281806Srpaulo mgmt->u.action.u.bss_tm_req.disassoc_timer = 761281806Srpaulo host_to_le16(disassoc_timer); 762281806Srpaulo mgmt->u.action.u.bss_tm_req.validity_interval = valid_int; 763281806Srpaulo 764281806Srpaulo pos = mgmt->u.action.u.bss_tm_req.variable; 765281806Srpaulo 766281806Srpaulo if ((req_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) && 767281806Srpaulo bss_term_dur) { 768281806Srpaulo os_memcpy(pos, bss_term_dur, 12); 769281806Srpaulo pos += 12; 770281806Srpaulo } 771281806Srpaulo 772281806Srpaulo if (url) { 773281806Srpaulo /* Session Information URL */ 774281806Srpaulo url_len = os_strlen(url); 775281806Srpaulo if (url_len > 255) { 776281806Srpaulo os_free(buf); 777281806Srpaulo return -1; 778281806Srpaulo } 779281806Srpaulo 780281806Srpaulo *pos++ = url_len; 781281806Srpaulo os_memcpy(pos, url, url_len); 782281806Srpaulo pos += url_len; 783281806Srpaulo } 784281806Srpaulo 785281806Srpaulo if (nei_rep) { 786281806Srpaulo os_memcpy(pos, nei_rep, nei_rep_len); 787281806Srpaulo pos += nei_rep_len; 788281806Srpaulo } 789281806Srpaulo 790337817Scy if (mbo_len > 0) { 791337817Scy pos += mbo_add_ie(pos, buf + sizeof(buf) - pos, mbo_attrs, 792337817Scy mbo_len); 793337817Scy } 794337817Scy 795281806Srpaulo if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0) < 0) { 796281806Srpaulo wpa_printf(MSG_DEBUG, 797281806Srpaulo "Failed to send BSS Transition Management Request frame"); 798281806Srpaulo os_free(buf); 799281806Srpaulo return -1; 800281806Srpaulo } 801281806Srpaulo os_free(buf); 802281806Srpaulo 803281806Srpaulo if (disassoc_timer) { 804281806Srpaulo /* send disassociation frame after time-out */ 805281806Srpaulo set_disassoc_timer(hapd, sta, disassoc_timer); 806281806Srpaulo } 807281806Srpaulo 808281806Srpaulo return 0; 809281806Srpaulo} 810346981Scy 811346981Scy 812346981Scyint wnm_send_coloc_intf_req(struct hostapd_data *hapd, struct sta_info *sta, 813346981Scy unsigned int auto_report, unsigned int timeout) 814346981Scy{ 815346981Scy u8 buf[100], *pos; 816346981Scy struct ieee80211_mgmt *mgmt; 817346981Scy u8 dialog_token = 1; 818346981Scy 819346981Scy if (auto_report > 3 || timeout > 63) 820346981Scy return -1; 821346981Scy os_memset(buf, 0, sizeof(buf)); 822346981Scy mgmt = (struct ieee80211_mgmt *) buf; 823346981Scy mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, 824346981Scy WLAN_FC_STYPE_ACTION); 825346981Scy os_memcpy(mgmt->da, sta->addr, ETH_ALEN); 826346981Scy os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN); 827346981Scy os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN); 828346981Scy mgmt->u.action.category = WLAN_ACTION_WNM; 829346981Scy mgmt->u.action.u.coloc_intf_req.action = 830346981Scy WNM_COLLOCATED_INTERFERENCE_REQ; 831346981Scy mgmt->u.action.u.coloc_intf_req.dialog_token = dialog_token; 832346981Scy mgmt->u.action.u.coloc_intf_req.req_info = auto_report | (timeout << 2); 833346981Scy pos = &mgmt->u.action.u.coloc_intf_req.req_info; 834346981Scy pos++; 835346981Scy 836346981Scy wpa_printf(MSG_DEBUG, "WNM: Sending Collocated Interference Request to " 837346981Scy MACSTR " (dialog_token=%u auto_report=%u timeout=%u)", 838346981Scy MAC2STR(sta->addr), dialog_token, auto_report, timeout); 839346981Scy if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0) < 0) { 840346981Scy wpa_printf(MSG_DEBUG, 841346981Scy "WNM: Failed to send Collocated Interference Request frame"); 842346981Scy return -1; 843346981Scy } 844346981Scy 845346981Scy return 0; 846346981Scy} 847