1f05cddf9SRui Paulo /*
2f05cddf9SRui Paulo * wpa_supplicant - WNM
35b9c547cSRui Paulo * Copyright (c) 2011-2013, 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"
12f05cddf9SRui Paulo #include "common/ieee802_11_defs.h"
135b9c547cSRui Paulo #include "common/ieee802_11_common.h"
145b9c547cSRui Paulo #include "common/wpa_ctrl.h"
154bc52338SCy Schubert #include "common/ocv.h"
16f05cddf9SRui Paulo #include "rsn_supp/wpa.h"
1785732ac8SCy Schubert #include "config.h"
18f05cddf9SRui Paulo #include "wpa_supplicant_i.h"
19f05cddf9SRui Paulo #include "driver_i.h"
20f05cddf9SRui Paulo #include "scan.h"
215b9c547cSRui Paulo #include "ctrl_iface.h"
225b9c547cSRui Paulo #include "bss.h"
235b9c547cSRui Paulo #include "wnm_sta.h"
244bc52338SCy Schubert #include "notify.h"
255b9c547cSRui Paulo #include "hs20_supplicant.h"
26f05cddf9SRui Paulo
27f05cddf9SRui Paulo #define MAX_TFS_IE_LEN 1024
285b9c547cSRui Paulo #define WNM_MAX_NEIGHBOR_REPORT 10
29f05cddf9SRui Paulo
30780fb4a2SCy Schubert #define WNM_SCAN_RESULT_AGE 2 /* 2 seconds */
31f05cddf9SRui Paulo
32f05cddf9SRui Paulo /* get the TFS IE from driver */
ieee80211_11_get_tfs_ie(struct wpa_supplicant * wpa_s,u8 * buf,u16 * buf_len,enum wnm_oper oper)33f05cddf9SRui Paulo static int ieee80211_11_get_tfs_ie(struct wpa_supplicant *wpa_s, u8 *buf,
34f05cddf9SRui Paulo u16 *buf_len, enum wnm_oper oper)
35f05cddf9SRui Paulo {
36f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "%s: TFS get operation %d", __func__, oper);
37f05cddf9SRui Paulo
38f05cddf9SRui Paulo return wpa_drv_wnm_oper(wpa_s, oper, wpa_s->bssid, buf, buf_len);
39f05cddf9SRui Paulo }
40f05cddf9SRui Paulo
41f05cddf9SRui Paulo
42f05cddf9SRui Paulo /* set the TFS IE to driver */
ieee80211_11_set_tfs_ie(struct wpa_supplicant * wpa_s,const u8 * addr,const u8 * buf,u16 buf_len,enum wnm_oper oper)43f05cddf9SRui Paulo static int ieee80211_11_set_tfs_ie(struct wpa_supplicant *wpa_s,
44780fb4a2SCy Schubert const u8 *addr, const u8 *buf, u16 buf_len,
45f05cddf9SRui Paulo enum wnm_oper oper)
46f05cddf9SRui Paulo {
47780fb4a2SCy Schubert u16 len = buf_len;
48780fb4a2SCy Schubert
49f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "%s: TFS set operation %d", __func__, oper);
50f05cddf9SRui Paulo
51780fb4a2SCy Schubert return wpa_drv_wnm_oper(wpa_s, oper, addr, (u8 *) buf, &len);
52f05cddf9SRui Paulo }
53f05cddf9SRui Paulo
54f05cddf9SRui Paulo
55f05cddf9SRui Paulo /* MLME-SLEEPMODE.request */
ieee802_11_send_wnmsleep_req(struct wpa_supplicant * wpa_s,u8 action,u16 intval,struct wpabuf * tfs_req)56f05cddf9SRui Paulo int ieee802_11_send_wnmsleep_req(struct wpa_supplicant *wpa_s,
57f05cddf9SRui Paulo u8 action, u16 intval, struct wpabuf *tfs_req)
58f05cddf9SRui Paulo {
59f05cddf9SRui Paulo struct ieee80211_mgmt *mgmt;
60f05cddf9SRui Paulo int res;
61f05cddf9SRui Paulo size_t len;
62f05cddf9SRui Paulo struct wnm_sleep_element *wnmsleep_ie;
634bc52338SCy Schubert u8 *wnmtfs_ie, *oci_ie;
644bc52338SCy Schubert u8 wnmsleep_ie_len, oci_ie_len;
65f05cddf9SRui Paulo u16 wnmtfs_ie_len; /* possibly multiple IE(s) */
66f05cddf9SRui Paulo enum wnm_oper tfs_oper = action == 0 ? WNM_SLEEP_TFS_REQ_IE_ADD :
67f05cddf9SRui Paulo WNM_SLEEP_TFS_REQ_IE_NONE;
68f05cddf9SRui Paulo
69f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "WNM: Request to send WNM-Sleep Mode Request "
70f05cddf9SRui Paulo "action=%s to " MACSTR,
71f05cddf9SRui Paulo action == 0 ? "enter" : "exit",
72f05cddf9SRui Paulo MAC2STR(wpa_s->bssid));
73f05cddf9SRui Paulo
74f05cddf9SRui Paulo /* WNM-Sleep Mode IE */
75f05cddf9SRui Paulo wnmsleep_ie_len = sizeof(struct wnm_sleep_element);
76f05cddf9SRui Paulo wnmsleep_ie = os_zalloc(sizeof(struct wnm_sleep_element));
77f05cddf9SRui Paulo if (wnmsleep_ie == NULL)
78f05cddf9SRui Paulo return -1;
79f05cddf9SRui Paulo wnmsleep_ie->eid = WLAN_EID_WNMSLEEP;
80f05cddf9SRui Paulo wnmsleep_ie->len = wnmsleep_ie_len - 2;
81f05cddf9SRui Paulo wnmsleep_ie->action_type = action;
82f05cddf9SRui Paulo wnmsleep_ie->status = WNM_STATUS_SLEEP_ACCEPT;
83f05cddf9SRui Paulo wnmsleep_ie->intval = host_to_le16(intval);
84f05cddf9SRui Paulo wpa_hexdump(MSG_DEBUG, "WNM: WNM-Sleep Mode element",
85f05cddf9SRui Paulo (u8 *) wnmsleep_ie, wnmsleep_ie_len);
86f05cddf9SRui Paulo
87f05cddf9SRui Paulo /* TFS IE(s) */
88f05cddf9SRui Paulo if (tfs_req) {
89f05cddf9SRui Paulo wnmtfs_ie_len = wpabuf_len(tfs_req);
9085732ac8SCy Schubert wnmtfs_ie = os_memdup(wpabuf_head(tfs_req), wnmtfs_ie_len);
91f05cddf9SRui Paulo if (wnmtfs_ie == NULL) {
92f05cddf9SRui Paulo os_free(wnmsleep_ie);
93f05cddf9SRui Paulo return -1;
94f05cddf9SRui Paulo }
95f05cddf9SRui Paulo } else {
96f05cddf9SRui Paulo wnmtfs_ie = os_zalloc(MAX_TFS_IE_LEN);
97f05cddf9SRui Paulo if (wnmtfs_ie == NULL) {
98f05cddf9SRui Paulo os_free(wnmsleep_ie);
99f05cddf9SRui Paulo return -1;
100f05cddf9SRui Paulo }
101f05cddf9SRui Paulo if (ieee80211_11_get_tfs_ie(wpa_s, wnmtfs_ie, &wnmtfs_ie_len,
102f05cddf9SRui Paulo tfs_oper)) {
103f05cddf9SRui Paulo wnmtfs_ie_len = 0;
104f05cddf9SRui Paulo os_free(wnmtfs_ie);
105f05cddf9SRui Paulo wnmtfs_ie = NULL;
106f05cddf9SRui Paulo }
107f05cddf9SRui Paulo }
108f05cddf9SRui Paulo wpa_hexdump(MSG_DEBUG, "WNM: TFS Request element",
109f05cddf9SRui Paulo (u8 *) wnmtfs_ie, wnmtfs_ie_len);
110f05cddf9SRui Paulo
1114bc52338SCy Schubert oci_ie = NULL;
1124bc52338SCy Schubert oci_ie_len = 0;
1134bc52338SCy Schubert #ifdef CONFIG_OCV
1144bc52338SCy Schubert if (action == WNM_SLEEP_MODE_EXIT && wpa_sm_ocv_enabled(wpa_s->wpa)) {
1154bc52338SCy Schubert struct wpa_channel_info ci;
1164bc52338SCy Schubert
1174bc52338SCy Schubert if (wpa_drv_channel_info(wpa_s, &ci) != 0) {
1184bc52338SCy Schubert wpa_printf(MSG_WARNING,
1194bc52338SCy Schubert "Failed to get channel info for OCI element in WNM-Sleep Mode frame");
1204bc52338SCy Schubert os_free(wnmsleep_ie);
1214bc52338SCy Schubert os_free(wnmtfs_ie);
1224bc52338SCy Schubert return -1;
1234bc52338SCy Schubert }
124c1d255d3SCy Schubert #ifdef CONFIG_TESTING_OPTIONS
125c1d255d3SCy Schubert if (wpa_s->oci_freq_override_wnm_sleep) {
126c1d255d3SCy Schubert wpa_printf(MSG_INFO,
127c1d255d3SCy Schubert "TEST: Override OCI KDE frequency %d -> %d MHz",
128c1d255d3SCy Schubert ci.frequency,
129c1d255d3SCy Schubert wpa_s->oci_freq_override_wnm_sleep);
130c1d255d3SCy Schubert ci.frequency = wpa_s->oci_freq_override_wnm_sleep;
131c1d255d3SCy Schubert }
132c1d255d3SCy Schubert #endif /* CONFIG_TESTING_OPTIONS */
1334bc52338SCy Schubert
1344bc52338SCy Schubert oci_ie_len = OCV_OCI_EXTENDED_LEN;
1354bc52338SCy Schubert oci_ie = os_zalloc(oci_ie_len);
1364bc52338SCy Schubert if (!oci_ie) {
1374bc52338SCy Schubert wpa_printf(MSG_WARNING,
1384bc52338SCy Schubert "Failed to allocate buffer for for OCI element in WNM-Sleep Mode frame");
1394bc52338SCy Schubert os_free(wnmsleep_ie);
1404bc52338SCy Schubert os_free(wnmtfs_ie);
1414bc52338SCy Schubert return -1;
1424bc52338SCy Schubert }
1434bc52338SCy Schubert
1444bc52338SCy Schubert if (ocv_insert_extended_oci(&ci, oci_ie) < 0) {
1454bc52338SCy Schubert os_free(wnmsleep_ie);
1464bc52338SCy Schubert os_free(wnmtfs_ie);
1474bc52338SCy Schubert os_free(oci_ie);
1484bc52338SCy Schubert return -1;
1494bc52338SCy Schubert }
1504bc52338SCy Schubert }
1514bc52338SCy Schubert #endif /* CONFIG_OCV */
1524bc52338SCy Schubert
1534bc52338SCy Schubert mgmt = os_zalloc(sizeof(*mgmt) + wnmsleep_ie_len + wnmtfs_ie_len +
1544bc52338SCy Schubert oci_ie_len);
155f05cddf9SRui Paulo if (mgmt == NULL) {
156f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "MLME: Failed to allocate buffer for "
157f05cddf9SRui Paulo "WNM-Sleep Request action frame");
158f05cddf9SRui Paulo os_free(wnmsleep_ie);
159f05cddf9SRui Paulo os_free(wnmtfs_ie);
160f05cddf9SRui Paulo return -1;
161f05cddf9SRui Paulo }
162f05cddf9SRui Paulo
163f05cddf9SRui Paulo os_memcpy(mgmt->da, wpa_s->bssid, ETH_ALEN);
164f05cddf9SRui Paulo os_memcpy(mgmt->sa, wpa_s->own_addr, ETH_ALEN);
165f05cddf9SRui Paulo os_memcpy(mgmt->bssid, wpa_s->bssid, ETH_ALEN);
166f05cddf9SRui Paulo mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
167f05cddf9SRui Paulo WLAN_FC_STYPE_ACTION);
168f05cddf9SRui Paulo mgmt->u.action.category = WLAN_ACTION_WNM;
169f05cddf9SRui Paulo mgmt->u.action.u.wnm_sleep_req.action = WNM_SLEEP_MODE_REQ;
170f05cddf9SRui Paulo mgmt->u.action.u.wnm_sleep_req.dialogtoken = 1;
171f05cddf9SRui Paulo os_memcpy(mgmt->u.action.u.wnm_sleep_req.variable, wnmsleep_ie,
172f05cddf9SRui Paulo wnmsleep_ie_len);
173f05cddf9SRui Paulo /* copy TFS IE here */
174f05cddf9SRui Paulo if (wnmtfs_ie_len > 0) {
175f05cddf9SRui Paulo os_memcpy(mgmt->u.action.u.wnm_sleep_req.variable +
176f05cddf9SRui Paulo wnmsleep_ie_len, wnmtfs_ie, wnmtfs_ie_len);
177f05cddf9SRui Paulo }
178f05cddf9SRui Paulo
1794bc52338SCy Schubert #ifdef CONFIG_OCV
1804bc52338SCy Schubert /* copy OCV OCI here */
1814bc52338SCy Schubert if (oci_ie_len > 0) {
1824bc52338SCy Schubert os_memcpy(mgmt->u.action.u.wnm_sleep_req.variable +
1834bc52338SCy Schubert wnmsleep_ie_len + wnmtfs_ie_len, oci_ie, oci_ie_len);
1844bc52338SCy Schubert }
1854bc52338SCy Schubert #endif /* CONFIG_OCV */
1864bc52338SCy Schubert
187f05cddf9SRui Paulo len = 1 + sizeof(mgmt->u.action.u.wnm_sleep_req) + wnmsleep_ie_len +
1884bc52338SCy Schubert wnmtfs_ie_len + oci_ie_len;
189f05cddf9SRui Paulo
190f05cddf9SRui Paulo res = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
191f05cddf9SRui Paulo wpa_s->own_addr, wpa_s->bssid,
192f05cddf9SRui Paulo &mgmt->u.action.category, len, 0);
193f05cddf9SRui Paulo if (res < 0)
194f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "Failed to send WNM-Sleep Request "
195f05cddf9SRui Paulo "(action=%d, intval=%d)", action, intval);
196a2063804SGordon Tetlow else
197a2063804SGordon Tetlow wpa_s->wnmsleep_used = 1;
198f05cddf9SRui Paulo
199f05cddf9SRui Paulo os_free(wnmsleep_ie);
200f05cddf9SRui Paulo os_free(wnmtfs_ie);
2014bc52338SCy Schubert os_free(oci_ie);
202f05cddf9SRui Paulo os_free(mgmt);
203f05cddf9SRui Paulo
204f05cddf9SRui Paulo return res;
205f05cddf9SRui Paulo }
206f05cddf9SRui Paulo
207f05cddf9SRui Paulo
wnm_sleep_mode_enter_success(struct wpa_supplicant * wpa_s,const u8 * tfsresp_ie_start,const u8 * tfsresp_ie_end)208f05cddf9SRui Paulo static void wnm_sleep_mode_enter_success(struct wpa_supplicant *wpa_s,
209780fb4a2SCy Schubert const u8 *tfsresp_ie_start,
210780fb4a2SCy Schubert const u8 *tfsresp_ie_end)
211f05cddf9SRui Paulo {
212f05cddf9SRui Paulo wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_ENTER_CONFIRM,
213f05cddf9SRui Paulo wpa_s->bssid, NULL, NULL);
214f05cddf9SRui Paulo /* remove GTK/IGTK ?? */
215f05cddf9SRui Paulo
216f05cddf9SRui Paulo /* set the TFS Resp IE(s) */
217f05cddf9SRui Paulo if (tfsresp_ie_start && tfsresp_ie_end &&
218f05cddf9SRui Paulo tfsresp_ie_end - tfsresp_ie_start >= 0) {
219f05cddf9SRui Paulo u16 tfsresp_ie_len;
220f05cddf9SRui Paulo tfsresp_ie_len = (tfsresp_ie_end + tfsresp_ie_end[1] + 2) -
221f05cddf9SRui Paulo tfsresp_ie_start;
222f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "TFS Resp IE(s) found");
223f05cddf9SRui Paulo /* pass the TFS Resp IE(s) to driver for processing */
224f05cddf9SRui Paulo if (ieee80211_11_set_tfs_ie(wpa_s, wpa_s->bssid,
225f05cddf9SRui Paulo tfsresp_ie_start,
226780fb4a2SCy Schubert tfsresp_ie_len,
227f05cddf9SRui Paulo WNM_SLEEP_TFS_RESP_IE_SET))
228f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "WNM: Fail to set TFS Resp IE");
229f05cddf9SRui Paulo }
230f05cddf9SRui Paulo }
231f05cddf9SRui Paulo
232f05cddf9SRui Paulo
wnm_sleep_mode_exit_success(struct wpa_supplicant * wpa_s,const u8 * frm,u16 key_len_total)233f05cddf9SRui Paulo static void wnm_sleep_mode_exit_success(struct wpa_supplicant *wpa_s,
234f05cddf9SRui Paulo const u8 *frm, u16 key_len_total)
235f05cddf9SRui Paulo {
236f05cddf9SRui Paulo u8 *ptr, *end;
237f05cddf9SRui Paulo u8 gtk_len;
238f05cddf9SRui Paulo
239f05cddf9SRui Paulo wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_EXIT_CONFIRM, wpa_s->bssid,
240f05cddf9SRui Paulo NULL, NULL);
241f05cddf9SRui Paulo
242f05cddf9SRui Paulo /* Install GTK/IGTK */
243f05cddf9SRui Paulo
244f05cddf9SRui Paulo /* point to key data field */
2455b9c547cSRui Paulo ptr = (u8 *) frm + 1 + 2;
246f05cddf9SRui Paulo end = ptr + key_len_total;
247f05cddf9SRui Paulo wpa_hexdump_key(MSG_DEBUG, "WNM: Key Data", ptr, key_len_total);
248f05cddf9SRui Paulo
249a2063804SGordon Tetlow if (key_len_total && !wpa_sm_pmf_enabled(wpa_s->wpa)) {
250a2063804SGordon Tetlow wpa_msg(wpa_s, MSG_INFO,
251a2063804SGordon Tetlow "WNM: Ignore Key Data in WNM-Sleep Mode Response - PMF not enabled");
252a2063804SGordon Tetlow return;
253a2063804SGordon Tetlow }
254a2063804SGordon Tetlow
25564987377SCy Schubert while (end - ptr > 1) {
25664987377SCy Schubert if (2 + ptr[1] > end - ptr) {
257f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "WNM: Invalid Key Data element "
258f05cddf9SRui Paulo "length");
259f05cddf9SRui Paulo if (end > ptr) {
260f05cddf9SRui Paulo wpa_hexdump(MSG_DEBUG, "WNM: Remaining data",
261f05cddf9SRui Paulo ptr, end - ptr);
262f05cddf9SRui Paulo }
263f05cddf9SRui Paulo break;
264f05cddf9SRui Paulo }
265f05cddf9SRui Paulo if (*ptr == WNM_SLEEP_SUBELEM_GTK) {
266f05cddf9SRui Paulo if (ptr[1] < 11 + 5) {
267f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "WNM: Too short GTK "
268f05cddf9SRui Paulo "subelem");
269f05cddf9SRui Paulo break;
270f05cddf9SRui Paulo }
271f05cddf9SRui Paulo gtk_len = *(ptr + 4);
272f05cddf9SRui Paulo if (ptr[1] < 11 + gtk_len ||
273f05cddf9SRui Paulo gtk_len < 5 || gtk_len > 32) {
274f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "WNM: Invalid GTK "
275f05cddf9SRui Paulo "subelem");
276f05cddf9SRui Paulo break;
277f05cddf9SRui Paulo }
278f05cddf9SRui Paulo wpa_wnmsleep_install_key(
279f05cddf9SRui Paulo wpa_s->wpa,
280f05cddf9SRui Paulo WNM_SLEEP_SUBELEM_GTK,
281f05cddf9SRui Paulo ptr);
282f05cddf9SRui Paulo ptr += 13 + gtk_len;
283f05cddf9SRui Paulo } else if (*ptr == WNM_SLEEP_SUBELEM_IGTK) {
284f05cddf9SRui Paulo if (ptr[1] < 2 + 6 + WPA_IGTK_LEN) {
285f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "WNM: Too short IGTK "
286f05cddf9SRui Paulo "subelem");
287f05cddf9SRui Paulo break;
288f05cddf9SRui Paulo }
289f05cddf9SRui Paulo wpa_wnmsleep_install_key(wpa_s->wpa,
290f05cddf9SRui Paulo WNM_SLEEP_SUBELEM_IGTK, ptr);
291f05cddf9SRui Paulo ptr += 10 + WPA_IGTK_LEN;
292c1d255d3SCy Schubert } else if (*ptr == WNM_SLEEP_SUBELEM_BIGTK) {
293c1d255d3SCy Schubert if (ptr[1] < 2 + 6 + WPA_BIGTK_LEN) {
294c1d255d3SCy Schubert wpa_printf(MSG_DEBUG,
295c1d255d3SCy Schubert "WNM: Too short BIGTK subelem");
296c1d255d3SCy Schubert break;
297c1d255d3SCy Schubert }
298c1d255d3SCy Schubert wpa_wnmsleep_install_key(wpa_s->wpa,
299c1d255d3SCy Schubert WNM_SLEEP_SUBELEM_BIGTK, ptr);
300c1d255d3SCy Schubert ptr += 10 + WPA_BIGTK_LEN;
301f05cddf9SRui Paulo } else
302f05cddf9SRui Paulo break; /* skip the loop */
303f05cddf9SRui Paulo }
304f05cddf9SRui Paulo }
305f05cddf9SRui Paulo
306f05cddf9SRui Paulo
ieee802_11_rx_wnmsleep_resp(struct wpa_supplicant * wpa_s,const u8 * frm,int len)307f05cddf9SRui Paulo static void ieee802_11_rx_wnmsleep_resp(struct wpa_supplicant *wpa_s,
308f05cddf9SRui Paulo const u8 *frm, int len)
309f05cddf9SRui Paulo {
310f05cddf9SRui Paulo /*
3115b9c547cSRui Paulo * Action [1] | Dialog Token [1] | Key Data Len [2] | Key Data |
312f05cddf9SRui Paulo * WNM-Sleep Mode IE | TFS Response IE
313f05cddf9SRui Paulo */
314780fb4a2SCy Schubert const u8 *pos = frm; /* point to payload after the action field */
3155b9c547cSRui Paulo u16 key_len_total;
316f05cddf9SRui Paulo struct wnm_sleep_element *wnmsleep_ie = NULL;
317f05cddf9SRui Paulo /* multiple TFS Resp IE (assuming consecutive) */
318780fb4a2SCy Schubert const u8 *tfsresp_ie_start = NULL;
319780fb4a2SCy Schubert const u8 *tfsresp_ie_end = NULL;
3204bc52338SCy Schubert #ifdef CONFIG_OCV
3214bc52338SCy Schubert const u8 *oci_ie = NULL;
3224bc52338SCy Schubert u8 oci_ie_len = 0;
3234bc52338SCy Schubert #endif /* CONFIG_OCV */
3245b9c547cSRui Paulo size_t left;
325f05cddf9SRui Paulo
326a2063804SGordon Tetlow if (!wpa_s->wnmsleep_used) {
327a2063804SGordon Tetlow wpa_printf(MSG_DEBUG,
328a2063804SGordon Tetlow "WNM: Ignore WNM-Sleep Mode Response frame since WNM-Sleep Mode operation has not been requested");
329a2063804SGordon Tetlow return;
330a2063804SGordon Tetlow }
331a2063804SGordon Tetlow
3325b9c547cSRui Paulo if (len < 3)
3335b9c547cSRui Paulo return;
3345b9c547cSRui Paulo key_len_total = WPA_GET_LE16(frm + 1);
3355b9c547cSRui Paulo
3365b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM-Sleep Mode Response token=%u key_len_total=%d",
3375b9c547cSRui Paulo frm[0], key_len_total);
3385b9c547cSRui Paulo left = len - 3;
3395b9c547cSRui Paulo if (key_len_total > left) {
340f05cddf9SRui Paulo wpa_printf(MSG_INFO, "WNM: Too short frame for Key Data field");
341f05cddf9SRui Paulo return;
342f05cddf9SRui Paulo }
3435b9c547cSRui Paulo pos += 3 + key_len_total;
344780fb4a2SCy Schubert while (pos - frm + 1 < len) {
345f05cddf9SRui Paulo u8 ie_len = *(pos + 1);
346780fb4a2SCy Schubert if (2 + ie_len > frm + len - pos) {
347f05cddf9SRui Paulo wpa_printf(MSG_INFO, "WNM: Invalid IE len %u", ie_len);
348f05cddf9SRui Paulo break;
349f05cddf9SRui Paulo }
350f05cddf9SRui Paulo wpa_hexdump(MSG_DEBUG, "WNM: Element", pos, 2 + ie_len);
351780fb4a2SCy Schubert if (*pos == WLAN_EID_WNMSLEEP && ie_len >= 4)
352f05cddf9SRui Paulo wnmsleep_ie = (struct wnm_sleep_element *) pos;
353f05cddf9SRui Paulo else if (*pos == WLAN_EID_TFS_RESP) {
354f05cddf9SRui Paulo if (!tfsresp_ie_start)
355f05cddf9SRui Paulo tfsresp_ie_start = pos;
356f05cddf9SRui Paulo tfsresp_ie_end = pos;
3574bc52338SCy Schubert #ifdef CONFIG_OCV
3584bc52338SCy Schubert } else if (*pos == WLAN_EID_EXTENSION && ie_len >= 1 &&
3594bc52338SCy Schubert pos[2] == WLAN_EID_EXT_OCV_OCI) {
3604bc52338SCy Schubert oci_ie = pos + 3;
3614bc52338SCy Schubert oci_ie_len = ie_len - 1;
3624bc52338SCy Schubert #endif /* CONFIG_OCV */
363f05cddf9SRui Paulo } else
364f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "EID %d not recognized", *pos);
365f05cddf9SRui Paulo pos += ie_len + 2;
366f05cddf9SRui Paulo }
367f05cddf9SRui Paulo
368f05cddf9SRui Paulo if (!wnmsleep_ie) {
369f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "No WNM-Sleep IE found");
370f05cddf9SRui Paulo return;
371f05cddf9SRui Paulo }
372f05cddf9SRui Paulo
3734bc52338SCy Schubert #ifdef CONFIG_OCV
3744bc52338SCy Schubert if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT &&
3754bc52338SCy Schubert wpa_sm_ocv_enabled(wpa_s->wpa)) {
3764bc52338SCy Schubert struct wpa_channel_info ci;
3774bc52338SCy Schubert
3784bc52338SCy Schubert if (wpa_drv_channel_info(wpa_s, &ci) != 0) {
3794bc52338SCy Schubert wpa_msg(wpa_s, MSG_WARNING,
3804bc52338SCy Schubert "Failed to get channel info to validate received OCI in WNM-Sleep Mode frame");
3814bc52338SCy Schubert return;
3824bc52338SCy Schubert }
3834bc52338SCy Schubert
3844bc52338SCy Schubert if (ocv_verify_tx_params(oci_ie, oci_ie_len, &ci,
3854bc52338SCy Schubert channel_width_to_int(ci.chanwidth),
386c1d255d3SCy Schubert ci.seg1_idx) != OCI_SUCCESS) {
387c1d255d3SCy Schubert wpa_msg(wpa_s, MSG_WARNING, "WNM: OCV failed: %s",
388c1d255d3SCy Schubert ocv_errorstr);
3894bc52338SCy Schubert return;
3904bc52338SCy Schubert }
3914bc52338SCy Schubert }
3924bc52338SCy Schubert #endif /* CONFIG_OCV */
3934bc52338SCy Schubert
394a2063804SGordon Tetlow wpa_s->wnmsleep_used = 0;
395a2063804SGordon Tetlow
396f05cddf9SRui Paulo if (wnmsleep_ie->status == WNM_STATUS_SLEEP_ACCEPT ||
397f05cddf9SRui Paulo wnmsleep_ie->status == WNM_STATUS_SLEEP_EXIT_ACCEPT_GTK_UPDATE) {
398f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "Successfully recv WNM-Sleep Response "
399f05cddf9SRui Paulo "frame (action=%d, intval=%d)",
400f05cddf9SRui Paulo wnmsleep_ie->action_type, wnmsleep_ie->intval);
401f05cddf9SRui Paulo if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_ENTER) {
402f05cddf9SRui Paulo wnm_sleep_mode_enter_success(wpa_s, tfsresp_ie_start,
403f05cddf9SRui Paulo tfsresp_ie_end);
404f05cddf9SRui Paulo } else if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT) {
405f05cddf9SRui Paulo wnm_sleep_mode_exit_success(wpa_s, frm, key_len_total);
406f05cddf9SRui Paulo }
407f05cddf9SRui Paulo } else {
408f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "Reject recv WNM-Sleep Response frame "
409f05cddf9SRui Paulo "(action=%d, intval=%d)",
410f05cddf9SRui Paulo wnmsleep_ie->action_type, wnmsleep_ie->intval);
411f05cddf9SRui Paulo if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_ENTER)
412f05cddf9SRui Paulo wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_ENTER_FAIL,
413f05cddf9SRui Paulo wpa_s->bssid, NULL, NULL);
414f05cddf9SRui Paulo else if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT)
415f05cddf9SRui Paulo wpa_drv_wnm_oper(wpa_s, WNM_SLEEP_EXIT_FAIL,
416f05cddf9SRui Paulo wpa_s->bssid, NULL, NULL);
417f05cddf9SRui Paulo }
418f05cddf9SRui Paulo }
419f05cddf9SRui Paulo
420f05cddf9SRui Paulo
wnm_btm_reset(struct wpa_supplicant * wpa_s)421*a90b9d01SCy Schubert void wnm_btm_reset(struct wpa_supplicant *wpa_s)
4225b9c547cSRui Paulo {
4235b9c547cSRui Paulo int i;
4245b9c547cSRui Paulo
4255b9c547cSRui Paulo for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) {
4265b9c547cSRui Paulo os_free(wpa_s->wnm_neighbor_report_elements[i].meas_pilot);
4275b9c547cSRui Paulo os_free(wpa_s->wnm_neighbor_report_elements[i].mul_bssid);
4285b9c547cSRui Paulo }
4295b9c547cSRui Paulo
4305b9c547cSRui Paulo wpa_s->wnm_num_neighbor_report = 0;
4315b9c547cSRui Paulo os_free(wpa_s->wnm_neighbor_report_elements);
4325b9c547cSRui Paulo wpa_s->wnm_neighbor_report_elements = NULL;
43385732ac8SCy Schubert
434*a90b9d01SCy Schubert wpa_s->wnm_cand_valid_until.sec = 0;
435*a90b9d01SCy Schubert wpa_s->wnm_cand_valid_until.usec = 0;
436*a90b9d01SCy Schubert
437*a90b9d01SCy Schubert wpa_s->wnm_mode = 0;
438*a90b9d01SCy Schubert wpa_s->wnm_dialog_token = 0;
439*a90b9d01SCy Schubert wpa_s->wnm_reply = 0;
440*a90b9d01SCy Schubert
441*a90b9d01SCy Schubert #ifdef CONFIG_MBO
442*a90b9d01SCy Schubert wpa_s->wnm_mbo_trans_reason_present = 0;
443*a90b9d01SCy Schubert wpa_s->wnm_mbo_transition_reason = 0;
444*a90b9d01SCy Schubert #endif /* CONFIG_MBO */
4455b9c547cSRui Paulo }
4465b9c547cSRui Paulo
4475b9c547cSRui Paulo
wnm_parse_neighbor_report_elem(struct neighbor_report * rep,u8 id,u8 elen,const u8 * pos)4485b9c547cSRui Paulo static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep,
4495b9c547cSRui Paulo u8 id, u8 elen, const u8 *pos)
4505b9c547cSRui Paulo {
4515b9c547cSRui Paulo switch (id) {
4525b9c547cSRui Paulo case WNM_NEIGHBOR_TSF:
4535b9c547cSRui Paulo if (elen < 2 + 2) {
4545b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Too short TSF");
4555b9c547cSRui Paulo break;
4565b9c547cSRui Paulo }
4575b9c547cSRui Paulo rep->tsf_offset = WPA_GET_LE16(pos);
4585b9c547cSRui Paulo rep->beacon_int = WPA_GET_LE16(pos + 2);
4595b9c547cSRui Paulo rep->tsf_present = 1;
4605b9c547cSRui Paulo break;
4615b9c547cSRui Paulo case WNM_NEIGHBOR_CONDENSED_COUNTRY_STRING:
4625b9c547cSRui Paulo if (elen < 2) {
4635b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Too short condensed "
4645b9c547cSRui Paulo "country string");
4655b9c547cSRui Paulo break;
4665b9c547cSRui Paulo }
4675b9c547cSRui Paulo os_memcpy(rep->country, pos, 2);
4685b9c547cSRui Paulo rep->country_present = 1;
4695b9c547cSRui Paulo break;
4705b9c547cSRui Paulo case WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE:
4715b9c547cSRui Paulo if (elen < 1) {
4725b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Too short BSS transition "
4735b9c547cSRui Paulo "candidate");
4745b9c547cSRui Paulo break;
4755b9c547cSRui Paulo }
4765b9c547cSRui Paulo rep->preference = pos[0];
4775b9c547cSRui Paulo rep->preference_present = 1;
4785b9c547cSRui Paulo break;
4795b9c547cSRui Paulo case WNM_NEIGHBOR_BSS_TERMINATION_DURATION:
480206b73d0SCy Schubert if (elen < 10) {
481206b73d0SCy Schubert wpa_printf(MSG_DEBUG,
482206b73d0SCy Schubert "WNM: Too short BSS termination duration");
483206b73d0SCy Schubert break;
484206b73d0SCy Schubert }
4855b9c547cSRui Paulo rep->bss_term_tsf = WPA_GET_LE64(pos);
4865b9c547cSRui Paulo rep->bss_term_dur = WPA_GET_LE16(pos + 8);
4875b9c547cSRui Paulo rep->bss_term_present = 1;
4885b9c547cSRui Paulo break;
4895b9c547cSRui Paulo case WNM_NEIGHBOR_BEARING:
4905b9c547cSRui Paulo if (elen < 8) {
4915b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Too short neighbor "
4925b9c547cSRui Paulo "bearing");
4935b9c547cSRui Paulo break;
4945b9c547cSRui Paulo }
4955b9c547cSRui Paulo rep->bearing = WPA_GET_LE16(pos);
4965b9c547cSRui Paulo rep->distance = WPA_GET_LE32(pos + 2);
4975b9c547cSRui Paulo rep->rel_height = WPA_GET_LE16(pos + 2 + 4);
4985b9c547cSRui Paulo rep->bearing_present = 1;
4995b9c547cSRui Paulo break;
5005b9c547cSRui Paulo case WNM_NEIGHBOR_MEASUREMENT_PILOT:
5015b9c547cSRui Paulo if (elen < 1) {
5025b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Too short measurement "
5035b9c547cSRui Paulo "pilot");
5045b9c547cSRui Paulo break;
5055b9c547cSRui Paulo }
5065b9c547cSRui Paulo os_free(rep->meas_pilot);
5075b9c547cSRui Paulo rep->meas_pilot = os_zalloc(sizeof(struct measurement_pilot));
5085b9c547cSRui Paulo if (rep->meas_pilot == NULL)
5095b9c547cSRui Paulo break;
5105b9c547cSRui Paulo rep->meas_pilot->measurement_pilot = pos[0];
5115b9c547cSRui Paulo rep->meas_pilot->subelem_len = elen - 1;
5125b9c547cSRui Paulo os_memcpy(rep->meas_pilot->subelems, pos + 1, elen - 1);
5135b9c547cSRui Paulo break;
5145b9c547cSRui Paulo case WNM_NEIGHBOR_RRM_ENABLED_CAPABILITIES:
5155b9c547cSRui Paulo if (elen < 5) {
5165b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Too short RRM enabled "
5175b9c547cSRui Paulo "capabilities");
5185b9c547cSRui Paulo break;
5195b9c547cSRui Paulo }
5205b9c547cSRui Paulo os_memcpy(rep->rm_capab, pos, 5);
5215b9c547cSRui Paulo rep->rm_capab_present = 1;
5225b9c547cSRui Paulo break;
5235b9c547cSRui Paulo case WNM_NEIGHBOR_MULTIPLE_BSSID:
5245b9c547cSRui Paulo if (elen < 1) {
5255b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Too short multiple BSSID");
5265b9c547cSRui Paulo break;
5275b9c547cSRui Paulo }
5285b9c547cSRui Paulo os_free(rep->mul_bssid);
5295b9c547cSRui Paulo rep->mul_bssid = os_zalloc(sizeof(struct multiple_bssid));
5305b9c547cSRui Paulo if (rep->mul_bssid == NULL)
5315b9c547cSRui Paulo break;
5325b9c547cSRui Paulo rep->mul_bssid->max_bssid_indicator = pos[0];
5335b9c547cSRui Paulo rep->mul_bssid->subelem_len = elen - 1;
5345b9c547cSRui Paulo os_memcpy(rep->mul_bssid->subelems, pos + 1, elen - 1);
5355b9c547cSRui Paulo break;
536*a90b9d01SCy Schubert default:
537*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG,
538*a90b9d01SCy Schubert "WNM: Unsupported neighbor report subelement id %u",
539*a90b9d01SCy Schubert id);
540*a90b9d01SCy Schubert break;
5415b9c547cSRui Paulo }
5425b9c547cSRui Paulo }
5435b9c547cSRui Paulo
5445b9c547cSRui Paulo
wnm_nei_get_chan(struct wpa_supplicant * wpa_s,u8 op_class,u8 chan)5455b9c547cSRui Paulo static int wnm_nei_get_chan(struct wpa_supplicant *wpa_s, u8 op_class, u8 chan)
5465b9c547cSRui Paulo {
5475b9c547cSRui Paulo struct wpa_bss *bss = wpa_s->current_bss;
5485b9c547cSRui Paulo const char *country = NULL;
549780fb4a2SCy Schubert int freq;
5505b9c547cSRui Paulo
5515b9c547cSRui Paulo if (bss) {
5525b9c547cSRui Paulo const u8 *elem = wpa_bss_get_ie(bss, WLAN_EID_COUNTRY);
5535b9c547cSRui Paulo
5545b9c547cSRui Paulo if (elem && elem[1] >= 2)
5555b9c547cSRui Paulo country = (const char *) (elem + 2);
5565b9c547cSRui Paulo }
5575b9c547cSRui Paulo
558780fb4a2SCy Schubert freq = ieee80211_chan_to_freq(country, op_class, chan);
559780fb4a2SCy Schubert if (freq <= 0 && op_class == 0) {
560780fb4a2SCy Schubert /*
561780fb4a2SCy Schubert * Some APs do not advertise correct operating class
562780fb4a2SCy Schubert * information. Try to determine the most likely operating
563780fb4a2SCy Schubert * frequency based on the channel number.
564780fb4a2SCy Schubert */
565780fb4a2SCy Schubert if (chan >= 1 && chan <= 13)
566780fb4a2SCy Schubert freq = 2407 + chan * 5;
567780fb4a2SCy Schubert else if (chan == 14)
568780fb4a2SCy Schubert freq = 2484;
569c1d255d3SCy Schubert else if (chan >= 36 && chan <= 177)
570780fb4a2SCy Schubert freq = 5000 + chan * 5;
571780fb4a2SCy Schubert }
572780fb4a2SCy Schubert return freq;
5735b9c547cSRui Paulo }
5745b9c547cSRui Paulo
5755b9c547cSRui Paulo
wnm_parse_neighbor_report(struct wpa_supplicant * wpa_s,const u8 * pos,u8 len,struct neighbor_report * rep)5765b9c547cSRui Paulo static void wnm_parse_neighbor_report(struct wpa_supplicant *wpa_s,
5775b9c547cSRui Paulo const u8 *pos, u8 len,
5785b9c547cSRui Paulo struct neighbor_report *rep)
5795b9c547cSRui Paulo {
5805b9c547cSRui Paulo u8 left = len;
5815b9c547cSRui Paulo
5825b9c547cSRui Paulo if (left < 13) {
5835b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Too short neighbor report");
5845b9c547cSRui Paulo return;
5855b9c547cSRui Paulo }
5865b9c547cSRui Paulo
5875b9c547cSRui Paulo os_memcpy(rep->bssid, pos, ETH_ALEN);
5885b9c547cSRui Paulo rep->bssid_info = WPA_GET_LE32(pos + ETH_ALEN);
5895b9c547cSRui Paulo rep->regulatory_class = *(pos + 10);
5905b9c547cSRui Paulo rep->channel_number = *(pos + 11);
5915b9c547cSRui Paulo rep->phy_type = *(pos + 12);
5925b9c547cSRui Paulo
5935b9c547cSRui Paulo pos += 13;
5945b9c547cSRui Paulo left -= 13;
5955b9c547cSRui Paulo
5965b9c547cSRui Paulo while (left >= 2) {
5975b9c547cSRui Paulo u8 id, elen;
5985b9c547cSRui Paulo
5995b9c547cSRui Paulo id = *pos++;
6005b9c547cSRui Paulo elen = *pos++;
6015b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Subelement id=%u len=%u", id, elen);
6025b9c547cSRui Paulo left -= 2;
6035b9c547cSRui Paulo if (elen > left) {
6045b9c547cSRui Paulo wpa_printf(MSG_DEBUG,
6055b9c547cSRui Paulo "WNM: Truncated neighbor report subelement");
6065b9c547cSRui Paulo break;
6075b9c547cSRui Paulo }
6085b9c547cSRui Paulo wnm_parse_neighbor_report_elem(rep, id, elen, pos);
6095b9c547cSRui Paulo left -= elen;
6105b9c547cSRui Paulo pos += elen;
6115b9c547cSRui Paulo }
6125b9c547cSRui Paulo
6135b9c547cSRui Paulo rep->freq = wnm_nei_get_chan(wpa_s, rep->regulatory_class,
6145b9c547cSRui Paulo rep->channel_number);
6155b9c547cSRui Paulo }
6165b9c547cSRui Paulo
6175b9c547cSRui Paulo
wnm_clear_acceptable(struct wpa_supplicant * wpa_s)61885732ac8SCy Schubert static void wnm_clear_acceptable(struct wpa_supplicant *wpa_s)
6195b9c547cSRui Paulo {
62085732ac8SCy Schubert unsigned int i;
6215b9c547cSRui Paulo
62285732ac8SCy Schubert for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++)
62385732ac8SCy Schubert wpa_s->wnm_neighbor_report_elements[i].acceptable = 0;
62485732ac8SCy Schubert }
62585732ac8SCy Schubert
62685732ac8SCy Schubert #ifdef CONFIG_MBO
62785732ac8SCy Schubert static struct wpa_bss *
get_mbo_transition_candidate(struct wpa_supplicant * wpa_s,enum mbo_transition_reject_reason * reason)62885732ac8SCy Schubert get_mbo_transition_candidate(struct wpa_supplicant *wpa_s,
62985732ac8SCy Schubert enum mbo_transition_reject_reason *reason)
63085732ac8SCy Schubert {
63185732ac8SCy Schubert struct wpa_bss *target = NULL;
63285732ac8SCy Schubert struct wpa_bss_trans_info params;
63385732ac8SCy Schubert struct wpa_bss_candidate_info *info = NULL;
63485732ac8SCy Schubert struct neighbor_report *nei = wpa_s->wnm_neighbor_report_elements;
63585732ac8SCy Schubert u8 *first_candidate_bssid = NULL, *pos;
63685732ac8SCy Schubert unsigned int i;
63785732ac8SCy Schubert
63885732ac8SCy Schubert params.mbo_transition_reason = wpa_s->wnm_mbo_transition_reason;
63985732ac8SCy Schubert params.n_candidates = 0;
64085732ac8SCy Schubert params.bssid = os_calloc(wpa_s->wnm_num_neighbor_report, ETH_ALEN);
64185732ac8SCy Schubert if (!params.bssid)
64285732ac8SCy Schubert return NULL;
64385732ac8SCy Schubert
64485732ac8SCy Schubert pos = params.bssid;
64585732ac8SCy Schubert for (i = 0; i < wpa_s->wnm_num_neighbor_report; nei++, i++) {
64685732ac8SCy Schubert if (nei->is_first)
64785732ac8SCy Schubert first_candidate_bssid = nei->bssid;
64885732ac8SCy Schubert if (!nei->acceptable)
64985732ac8SCy Schubert continue;
65085732ac8SCy Schubert os_memcpy(pos, nei->bssid, ETH_ALEN);
65185732ac8SCy Schubert pos += ETH_ALEN;
65285732ac8SCy Schubert params.n_candidates++;
65385732ac8SCy Schubert }
65485732ac8SCy Schubert
65585732ac8SCy Schubert if (!params.n_candidates)
65685732ac8SCy Schubert goto end;
65785732ac8SCy Schubert
65885732ac8SCy Schubert info = wpa_drv_get_bss_trans_status(wpa_s, ¶ms);
65985732ac8SCy Schubert if (!info) {
66085732ac8SCy Schubert /* If failed to get candidate BSS transition status from driver,
66185732ac8SCy Schubert * get the first acceptable candidate from wpa_supplicant.
66285732ac8SCy Schubert */
66385732ac8SCy Schubert target = wpa_bss_get_bssid(wpa_s, params.bssid);
66485732ac8SCy Schubert goto end;
66585732ac8SCy Schubert }
66685732ac8SCy Schubert
66785732ac8SCy Schubert /* Get the first acceptable candidate from driver */
66885732ac8SCy Schubert for (i = 0; i < info->num; i++) {
66985732ac8SCy Schubert if (info->candidates[i].is_accept) {
67085732ac8SCy Schubert target = wpa_bss_get_bssid(wpa_s,
67185732ac8SCy Schubert info->candidates[i].bssid);
67285732ac8SCy Schubert goto end;
67385732ac8SCy Schubert }
67485732ac8SCy Schubert }
67585732ac8SCy Schubert
67685732ac8SCy Schubert /* If Disassociation Imminent is set and driver rejects all the
67785732ac8SCy Schubert * candidate select first acceptable candidate which has
67885732ac8SCy Schubert * rssi > disassoc_imminent_rssi_threshold
67985732ac8SCy Schubert */
68085732ac8SCy Schubert if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT) {
68185732ac8SCy Schubert for (i = 0; i < info->num; i++) {
68285732ac8SCy Schubert target = wpa_bss_get_bssid(wpa_s,
68385732ac8SCy Schubert info->candidates[i].bssid);
68485732ac8SCy Schubert if (target &&
68585732ac8SCy Schubert (target->level <
68685732ac8SCy Schubert wpa_s->conf->disassoc_imminent_rssi_threshold))
68785732ac8SCy Schubert continue;
68885732ac8SCy Schubert goto end;
68985732ac8SCy Schubert }
69085732ac8SCy Schubert }
69185732ac8SCy Schubert
69285732ac8SCy Schubert /* While sending BTM reject use reason code of the first candidate
69385732ac8SCy Schubert * received in BTM request frame
69485732ac8SCy Schubert */
69585732ac8SCy Schubert if (reason) {
69685732ac8SCy Schubert for (i = 0; i < info->num; i++) {
69785732ac8SCy Schubert if (first_candidate_bssid &&
698*a90b9d01SCy Schubert ether_addr_equal(first_candidate_bssid,
699*a90b9d01SCy Schubert info->candidates[i].bssid)) {
70085732ac8SCy Schubert *reason = info->candidates[i].reject_reason;
70185732ac8SCy Schubert break;
70285732ac8SCy Schubert }
70385732ac8SCy Schubert }
70485732ac8SCy Schubert }
70585732ac8SCy Schubert
70685732ac8SCy Schubert target = NULL;
70785732ac8SCy Schubert
70885732ac8SCy Schubert end:
70985732ac8SCy Schubert os_free(params.bssid);
71085732ac8SCy Schubert if (info) {
71185732ac8SCy Schubert os_free(info->candidates);
71285732ac8SCy Schubert os_free(info);
71385732ac8SCy Schubert }
71485732ac8SCy Schubert return target;
71585732ac8SCy Schubert }
71685732ac8SCy Schubert #endif /* CONFIG_MBO */
71785732ac8SCy Schubert
71885732ac8SCy Schubert
find_better_target(struct wpa_bss * a,struct wpa_bss * b)719*a90b9d01SCy Schubert static struct wpa_bss * find_better_target(struct wpa_bss *a,
720*a90b9d01SCy Schubert struct wpa_bss *b)
721*a90b9d01SCy Schubert {
722*a90b9d01SCy Schubert if (!a)
723*a90b9d01SCy Schubert return b;
724*a90b9d01SCy Schubert if (!b)
725*a90b9d01SCy Schubert return a;
726*a90b9d01SCy Schubert
727*a90b9d01SCy Schubert if (a->est_throughput > b->est_throughput) {
728*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "WNM: A is better: " MACSTR
729*a90b9d01SCy Schubert " est-tput: %d B: " MACSTR " est-tput: %d",
730*a90b9d01SCy Schubert MAC2STR(a->bssid), a->est_throughput,
731*a90b9d01SCy Schubert MAC2STR(b->bssid), b->est_throughput);
732*a90b9d01SCy Schubert return a;
733*a90b9d01SCy Schubert }
734*a90b9d01SCy Schubert
735*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "WNM: B is better, A: " MACSTR
736*a90b9d01SCy Schubert " est-tput: %d B: " MACSTR " est-tput: %d",
737*a90b9d01SCy Schubert MAC2STR(a->bssid), a->est_throughput,
738*a90b9d01SCy Schubert MAC2STR(b->bssid), b->est_throughput);
739*a90b9d01SCy Schubert return b;
740*a90b9d01SCy Schubert }
741*a90b9d01SCy Schubert
74285732ac8SCy Schubert static struct wpa_bss *
compare_scan_neighbor_results(struct wpa_supplicant * wpa_s,os_time_t age_secs,enum mbo_transition_reject_reason * reason)74385732ac8SCy Schubert compare_scan_neighbor_results(struct wpa_supplicant *wpa_s, os_time_t age_secs,
74485732ac8SCy Schubert enum mbo_transition_reject_reason *reason)
74585732ac8SCy Schubert {
7465b9c547cSRui Paulo u8 i;
7475b9c547cSRui Paulo struct wpa_bss *bss = wpa_s->current_bss;
7485b9c547cSRui Paulo struct wpa_bss *target;
749*a90b9d01SCy Schubert struct wpa_bss *best_target = NULL;
750*a90b9d01SCy Schubert struct wpa_bss *bss_in_list = NULL;
7515b9c547cSRui Paulo
7525b9c547cSRui Paulo if (!bss)
753780fb4a2SCy Schubert return NULL;
7545b9c547cSRui Paulo
7555b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Current BSS " MACSTR " RSSI %d",
7565b9c547cSRui Paulo MAC2STR(wpa_s->bssid), bss->level);
7575b9c547cSRui Paulo
75885732ac8SCy Schubert wnm_clear_acceptable(wpa_s);
75985732ac8SCy Schubert
7605b9c547cSRui Paulo for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) {
7615b9c547cSRui Paulo struct neighbor_report *nei;
7625b9c547cSRui Paulo
7635b9c547cSRui Paulo nei = &wpa_s->wnm_neighbor_report_elements[i];
7645b9c547cSRui Paulo if (nei->preference_present && nei->preference == 0) {
7655b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "Skip excluded BSS " MACSTR,
7665b9c547cSRui Paulo MAC2STR(nei->bssid));
7675b9c547cSRui Paulo continue;
7685b9c547cSRui Paulo }
7695b9c547cSRui Paulo
7705b9c547cSRui Paulo target = wpa_bss_get_bssid(wpa_s, nei->bssid);
7715b9c547cSRui Paulo if (!target) {
7725b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR
7735b9c547cSRui Paulo " (pref %d) not found in scan results",
7745b9c547cSRui Paulo MAC2STR(nei->bssid),
7755b9c547cSRui Paulo nei->preference_present ? nei->preference :
7765b9c547cSRui Paulo -1);
7775b9c547cSRui Paulo continue;
7785b9c547cSRui Paulo }
7795b9c547cSRui Paulo
780780fb4a2SCy Schubert if (age_secs) {
781780fb4a2SCy Schubert struct os_reltime now;
782780fb4a2SCy Schubert
783780fb4a2SCy Schubert if (os_get_reltime(&now) == 0 &&
784780fb4a2SCy Schubert os_reltime_expired(&now, &target->last_update,
785780fb4a2SCy Schubert age_secs)) {
786780fb4a2SCy Schubert wpa_printf(MSG_DEBUG,
787780fb4a2SCy Schubert "Candidate BSS is more than %ld seconds old",
788780fb4a2SCy Schubert age_secs);
789780fb4a2SCy Schubert continue;
790780fb4a2SCy Schubert }
791780fb4a2SCy Schubert }
792780fb4a2SCy Schubert
7935b9c547cSRui Paulo /*
794*a90b9d01SCy Schubert * TODO: Could consider allowing transition to another ESS if
795*a90b9d01SCy Schubert * PMF was enabled for the association.
7965b9c547cSRui Paulo */
797*a90b9d01SCy Schubert if (!wpa_scan_res_match(wpa_s, 0, target, wpa_s->current_ssid,
79885732ac8SCy Schubert 1, 0)) {
799780fb4a2SCy Schubert wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR
800780fb4a2SCy Schubert " (pref %d) does not match the current network profile",
801780fb4a2SCy Schubert MAC2STR(nei->bssid),
802780fb4a2SCy Schubert nei->preference_present ? nei->preference :
803780fb4a2SCy Schubert -1);
804780fb4a2SCy Schubert continue;
805780fb4a2SCy Schubert }
806780fb4a2SCy Schubert
8075b9c547cSRui Paulo if (target->level < bss->level && target->level < -80) {
8085b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR
8095b9c547cSRui Paulo " (pref %d) does not have sufficient signal level (%d)",
8105b9c547cSRui Paulo MAC2STR(nei->bssid),
8115b9c547cSRui Paulo nei->preference_present ? nei->preference :
8125b9c547cSRui Paulo -1,
8135b9c547cSRui Paulo target->level);
8145b9c547cSRui Paulo continue;
8155b9c547cSRui Paulo }
8165b9c547cSRui Paulo
81785732ac8SCy Schubert nei->acceptable = 1;
818*a90b9d01SCy Schubert
819*a90b9d01SCy Schubert best_target = find_better_target(target, best_target);
820*a90b9d01SCy Schubert if (target == bss)
821*a90b9d01SCy Schubert bss_in_list = bss;
82285732ac8SCy Schubert }
82385732ac8SCy Schubert
82485732ac8SCy Schubert #ifdef CONFIG_MBO
82585732ac8SCy Schubert if (wpa_s->wnm_mbo_trans_reason_present)
82685732ac8SCy Schubert target = get_mbo_transition_candidate(wpa_s, reason);
82785732ac8SCy Schubert else
828*a90b9d01SCy Schubert target = best_target;
82985732ac8SCy Schubert #else /* CONFIG_MBO */
830*a90b9d01SCy Schubert target = best_target;
83185732ac8SCy Schubert #endif /* CONFIG_MBO */
83285732ac8SCy Schubert
833*a90b9d01SCy Schubert if (!target)
834*a90b9d01SCy Schubert return NULL;
835*a90b9d01SCy Schubert
8365b9c547cSRui Paulo wpa_printf(MSG_DEBUG,
8375b9c547cSRui Paulo "WNM: Found an acceptable preferred transition candidate BSS "
838*a90b9d01SCy Schubert MACSTR " (RSSI %d, tput: %d bss-tput: %d)",
839*a90b9d01SCy Schubert MAC2STR(target->bssid), target->level,
840*a90b9d01SCy Schubert target->est_throughput, bss->est_throughput);
841*a90b9d01SCy Schubert
842*a90b9d01SCy Schubert if (!bss_in_list)
843*a90b9d01SCy Schubert return target;
844*a90b9d01SCy Schubert
845*a90b9d01SCy Schubert if ((!target->est_throughput && !bss_in_list->est_throughput) ||
846*a90b9d01SCy Schubert (target->est_throughput > bss_in_list->est_throughput &&
847*a90b9d01SCy Schubert target->est_throughput - bss_in_list->est_throughput >
848*a90b9d01SCy Schubert bss_in_list->est_throughput >> 4)) {
849*a90b9d01SCy Schubert /* It is more than 100/16 percent better, so switch. */
850*a90b9d01SCy Schubert return target;
8515b9c547cSRui Paulo }
8525b9c547cSRui Paulo
853*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG,
854*a90b9d01SCy Schubert "WNM: Stay with our current BSS, not enough change in estimated throughput to switch");
855*a90b9d01SCy Schubert return bss_in_list;
8565b9c547cSRui Paulo }
8575b9c547cSRui Paulo
8585b9c547cSRui Paulo
wpa_bss_ies_eq(struct wpa_bss * a,struct wpa_bss * b,u8 eid)859780fb4a2SCy Schubert static int wpa_bss_ies_eq(struct wpa_bss *a, struct wpa_bss *b, u8 eid)
860780fb4a2SCy Schubert {
861780fb4a2SCy Schubert const u8 *ie_a, *ie_b;
862780fb4a2SCy Schubert
863780fb4a2SCy Schubert if (!a || !b)
864780fb4a2SCy Schubert return 0;
865780fb4a2SCy Schubert
866780fb4a2SCy Schubert ie_a = wpa_bss_get_ie(a, eid);
867780fb4a2SCy Schubert ie_b = wpa_bss_get_ie(b, eid);
868780fb4a2SCy Schubert
869780fb4a2SCy Schubert if (!ie_a || !ie_b || ie_a[1] != ie_b[1])
870780fb4a2SCy Schubert return 0;
871780fb4a2SCy Schubert
872780fb4a2SCy Schubert return os_memcmp(ie_a, ie_b, ie_a[1]) == 0;
873780fb4a2SCy Schubert }
874780fb4a2SCy Schubert
875780fb4a2SCy Schubert
wnm_get_bss_info(struct wpa_supplicant * wpa_s,struct wpa_bss * bss)876780fb4a2SCy Schubert static u32 wnm_get_bss_info(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
877780fb4a2SCy Schubert {
878780fb4a2SCy Schubert u32 info = 0;
879780fb4a2SCy Schubert
880780fb4a2SCy Schubert info |= NEI_REP_BSSID_INFO_AP_UNKNOWN_REACH;
881780fb4a2SCy Schubert
882780fb4a2SCy Schubert /*
883780fb4a2SCy Schubert * Leave the security and key scope bits unset to indicate that the
884780fb4a2SCy Schubert * security information is not available.
885780fb4a2SCy Schubert */
886780fb4a2SCy Schubert
887780fb4a2SCy Schubert if (bss->caps & WLAN_CAPABILITY_SPECTRUM_MGMT)
888780fb4a2SCy Schubert info |= NEI_REP_BSSID_INFO_SPECTRUM_MGMT;
889780fb4a2SCy Schubert if (bss->caps & WLAN_CAPABILITY_QOS)
890780fb4a2SCy Schubert info |= NEI_REP_BSSID_INFO_QOS;
891780fb4a2SCy Schubert if (bss->caps & WLAN_CAPABILITY_APSD)
892780fb4a2SCy Schubert info |= NEI_REP_BSSID_INFO_APSD;
893780fb4a2SCy Schubert if (bss->caps & WLAN_CAPABILITY_RADIO_MEASUREMENT)
894780fb4a2SCy Schubert info |= NEI_REP_BSSID_INFO_RM;
895780fb4a2SCy Schubert if (bss->caps & WLAN_CAPABILITY_DELAYED_BLOCK_ACK)
896780fb4a2SCy Schubert info |= NEI_REP_BSSID_INFO_DELAYED_BA;
897780fb4a2SCy Schubert if (bss->caps & WLAN_CAPABILITY_IMM_BLOCK_ACK)
898780fb4a2SCy Schubert info |= NEI_REP_BSSID_INFO_IMM_BA;
899780fb4a2SCy Schubert if (wpa_bss_ies_eq(bss, wpa_s->current_bss, WLAN_EID_MOBILITY_DOMAIN))
900780fb4a2SCy Schubert info |= NEI_REP_BSSID_INFO_MOBILITY_DOMAIN;
901780fb4a2SCy Schubert if (wpa_bss_ies_eq(bss, wpa_s->current_bss, WLAN_EID_HT_CAP))
902780fb4a2SCy Schubert info |= NEI_REP_BSSID_INFO_HT;
903780fb4a2SCy Schubert
904780fb4a2SCy Schubert return info;
905780fb4a2SCy Schubert }
906780fb4a2SCy Schubert
907780fb4a2SCy Schubert
wnm_add_nei_rep(struct wpabuf ** buf,const u8 * bssid,u32 bss_info,u8 op_class,u8 chan,u8 phy_type,u8 pref)90885732ac8SCy Schubert static int wnm_add_nei_rep(struct wpabuf **buf, const u8 *bssid,
90985732ac8SCy Schubert u32 bss_info, u8 op_class, u8 chan, u8 phy_type,
91085732ac8SCy Schubert u8 pref)
911780fb4a2SCy Schubert {
91285732ac8SCy Schubert if (wpabuf_len(*buf) + 18 >
91385732ac8SCy Schubert IEEE80211_MAX_MMPDU_SIZE - IEEE80211_HDRLEN) {
914780fb4a2SCy Schubert wpa_printf(MSG_DEBUG,
91585732ac8SCy Schubert "WNM: No room in frame for Neighbor Report element");
916780fb4a2SCy Schubert return -1;
917780fb4a2SCy Schubert }
918780fb4a2SCy Schubert
91985732ac8SCy Schubert if (wpabuf_resize(buf, 18) < 0) {
92085732ac8SCy Schubert wpa_printf(MSG_DEBUG,
92185732ac8SCy Schubert "WNM: Failed to allocate memory for Neighbor Report element");
92285732ac8SCy Schubert return -1;
92385732ac8SCy Schubert }
92485732ac8SCy Schubert
92585732ac8SCy Schubert wpabuf_put_u8(*buf, WLAN_EID_NEIGHBOR_REPORT);
926780fb4a2SCy Schubert /* length: 13 for basic neighbor report + 3 for preference subelement */
92785732ac8SCy Schubert wpabuf_put_u8(*buf, 16);
92885732ac8SCy Schubert wpabuf_put_data(*buf, bssid, ETH_ALEN);
92985732ac8SCy Schubert wpabuf_put_le32(*buf, bss_info);
93085732ac8SCy Schubert wpabuf_put_u8(*buf, op_class);
93185732ac8SCy Schubert wpabuf_put_u8(*buf, chan);
93285732ac8SCy Schubert wpabuf_put_u8(*buf, phy_type);
93385732ac8SCy Schubert wpabuf_put_u8(*buf, WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE);
93485732ac8SCy Schubert wpabuf_put_u8(*buf, 1);
93585732ac8SCy Schubert wpabuf_put_u8(*buf, pref);
93685732ac8SCy Schubert return 0;
937780fb4a2SCy Schubert }
938780fb4a2SCy Schubert
939780fb4a2SCy Schubert
wnm_nei_rep_add_bss(struct wpa_supplicant * wpa_s,struct wpa_bss * bss,struct wpabuf ** buf,u8 pref)940780fb4a2SCy Schubert static int wnm_nei_rep_add_bss(struct wpa_supplicant *wpa_s,
94185732ac8SCy Schubert struct wpa_bss *bss, struct wpabuf **buf,
942780fb4a2SCy Schubert u8 pref)
943780fb4a2SCy Schubert {
944780fb4a2SCy Schubert const u8 *ie;
945780fb4a2SCy Schubert u8 op_class, chan;
946*a90b9d01SCy Schubert int sec_chan = 0;
947*a90b9d01SCy Schubert enum oper_chan_width vht = CONF_OPER_CHWIDTH_USE_HT;
948780fb4a2SCy Schubert enum phy_type phy_type;
949780fb4a2SCy Schubert u32 info;
950780fb4a2SCy Schubert struct ieee80211_ht_operation *ht_oper = NULL;
951780fb4a2SCy Schubert struct ieee80211_vht_operation *vht_oper = NULL;
952780fb4a2SCy Schubert
953780fb4a2SCy Schubert ie = wpa_bss_get_ie(bss, WLAN_EID_HT_OPERATION);
954780fb4a2SCy Schubert if (ie && ie[1] >= 2) {
955780fb4a2SCy Schubert ht_oper = (struct ieee80211_ht_operation *) (ie + 2);
956780fb4a2SCy Schubert
957780fb4a2SCy Schubert if (ht_oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE)
958780fb4a2SCy Schubert sec_chan = 1;
959780fb4a2SCy Schubert else if (ht_oper->ht_param &
960780fb4a2SCy Schubert HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW)
961780fb4a2SCy Schubert sec_chan = -1;
962780fb4a2SCy Schubert }
963780fb4a2SCy Schubert
964780fb4a2SCy Schubert ie = wpa_bss_get_ie(bss, WLAN_EID_VHT_OPERATION);
965780fb4a2SCy Schubert if (ie && ie[1] >= 1) {
966780fb4a2SCy Schubert vht_oper = (struct ieee80211_vht_operation *) (ie + 2);
967780fb4a2SCy Schubert
968206b73d0SCy Schubert if (vht_oper->vht_op_info_chwidth == CHANWIDTH_80MHZ ||
969206b73d0SCy Schubert vht_oper->vht_op_info_chwidth == CHANWIDTH_160MHZ ||
970206b73d0SCy Schubert vht_oper->vht_op_info_chwidth == CHANWIDTH_80P80MHZ)
971780fb4a2SCy Schubert vht = vht_oper->vht_op_info_chwidth;
972780fb4a2SCy Schubert }
973780fb4a2SCy Schubert
974780fb4a2SCy Schubert if (ieee80211_freq_to_channel_ext(bss->freq, sec_chan, vht, &op_class,
975780fb4a2SCy Schubert &chan) == NUM_HOSTAPD_MODES) {
976780fb4a2SCy Schubert wpa_printf(MSG_DEBUG,
977780fb4a2SCy Schubert "WNM: Cannot determine operating class and channel");
978780fb4a2SCy Schubert return -2;
979780fb4a2SCy Schubert }
980780fb4a2SCy Schubert
981780fb4a2SCy Schubert phy_type = ieee80211_get_phy_type(bss->freq, (ht_oper != NULL),
982780fb4a2SCy Schubert (vht_oper != NULL));
983780fb4a2SCy Schubert if (phy_type == PHY_TYPE_UNSPECIFIED) {
984780fb4a2SCy Schubert wpa_printf(MSG_DEBUG,
985780fb4a2SCy Schubert "WNM: Cannot determine BSS phy type for Neighbor Report");
986780fb4a2SCy Schubert return -2;
987780fb4a2SCy Schubert }
988780fb4a2SCy Schubert
989780fb4a2SCy Schubert info = wnm_get_bss_info(wpa_s, bss);
990780fb4a2SCy Schubert
99185732ac8SCy Schubert return wnm_add_nei_rep(buf, bss->bssid, info, op_class, chan, phy_type,
99285732ac8SCy Schubert pref);
993780fb4a2SCy Schubert }
994780fb4a2SCy Schubert
995780fb4a2SCy Schubert
wnm_add_cand_list(struct wpa_supplicant * wpa_s,struct wpabuf ** buf)99685732ac8SCy Schubert static void wnm_add_cand_list(struct wpa_supplicant *wpa_s, struct wpabuf **buf)
997780fb4a2SCy Schubert {
998780fb4a2SCy Schubert unsigned int i, pref = 255;
999780fb4a2SCy Schubert struct os_reltime now;
1000780fb4a2SCy Schubert struct wpa_ssid *ssid = wpa_s->current_ssid;
1001780fb4a2SCy Schubert
1002780fb4a2SCy Schubert if (!ssid)
100385732ac8SCy Schubert return;
1004780fb4a2SCy Schubert
1005780fb4a2SCy Schubert /*
1006780fb4a2SCy Schubert * TODO: Define when scan results are no longer valid for the candidate
1007780fb4a2SCy Schubert * list.
1008780fb4a2SCy Schubert */
1009780fb4a2SCy Schubert os_get_reltime(&now);
1010780fb4a2SCy Schubert if (os_reltime_expired(&now, &wpa_s->last_scan, 10))
101185732ac8SCy Schubert return;
1012780fb4a2SCy Schubert
1013780fb4a2SCy Schubert wpa_printf(MSG_DEBUG,
1014780fb4a2SCy Schubert "WNM: Add candidate list to BSS Transition Management Response frame");
1015780fb4a2SCy Schubert for (i = 0; i < wpa_s->last_scan_res_used && pref; i++) {
1016780fb4a2SCy Schubert struct wpa_bss *bss = wpa_s->last_scan_res[i];
1017780fb4a2SCy Schubert int res;
1018780fb4a2SCy Schubert
101985732ac8SCy Schubert if (wpa_scan_res_match(wpa_s, i, bss, ssid, 1, 0)) {
102085732ac8SCy Schubert res = wnm_nei_rep_add_bss(wpa_s, bss, buf, pref--);
1021780fb4a2SCy Schubert if (res == -2)
1022780fb4a2SCy Schubert continue; /* could not build entry for BSS */
1023780fb4a2SCy Schubert if (res < 0)
1024780fb4a2SCy Schubert break; /* no more room for candidates */
1025780fb4a2SCy Schubert if (pref == 1)
1026780fb4a2SCy Schubert break;
1027780fb4a2SCy Schubert }
1028780fb4a2SCy Schubert }
1029780fb4a2SCy Schubert
103085732ac8SCy Schubert wpa_hexdump_buf(MSG_DEBUG,
1031780fb4a2SCy Schubert "WNM: BSS Transition Management Response candidate list",
103285732ac8SCy Schubert *buf);
1033780fb4a2SCy Schubert }
1034780fb4a2SCy Schubert
1035780fb4a2SCy Schubert
103685732ac8SCy Schubert #define BTM_RESP_MIN_SIZE 5 + ETH_ALEN
103785732ac8SCy Schubert
wnm_send_bss_transition_mgmt_resp(struct wpa_supplicant * wpa_s,enum bss_trans_mgmt_status_code status,enum mbo_transition_reject_reason reason,u8 delay,const u8 * target_bssid)1038*a90b9d01SCy Schubert static int wnm_send_bss_transition_mgmt_resp(
1039*a90b9d01SCy Schubert struct wpa_supplicant *wpa_s,
104085732ac8SCy Schubert enum bss_trans_mgmt_status_code status,
104185732ac8SCy Schubert enum mbo_transition_reject_reason reason,
104285732ac8SCy Schubert u8 delay, const u8 *target_bssid)
1043f05cddf9SRui Paulo {
104485732ac8SCy Schubert struct wpabuf *buf;
10455b9c547cSRui Paulo int res;
1046f05cddf9SRui Paulo
1047*a90b9d01SCy Schubert wpa_s->wnm_reply = 0;
1048*a90b9d01SCy Schubert
104985732ac8SCy Schubert wpa_printf(MSG_DEBUG,
105085732ac8SCy Schubert "WNM: Send BSS Transition Management Response to " MACSTR
105185732ac8SCy Schubert " dialog_token=%u status=%u reason=%u delay=%d",
1052*a90b9d01SCy Schubert MAC2STR(wpa_s->bssid), wpa_s->wnm_dialog_token, status,
1053*a90b9d01SCy Schubert reason, delay);
10545b9c547cSRui Paulo if (!wpa_s->current_bss) {
10555b9c547cSRui Paulo wpa_printf(MSG_DEBUG,
10565b9c547cSRui Paulo "WNM: Current BSS not known - drop response");
1057*a90b9d01SCy Schubert return -1;
10585b9c547cSRui Paulo }
1059f05cddf9SRui Paulo
106085732ac8SCy Schubert buf = wpabuf_alloc(BTM_RESP_MIN_SIZE);
106185732ac8SCy Schubert if (!buf) {
106285732ac8SCy Schubert wpa_printf(MSG_DEBUG,
106385732ac8SCy Schubert "WNM: Failed to allocate memory for BTM response");
1064*a90b9d01SCy Schubert return -1;
106585732ac8SCy Schubert }
106685732ac8SCy Schubert
10674bc52338SCy Schubert wpa_s->bss_tm_status = status;
10684bc52338SCy Schubert wpas_notify_bss_tm_status(wpa_s);
10694bc52338SCy Schubert
107085732ac8SCy Schubert wpabuf_put_u8(buf, WLAN_ACTION_WNM);
107185732ac8SCy Schubert wpabuf_put_u8(buf, WNM_BSS_TRANS_MGMT_RESP);
1072*a90b9d01SCy Schubert wpabuf_put_u8(buf, wpa_s->wnm_dialog_token);
107385732ac8SCy Schubert wpabuf_put_u8(buf, status);
107485732ac8SCy Schubert wpabuf_put_u8(buf, delay);
1075f05cddf9SRui Paulo if (target_bssid) {
107685732ac8SCy Schubert wpabuf_put_data(buf, target_bssid, ETH_ALEN);
10775b9c547cSRui Paulo } else if (status == WNM_BSS_TM_ACCEPT) {
10785b9c547cSRui Paulo /*
10795b9c547cSRui Paulo * P802.11-REVmc clarifies that the Target BSSID field is always
10805b9c547cSRui Paulo * present when status code is zero, so use a fake value here if
10815b9c547cSRui Paulo * no BSSID is yet known.
10825b9c547cSRui Paulo */
108385732ac8SCy Schubert wpabuf_put_data(buf, "\0\0\0\0\0\0", ETH_ALEN);
1084f05cddf9SRui Paulo }
1085f05cddf9SRui Paulo
1086*a90b9d01SCy Schubert if (status == WNM_BSS_TM_ACCEPT && target_bssid)
108785732ac8SCy Schubert wnm_add_cand_list(wpa_s, &buf);
1088780fb4a2SCy Schubert
1089780fb4a2SCy Schubert #ifdef CONFIG_MBO
109085732ac8SCy Schubert if (status != WNM_BSS_TM_ACCEPT &&
109185732ac8SCy Schubert wpa_bss_get_vendor_ie(wpa_s->current_bss, MBO_IE_VENDOR_TYPE)) {
109285732ac8SCy Schubert u8 mbo[10];
109385732ac8SCy Schubert size_t ret;
109485732ac8SCy Schubert
109585732ac8SCy Schubert ret = wpas_mbo_ie_bss_trans_reject(wpa_s, mbo, sizeof(mbo),
109685732ac8SCy Schubert reason);
109785732ac8SCy Schubert if (ret) {
109885732ac8SCy Schubert if (wpabuf_resize(&buf, ret) < 0) {
109985732ac8SCy Schubert wpabuf_free(buf);
110085732ac8SCy Schubert wpa_printf(MSG_DEBUG,
110185732ac8SCy Schubert "WNM: Failed to allocate memory for MBO IE");
1102*a90b9d01SCy Schubert return -1;
110385732ac8SCy Schubert }
110485732ac8SCy Schubert
110585732ac8SCy Schubert wpabuf_put_data(buf, mbo, ret);
110685732ac8SCy Schubert }
1107780fb4a2SCy Schubert }
1108780fb4a2SCy Schubert #endif /* CONFIG_MBO */
1109780fb4a2SCy Schubert
11105b9c547cSRui Paulo res = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
1111f05cddf9SRui Paulo wpa_s->own_addr, wpa_s->bssid,
111285732ac8SCy Schubert wpabuf_head_u8(buf), wpabuf_len(buf), 0);
11135b9c547cSRui Paulo if (res < 0) {
11145b9c547cSRui Paulo wpa_printf(MSG_DEBUG,
11155b9c547cSRui Paulo "WNM: Failed to send BSS Transition Management Response");
11165b9c547cSRui Paulo }
111785732ac8SCy Schubert
111885732ac8SCy Schubert wpabuf_free(buf);
1119*a90b9d01SCy Schubert
1120*a90b9d01SCy Schubert return res;
11215b9c547cSRui Paulo }
11225b9c547cSRui Paulo
11235b9c547cSRui Paulo
wnm_bss_tm_connect(struct wpa_supplicant * wpa_s,struct wpa_bss * bss,struct wpa_ssid * ssid,int after_new_scan)1124780fb4a2SCy Schubert static void wnm_bss_tm_connect(struct wpa_supplicant *wpa_s,
1125780fb4a2SCy Schubert struct wpa_bss *bss, struct wpa_ssid *ssid,
1126780fb4a2SCy Schubert int after_new_scan)
1127780fb4a2SCy Schubert {
1128ec080394SCy Schubert struct wpa_radio_work *already_connecting;
1129ec080394SCy Schubert
1130780fb4a2SCy Schubert wpa_dbg(wpa_s, MSG_DEBUG,
1131780fb4a2SCy Schubert "WNM: Transition to BSS " MACSTR
1132780fb4a2SCy Schubert " based on BSS Transition Management Request (old BSSID "
1133780fb4a2SCy Schubert MACSTR " after_new_scan=%d)",
1134780fb4a2SCy Schubert MAC2STR(bss->bssid), MAC2STR(wpa_s->bssid), after_new_scan);
1135780fb4a2SCy Schubert
1136780fb4a2SCy Schubert /* Send the BSS Management Response - Accept */
1137780fb4a2SCy Schubert if (wpa_s->wnm_reply) {
1138*a90b9d01SCy Schubert wpa_s->wnm_target_bss = bss;
1139780fb4a2SCy Schubert wpa_printf(MSG_DEBUG,
1140780fb4a2SCy Schubert "WNM: Sending successful BSS Transition Management Response");
1141*a90b9d01SCy Schubert
1142*a90b9d01SCy Schubert /* This function will be called again from the TX handler to
1143*a90b9d01SCy Schubert * start the actual reassociation after this response has been
1144*a90b9d01SCy Schubert * delivered to the current AP. */
1145*a90b9d01SCy Schubert if (wnm_send_bss_transition_mgmt_resp(
1146*a90b9d01SCy Schubert wpa_s, WNM_BSS_TM_ACCEPT,
114785732ac8SCy Schubert MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0,
1148*a90b9d01SCy Schubert bss->bssid) >= 0)
1149*a90b9d01SCy Schubert return;
1150780fb4a2SCy Schubert }
1151780fb4a2SCy Schubert
1152780fb4a2SCy Schubert if (bss == wpa_s->current_bss) {
1153780fb4a2SCy Schubert wpa_printf(MSG_DEBUG,
1154780fb4a2SCy Schubert "WNM: Already associated with the preferred candidate");
1155*a90b9d01SCy Schubert wnm_btm_reset(wpa_s);
1156780fb4a2SCy Schubert return;
1157780fb4a2SCy Schubert }
1158780fb4a2SCy Schubert
1159ec080394SCy Schubert already_connecting = radio_work_pending(wpa_s, "sme-connect");
1160780fb4a2SCy Schubert wpa_s->reassociate = 1;
1161780fb4a2SCy Schubert wpa_printf(MSG_DEBUG, "WNM: Issuing connect");
1162780fb4a2SCy Schubert wpa_supplicant_connect(wpa_s, bss, ssid);
1163ec080394SCy Schubert
1164ec080394SCy Schubert /*
1165ec080394SCy Schubert * Indicate that a BSS transition is in progress so scan results that
1166ec080394SCy Schubert * come in before the 'sme-connect' radio work gets executed do not
1167ec080394SCy Schubert * override the original connection attempt.
1168ec080394SCy Schubert */
1169ec080394SCy Schubert if (!already_connecting && radio_work_pending(wpa_s, "sme-connect"))
1170ec080394SCy Schubert wpa_s->bss_trans_mgmt_in_progress = true;
1171780fb4a2SCy Schubert }
1172780fb4a2SCy Schubert
1173780fb4a2SCy Schubert
wnm_scan_process(struct wpa_supplicant * wpa_s,bool pre_scan_check)1174*a90b9d01SCy Schubert int wnm_scan_process(struct wpa_supplicant *wpa_s, bool pre_scan_check)
11755b9c547cSRui Paulo {
11765b9c547cSRui Paulo struct wpa_bss *bss;
11775b9c547cSRui Paulo struct wpa_ssid *ssid = wpa_s->current_ssid;
11785b9c547cSRui Paulo enum bss_trans_mgmt_status_code status = WNM_BSS_TM_REJECT_UNSPECIFIED;
117985732ac8SCy Schubert enum mbo_transition_reject_reason reason =
118085732ac8SCy Schubert MBO_TRANSITION_REJECT_REASON_UNSPECIFIED;
11815b9c547cSRui Paulo
1182*a90b9d01SCy Schubert if (!wpa_s->wnm_dialog_token)
11835b9c547cSRui Paulo return 0;
11845b9c547cSRui Paulo
1185780fb4a2SCy Schubert wpa_dbg(wpa_s, MSG_DEBUG,
1186780fb4a2SCy Schubert "WNM: Process scan results for BSS Transition Management");
1187*a90b9d01SCy Schubert if (!pre_scan_check &&
1188*a90b9d01SCy Schubert os_reltime_initialized(&wpa_s->wnm_cand_valid_until) &&
1189*a90b9d01SCy Schubert os_reltime_before(&wpa_s->wnm_cand_valid_until,
11905b9c547cSRui Paulo &wpa_s->scan_trigger_time)) {
11915b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Previously stored BSS transition candidate list is not valid anymore - drop it");
1192*a90b9d01SCy Schubert goto send_bss_resp_fail;
11935b9c547cSRui Paulo }
11945b9c547cSRui Paulo
11955b9c547cSRui Paulo /* Compare the Neighbor Report and scan results */
119685732ac8SCy Schubert bss = compare_scan_neighbor_results(wpa_s, 0, &reason);
1197*a90b9d01SCy Schubert
1198*a90b9d01SCy Schubert /*
1199*a90b9d01SCy Schubert * If this is a pre-scan check, returning 0 will trigger a scan and
1200*a90b9d01SCy Schubert * another call. In that case, reject "bad" candidates in the hope of
1201*a90b9d01SCy Schubert * finding a better candidate after scanning.
1202*a90b9d01SCy Schubert *
1203*a90b9d01SCy Schubert * Use a simple heuristic to check whether the selection is reasonable
1204*a90b9d01SCy Schubert * or a scan is a good idea. For that, we need to have found a
1205*a90b9d01SCy Schubert * candidate BSS (which might be the current one), it is up-to-date,
1206*a90b9d01SCy Schubert * and we don't want to immediately roam back again.
1207*a90b9d01SCy Schubert */
1208*a90b9d01SCy Schubert if (pre_scan_check) {
1209*a90b9d01SCy Schubert struct os_reltime age;
1210*a90b9d01SCy Schubert
1211*a90b9d01SCy Schubert if (!bss)
1212*a90b9d01SCy Schubert return 0;
1213*a90b9d01SCy Schubert
1214*a90b9d01SCy Schubert os_reltime_age(&bss->last_update, &age);
1215*a90b9d01SCy Schubert if (age.sec >= 10)
1216*a90b9d01SCy Schubert return 0;
1217*a90b9d01SCy Schubert
1218*a90b9d01SCy Schubert #ifndef CONFIG_NO_ROAMING
1219*a90b9d01SCy Schubert if (wpa_s->current_bss && bss != wpa_s->current_bss &&
1220*a90b9d01SCy Schubert wpa_supplicant_need_to_roam_within_ess(wpa_s,
1221*a90b9d01SCy Schubert wpa_s->current_bss,
1222*a90b9d01SCy Schubert bss))
1223*a90b9d01SCy Schubert return 0;
1224*a90b9d01SCy Schubert #endif /* CONFIG_NO_ROAMING */
1225*a90b9d01SCy Schubert }
1226*a90b9d01SCy Schubert
12275b9c547cSRui Paulo if (!bss) {
12285b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: No BSS transition candidate match found");
12295b9c547cSRui Paulo status = WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES;
12305b9c547cSRui Paulo goto send_bss_resp_fail;
12315b9c547cSRui Paulo }
12325b9c547cSRui Paulo
12335b9c547cSRui Paulo /* Associate to the network */
1234780fb4a2SCy Schubert wnm_bss_tm_connect(wpa_s, bss, ssid, 1);
12355b9c547cSRui Paulo return 1;
12365b9c547cSRui Paulo
12375b9c547cSRui Paulo send_bss_resp_fail:
12385b9c547cSRui Paulo /* Send reject response for all the failures */
12395b9c547cSRui Paulo
1240*a90b9d01SCy Schubert if (wpa_s->wnm_reply)
1241*a90b9d01SCy Schubert wnm_send_bss_transition_mgmt_resp(wpa_s, status, reason,
1242*a90b9d01SCy Schubert 0, NULL);
1243*a90b9d01SCy Schubert
1244*a90b9d01SCy Schubert wnm_btm_reset(wpa_s);
12455b9c547cSRui Paulo
12465b9c547cSRui Paulo return 0;
12475b9c547cSRui Paulo }
12485b9c547cSRui Paulo
12495b9c547cSRui Paulo
cand_pref_compar(const void * a,const void * b)12505b9c547cSRui Paulo static int cand_pref_compar(const void *a, const void *b)
12515b9c547cSRui Paulo {
12525b9c547cSRui Paulo const struct neighbor_report *aa = a;
12535b9c547cSRui Paulo const struct neighbor_report *bb = b;
12545b9c547cSRui Paulo
1255*a90b9d01SCy Schubert if (aa->disassoc_imminent && !bb->disassoc_imminent)
1256*a90b9d01SCy Schubert return 1;
1257*a90b9d01SCy Schubert if (bb->disassoc_imminent && !aa->disassoc_imminent)
1258*a90b9d01SCy Schubert return -1;
1259*a90b9d01SCy Schubert
12605b9c547cSRui Paulo if (!aa->preference_present && !bb->preference_present)
12615b9c547cSRui Paulo return 0;
12625b9c547cSRui Paulo if (!aa->preference_present)
12635b9c547cSRui Paulo return 1;
12645b9c547cSRui Paulo if (!bb->preference_present)
12655b9c547cSRui Paulo return -1;
12665b9c547cSRui Paulo if (bb->preference > aa->preference)
12675b9c547cSRui Paulo return 1;
12685b9c547cSRui Paulo if (bb->preference < aa->preference)
12695b9c547cSRui Paulo return -1;
12705b9c547cSRui Paulo return 0;
12715b9c547cSRui Paulo }
12725b9c547cSRui Paulo
12735b9c547cSRui Paulo
wnm_sort_cand_list(struct wpa_supplicant * wpa_s)12745b9c547cSRui Paulo static void wnm_sort_cand_list(struct wpa_supplicant *wpa_s)
12755b9c547cSRui Paulo {
12765b9c547cSRui Paulo if (!wpa_s->wnm_neighbor_report_elements)
12775b9c547cSRui Paulo return;
12785b9c547cSRui Paulo qsort(wpa_s->wnm_neighbor_report_elements,
12795b9c547cSRui Paulo wpa_s->wnm_num_neighbor_report, sizeof(struct neighbor_report),
12805b9c547cSRui Paulo cand_pref_compar);
12815b9c547cSRui Paulo }
12825b9c547cSRui Paulo
12835b9c547cSRui Paulo
wnm_dump_cand_list(struct wpa_supplicant * wpa_s)12845b9c547cSRui Paulo static void wnm_dump_cand_list(struct wpa_supplicant *wpa_s)
12855b9c547cSRui Paulo {
12865b9c547cSRui Paulo unsigned int i;
12875b9c547cSRui Paulo
12885b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: BSS Transition Candidate List");
12895b9c547cSRui Paulo if (!wpa_s->wnm_neighbor_report_elements)
12905b9c547cSRui Paulo return;
12915b9c547cSRui Paulo for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) {
12925b9c547cSRui Paulo struct neighbor_report *nei;
12935b9c547cSRui Paulo
12945b9c547cSRui Paulo nei = &wpa_s->wnm_neighbor_report_elements[i];
12955b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "%u: " MACSTR
12965b9c547cSRui Paulo " info=0x%x op_class=%u chan=%u phy=%u pref=%d freq=%d",
12975b9c547cSRui Paulo i, MAC2STR(nei->bssid), nei->bssid_info,
12985b9c547cSRui Paulo nei->regulatory_class,
12995b9c547cSRui Paulo nei->channel_number, nei->phy_type,
13005b9c547cSRui Paulo nei->preference_present ? nei->preference : -1,
13015b9c547cSRui Paulo nei->freq);
13025b9c547cSRui Paulo }
13035b9c547cSRui Paulo }
13045b9c547cSRui Paulo
13055b9c547cSRui Paulo
chan_supported(struct wpa_supplicant * wpa_s,int freq)13065b9c547cSRui Paulo static int chan_supported(struct wpa_supplicant *wpa_s, int freq)
13075b9c547cSRui Paulo {
13085b9c547cSRui Paulo unsigned int i;
13095b9c547cSRui Paulo
13105b9c547cSRui Paulo for (i = 0; i < wpa_s->hw.num_modes; i++) {
13115b9c547cSRui Paulo struct hostapd_hw_modes *mode = &wpa_s->hw.modes[i];
13125b9c547cSRui Paulo int j;
13135b9c547cSRui Paulo
13145b9c547cSRui Paulo for (j = 0; j < mode->num_channels; j++) {
13155b9c547cSRui Paulo struct hostapd_channel_data *chan;
13165b9c547cSRui Paulo
13175b9c547cSRui Paulo chan = &mode->channels[j];
13185b9c547cSRui Paulo if (chan->freq == freq &&
13195b9c547cSRui Paulo !(chan->flag & HOSTAPD_CHAN_DISABLED))
13205b9c547cSRui Paulo return 1;
13215b9c547cSRui Paulo }
13225b9c547cSRui Paulo }
13235b9c547cSRui Paulo
13245b9c547cSRui Paulo return 0;
13255b9c547cSRui Paulo }
13265b9c547cSRui Paulo
13275b9c547cSRui Paulo
wnm_set_scan_freqs(struct wpa_supplicant * wpa_s)13285b9c547cSRui Paulo static void wnm_set_scan_freqs(struct wpa_supplicant *wpa_s)
13295b9c547cSRui Paulo {
13305b9c547cSRui Paulo int *freqs;
13315b9c547cSRui Paulo int num_freqs = 0;
13325b9c547cSRui Paulo unsigned int i;
13335b9c547cSRui Paulo
13345b9c547cSRui Paulo if (!wpa_s->wnm_neighbor_report_elements)
13355b9c547cSRui Paulo return;
13365b9c547cSRui Paulo
13375b9c547cSRui Paulo if (wpa_s->hw.modes == NULL)
13385b9c547cSRui Paulo return;
13395b9c547cSRui Paulo
13405b9c547cSRui Paulo os_free(wpa_s->next_scan_freqs);
13415b9c547cSRui Paulo wpa_s->next_scan_freqs = NULL;
13425b9c547cSRui Paulo
13435b9c547cSRui Paulo freqs = os_calloc(wpa_s->wnm_num_neighbor_report + 1, sizeof(int));
13445b9c547cSRui Paulo if (freqs == NULL)
13455b9c547cSRui Paulo return;
13465b9c547cSRui Paulo
13475b9c547cSRui Paulo for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) {
13485b9c547cSRui Paulo struct neighbor_report *nei;
13495b9c547cSRui Paulo
13505b9c547cSRui Paulo nei = &wpa_s->wnm_neighbor_report_elements[i];
1351*a90b9d01SCy Schubert
1352*a90b9d01SCy Schubert if (nei->preference_present && nei->preference == 0)
1353*a90b9d01SCy Schubert continue;
1354*a90b9d01SCy Schubert
13555b9c547cSRui Paulo if (nei->freq <= 0) {
13565b9c547cSRui Paulo wpa_printf(MSG_DEBUG,
13575b9c547cSRui Paulo "WNM: Unknown neighbor operating frequency for "
13585b9c547cSRui Paulo MACSTR " - scan all channels",
13595b9c547cSRui Paulo MAC2STR(nei->bssid));
13605b9c547cSRui Paulo os_free(freqs);
13615b9c547cSRui Paulo return;
13625b9c547cSRui Paulo }
13635b9c547cSRui Paulo if (chan_supported(wpa_s, nei->freq))
13645b9c547cSRui Paulo add_freq(freqs, &num_freqs, nei->freq);
13655b9c547cSRui Paulo }
13665b9c547cSRui Paulo
13675b9c547cSRui Paulo if (num_freqs == 0) {
13685b9c547cSRui Paulo os_free(freqs);
13695b9c547cSRui Paulo return;
13705b9c547cSRui Paulo }
13715b9c547cSRui Paulo
13725b9c547cSRui Paulo wpa_printf(MSG_DEBUG,
13735b9c547cSRui Paulo "WNM: Scan %d frequencies based on transition candidate list",
13745b9c547cSRui Paulo num_freqs);
13755b9c547cSRui Paulo wpa_s->next_scan_freqs = freqs;
1376f05cddf9SRui Paulo }
1377f05cddf9SRui Paulo
1378f05cddf9SRui Paulo
ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant * wpa_s,const u8 * pos,const u8 * end,int reply)1379f05cddf9SRui Paulo static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s,
1380f05cddf9SRui Paulo const u8 *pos, const u8 *end,
1381f05cddf9SRui Paulo int reply)
1382f05cddf9SRui Paulo {
13835b9c547cSRui Paulo unsigned int beacon_int;
13845b9c547cSRui Paulo u8 valid_int;
1385780fb4a2SCy Schubert #ifdef CONFIG_MBO
1386780fb4a2SCy Schubert const u8 *vendor;
1387780fb4a2SCy Schubert #endif /* CONFIG_MBO */
1388*a90b9d01SCy Schubert bool disassoc_imminent;
1389f05cddf9SRui Paulo
1390c1d255d3SCy Schubert if (wpa_s->disable_mbo_oce || wpa_s->conf->disable_btm)
1391206b73d0SCy Schubert return;
1392206b73d0SCy Schubert
1393780fb4a2SCy Schubert if (end - pos < 5)
1394f05cddf9SRui Paulo return;
1395f05cddf9SRui Paulo
13965b9c547cSRui Paulo if (wpa_s->current_bss)
13975b9c547cSRui Paulo beacon_int = wpa_s->current_bss->beacon_int;
13985b9c547cSRui Paulo else
13995b9c547cSRui Paulo beacon_int = 100; /* best guess */
14005b9c547cSRui Paulo
1401*a90b9d01SCy Schubert wnm_btm_reset(wpa_s);
1402*a90b9d01SCy Schubert
14035b9c547cSRui Paulo wpa_s->wnm_dialog_token = pos[0];
14045b9c547cSRui Paulo wpa_s->wnm_mode = pos[1];
14055b9c547cSRui Paulo wpa_s->wnm_dissoc_timer = WPA_GET_LE16(pos + 2);
1406*a90b9d01SCy Schubert wpa_s->wnm_link_removal = false;
14075b9c547cSRui Paulo valid_int = pos[4];
14085b9c547cSRui Paulo wpa_s->wnm_reply = reply;
1409f05cddf9SRui Paulo
1410f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Request: "
1411f05cddf9SRui Paulo "dialog_token=%u request_mode=0x%x "
1412f05cddf9SRui Paulo "disassoc_timer=%u validity_interval=%u",
14135b9c547cSRui Paulo wpa_s->wnm_dialog_token, wpa_s->wnm_mode,
14145b9c547cSRui Paulo wpa_s->wnm_dissoc_timer, valid_int);
14155b9c547cSRui Paulo
1416780fb4a2SCy Schubert #if defined(CONFIG_MBO) && defined(CONFIG_TESTING_OPTIONS)
1417780fb4a2SCy Schubert if (wpa_s->reject_btm_req_reason) {
1418780fb4a2SCy Schubert wpa_printf(MSG_INFO,
1419780fb4a2SCy Schubert "WNM: Testing - reject BSS Transition Management Request: reject_btm_req_reason=%d",
1420780fb4a2SCy Schubert wpa_s->reject_btm_req_reason);
142185732ac8SCy Schubert wnm_send_bss_transition_mgmt_resp(
1422*a90b9d01SCy Schubert wpa_s, wpa_s->reject_btm_req_reason,
142385732ac8SCy Schubert MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0, NULL);
1424780fb4a2SCy Schubert return;
1425780fb4a2SCy Schubert }
1426780fb4a2SCy Schubert #endif /* CONFIG_MBO && CONFIG_TESTING_OPTIONS */
1427780fb4a2SCy Schubert
1428f05cddf9SRui Paulo pos += 5;
14295b9c547cSRui Paulo
14305b9c547cSRui Paulo if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) {
1431780fb4a2SCy Schubert if (end - pos < 12) {
14325b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Too short BSS TM Request");
14335b9c547cSRui Paulo return;
14345b9c547cSRui Paulo }
14355b9c547cSRui Paulo os_memcpy(wpa_s->wnm_bss_termination_duration, pos, 12);
1436f05cddf9SRui Paulo pos += 12; /* BSS Termination Duration */
14375b9c547cSRui Paulo }
14385b9c547cSRui Paulo
14395b9c547cSRui Paulo if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT) {
1440f05cddf9SRui Paulo char url[256];
1441*a90b9d01SCy Schubert u8 url_len;
14425b9c547cSRui Paulo
1443*a90b9d01SCy Schubert if (end - pos < 1) {
1444f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "WNM: Invalid BSS Transition "
1445f05cddf9SRui Paulo "Management Request (URL)");
1446f05cddf9SRui Paulo return;
1447f05cddf9SRui Paulo }
1448*a90b9d01SCy Schubert url_len = *pos++;
1449*a90b9d01SCy Schubert if (url_len > end - pos) {
1450*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG,
1451*a90b9d01SCy Schubert "WNM: Invalid BSS Transition Management Request (URL truncated)");
1452*a90b9d01SCy Schubert return;
1453*a90b9d01SCy Schubert }
1454*a90b9d01SCy Schubert os_memcpy(url, pos, url_len);
1455*a90b9d01SCy Schubert url[url_len] = '\0';
1456*a90b9d01SCy Schubert pos += url_len;
14575b9c547cSRui Paulo
14585b9c547cSRui Paulo wpa_msg(wpa_s, MSG_INFO, ESS_DISASSOC_IMMINENT "%d %u %s",
14595b9c547cSRui Paulo wpa_sm_pmf_enabled(wpa_s->wpa),
14605b9c547cSRui Paulo wpa_s->wnm_dissoc_timer * beacon_int * 128 / 125, url);
1461f05cddf9SRui Paulo }
1462f05cddf9SRui Paulo
1463*a90b9d01SCy Schubert disassoc_imminent = wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT;
1464*a90b9d01SCy Schubert
1465*a90b9d01SCy Schubert /*
1466*a90b9d01SCy Schubert * Based on IEEE P802.11be/D5.0, when a station is a non-AP MLD with
1467*a90b9d01SCy Schubert * more than one affiliated link, the Link Removal Imminent field is
1468*a90b9d01SCy Schubert * set to 1, and the BSS Termination Included field is set to 1, only
1469*a90b9d01SCy Schubert * one of the links is removed and the other links remain associated.
1470*a90b9d01SCy Schubert * Ignore the Disassociation Imminent field in such a case.
1471*a90b9d01SCy Schubert *
1472*a90b9d01SCy Schubert * TODO: We should check if the AP has more than one link.
1473*a90b9d01SCy Schubert * TODO: We should pass the RX link and use that
1474*a90b9d01SCy Schubert */
1475*a90b9d01SCy Schubert if (disassoc_imminent && wpa_s->valid_links &&
1476*a90b9d01SCy Schubert (wpa_s->wnm_mode & WNM_BSS_TM_REQ_LINK_REMOVAL_IMMINENT) &&
1477*a90b9d01SCy Schubert (wpa_s->wnm_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED)) {
1478*a90b9d01SCy Schubert /* If we still have a link, then just accept the request */
1479*a90b9d01SCy Schubert if (wpa_s->valid_links & (wpa_s->valid_links - 1)) {
1480*a90b9d01SCy Schubert wpa_printf(MSG_INFO,
1481*a90b9d01SCy Schubert "WNM: BTM request for a single MLO link - ignore disassociation imminent since other links remain associated");
1482*a90b9d01SCy Schubert disassoc_imminent = false;
1483*a90b9d01SCy Schubert
1484*a90b9d01SCy Schubert wnm_send_bss_transition_mgmt_resp(
1485*a90b9d01SCy Schubert wpa_s, WNM_BSS_TM_ACCEPT, 0, 0, NULL);
1486*a90b9d01SCy Schubert
1487*a90b9d01SCy Schubert return;
1488*a90b9d01SCy Schubert }
1489*a90b9d01SCy Schubert
1490*a90b9d01SCy Schubert /* The last link is being removed (which must be the assoc link)
1491*a90b9d01SCy Schubert */
1492*a90b9d01SCy Schubert wpa_s->wnm_link_removal = true;
1493*a90b9d01SCy Schubert os_memcpy(wpa_s->wnm_dissoc_addr,
1494*a90b9d01SCy Schubert wpa_s->links[wpa_s->mlo_assoc_link_id].bssid,
1495*a90b9d01SCy Schubert ETH_ALEN);
1496*a90b9d01SCy Schubert } else {
1497*a90b9d01SCy Schubert os_memcpy(wpa_s->wnm_dissoc_addr, wpa_s->valid_links ?
1498*a90b9d01SCy Schubert wpa_s->ap_mld_addr : wpa_s->bssid, ETH_ALEN);
1499*a90b9d01SCy Schubert }
1500*a90b9d01SCy Schubert
1501*a90b9d01SCy Schubert if (disassoc_imminent) {
1502f05cddf9SRui Paulo wpa_msg(wpa_s, MSG_INFO, "WNM: Disassociation Imminent - "
15035b9c547cSRui Paulo "Disassociation Timer %u", wpa_s->wnm_dissoc_timer);
1504*a90b9d01SCy Schubert if (wpa_s->wnm_dissoc_timer && !wpa_s->scanning &&
1505*a90b9d01SCy Schubert (!wpa_s->current_ssid || !wpa_s->current_ssid->bssid_set)) {
1506f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "Trying to find another BSS");
1507f05cddf9SRui Paulo wpa_supplicant_req_scan(wpa_s, 0, 0);
1508f05cddf9SRui Paulo }
1509f05cddf9SRui Paulo }
1510f05cddf9SRui Paulo
1511780fb4a2SCy Schubert #ifdef CONFIG_MBO
1512780fb4a2SCy Schubert vendor = get_ie(pos, end - pos, WLAN_EID_VENDOR_SPECIFIC);
1513780fb4a2SCy Schubert if (vendor)
1514780fb4a2SCy Schubert wpas_mbo_ie_trans_req(wpa_s, vendor + 2, vendor[1]);
1515780fb4a2SCy Schubert #endif /* CONFIG_MBO */
1516780fb4a2SCy Schubert
15175b9c547cSRui Paulo if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_PREF_CAND_LIST_INCLUDED) {
15185b9c547cSRui Paulo unsigned int valid_ms;
15195b9c547cSRui Paulo
15205b9c547cSRui Paulo wpa_msg(wpa_s, MSG_INFO, "WNM: Preferred List Available");
15215b9c547cSRui Paulo wpa_s->wnm_neighbor_report_elements = os_calloc(
15225b9c547cSRui Paulo WNM_MAX_NEIGHBOR_REPORT,
15235b9c547cSRui Paulo sizeof(struct neighbor_report));
15245b9c547cSRui Paulo if (wpa_s->wnm_neighbor_report_elements == NULL)
15255b9c547cSRui Paulo return;
15265b9c547cSRui Paulo
1527780fb4a2SCy Schubert while (end - pos >= 2 &&
15285b9c547cSRui Paulo wpa_s->wnm_num_neighbor_report < WNM_MAX_NEIGHBOR_REPORT)
15295b9c547cSRui Paulo {
15305b9c547cSRui Paulo u8 tag = *pos++;
15315b9c547cSRui Paulo u8 len = *pos++;
15325b9c547cSRui Paulo
15335b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Neighbor report tag %u",
15345b9c547cSRui Paulo tag);
1535780fb4a2SCy Schubert if (len > end - pos) {
15365b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Truncated request");
15375b9c547cSRui Paulo return;
15385b9c547cSRui Paulo }
15395b9c547cSRui Paulo if (tag == WLAN_EID_NEIGHBOR_REPORT) {
15405b9c547cSRui Paulo struct neighbor_report *rep;
15415b9c547cSRui Paulo rep = &wpa_s->wnm_neighbor_report_elements[
15425b9c547cSRui Paulo wpa_s->wnm_num_neighbor_report];
15435b9c547cSRui Paulo wnm_parse_neighbor_report(wpa_s, pos, len, rep);
1544*a90b9d01SCy Schubert if ((wpa_s->wnm_mode &
1545*a90b9d01SCy Schubert WNM_BSS_TM_REQ_DISASSOC_IMMINENT) &&
1546*a90b9d01SCy Schubert ether_addr_equal(rep->bssid, wpa_s->bssid))
1547*a90b9d01SCy Schubert rep->disassoc_imminent = 1;
1548*a90b9d01SCy Schubert
1549780fb4a2SCy Schubert wpa_s->wnm_num_neighbor_report++;
155085732ac8SCy Schubert #ifdef CONFIG_MBO
155185732ac8SCy Schubert if (wpa_s->wnm_mbo_trans_reason_present &&
155285732ac8SCy Schubert wpa_s->wnm_num_neighbor_report == 1) {
155385732ac8SCy Schubert rep->is_first = 1;
155485732ac8SCy Schubert wpa_printf(MSG_DEBUG,
155585732ac8SCy Schubert "WNM: First transition candidate is "
155685732ac8SCy Schubert MACSTR, MAC2STR(rep->bssid));
155785732ac8SCy Schubert }
155885732ac8SCy Schubert #endif /* CONFIG_MBO */
15595b9c547cSRui Paulo }
15605b9c547cSRui Paulo
15615b9c547cSRui Paulo pos += len;
15625b9c547cSRui Paulo }
1563780fb4a2SCy Schubert
1564780fb4a2SCy Schubert if (!wpa_s->wnm_num_neighbor_report) {
1565780fb4a2SCy Schubert wpa_printf(MSG_DEBUG,
1566780fb4a2SCy Schubert "WNM: Candidate list included bit is set, but no candidates found");
1567780fb4a2SCy Schubert wnm_send_bss_transition_mgmt_resp(
1568*a90b9d01SCy Schubert wpa_s, WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES,
1569*a90b9d01SCy Schubert MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0,
1570*a90b9d01SCy Schubert NULL);
1571*a90b9d01SCy Schubert return;
1572*a90b9d01SCy Schubert }
1573*a90b9d01SCy Schubert
1574*a90b9d01SCy Schubert if (wpa_s->current_ssid && wpa_s->current_ssid->bssid_set) {
1575*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG,
1576*a90b9d01SCy Schubert "WNM: Configuration prevents roaming (BSSID set)");
1577*a90b9d01SCy Schubert wnm_send_bss_transition_mgmt_resp(
1578*a90b9d01SCy Schubert wpa_s, WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES,
157985732ac8SCy Schubert MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0,
158085732ac8SCy Schubert NULL);
1581780fb4a2SCy Schubert return;
1582780fb4a2SCy Schubert }
1583780fb4a2SCy Schubert
15845b9c547cSRui Paulo wnm_sort_cand_list(wpa_s);
15855b9c547cSRui Paulo wnm_dump_cand_list(wpa_s);
15865b9c547cSRui Paulo valid_ms = valid_int * beacon_int * 128 / 125;
15875b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Candidate list valid for %u ms",
15885b9c547cSRui Paulo valid_ms);
15895b9c547cSRui Paulo os_get_reltime(&wpa_s->wnm_cand_valid_until);
15905b9c547cSRui Paulo wpa_s->wnm_cand_valid_until.sec += valid_ms / 1000;
15915b9c547cSRui Paulo wpa_s->wnm_cand_valid_until.usec += (valid_ms % 1000) * 1000;
15925b9c547cSRui Paulo wpa_s->wnm_cand_valid_until.sec +=
15935b9c547cSRui Paulo wpa_s->wnm_cand_valid_until.usec / 1000000;
15945b9c547cSRui Paulo wpa_s->wnm_cand_valid_until.usec %= 1000000;
15955b9c547cSRui Paulo
1596780fb4a2SCy Schubert /*
1597*a90b9d01SCy Schubert * Try fetching the latest scan results from the kernel.
1598*a90b9d01SCy Schubert * This can help in finding more up-to-date information should
1599*a90b9d01SCy Schubert * the driver have done some internal scanning operations after
1600*a90b9d01SCy Schubert * the last scan result update in wpa_supplicant.
1601*a90b9d01SCy Schubert *
1602*a90b9d01SCy Schubert * It is not a new scan, this does not update the last_scan
1603*a90b9d01SCy Schubert * timestamp nor will it expire old BSSs.
1604780fb4a2SCy Schubert */
1605*a90b9d01SCy Schubert wpa_supplicant_update_scan_results(wpa_s, NULL);
1606*a90b9d01SCy Schubert if (wnm_scan_process(wpa_s, true) > 0)
16075b9c547cSRui Paulo return;
16085b9c547cSRui Paulo wpa_printf(MSG_DEBUG,
1609*a90b9d01SCy Schubert "WNM: No valid match in previous scan results - try a new scan");
16105b9c547cSRui Paulo
16115b9c547cSRui Paulo wnm_set_scan_freqs(wpa_s);
1612780fb4a2SCy Schubert if (wpa_s->wnm_num_neighbor_report == 1) {
1613780fb4a2SCy Schubert os_memcpy(wpa_s->next_scan_bssid,
1614780fb4a2SCy Schubert wpa_s->wnm_neighbor_report_elements[0].bssid,
1615780fb4a2SCy Schubert ETH_ALEN);
1616780fb4a2SCy Schubert wpa_printf(MSG_DEBUG,
1617780fb4a2SCy Schubert "WNM: Scan only for a specific BSSID since there is only a single candidate "
1618780fb4a2SCy Schubert MACSTR, MAC2STR(wpa_s->next_scan_bssid));
1619780fb4a2SCy Schubert }
16205b9c547cSRui Paulo wpa_supplicant_req_scan(wpa_s, 0, 0);
16215b9c547cSRui Paulo } else if (reply) {
16225b9c547cSRui Paulo enum bss_trans_mgmt_status_code status;
1623*a90b9d01SCy Schubert
1624*a90b9d01SCy Schubert if ((wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT) ||
1625*a90b9d01SCy Schubert wpa_s->wnm_link_removal)
16265b9c547cSRui Paulo status = WNM_BSS_TM_ACCEPT;
16275b9c547cSRui Paulo else {
16285b9c547cSRui Paulo wpa_msg(wpa_s, MSG_INFO, "WNM: BSS Transition Management Request did not include candidates");
16295b9c547cSRui Paulo status = WNM_BSS_TM_REJECT_UNSPECIFIED;
16305b9c547cSRui Paulo }
163185732ac8SCy Schubert wnm_send_bss_transition_mgmt_resp(
1632*a90b9d01SCy Schubert wpa_s, status,
163385732ac8SCy Schubert MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0, NULL);
16345b9c547cSRui Paulo }
16355b9c547cSRui Paulo }
16365b9c547cSRui Paulo
16375b9c547cSRui Paulo
wnm_btm_resp_tx_status(struct wpa_supplicant * wpa_s,const u8 * data,size_t data_len)1638*a90b9d01SCy Schubert int wnm_btm_resp_tx_status(struct wpa_supplicant *wpa_s, const u8 *data,
1639*a90b9d01SCy Schubert size_t data_len)
1640*a90b9d01SCy Schubert {
1641*a90b9d01SCy Schubert const struct ieee80211_mgmt *frame =
1642*a90b9d01SCy Schubert (const struct ieee80211_mgmt *) data;
1643*a90b9d01SCy Schubert
1644*a90b9d01SCy Schubert if (data_len <
1645*a90b9d01SCy Schubert IEEE80211_HDRLEN + sizeof(frame->u.action.u.bss_tm_resp) ||
1646*a90b9d01SCy Schubert frame->u.action.category != WLAN_ACTION_WNM ||
1647*a90b9d01SCy Schubert frame->u.action.u.bss_tm_resp.action != WNM_BSS_TRANS_MGMT_RESP ||
1648*a90b9d01SCy Schubert frame->u.action.u.bss_tm_resp.status_code != WNM_BSS_TM_ACCEPT)
1649*a90b9d01SCy Schubert return -1;
1650*a90b9d01SCy Schubert
1651*a90b9d01SCy Schubert /*
1652*a90b9d01SCy Schubert * If disassoc imminent bit was set in the request, the response may
1653*a90b9d01SCy Schubert * indicate accept even if no candidate was found, so bail out here.
1654*a90b9d01SCy Schubert */
1655*a90b9d01SCy Schubert if (!wpa_s->wnm_target_bss) {
1656*a90b9d01SCy Schubert wpa_printf(MSG_DEBUG, "WNM: Target BSS is not set");
1657*a90b9d01SCy Schubert return 0;
1658*a90b9d01SCy Schubert }
1659*a90b9d01SCy Schubert
1660*a90b9d01SCy Schubert if (!wpa_s->current_ssid)
1661*a90b9d01SCy Schubert return 0;
1662*a90b9d01SCy Schubert
1663*a90b9d01SCy Schubert wnm_bss_tm_connect(wpa_s, wpa_s->wnm_target_bss, wpa_s->current_ssid,
1664*a90b9d01SCy Schubert 0);
1665*a90b9d01SCy Schubert
1666*a90b9d01SCy Schubert wpa_s->wnm_target_bss = NULL;
1667*a90b9d01SCy Schubert return 0;
1668*a90b9d01SCy Schubert }
1669*a90b9d01SCy Schubert
1670*a90b9d01SCy Schubert
167185732ac8SCy Schubert #define BTM_QUERY_MIN_SIZE 4
167285732ac8SCy Schubert
wnm_send_bss_transition_mgmt_query(struct wpa_supplicant * wpa_s,u8 query_reason,const char * btm_candidates,int cand_list)16735b9c547cSRui Paulo int wnm_send_bss_transition_mgmt_query(struct wpa_supplicant *wpa_s,
167485732ac8SCy Schubert u8 query_reason,
167585732ac8SCy Schubert const char *btm_candidates,
167685732ac8SCy Schubert int cand_list)
16775b9c547cSRui Paulo {
167885732ac8SCy Schubert struct wpabuf *buf;
16795b9c547cSRui Paulo int ret;
16805b9c547cSRui Paulo
16815b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Query to "
1682780fb4a2SCy Schubert MACSTR " query_reason=%u%s",
1683780fb4a2SCy Schubert MAC2STR(wpa_s->bssid), query_reason,
1684780fb4a2SCy Schubert cand_list ? " candidate list" : "");
16855b9c547cSRui Paulo
168685732ac8SCy Schubert buf = wpabuf_alloc(BTM_QUERY_MIN_SIZE);
168785732ac8SCy Schubert if (!buf)
168885732ac8SCy Schubert return -1;
168985732ac8SCy Schubert
169085732ac8SCy Schubert wpabuf_put_u8(buf, WLAN_ACTION_WNM);
169185732ac8SCy Schubert wpabuf_put_u8(buf, WNM_BSS_TRANS_MGMT_QUERY);
169285732ac8SCy Schubert wpabuf_put_u8(buf, 1);
169385732ac8SCy Schubert wpabuf_put_u8(buf, query_reason);
16945b9c547cSRui Paulo
1695780fb4a2SCy Schubert if (cand_list)
169685732ac8SCy Schubert wnm_add_cand_list(wpa_s, &buf);
1697780fb4a2SCy Schubert
169885732ac8SCy Schubert if (btm_candidates) {
169985732ac8SCy Schubert const size_t max_len = 1000;
170085732ac8SCy Schubert
170185732ac8SCy Schubert ret = wpabuf_resize(&buf, max_len);
170285732ac8SCy Schubert if (ret < 0) {
170385732ac8SCy Schubert wpabuf_free(buf);
170485732ac8SCy Schubert return ret;
170585732ac8SCy Schubert }
170685732ac8SCy Schubert
170785732ac8SCy Schubert ret = ieee802_11_parse_candidate_list(btm_candidates,
170885732ac8SCy Schubert wpabuf_put(buf, 0),
170985732ac8SCy Schubert max_len);
171085732ac8SCy Schubert if (ret < 0) {
171185732ac8SCy Schubert wpabuf_free(buf);
171285732ac8SCy Schubert return ret;
171385732ac8SCy Schubert }
171485732ac8SCy Schubert
171585732ac8SCy Schubert wpabuf_put(buf, ret);
171685732ac8SCy Schubert }
17175b9c547cSRui Paulo
17185b9c547cSRui Paulo ret = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
17195b9c547cSRui Paulo wpa_s->own_addr, wpa_s->bssid,
172085732ac8SCy Schubert wpabuf_head_u8(buf), wpabuf_len(buf), 0);
17215b9c547cSRui Paulo
172285732ac8SCy Schubert wpabuf_free(buf);
17235b9c547cSRui Paulo return ret;
17245b9c547cSRui Paulo }
17255b9c547cSRui Paulo
17265b9c547cSRui Paulo
ieee802_11_rx_wnm_notif_req_wfa(struct wpa_supplicant * wpa_s,const u8 * sa,const u8 * data,int len)17275b9c547cSRui Paulo static void ieee802_11_rx_wnm_notif_req_wfa(struct wpa_supplicant *wpa_s,
17285b9c547cSRui Paulo const u8 *sa, const u8 *data,
17295b9c547cSRui Paulo int len)
17305b9c547cSRui Paulo {
17315b9c547cSRui Paulo const u8 *pos, *end, *next;
17325b9c547cSRui Paulo u8 ie, ie_len;
17335b9c547cSRui Paulo
17345b9c547cSRui Paulo pos = data;
17355b9c547cSRui Paulo end = data + len;
17365b9c547cSRui Paulo
1737780fb4a2SCy Schubert while (end - pos > 1) {
17385b9c547cSRui Paulo ie = *pos++;
17395b9c547cSRui Paulo ie_len = *pos++;
17405b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: WFA subelement %u len %u",
17415b9c547cSRui Paulo ie, ie_len);
17425b9c547cSRui Paulo if (ie_len > end - pos) {
17435b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Not enough room for "
17445b9c547cSRui Paulo "subelement");
17455b9c547cSRui Paulo break;
17465b9c547cSRui Paulo }
17475b9c547cSRui Paulo next = pos + ie_len;
17485b9c547cSRui Paulo if (ie_len < 4) {
17495b9c547cSRui Paulo pos = next;
17505b9c547cSRui Paulo continue;
17515b9c547cSRui Paulo }
17525b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Subelement OUI %06x type %u",
17535b9c547cSRui Paulo WPA_GET_BE24(pos), pos[3]);
17545b9c547cSRui Paulo
17555b9c547cSRui Paulo #ifdef CONFIG_HS20
17565b9c547cSRui Paulo if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 5 &&
17575b9c547cSRui Paulo WPA_GET_BE24(pos) == OUI_WFA &&
17585b9c547cSRui Paulo pos[3] == HS20_WNM_SUB_REM_NEEDED) {
17595b9c547cSRui Paulo /* Subscription Remediation subelement */
17605b9c547cSRui Paulo const u8 *ie_end;
17615b9c547cSRui Paulo u8 url_len;
17625b9c547cSRui Paulo char *url;
17635b9c547cSRui Paulo u8 osu_method;
17645b9c547cSRui Paulo
17655b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Subscription Remediation "
17665b9c547cSRui Paulo "subelement");
17675b9c547cSRui Paulo ie_end = pos + ie_len;
17685b9c547cSRui Paulo pos += 4;
17695b9c547cSRui Paulo url_len = *pos++;
17705b9c547cSRui Paulo if (url_len == 0) {
17715b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: No Server URL included");
17725b9c547cSRui Paulo url = NULL;
17735b9c547cSRui Paulo osu_method = 1;
17745b9c547cSRui Paulo } else {
1775780fb4a2SCy Schubert if (url_len + 1 > ie_end - pos) {
17765b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: Not enough room for Server URL (len=%u) and Server Method (left %d)",
17775b9c547cSRui Paulo url_len,
17785b9c547cSRui Paulo (int) (ie_end - pos));
17795b9c547cSRui Paulo break;
17805b9c547cSRui Paulo }
17815b9c547cSRui Paulo url = os_malloc(url_len + 1);
17825b9c547cSRui Paulo if (url == NULL)
17835b9c547cSRui Paulo break;
17845b9c547cSRui Paulo os_memcpy(url, pos, url_len);
17855b9c547cSRui Paulo url[url_len] = '\0';
17865b9c547cSRui Paulo osu_method = pos[url_len];
17875b9c547cSRui Paulo }
17885b9c547cSRui Paulo hs20_rx_subscription_remediation(wpa_s, url,
17895b9c547cSRui Paulo osu_method);
17905b9c547cSRui Paulo os_free(url);
17915b9c547cSRui Paulo pos = next;
17925b9c547cSRui Paulo continue;
17935b9c547cSRui Paulo }
17945b9c547cSRui Paulo
17955b9c547cSRui Paulo if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 8 &&
17965b9c547cSRui Paulo WPA_GET_BE24(pos) == OUI_WFA &&
17975b9c547cSRui Paulo pos[3] == HS20_WNM_DEAUTH_IMMINENT_NOTICE) {
17985b9c547cSRui Paulo const u8 *ie_end;
17995b9c547cSRui Paulo u8 url_len;
18005b9c547cSRui Paulo char *url;
18015b9c547cSRui Paulo u8 code;
18025b9c547cSRui Paulo u16 reauth_delay;
18035b9c547cSRui Paulo
18045b9c547cSRui Paulo ie_end = pos + ie_len;
18055b9c547cSRui Paulo pos += 4;
18065b9c547cSRui Paulo code = *pos++;
18075b9c547cSRui Paulo reauth_delay = WPA_GET_LE16(pos);
18085b9c547cSRui Paulo pos += 2;
18095b9c547cSRui Paulo url_len = *pos++;
18105b9c547cSRui Paulo wpa_printf(MSG_DEBUG, "WNM: HS 2.0 Deauthentication "
18115b9c547cSRui Paulo "Imminent - Reason Code %u "
18125b9c547cSRui Paulo "Re-Auth Delay %u URL Length %u",
18135b9c547cSRui Paulo code, reauth_delay, url_len);
1814780fb4a2SCy Schubert if (url_len > ie_end - pos)
18155b9c547cSRui Paulo break;
18165b9c547cSRui Paulo url = os_malloc(url_len + 1);
18175b9c547cSRui Paulo if (url == NULL)
18185b9c547cSRui Paulo break;
18195b9c547cSRui Paulo os_memcpy(url, pos, url_len);
18205b9c547cSRui Paulo url[url_len] = '\0';
18215b9c547cSRui Paulo hs20_rx_deauth_imminent_notice(wpa_s, code,
18225b9c547cSRui Paulo reauth_delay, url);
18235b9c547cSRui Paulo os_free(url);
18245b9c547cSRui Paulo pos = next;
18255b9c547cSRui Paulo continue;
18265b9c547cSRui Paulo }
182785732ac8SCy Schubert
182885732ac8SCy Schubert if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 5 &&
182985732ac8SCy Schubert WPA_GET_BE24(pos) == OUI_WFA &&
183085732ac8SCy Schubert pos[3] == HS20_WNM_T_C_ACCEPTANCE) {
183185732ac8SCy Schubert const u8 *ie_end;
183285732ac8SCy Schubert u8 url_len;
183385732ac8SCy Schubert char *url;
183485732ac8SCy Schubert
183585732ac8SCy Schubert ie_end = pos + ie_len;
183685732ac8SCy Schubert pos += 4;
183785732ac8SCy Schubert url_len = *pos++;
183885732ac8SCy Schubert wpa_printf(MSG_DEBUG,
183985732ac8SCy Schubert "WNM: HS 2.0 Terms and Conditions Acceptance (URL Length %u)",
184085732ac8SCy Schubert url_len);
184185732ac8SCy Schubert if (url_len > ie_end - pos)
184285732ac8SCy Schubert break;
184385732ac8SCy Schubert url = os_malloc(url_len + 1);
184485732ac8SCy Schubert if (!url)
184585732ac8SCy Schubert break;
184685732ac8SCy Schubert os_memcpy(url, pos, url_len);
184785732ac8SCy Schubert url[url_len] = '\0';
184885732ac8SCy Schubert hs20_rx_t_c_acceptance(wpa_s, url);
184985732ac8SCy Schubert os_free(url);
185085732ac8SCy Schubert pos = next;
185185732ac8SCy Schubert continue;
185285732ac8SCy Schubert }
18535b9c547cSRui Paulo #endif /* CONFIG_HS20 */
18545b9c547cSRui Paulo
18555b9c547cSRui Paulo pos = next;
18565b9c547cSRui Paulo }
18575b9c547cSRui Paulo }
18585b9c547cSRui Paulo
18595b9c547cSRui Paulo
ieee802_11_rx_wnm_notif_req(struct wpa_supplicant * wpa_s,const u8 * sa,const u8 * frm,int len)18605b9c547cSRui Paulo static void ieee802_11_rx_wnm_notif_req(struct wpa_supplicant *wpa_s,
18615b9c547cSRui Paulo const u8 *sa, const u8 *frm, int len)
18625b9c547cSRui Paulo {
18635b9c547cSRui Paulo const u8 *pos, *end;
18645b9c547cSRui Paulo u8 dialog_token, type;
18655b9c547cSRui Paulo
18665b9c547cSRui Paulo /* Dialog Token [1] | Type [1] | Subelements */
18675b9c547cSRui Paulo
18685b9c547cSRui Paulo if (len < 2 || sa == NULL)
18695b9c547cSRui Paulo return;
18705b9c547cSRui Paulo end = frm + len;
18715b9c547cSRui Paulo pos = frm;
18725b9c547cSRui Paulo dialog_token = *pos++;
18735b9c547cSRui Paulo type = *pos++;
18745b9c547cSRui Paulo
18755b9c547cSRui Paulo wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Received WNM-Notification Request "
18765b9c547cSRui Paulo "(dialog_token %u type %u sa " MACSTR ")",
18775b9c547cSRui Paulo dialog_token, type, MAC2STR(sa));
18785b9c547cSRui Paulo wpa_hexdump(MSG_DEBUG, "WNM-Notification Request subelements",
18795b9c547cSRui Paulo pos, end - pos);
18805b9c547cSRui Paulo
18815b9c547cSRui Paulo if (wpa_s->wpa_state != WPA_COMPLETED ||
1882*a90b9d01SCy Schubert (!ether_addr_equal(sa, wpa_s->bssid) &&
1883*a90b9d01SCy Schubert (!wpa_s->valid_links ||
1884*a90b9d01SCy Schubert !ether_addr_equal(sa, wpa_s->ap_mld_addr)))) {
18855b9c547cSRui Paulo wpa_dbg(wpa_s, MSG_DEBUG, "WNM: WNM-Notification frame not "
18865b9c547cSRui Paulo "from our AP - ignore it");
18875b9c547cSRui Paulo return;
18885b9c547cSRui Paulo }
18895b9c547cSRui Paulo
18905b9c547cSRui Paulo switch (type) {
18915b9c547cSRui Paulo case 1:
18925b9c547cSRui Paulo ieee802_11_rx_wnm_notif_req_wfa(wpa_s, sa, pos, end - pos);
18935b9c547cSRui Paulo break;
18945b9c547cSRui Paulo default:
18955b9c547cSRui Paulo wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Ignore unknown "
18965b9c547cSRui Paulo "WNM-Notification type %u", type);
18975b9c547cSRui Paulo break;
1898f05cddf9SRui Paulo }
1899f05cddf9SRui Paulo }
1900f05cddf9SRui Paulo
1901f05cddf9SRui Paulo
ieee802_11_rx_wnm_coloc_intf_req(struct wpa_supplicant * wpa_s,const u8 * sa,const u8 * frm,int len)190285732ac8SCy Schubert static void ieee802_11_rx_wnm_coloc_intf_req(struct wpa_supplicant *wpa_s,
190385732ac8SCy Schubert const u8 *sa, const u8 *frm,
190485732ac8SCy Schubert int len)
190585732ac8SCy Schubert {
190685732ac8SCy Schubert u8 dialog_token, req_info, auto_report, timeout;
190785732ac8SCy Schubert
190885732ac8SCy Schubert if (!wpa_s->conf->coloc_intf_reporting)
190985732ac8SCy Schubert return;
191085732ac8SCy Schubert
191185732ac8SCy Schubert /* Dialog Token [1] | Request Info [1] */
191285732ac8SCy Schubert
191385732ac8SCy Schubert if (len < 2)
191485732ac8SCy Schubert return;
191585732ac8SCy Schubert dialog_token = frm[0];
191685732ac8SCy Schubert req_info = frm[1];
191785732ac8SCy Schubert auto_report = req_info & 0x03;
191885732ac8SCy Schubert timeout = req_info >> 2;
191985732ac8SCy Schubert
192085732ac8SCy Schubert wpa_dbg(wpa_s, MSG_DEBUG,
192185732ac8SCy Schubert "WNM: Received Collocated Interference Request (dialog_token %u auto_report %u timeout %u sa " MACSTR ")",
192285732ac8SCy Schubert dialog_token, auto_report, timeout, MAC2STR(sa));
192385732ac8SCy Schubert
192485732ac8SCy Schubert if (dialog_token == 0)
192585732ac8SCy Schubert return; /* only nonzero values are used for request */
192685732ac8SCy Schubert
192785732ac8SCy Schubert if (wpa_s->wpa_state != WPA_COMPLETED ||
1928*a90b9d01SCy Schubert (!ether_addr_equal(sa, wpa_s->bssid) &&
1929*a90b9d01SCy Schubert (!wpa_s->valid_links ||
1930*a90b9d01SCy Schubert !ether_addr_equal(sa, wpa_s->ap_mld_addr)))) {
193185732ac8SCy Schubert wpa_dbg(wpa_s, MSG_DEBUG,
193285732ac8SCy Schubert "WNM: Collocated Interference Request frame not from current AP - ignore it");
193385732ac8SCy Schubert return;
193485732ac8SCy Schubert }
193585732ac8SCy Schubert
193685732ac8SCy Schubert wpa_msg(wpa_s, MSG_INFO, COLOC_INTF_REQ "%u %u %u",
193785732ac8SCy Schubert dialog_token, auto_report, timeout);
193885732ac8SCy Schubert wpa_s->coloc_intf_dialog_token = dialog_token;
193985732ac8SCy Schubert wpa_s->coloc_intf_auto_report = auto_report;
194085732ac8SCy Schubert wpa_s->coloc_intf_timeout = timeout;
194185732ac8SCy Schubert }
194285732ac8SCy Schubert
194385732ac8SCy Schubert
ieee802_11_rx_wnm_action(struct wpa_supplicant * wpa_s,const struct ieee80211_mgmt * mgmt,size_t len)1944f05cddf9SRui Paulo void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s,
19455b9c547cSRui Paulo const struct ieee80211_mgmt *mgmt, size_t len)
1946f05cddf9SRui Paulo {
1947f05cddf9SRui Paulo const u8 *pos, *end;
1948f05cddf9SRui Paulo u8 act;
1949f05cddf9SRui Paulo
19505b9c547cSRui Paulo if (len < IEEE80211_HDRLEN + 2)
1951f05cddf9SRui Paulo return;
1952f05cddf9SRui Paulo
19535b9c547cSRui Paulo pos = ((const u8 *) mgmt) + IEEE80211_HDRLEN + 1;
1954f05cddf9SRui Paulo act = *pos++;
19555b9c547cSRui Paulo end = ((const u8 *) mgmt) + len;
1956f05cddf9SRui Paulo
1957f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "WNM: RX action %u from " MACSTR,
19585b9c547cSRui Paulo act, MAC2STR(mgmt->sa));
1959f05cddf9SRui Paulo if (wpa_s->wpa_state < WPA_ASSOCIATED ||
1960*a90b9d01SCy Schubert (!ether_addr_equal(mgmt->sa, wpa_s->bssid) &&
1961*a90b9d01SCy Schubert (!wpa_s->valid_links ||
1962*a90b9d01SCy Schubert !ether_addr_equal(mgmt->sa, wpa_s->ap_mld_addr)))) {
1963f05cddf9SRui Paulo wpa_printf(MSG_DEBUG, "WNM: Ignore unexpected WNM Action "
1964f05cddf9SRui Paulo "frame");
1965f05cddf9SRui Paulo return;
1966f05cddf9SRui Paulo }
1967f05cddf9SRui Paulo
1968f05cddf9SRui Paulo switch (act) {
1969f05cddf9SRui Paulo case WNM_BSS_TRANS_MGMT_REQ:
1970f05cddf9SRui Paulo ieee802_11_rx_bss_trans_mgmt_req(wpa_s, pos, end,
19715b9c547cSRui Paulo !(mgmt->da[0] & 0x01));
1972f05cddf9SRui Paulo break;
1973f05cddf9SRui Paulo case WNM_SLEEP_MODE_RESP:
19745b9c547cSRui Paulo ieee802_11_rx_wnmsleep_resp(wpa_s, pos, end - pos);
19755b9c547cSRui Paulo break;
19765b9c547cSRui Paulo case WNM_NOTIFICATION_REQ:
19775b9c547cSRui Paulo ieee802_11_rx_wnm_notif_req(wpa_s, mgmt->sa, pos, end - pos);
1978f05cddf9SRui Paulo break;
197985732ac8SCy Schubert case WNM_COLLOCATED_INTERFERENCE_REQ:
198085732ac8SCy Schubert ieee802_11_rx_wnm_coloc_intf_req(wpa_s, mgmt->sa, pos,
198185732ac8SCy Schubert end - pos);
198285732ac8SCy Schubert break;
1983f05cddf9SRui Paulo default:
19845b9c547cSRui Paulo wpa_printf(MSG_ERROR, "WNM: Unknown request");
1985f05cddf9SRui Paulo break;
1986f05cddf9SRui Paulo }
1987f05cddf9SRui Paulo }
198885732ac8SCy Schubert
198985732ac8SCy Schubert
wnm_send_coloc_intf_report(struct wpa_supplicant * wpa_s,u8 dialog_token,const struct wpabuf * elems)199085732ac8SCy Schubert int wnm_send_coloc_intf_report(struct wpa_supplicant *wpa_s, u8 dialog_token,
199185732ac8SCy Schubert const struct wpabuf *elems)
199285732ac8SCy Schubert {
199385732ac8SCy Schubert struct wpabuf *buf;
199485732ac8SCy Schubert int ret;
199585732ac8SCy Schubert
199685732ac8SCy Schubert if (wpa_s->wpa_state < WPA_ASSOCIATED || !elems)
199785732ac8SCy Schubert return -1;
199885732ac8SCy Schubert
199985732ac8SCy Schubert wpa_printf(MSG_DEBUG, "WNM: Send Collocated Interference Report to "
200085732ac8SCy Schubert MACSTR " (dialog token %u)",
200185732ac8SCy Schubert MAC2STR(wpa_s->bssid), dialog_token);
200285732ac8SCy Schubert
200385732ac8SCy Schubert buf = wpabuf_alloc(3 + wpabuf_len(elems));
200485732ac8SCy Schubert if (!buf)
200585732ac8SCy Schubert return -1;
200685732ac8SCy Schubert
200785732ac8SCy Schubert wpabuf_put_u8(buf, WLAN_ACTION_WNM);
200885732ac8SCy Schubert wpabuf_put_u8(buf, WNM_COLLOCATED_INTERFERENCE_REPORT);
200985732ac8SCy Schubert wpabuf_put_u8(buf, dialog_token);
201085732ac8SCy Schubert wpabuf_put_buf(buf, elems);
201185732ac8SCy Schubert
201285732ac8SCy Schubert ret = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
201385732ac8SCy Schubert wpa_s->own_addr, wpa_s->bssid,
201485732ac8SCy Schubert wpabuf_head_u8(buf), wpabuf_len(buf), 0);
201585732ac8SCy Schubert wpabuf_free(buf);
201685732ac8SCy Schubert return ret;
201785732ac8SCy Schubert }
201885732ac8SCy Schubert
201985732ac8SCy Schubert
wnm_set_coloc_intf_elems(struct wpa_supplicant * wpa_s,struct wpabuf * elems)202085732ac8SCy Schubert void wnm_set_coloc_intf_elems(struct wpa_supplicant *wpa_s,
202185732ac8SCy Schubert struct wpabuf *elems)
202285732ac8SCy Schubert {
202385732ac8SCy Schubert if (elems && wpabuf_len(elems) == 0) {
202485732ac8SCy Schubert wpabuf_free(elems);
202585732ac8SCy Schubert elems = NULL;
202685732ac8SCy Schubert }
202785732ac8SCy Schubert
2028*a90b9d01SCy Schubert /* NOTE: The elements are not stored as they are only send out once */
2029*a90b9d01SCy Schubert
2030*a90b9d01SCy Schubert if (wpa_s->conf->coloc_intf_reporting && elems &&
203185732ac8SCy Schubert wpa_s->coloc_intf_dialog_token &&
203285732ac8SCy Schubert (wpa_s->coloc_intf_auto_report == 1 ||
203385732ac8SCy Schubert wpa_s->coloc_intf_auto_report == 3)) {
203485732ac8SCy Schubert /* TODO: Check that there has not been less than
203585732ac8SCy Schubert * wpa_s->coloc_intf_timeout * 200 TU from the last report.
203685732ac8SCy Schubert */
203785732ac8SCy Schubert wnm_send_coloc_intf_report(wpa_s,
203885732ac8SCy Schubert wpa_s->coloc_intf_dialog_token,
2039*a90b9d01SCy Schubert elems);
204085732ac8SCy Schubert }
2041*a90b9d01SCy Schubert
2042*a90b9d01SCy Schubert wpabuf_free(elems);
204385732ac8SCy Schubert }
204485732ac8SCy Schubert
204585732ac8SCy Schubert
wnm_clear_coloc_intf_reporting(struct wpa_supplicant * wpa_s)204685732ac8SCy Schubert void wnm_clear_coloc_intf_reporting(struct wpa_supplicant *wpa_s)
204785732ac8SCy Schubert {
204885732ac8SCy Schubert wpa_s->coloc_intf_dialog_token = 0;
204985732ac8SCy Schubert wpa_s->coloc_intf_auto_report = 0;
2050*a90b9d01SCy Schubert }
2051*a90b9d01SCy Schubert
2052*a90b9d01SCy Schubert
wnm_is_bss_excluded(struct wpa_supplicant * wpa_s,struct wpa_bss * bss)2053*a90b9d01SCy Schubert bool wnm_is_bss_excluded(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
2054*a90b9d01SCy Schubert {
2055*a90b9d01SCy Schubert if (!(wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT))
2056*a90b9d01SCy Schubert return false;
2057*a90b9d01SCy Schubert
2058*a90b9d01SCy Schubert /*
2059*a90b9d01SCy Schubert * In case disassociation imminent is set, do no try to use a BSS to
2060*a90b9d01SCy Schubert * which we are connected.
2061*a90b9d01SCy Schubert */
2062*a90b9d01SCy Schubert if (wpa_s->wnm_link_removal ||
2063*a90b9d01SCy Schubert !(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) ||
2064*a90b9d01SCy Schubert is_zero_ether_addr(bss->mld_addr)) {
2065*a90b9d01SCy Schubert if (ether_addr_equal(bss->bssid, wpa_s->wnm_dissoc_addr))
2066*a90b9d01SCy Schubert return true;
2067*a90b9d01SCy Schubert } else {
2068*a90b9d01SCy Schubert if (ether_addr_equal(bss->mld_addr, wpa_s->wnm_dissoc_addr))
2069*a90b9d01SCy Schubert return true;
2070*a90b9d01SCy Schubert }
2071*a90b9d01SCy Schubert
2072*a90b9d01SCy Schubert return false;
207385732ac8SCy Schubert }
2074