xref: /freebsd/contrib/wpa/src/ap/wnm_ap.c (revision 5b9c547c072b84410b50897cc53710c75b2f6b74)
1f05cddf9SRui Paulo /*
2f05cddf9SRui Paulo  * hostapd - WNM
3*5b9c547cSRui Paulo  * Copyright (c) 2011-2014, Qualcomm Atheros, Inc.
4f05cddf9SRui Paulo  *
5f05cddf9SRui Paulo  * This software may be distributed under the terms of the BSD license.
6f05cddf9SRui Paulo  * See README for more details.
7f05cddf9SRui Paulo  */
8f05cddf9SRui Paulo 
9f05cddf9SRui Paulo #include "utils/includes.h"
10f05cddf9SRui Paulo 
11f05cddf9SRui Paulo #include "utils/common.h"
12*5b9c547cSRui Paulo #include "utils/eloop.h"
13f05cddf9SRui Paulo #include "common/ieee802_11_defs.h"
14*5b9c547cSRui Paulo #include "common/wpa_ctrl.h"
15f05cddf9SRui Paulo #include "ap/hostapd.h"
16f05cddf9SRui Paulo #include "ap/sta_info.h"
17f05cddf9SRui Paulo #include "ap/ap_config.h"
18f05cddf9SRui Paulo #include "ap/ap_drv_ops.h"
19f05cddf9SRui Paulo #include "ap/wpa_auth.h"
20f05cddf9SRui Paulo #include "wnm_ap.h"
21f05cddf9SRui Paulo 
22f05cddf9SRui Paulo #define MAX_TFS_IE_LEN  1024
23f05cddf9SRui Paulo 
24f05cddf9SRui Paulo 
25f05cddf9SRui Paulo /* get the TFS IE from driver */
26f05cddf9SRui Paulo static int ieee80211_11_get_tfs_ie(struct hostapd_data *hapd, const u8 *addr,
27f05cddf9SRui Paulo 				   u8 *buf, u16 *buf_len, enum wnm_oper oper)
28f05cddf9SRui Paulo {
29f05cddf9SRui Paulo 	wpa_printf(MSG_DEBUG, "%s: TFS get operation %d", __func__, oper);
30f05cddf9SRui Paulo 
31f05cddf9SRui Paulo 	return hostapd_drv_wnm_oper(hapd, oper, addr, buf, buf_len);
32f05cddf9SRui Paulo }
33f05cddf9SRui Paulo 
34f05cddf9SRui Paulo 
35f05cddf9SRui Paulo /* set the TFS IE to driver */
36f05cddf9SRui Paulo static int ieee80211_11_set_tfs_ie(struct hostapd_data *hapd, const u8 *addr,
37f05cddf9SRui Paulo 				   u8 *buf, u16 *buf_len, enum wnm_oper oper)
38f05cddf9SRui Paulo {
39f05cddf9SRui Paulo 	wpa_printf(MSG_DEBUG, "%s: TFS set operation %d", __func__, oper);
40f05cddf9SRui Paulo 
41f05cddf9SRui Paulo 	return hostapd_drv_wnm_oper(hapd, oper, addr, buf, buf_len);
42f05cddf9SRui Paulo }
43f05cddf9SRui Paulo 
44f05cddf9SRui Paulo 
45f05cddf9SRui Paulo /* MLME-SLEEPMODE.response */
46f05cddf9SRui Paulo static int ieee802_11_send_wnmsleep_resp(struct hostapd_data *hapd,
47f05cddf9SRui Paulo 					 const u8 *addr, u8 dialog_token,
48f05cddf9SRui Paulo 					 u8 action_type, u16 intval)
49f05cddf9SRui Paulo {
50f05cddf9SRui Paulo 	struct ieee80211_mgmt *mgmt;
51f05cddf9SRui Paulo 	int res;
52f05cddf9SRui Paulo 	size_t len;
53f05cddf9SRui Paulo 	size_t gtk_elem_len = 0;
54f05cddf9SRui Paulo 	size_t igtk_elem_len = 0;
55f05cddf9SRui Paulo 	struct wnm_sleep_element wnmsleep_ie;
56f05cddf9SRui Paulo 	u8 *wnmtfs_ie;
57f05cddf9SRui Paulo 	u8 wnmsleep_ie_len;
58f05cddf9SRui Paulo 	u16 wnmtfs_ie_len;
59f05cddf9SRui Paulo 	u8 *pos;
60f05cddf9SRui Paulo 	struct sta_info *sta;
61f05cddf9SRui Paulo 	enum wnm_oper tfs_oper = action_type == WNM_SLEEP_MODE_ENTER ?
62f05cddf9SRui Paulo 		WNM_SLEEP_TFS_RESP_IE_ADD : WNM_SLEEP_TFS_RESP_IE_NONE;
63f05cddf9SRui Paulo 
64f05cddf9SRui Paulo 	sta = ap_get_sta(hapd, addr);
65f05cddf9SRui Paulo 	if (sta == NULL) {
66f05cddf9SRui Paulo 		wpa_printf(MSG_DEBUG, "%s: station not found", __func__);
67f05cddf9SRui Paulo 		return -EINVAL;
68f05cddf9SRui Paulo 	}
69f05cddf9SRui Paulo 
70f05cddf9SRui Paulo 	/* WNM-Sleep Mode IE */
71f05cddf9SRui Paulo 	os_memset(&wnmsleep_ie, 0, sizeof(struct wnm_sleep_element));
72f05cddf9SRui Paulo 	wnmsleep_ie_len = sizeof(struct wnm_sleep_element);
73f05cddf9SRui Paulo 	wnmsleep_ie.eid = WLAN_EID_WNMSLEEP;
74f05cddf9SRui Paulo 	wnmsleep_ie.len = wnmsleep_ie_len - 2;
75f05cddf9SRui Paulo 	wnmsleep_ie.action_type = action_type;
76f05cddf9SRui Paulo 	wnmsleep_ie.status = WNM_STATUS_SLEEP_ACCEPT;
77*5b9c547cSRui Paulo 	wnmsleep_ie.intval = host_to_le16(intval);
78f05cddf9SRui Paulo 
79f05cddf9SRui Paulo 	/* TFS IE(s) */
80f05cddf9SRui Paulo 	wnmtfs_ie = os_zalloc(MAX_TFS_IE_LEN);
81f05cddf9SRui Paulo 	if (wnmtfs_ie == NULL)
82f05cddf9SRui Paulo 		return -1;
83f05cddf9SRui Paulo 	if (ieee80211_11_get_tfs_ie(hapd, addr, wnmtfs_ie, &wnmtfs_ie_len,
84f05cddf9SRui Paulo 				    tfs_oper)) {
85f05cddf9SRui Paulo 		wnmtfs_ie_len = 0;
86f05cddf9SRui Paulo 		os_free(wnmtfs_ie);
87f05cddf9SRui Paulo 		wnmtfs_ie = NULL;
88f05cddf9SRui Paulo 	}
89f05cddf9SRui Paulo 
90f05cddf9SRui Paulo #define MAX_GTK_SUBELEM_LEN 45
91f05cddf9SRui Paulo #define MAX_IGTK_SUBELEM_LEN 26
92f05cddf9SRui Paulo 	mgmt = os_zalloc(sizeof(*mgmt) + wnmsleep_ie_len +
93f05cddf9SRui Paulo 			 MAX_GTK_SUBELEM_LEN + MAX_IGTK_SUBELEM_LEN);
94f05cddf9SRui Paulo 	if (mgmt == NULL) {
95f05cddf9SRui Paulo 		wpa_printf(MSG_DEBUG, "MLME: Failed to allocate buffer for "
96f05cddf9SRui Paulo 			   "WNM-Sleep Response action frame");
97f05cddf9SRui Paulo 		return -1;
98f05cddf9SRui Paulo 	}
99f05cddf9SRui Paulo 	os_memcpy(mgmt->da, addr, ETH_ALEN);
100f05cddf9SRui Paulo 	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
101f05cddf9SRui Paulo 	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
102f05cddf9SRui Paulo 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
103f05cddf9SRui Paulo 					   WLAN_FC_STYPE_ACTION);
104f05cddf9SRui Paulo 	mgmt->u.action.category = WLAN_ACTION_WNM;
105f05cddf9SRui Paulo 	mgmt->u.action.u.wnm_sleep_resp.action = WNM_SLEEP_MODE_RESP;
106f05cddf9SRui Paulo 	mgmt->u.action.u.wnm_sleep_resp.dialogtoken = dialog_token;
107f05cddf9SRui Paulo 	pos = (u8 *)mgmt->u.action.u.wnm_sleep_resp.variable;
108f05cddf9SRui Paulo 	/* add key data if MFP is enabled */
109f05cddf9SRui Paulo 	if (!wpa_auth_uses_mfp(sta->wpa_sm) ||
110f05cddf9SRui Paulo 	    action_type != WNM_SLEEP_MODE_EXIT) {
111f05cddf9SRui Paulo 		mgmt->u.action.u.wnm_sleep_resp.keydata_len = 0;
112f05cddf9SRui Paulo 	} else {
113f05cddf9SRui Paulo 		gtk_elem_len = wpa_wnmsleep_gtk_subelem(sta->wpa_sm, pos);
114f05cddf9SRui Paulo 		pos += gtk_elem_len;
115f05cddf9SRui Paulo 		wpa_printf(MSG_DEBUG, "Pass 4, gtk_len = %d",
116f05cddf9SRui Paulo 			   (int) gtk_elem_len);
117f05cddf9SRui Paulo #ifdef CONFIG_IEEE80211W
118f05cddf9SRui Paulo 		res = wpa_wnmsleep_igtk_subelem(sta->wpa_sm, pos);
119f05cddf9SRui Paulo 		if (res < 0) {
120f05cddf9SRui Paulo 			os_free(wnmtfs_ie);
121f05cddf9SRui Paulo 			os_free(mgmt);
122f05cddf9SRui Paulo 			return -1;
123f05cddf9SRui Paulo 		}
124f05cddf9SRui Paulo 		igtk_elem_len = res;
125f05cddf9SRui Paulo 		pos += igtk_elem_len;
126f05cddf9SRui Paulo 		wpa_printf(MSG_DEBUG, "Pass 4 igtk_len = %d",
127f05cddf9SRui Paulo 			   (int) igtk_elem_len);
128f05cddf9SRui Paulo #endif /* CONFIG_IEEE80211W */
129f05cddf9SRui Paulo 
130f05cddf9SRui Paulo 		WPA_PUT_LE16((u8 *)
131f05cddf9SRui Paulo 			     &mgmt->u.action.u.wnm_sleep_resp.keydata_len,
132f05cddf9SRui Paulo 			     gtk_elem_len + igtk_elem_len);
133f05cddf9SRui Paulo 	}
134f05cddf9SRui Paulo 	os_memcpy(pos, &wnmsleep_ie, wnmsleep_ie_len);
135f05cddf9SRui Paulo 	/* copy TFS IE here */
136f05cddf9SRui Paulo 	pos += wnmsleep_ie_len;
137f05cddf9SRui Paulo 	if (wnmtfs_ie)
138f05cddf9SRui Paulo 		os_memcpy(pos, wnmtfs_ie, wnmtfs_ie_len);
139f05cddf9SRui Paulo 
140f05cddf9SRui Paulo 	len = 1 + sizeof(mgmt->u.action.u.wnm_sleep_resp) + gtk_elem_len +
141f05cddf9SRui Paulo 		igtk_elem_len + wnmsleep_ie_len + wnmtfs_ie_len;
142f05cddf9SRui Paulo 
143f05cddf9SRui Paulo 	/* In driver, response frame should be forced to sent when STA is in
144f05cddf9SRui Paulo 	 * PS mode */
145f05cddf9SRui Paulo 	res = hostapd_drv_send_action(hapd, hapd->iface->freq, 0,
146f05cddf9SRui Paulo 				      mgmt->da, &mgmt->u.action.category, len);
147f05cddf9SRui Paulo 
148f05cddf9SRui Paulo 	if (!res) {
149f05cddf9SRui Paulo 		wpa_printf(MSG_DEBUG, "Successfully send WNM-Sleep Response "
150f05cddf9SRui Paulo 			   "frame");
151f05cddf9SRui Paulo 
152f05cddf9SRui Paulo 		/* when entering wnmsleep
153f05cddf9SRui Paulo 		 * 1. pause the node in driver
154f05cddf9SRui Paulo 		 * 2. mark the node so that AP won't update GTK/IGTK during
155f05cddf9SRui Paulo 		 * WNM Sleep
156f05cddf9SRui Paulo 		 */
157f05cddf9SRui Paulo 		if (wnmsleep_ie.status == WNM_STATUS_SLEEP_ACCEPT &&
158f05cddf9SRui Paulo 		    wnmsleep_ie.action_type == WNM_SLEEP_MODE_ENTER) {
159*5b9c547cSRui Paulo 			sta->flags |= WLAN_STA_WNM_SLEEP_MODE;
160f05cddf9SRui Paulo 			hostapd_drv_wnm_oper(hapd, WNM_SLEEP_ENTER_CONFIRM,
161f05cddf9SRui Paulo 					     addr, NULL, NULL);
162f05cddf9SRui Paulo 			wpa_set_wnmsleep(sta->wpa_sm, 1);
163f05cddf9SRui Paulo 		}
164f05cddf9SRui Paulo 		/* when exiting wnmsleep
165f05cddf9SRui Paulo 		 * 1. unmark the node
166f05cddf9SRui Paulo 		 * 2. start GTK/IGTK update if MFP is not used
167f05cddf9SRui Paulo 		 * 3. unpause the node in driver
168f05cddf9SRui Paulo 		 */
169f05cddf9SRui Paulo 		if ((wnmsleep_ie.status == WNM_STATUS_SLEEP_ACCEPT ||
170f05cddf9SRui Paulo 		     wnmsleep_ie.status ==
171f05cddf9SRui Paulo 		     WNM_STATUS_SLEEP_EXIT_ACCEPT_GTK_UPDATE) &&
172f05cddf9SRui Paulo 		    wnmsleep_ie.action_type == WNM_SLEEP_MODE_EXIT) {
173*5b9c547cSRui Paulo 			sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE;
174f05cddf9SRui Paulo 			wpa_set_wnmsleep(sta->wpa_sm, 0);
175f05cddf9SRui Paulo 			hostapd_drv_wnm_oper(hapd, WNM_SLEEP_EXIT_CONFIRM,
176f05cddf9SRui Paulo 					     addr, NULL, NULL);
177f05cddf9SRui Paulo 			if (!wpa_auth_uses_mfp(sta->wpa_sm))
178f05cddf9SRui Paulo 				wpa_wnmsleep_rekey_gtk(sta->wpa_sm);
179f05cddf9SRui Paulo 		}
180f05cddf9SRui Paulo 	} else
181f05cddf9SRui Paulo 		wpa_printf(MSG_DEBUG, "Fail to send WNM-Sleep Response frame");
182f05cddf9SRui Paulo 
183f05cddf9SRui Paulo #undef MAX_GTK_SUBELEM_LEN
184f05cddf9SRui Paulo #undef MAX_IGTK_SUBELEM_LEN
185f05cddf9SRui Paulo 	os_free(wnmtfs_ie);
186f05cddf9SRui Paulo 	os_free(mgmt);
187f05cddf9SRui Paulo 	return res;
188f05cddf9SRui Paulo }
189f05cddf9SRui Paulo 
190f05cddf9SRui Paulo 
191f05cddf9SRui Paulo static void ieee802_11_rx_wnmsleep_req(struct hostapd_data *hapd,
192f05cddf9SRui Paulo 				       const u8 *addr, const u8 *frm, int len)
193f05cddf9SRui Paulo {
194f05cddf9SRui Paulo 	/* Dialog Token [1] | WNM-Sleep Mode IE | TFS Response IE */
195f05cddf9SRui Paulo 	const u8 *pos = frm;
196f05cddf9SRui Paulo 	u8 dialog_token;
197f05cddf9SRui Paulo 	struct wnm_sleep_element *wnmsleep_ie = NULL;
198f05cddf9SRui Paulo 	/* multiple TFS Req IE (assuming consecutive) */
199f05cddf9SRui Paulo 	u8 *tfsreq_ie_start = NULL;
200f05cddf9SRui Paulo 	u8 *tfsreq_ie_end = NULL;
201f05cddf9SRui Paulo 	u16 tfsreq_ie_len = 0;
202f05cddf9SRui Paulo 
203f05cddf9SRui Paulo 	dialog_token = *pos++;
204f05cddf9SRui Paulo 	while (pos + 1 < frm + len) {
205f05cddf9SRui Paulo 		u8 ie_len = pos[1];
206f05cddf9SRui Paulo 		if (pos + 2 + ie_len > frm + len)
207f05cddf9SRui Paulo 			break;
208f05cddf9SRui Paulo 		if (*pos == WLAN_EID_WNMSLEEP)
209f05cddf9SRui Paulo 			wnmsleep_ie = (struct wnm_sleep_element *) pos;
210f05cddf9SRui Paulo 		else if (*pos == WLAN_EID_TFS_REQ) {
211f05cddf9SRui Paulo 			if (!tfsreq_ie_start)
212f05cddf9SRui Paulo 				tfsreq_ie_start = (u8 *) pos;
213f05cddf9SRui Paulo 			tfsreq_ie_end = (u8 *) pos;
214f05cddf9SRui Paulo 		} else
215f05cddf9SRui Paulo 			wpa_printf(MSG_DEBUG, "WNM: EID %d not recognized",
216f05cddf9SRui Paulo 				   *pos);
217f05cddf9SRui Paulo 		pos += ie_len + 2;
218f05cddf9SRui Paulo 	}
219f05cddf9SRui Paulo 
220f05cddf9SRui Paulo 	if (!wnmsleep_ie) {
221f05cddf9SRui Paulo 		wpa_printf(MSG_DEBUG, "No WNM-Sleep IE found");
222f05cddf9SRui Paulo 		return;
223f05cddf9SRui Paulo 	}
224f05cddf9SRui Paulo 
225f05cddf9SRui Paulo 	if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_ENTER &&
226f05cddf9SRui Paulo 	    tfsreq_ie_start && tfsreq_ie_end &&
227f05cddf9SRui Paulo 	    tfsreq_ie_end - tfsreq_ie_start >= 0) {
228f05cddf9SRui Paulo 		tfsreq_ie_len = (tfsreq_ie_end + tfsreq_ie_end[1] + 2) -
229f05cddf9SRui Paulo 			tfsreq_ie_start;
230f05cddf9SRui Paulo 		wpa_printf(MSG_DEBUG, "TFS Req IE(s) found");
231f05cddf9SRui Paulo 		/* pass the TFS Req IE(s) to driver for processing */
232f05cddf9SRui Paulo 		if (ieee80211_11_set_tfs_ie(hapd, addr, tfsreq_ie_start,
233f05cddf9SRui Paulo 					    &tfsreq_ie_len,
234f05cddf9SRui Paulo 					    WNM_SLEEP_TFS_REQ_IE_SET))
235f05cddf9SRui Paulo 			wpa_printf(MSG_DEBUG, "Fail to set TFS Req IE");
236f05cddf9SRui Paulo 	}
237f05cddf9SRui Paulo 
238f05cddf9SRui Paulo 	ieee802_11_send_wnmsleep_resp(hapd, addr, dialog_token,
239f05cddf9SRui Paulo 				      wnmsleep_ie->action_type,
240*5b9c547cSRui Paulo 				      le_to_host16(wnmsleep_ie->intval));
241f05cddf9SRui Paulo 
242f05cddf9SRui Paulo 	if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT) {
243f05cddf9SRui Paulo 		/* clear the tfs after sending the resp frame */
244f05cddf9SRui Paulo 		ieee80211_11_set_tfs_ie(hapd, addr, tfsreq_ie_start,
245f05cddf9SRui Paulo 					&tfsreq_ie_len, WNM_SLEEP_TFS_IE_DEL);
246f05cddf9SRui Paulo 	}
247f05cddf9SRui Paulo }
248f05cddf9SRui Paulo 
249f05cddf9SRui Paulo 
250*5b9c547cSRui Paulo static int ieee802_11_send_bss_trans_mgmt_request(struct hostapd_data *hapd,
251*5b9c547cSRui Paulo 						  const u8 *addr,
252*5b9c547cSRui Paulo 						  u8 dialog_token,
253*5b9c547cSRui Paulo 						  const char *url)
254f05cddf9SRui Paulo {
255*5b9c547cSRui Paulo 	struct ieee80211_mgmt *mgmt;
256*5b9c547cSRui Paulo 	size_t url_len, len;
257*5b9c547cSRui Paulo 	u8 *pos;
258*5b9c547cSRui Paulo 	int res;
259*5b9c547cSRui Paulo 
260*5b9c547cSRui Paulo 	if (url)
261*5b9c547cSRui Paulo 		url_len = os_strlen(url);
262*5b9c547cSRui Paulo 	else
263*5b9c547cSRui Paulo 		url_len = 0;
264*5b9c547cSRui Paulo 
265*5b9c547cSRui Paulo 	mgmt = os_zalloc(sizeof(*mgmt) + (url_len ? 1 + url_len : 0));
266*5b9c547cSRui Paulo 	if (mgmt == NULL)
267*5b9c547cSRui Paulo 		return -1;
268*5b9c547cSRui Paulo 	os_memcpy(mgmt->da, addr, ETH_ALEN);
269*5b9c547cSRui Paulo 	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
270*5b9c547cSRui Paulo 	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
271*5b9c547cSRui Paulo 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
272*5b9c547cSRui Paulo 					   WLAN_FC_STYPE_ACTION);
273*5b9c547cSRui Paulo 	mgmt->u.action.category = WLAN_ACTION_WNM;
274*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ;
275*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.dialog_token = dialog_token;
276*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.req_mode = 0;
277*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.disassoc_timer = host_to_le16(0);
278*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.validity_interval = 1;
279*5b9c547cSRui Paulo 	pos = mgmt->u.action.u.bss_tm_req.variable;
280*5b9c547cSRui Paulo 	if (url) {
281*5b9c547cSRui Paulo 		*pos++ += url_len;
282*5b9c547cSRui Paulo 		os_memcpy(pos, url, url_len);
283*5b9c547cSRui Paulo 		pos += url_len;
284*5b9c547cSRui Paulo 	}
285*5b9c547cSRui Paulo 
286*5b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request to "
287*5b9c547cSRui Paulo 		   MACSTR " dialog_token=%u req_mode=0x%x disassoc_timer=%u "
288*5b9c547cSRui Paulo 		   "validity_interval=%u",
289*5b9c547cSRui Paulo 		   MAC2STR(addr), dialog_token,
290*5b9c547cSRui Paulo 		   mgmt->u.action.u.bss_tm_req.req_mode,
291*5b9c547cSRui Paulo 		   le_to_host16(mgmt->u.action.u.bss_tm_req.disassoc_timer),
292*5b9c547cSRui Paulo 		   mgmt->u.action.u.bss_tm_req.validity_interval);
293*5b9c547cSRui Paulo 
294*5b9c547cSRui Paulo 	len = pos - &mgmt->u.action.category;
295*5b9c547cSRui Paulo 	res = hostapd_drv_send_action(hapd, hapd->iface->freq, 0,
296*5b9c547cSRui Paulo 				      mgmt->da, &mgmt->u.action.category, len);
297*5b9c547cSRui Paulo 	os_free(mgmt);
298*5b9c547cSRui Paulo 	return res;
299*5b9c547cSRui Paulo }
300*5b9c547cSRui Paulo 
301*5b9c547cSRui Paulo 
302*5b9c547cSRui Paulo static void ieee802_11_rx_bss_trans_mgmt_query(struct hostapd_data *hapd,
303*5b9c547cSRui Paulo 					       const u8 *addr, const u8 *frm,
304*5b9c547cSRui Paulo 					       size_t len)
305*5b9c547cSRui Paulo {
306*5b9c547cSRui Paulo 	u8 dialog_token, reason;
307*5b9c547cSRui Paulo 	const u8 *pos, *end;
308*5b9c547cSRui Paulo 
309*5b9c547cSRui Paulo 	if (len < 2) {
310*5b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG, "WNM: Ignore too short BSS Transition Management Query from "
311*5b9c547cSRui Paulo 			   MACSTR, MAC2STR(addr));
312*5b9c547cSRui Paulo 		return;
313*5b9c547cSRui Paulo 	}
314*5b9c547cSRui Paulo 
315*5b9c547cSRui Paulo 	pos = frm;
316*5b9c547cSRui Paulo 	end = pos + len;
317*5b9c547cSRui Paulo 	dialog_token = *pos++;
318*5b9c547cSRui Paulo 	reason = *pos++;
319*5b9c547cSRui Paulo 
320*5b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Query from "
321*5b9c547cSRui Paulo 		   MACSTR " dialog_token=%u reason=%u",
322*5b9c547cSRui Paulo 		   MAC2STR(addr), dialog_token, reason);
323*5b9c547cSRui Paulo 
324*5b9c547cSRui Paulo 	wpa_hexdump(MSG_DEBUG, "WNM: BSS Transition Candidate List Entries",
325*5b9c547cSRui Paulo 		    pos, end - pos);
326*5b9c547cSRui Paulo 
327*5b9c547cSRui Paulo 	ieee802_11_send_bss_trans_mgmt_request(hapd, addr, dialog_token, NULL);
328*5b9c547cSRui Paulo }
329*5b9c547cSRui Paulo 
330*5b9c547cSRui Paulo 
331*5b9c547cSRui Paulo static void ieee802_11_rx_bss_trans_mgmt_resp(struct hostapd_data *hapd,
332*5b9c547cSRui Paulo 					      const u8 *addr, const u8 *frm,
333*5b9c547cSRui Paulo 					      size_t len)
334*5b9c547cSRui Paulo {
335*5b9c547cSRui Paulo 	u8 dialog_token, status_code, bss_termination_delay;
336*5b9c547cSRui Paulo 	const u8 *pos, *end;
337*5b9c547cSRui Paulo 
338*5b9c547cSRui Paulo 	if (len < 3) {
339*5b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG, "WNM: Ignore too short BSS Transition Management Response from "
340*5b9c547cSRui Paulo 			   MACSTR, MAC2STR(addr));
341*5b9c547cSRui Paulo 		return;
342*5b9c547cSRui Paulo 	}
343*5b9c547cSRui Paulo 
344*5b9c547cSRui Paulo 	pos = frm;
345*5b9c547cSRui Paulo 	end = pos + len;
346*5b9c547cSRui Paulo 	dialog_token = *pos++;
347*5b9c547cSRui Paulo 	status_code = *pos++;
348*5b9c547cSRui Paulo 	bss_termination_delay = *pos++;
349*5b9c547cSRui Paulo 
350*5b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Response from "
351*5b9c547cSRui Paulo 		   MACSTR " dialog_token=%u status_code=%u "
352*5b9c547cSRui Paulo 		   "bss_termination_delay=%u", MAC2STR(addr), dialog_token,
353*5b9c547cSRui Paulo 		   status_code, bss_termination_delay);
354*5b9c547cSRui Paulo 
355*5b9c547cSRui Paulo 	if (status_code == WNM_BSS_TM_ACCEPT) {
356*5b9c547cSRui Paulo 		if (end - pos < ETH_ALEN) {
357*5b9c547cSRui Paulo 			wpa_printf(MSG_DEBUG, "WNM: not enough room for Target BSSID field");
358*5b9c547cSRui Paulo 			return;
359*5b9c547cSRui Paulo 		}
360*5b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG, "WNM: Target BSSID: " MACSTR,
361*5b9c547cSRui Paulo 			   MAC2STR(pos));
362*5b9c547cSRui Paulo 		wpa_msg(hapd->msg_ctx, MSG_INFO, BSS_TM_RESP MACSTR
363*5b9c547cSRui Paulo 			" status_code=%u bss_termination_delay=%u target_bssid="
364*5b9c547cSRui Paulo 			MACSTR,
365*5b9c547cSRui Paulo 			MAC2STR(addr), status_code, bss_termination_delay,
366*5b9c547cSRui Paulo 			MAC2STR(pos));
367*5b9c547cSRui Paulo 		pos += ETH_ALEN;
368*5b9c547cSRui Paulo 	} else {
369*5b9c547cSRui Paulo 		wpa_msg(hapd->msg_ctx, MSG_INFO, BSS_TM_RESP MACSTR
370*5b9c547cSRui Paulo 			" status_code=%u bss_termination_delay=%u",
371*5b9c547cSRui Paulo 			MAC2STR(addr), status_code, bss_termination_delay);
372*5b9c547cSRui Paulo 	}
373*5b9c547cSRui Paulo 
374*5b9c547cSRui Paulo 	wpa_hexdump(MSG_DEBUG, "WNM: BSS Transition Candidate List Entries",
375*5b9c547cSRui Paulo 		    pos, end - pos);
376*5b9c547cSRui Paulo }
377*5b9c547cSRui Paulo 
378*5b9c547cSRui Paulo 
379*5b9c547cSRui Paulo int ieee802_11_rx_wnm_action_ap(struct hostapd_data *hapd,
380*5b9c547cSRui Paulo 				const struct ieee80211_mgmt *mgmt, size_t len)
381*5b9c547cSRui Paulo {
382*5b9c547cSRui Paulo 	u8 action;
383*5b9c547cSRui Paulo 	const u8 *payload;
384*5b9c547cSRui Paulo 	size_t plen;
385*5b9c547cSRui Paulo 
386*5b9c547cSRui Paulo 	if (len < IEEE80211_HDRLEN + 2)
387f05cddf9SRui Paulo 		return -1;
388f05cddf9SRui Paulo 
389*5b9c547cSRui Paulo 	payload = ((const u8 *) mgmt) + IEEE80211_HDRLEN + 1;
390*5b9c547cSRui Paulo 	action = *payload++;
391*5b9c547cSRui Paulo 	plen = len - IEEE80211_HDRLEN - 2;
392*5b9c547cSRui Paulo 
393*5b9c547cSRui Paulo 	switch (action) {
394f05cddf9SRui Paulo 	case WNM_BSS_TRANS_MGMT_QUERY:
395*5b9c547cSRui Paulo 		ieee802_11_rx_bss_trans_mgmt_query(hapd, mgmt->sa, payload,
396*5b9c547cSRui Paulo 						   plen);
397*5b9c547cSRui Paulo 		return 0;
398f05cddf9SRui Paulo 	case WNM_BSS_TRANS_MGMT_RESP:
399*5b9c547cSRui Paulo 		ieee802_11_rx_bss_trans_mgmt_resp(hapd, mgmt->sa, payload,
400*5b9c547cSRui Paulo 						  plen);
401*5b9c547cSRui Paulo 		return 0;
402f05cddf9SRui Paulo 	case WNM_SLEEP_MODE_REQ:
403*5b9c547cSRui Paulo 		ieee802_11_rx_wnmsleep_req(hapd, mgmt->sa, payload, plen);
404f05cddf9SRui Paulo 		return 0;
405f05cddf9SRui Paulo 	}
406f05cddf9SRui Paulo 
407f05cddf9SRui Paulo 	wpa_printf(MSG_DEBUG, "WNM: Unsupported WNM Action %u from " MACSTR,
408*5b9c547cSRui Paulo 		   action, MAC2STR(mgmt->sa));
409f05cddf9SRui Paulo 	return -1;
410f05cddf9SRui Paulo }
411*5b9c547cSRui Paulo 
412*5b9c547cSRui Paulo 
413*5b9c547cSRui Paulo int wnm_send_disassoc_imminent(struct hostapd_data *hapd,
414*5b9c547cSRui Paulo 			       struct sta_info *sta, int disassoc_timer)
415*5b9c547cSRui Paulo {
416*5b9c547cSRui Paulo 	u8 buf[1000], *pos;
417*5b9c547cSRui Paulo 	struct ieee80211_mgmt *mgmt;
418*5b9c547cSRui Paulo 
419*5b9c547cSRui Paulo 	os_memset(buf, 0, sizeof(buf));
420*5b9c547cSRui Paulo 	mgmt = (struct ieee80211_mgmt *) buf;
421*5b9c547cSRui Paulo 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
422*5b9c547cSRui Paulo 					   WLAN_FC_STYPE_ACTION);
423*5b9c547cSRui Paulo 	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
424*5b9c547cSRui Paulo 	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
425*5b9c547cSRui Paulo 	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
426*5b9c547cSRui Paulo 	mgmt->u.action.category = WLAN_ACTION_WNM;
427*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ;
428*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.dialog_token = 1;
429*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.req_mode =
430*5b9c547cSRui Paulo 		WNM_BSS_TM_REQ_DISASSOC_IMMINENT;
431*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.disassoc_timer =
432*5b9c547cSRui Paulo 		host_to_le16(disassoc_timer);
433*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.validity_interval = 0;
434*5b9c547cSRui Paulo 
435*5b9c547cSRui Paulo 	pos = mgmt->u.action.u.bss_tm_req.variable;
436*5b9c547cSRui Paulo 
437*5b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request frame to indicate imminent disassociation (disassoc_timer=%d) to "
438*5b9c547cSRui Paulo 		   MACSTR, disassoc_timer, MAC2STR(sta->addr));
439*5b9c547cSRui Paulo 	if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0) < 0) {
440*5b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG, "Failed to send BSS Transition "
441*5b9c547cSRui Paulo 			   "Management Request frame");
442*5b9c547cSRui Paulo 		return -1;
443*5b9c547cSRui Paulo 	}
444*5b9c547cSRui Paulo 
445*5b9c547cSRui Paulo 	return 0;
446*5b9c547cSRui Paulo }
447*5b9c547cSRui Paulo 
448*5b9c547cSRui Paulo 
449*5b9c547cSRui Paulo static void set_disassoc_timer(struct hostapd_data *hapd, struct sta_info *sta,
450*5b9c547cSRui Paulo 			       int disassoc_timer)
451*5b9c547cSRui Paulo {
452*5b9c547cSRui Paulo 	int timeout, beacon_int;
453*5b9c547cSRui Paulo 
454*5b9c547cSRui Paulo 	/*
455*5b9c547cSRui Paulo 	 * Prevent STA from reconnecting using cached PMKSA to force
456*5b9c547cSRui Paulo 	 * full authentication with the authentication server (which may
457*5b9c547cSRui Paulo 	 * decide to reject the connection),
458*5b9c547cSRui Paulo 	 */
459*5b9c547cSRui Paulo 	wpa_auth_pmksa_remove(hapd->wpa_auth, sta->addr);
460*5b9c547cSRui Paulo 
461*5b9c547cSRui Paulo 	beacon_int = hapd->iconf->beacon_int;
462*5b9c547cSRui Paulo 	if (beacon_int < 1)
463*5b9c547cSRui Paulo 		beacon_int = 100; /* best guess */
464*5b9c547cSRui Paulo 	/* Calculate timeout in ms based on beacon_int in TU */
465*5b9c547cSRui Paulo 	timeout = disassoc_timer * beacon_int * 128 / 125;
466*5b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "Disassociation timer for " MACSTR
467*5b9c547cSRui Paulo 		   " set to %d ms", MAC2STR(sta->addr), timeout);
468*5b9c547cSRui Paulo 
469*5b9c547cSRui Paulo 	sta->timeout_next = STA_DISASSOC_FROM_CLI;
470*5b9c547cSRui Paulo 	eloop_cancel_timeout(ap_handle_timer, hapd, sta);
471*5b9c547cSRui Paulo 	eloop_register_timeout(timeout / 1000,
472*5b9c547cSRui Paulo 			       timeout % 1000 * 1000,
473*5b9c547cSRui Paulo 			       ap_handle_timer, hapd, sta);
474*5b9c547cSRui Paulo }
475*5b9c547cSRui Paulo 
476*5b9c547cSRui Paulo 
477*5b9c547cSRui Paulo int wnm_send_ess_disassoc_imminent(struct hostapd_data *hapd,
478*5b9c547cSRui Paulo 				   struct sta_info *sta, const char *url,
479*5b9c547cSRui Paulo 				   int disassoc_timer)
480*5b9c547cSRui Paulo {
481*5b9c547cSRui Paulo 	u8 buf[1000], *pos;
482*5b9c547cSRui Paulo 	struct ieee80211_mgmt *mgmt;
483*5b9c547cSRui Paulo 	size_t url_len;
484*5b9c547cSRui Paulo 
485*5b9c547cSRui Paulo 	os_memset(buf, 0, sizeof(buf));
486*5b9c547cSRui Paulo 	mgmt = (struct ieee80211_mgmt *) buf;
487*5b9c547cSRui Paulo 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
488*5b9c547cSRui Paulo 					   WLAN_FC_STYPE_ACTION);
489*5b9c547cSRui Paulo 	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
490*5b9c547cSRui Paulo 	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
491*5b9c547cSRui Paulo 	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
492*5b9c547cSRui Paulo 	mgmt->u.action.category = WLAN_ACTION_WNM;
493*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ;
494*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.dialog_token = 1;
495*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.req_mode =
496*5b9c547cSRui Paulo 		WNM_BSS_TM_REQ_DISASSOC_IMMINENT |
497*5b9c547cSRui Paulo 		WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT;
498*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.disassoc_timer =
499*5b9c547cSRui Paulo 		host_to_le16(disassoc_timer);
500*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.validity_interval = 0x01;
501*5b9c547cSRui Paulo 
502*5b9c547cSRui Paulo 	pos = mgmt->u.action.u.bss_tm_req.variable;
503*5b9c547cSRui Paulo 
504*5b9c547cSRui Paulo 	/* Session Information URL */
505*5b9c547cSRui Paulo 	url_len = os_strlen(url);
506*5b9c547cSRui Paulo 	if (url_len > 255)
507*5b9c547cSRui Paulo 		return -1;
508*5b9c547cSRui Paulo 	*pos++ = url_len;
509*5b9c547cSRui Paulo 	os_memcpy(pos, url, url_len);
510*5b9c547cSRui Paulo 	pos += url_len;
511*5b9c547cSRui Paulo 
512*5b9c547cSRui Paulo 	if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0) < 0) {
513*5b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG, "Failed to send BSS Transition "
514*5b9c547cSRui Paulo 			   "Management Request frame");
515*5b9c547cSRui Paulo 		return -1;
516*5b9c547cSRui Paulo 	}
517*5b9c547cSRui Paulo 
518*5b9c547cSRui Paulo 	if (disassoc_timer) {
519*5b9c547cSRui Paulo 		/* send disassociation frame after time-out */
520*5b9c547cSRui Paulo 		set_disassoc_timer(hapd, sta, disassoc_timer);
521*5b9c547cSRui Paulo 	}
522*5b9c547cSRui Paulo 
523*5b9c547cSRui Paulo 	return 0;
524*5b9c547cSRui Paulo }
525*5b9c547cSRui Paulo 
526*5b9c547cSRui Paulo 
527*5b9c547cSRui Paulo int wnm_send_bss_tm_req(struct hostapd_data *hapd, struct sta_info *sta,
528*5b9c547cSRui Paulo 			u8 req_mode, int disassoc_timer, u8 valid_int,
529*5b9c547cSRui Paulo 			const u8 *bss_term_dur, const char *url,
530*5b9c547cSRui Paulo 			const u8 *nei_rep, size_t nei_rep_len)
531*5b9c547cSRui Paulo {
532*5b9c547cSRui Paulo 	u8 *buf, *pos;
533*5b9c547cSRui Paulo 	struct ieee80211_mgmt *mgmt;
534*5b9c547cSRui Paulo 	size_t url_len;
535*5b9c547cSRui Paulo 
536*5b9c547cSRui Paulo 	wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request to "
537*5b9c547cSRui Paulo 		   MACSTR " req_mode=0x%x disassoc_timer=%d valid_int=0x%x",
538*5b9c547cSRui Paulo 		   MAC2STR(sta->addr), req_mode, disassoc_timer, valid_int);
539*5b9c547cSRui Paulo 	buf = os_zalloc(1000 + nei_rep_len);
540*5b9c547cSRui Paulo 	if (buf == NULL)
541*5b9c547cSRui Paulo 		return -1;
542*5b9c547cSRui Paulo 	mgmt = (struct ieee80211_mgmt *) buf;
543*5b9c547cSRui Paulo 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
544*5b9c547cSRui Paulo 					   WLAN_FC_STYPE_ACTION);
545*5b9c547cSRui Paulo 	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
546*5b9c547cSRui Paulo 	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
547*5b9c547cSRui Paulo 	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
548*5b9c547cSRui Paulo 	mgmt->u.action.category = WLAN_ACTION_WNM;
549*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ;
550*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.dialog_token = 1;
551*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.req_mode = req_mode;
552*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.disassoc_timer =
553*5b9c547cSRui Paulo 		host_to_le16(disassoc_timer);
554*5b9c547cSRui Paulo 	mgmt->u.action.u.bss_tm_req.validity_interval = valid_int;
555*5b9c547cSRui Paulo 
556*5b9c547cSRui Paulo 	pos = mgmt->u.action.u.bss_tm_req.variable;
557*5b9c547cSRui Paulo 
558*5b9c547cSRui Paulo 	if ((req_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) &&
559*5b9c547cSRui Paulo 	    bss_term_dur) {
560*5b9c547cSRui Paulo 		os_memcpy(pos, bss_term_dur, 12);
561*5b9c547cSRui Paulo 		pos += 12;
562*5b9c547cSRui Paulo 	}
563*5b9c547cSRui Paulo 
564*5b9c547cSRui Paulo 	if (url) {
565*5b9c547cSRui Paulo 		/* Session Information URL */
566*5b9c547cSRui Paulo 		url_len = os_strlen(url);
567*5b9c547cSRui Paulo 		if (url_len > 255) {
568*5b9c547cSRui Paulo 			os_free(buf);
569*5b9c547cSRui Paulo 			return -1;
570*5b9c547cSRui Paulo 		}
571*5b9c547cSRui Paulo 
572*5b9c547cSRui Paulo 		*pos++ = url_len;
573*5b9c547cSRui Paulo 		os_memcpy(pos, url, url_len);
574*5b9c547cSRui Paulo 		pos += url_len;
575*5b9c547cSRui Paulo 	}
576*5b9c547cSRui Paulo 
577*5b9c547cSRui Paulo 	if (nei_rep) {
578*5b9c547cSRui Paulo 		os_memcpy(pos, nei_rep, nei_rep_len);
579*5b9c547cSRui Paulo 		pos += nei_rep_len;
580*5b9c547cSRui Paulo 	}
581*5b9c547cSRui Paulo 
582*5b9c547cSRui Paulo 	if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0) < 0) {
583*5b9c547cSRui Paulo 		wpa_printf(MSG_DEBUG,
584*5b9c547cSRui Paulo 			   "Failed to send BSS Transition Management Request frame");
585*5b9c547cSRui Paulo 		os_free(buf);
586*5b9c547cSRui Paulo 		return -1;
587*5b9c547cSRui Paulo 	}
588*5b9c547cSRui Paulo 	os_free(buf);
589*5b9c547cSRui Paulo 
590*5b9c547cSRui Paulo 	if (disassoc_timer) {
591*5b9c547cSRui Paulo 		/* send disassociation frame after time-out */
592*5b9c547cSRui Paulo 		set_disassoc_timer(hapd, sta, disassoc_timer);
593*5b9c547cSRui Paulo 	}
594*5b9c547cSRui Paulo 
595*5b9c547cSRui Paulo 	return 0;
596*5b9c547cSRui Paulo }
597