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