1 /*
2 * wpa_supplicant - WNM
3 * Copyright (c) 2011-2013, Qualcomm Atheros, Inc.
4 *
5 * This software may be distributed under the terms of the BSD license.
6 * See README for more details.
7 */
8
9 #include "utils/includes.h"
10
11 #include "utils/common.h"
12 #include "common/ieee802_11_defs.h"
13 #include "common/ieee802_11_common.h"
14 #include "rsn_supp/wpa.h"
15 #include "wpa_supplicant_i.h"
16 #include "drivers/driver.h"
17 #include "scan.h"
18 #include "bss.h"
19 #include "wnm_sta.h"
20
21 #define MAX_TFS_IE_LEN 1024
22 #define WNM_MAX_NEIGHBOR_REPORT 10
23
24 #define WNM_SCAN_RESULT_AGE 2 /* 2 seconds */
25
wnm_deallocate_memory(struct wpa_supplicant * wpa_s)26 void wnm_deallocate_memory(struct wpa_supplicant *wpa_s)
27 {
28 int i;
29
30 for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) {
31 os_free(wpa_s->wnm_neighbor_report_elements[i].meas_pilot);
32 os_free(wpa_s->wnm_neighbor_report_elements[i].mul_bssid);
33 }
34
35 wpa_s->wnm_num_neighbor_report = 0;
36 os_free(wpa_s->wnm_neighbor_report_elements);
37 wpa_s->wnm_neighbor_report_elements = NULL;
38 }
39
40
wnm_parse_neighbor_report_elem(struct neighbor_report * rep,u8 id,u8 elen,const u8 * pos)41 static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep,
42 u8 id, u8 elen, const u8 *pos)
43 {
44 switch (id) {
45 case WNM_NEIGHBOR_TSF:
46 if (elen < 2 + 2) {
47 wpa_printf(MSG_DEBUG, "WNM: Too short TSF");
48 break;
49 }
50 rep->tsf_offset = WPA_GET_LE16(pos);
51 rep->beacon_int = WPA_GET_LE16(pos + 2);
52 rep->tsf_present = 1;
53 break;
54 case WNM_NEIGHBOR_CONDENSED_COUNTRY_STRING:
55 if (elen < 2) {
56 wpa_printf(MSG_DEBUG, "WNM: Too short condensed "
57 "country string");
58 break;
59 }
60 os_memcpy(rep->country, pos, 2);
61 rep->country_present = 1;
62 break;
63 case WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE:
64 if (elen < 1) {
65 wpa_printf(MSG_DEBUG, "WNM: Too short BSS transition "
66 "candidate");
67 break;
68 }
69 rep->preference = pos[0];
70 rep->preference_present = 1;
71 break;
72 case WNM_NEIGHBOR_BSS_TERMINATION_DURATION:
73 if (elen < 10) {
74 wpa_printf(MSG_DEBUG,
75 "WNM: Too short BSS termination duration");
76 break;
77 }
78 rep->bss_term_tsf = WPA_GET_LE64(pos);
79 rep->bss_term_dur = WPA_GET_LE16(pos + 8);
80 rep->bss_term_present = 1;
81 break;
82 case WNM_NEIGHBOR_BEARING:
83 if (elen < 8) {
84 wpa_printf(MSG_DEBUG, "WNM: Too short neighbor "
85 "bearing");
86 break;
87 }
88 rep->bearing = WPA_GET_LE16(pos);
89 rep->distance = WPA_GET_LE32(pos + 2);
90 rep->rel_height = WPA_GET_LE16(pos + 2 + 4);
91 rep->bearing_present = 1;
92 break;
93 case WNM_NEIGHBOR_MEASUREMENT_PILOT:
94 if (elen < 1) {
95 wpa_printf(MSG_DEBUG, "WNM: Too short measurement "
96 "pilot");
97 break;
98 }
99 os_free(rep->meas_pilot);
100 rep->meas_pilot = os_zalloc(sizeof(struct measurement_pilot));
101 if (rep->meas_pilot == NULL)
102 break;
103 rep->meas_pilot->measurement_pilot = pos[0];
104 rep->meas_pilot->subelem_len = elen - 1;
105 os_memcpy(rep->meas_pilot->subelems, pos + 1, elen - 1);
106 break;
107 case WNM_NEIGHBOR_RRM_ENABLED_CAPABILITIES:
108 if (elen < 5) {
109 wpa_printf(MSG_DEBUG, "WNM: Too short RRM enabled "
110 "capabilities");
111 break;
112 }
113 os_memcpy(rep->rm_capab, pos, 5);
114 rep->rm_capab_present = 1;
115 break;
116 case WNM_NEIGHBOR_MULTIPLE_BSSID:
117 if (elen < 1) {
118 wpa_printf(MSG_DEBUG, "WNM: Too short multiple BSSID");
119 break;
120 }
121 os_free(rep->mul_bssid);
122 rep->mul_bssid = os_zalloc(sizeof(struct multiple_bssid));
123 if (rep->mul_bssid == NULL)
124 break;
125 rep->mul_bssid->max_bssid_indicator = pos[0];
126 rep->mul_bssid->subelem_len = elen - 1;
127 os_memcpy(rep->mul_bssid->subelems, pos + 1, elen - 1);
128 break;
129 }
130 }
131
wnm_parse_neighbor_report(struct wpa_supplicant * wpa_s,const u8 * pos,u8 len,struct neighbor_report * rep)132 static void wnm_parse_neighbor_report(struct wpa_supplicant *wpa_s,
133 const u8 *pos, u8 len,
134 struct neighbor_report *rep)
135 {
136 u8 left = len;
137
138 if (left < 13) {
139 wpa_printf(MSG_DEBUG, "WNM: Too short neighbor report");
140 return;
141 }
142
143 os_memcpy(rep->bssid, pos, ETH_ALEN);
144 rep->bssid_info = WPA_GET_LE32(pos + ETH_ALEN);
145 rep->regulatory_class = *(pos + 10);
146 rep->channel_number = *(pos + 11);
147 rep->phy_type = *(pos + 12);
148
149 pos += 13;
150 left -= 13;
151
152 while (left >= 2) {
153 u8 id, elen;
154
155 id = *pos++;
156 elen = *pos++;
157 wpa_printf(MSG_DEBUG, "WNM: Subelement id=%u len=%u", id, elen);
158 left -= 2;
159 if (elen > left) {
160 wpa_printf(MSG_DEBUG,
161 "WNM: Truncated neighbor report subelement");
162 break;
163 }
164 wnm_parse_neighbor_report_elem(rep, id, elen, pos);
165 left -= elen;
166 pos += elen;
167 }
168 }
169
170
wnm_clear_acceptable(struct wpa_supplicant * wpa_s)171 static void wnm_clear_acceptable(struct wpa_supplicant *wpa_s)
172 {
173 unsigned int i;
174
175 for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++)
176 wpa_s->wnm_neighbor_report_elements[i].acceptable = 0;
177 }
178
179
get_first_acceptable(struct wpa_supplicant * wpa_s)180 static struct wpa_bss * get_first_acceptable(struct wpa_supplicant *wpa_s)
181 {
182 unsigned int i;
183 struct neighbor_report *nei;
184
185 for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) {
186 nei = &wpa_s->wnm_neighbor_report_elements[i];
187 if (nei->acceptable)
188 return wpa_bss_get_bssid(wpa_s, nei->bssid);
189 }
190
191 return NULL;
192 }
193
194
195 #ifdef CONFIG_MBO
196 static struct wpa_bss *
get_mbo_transition_candidate(struct wpa_supplicant * wpa_s,enum mbo_transition_reject_reason * reason)197 get_mbo_transition_candidate(struct wpa_supplicant *wpa_s,
198 enum mbo_transition_reject_reason *reason)
199 {
200 struct wpa_bss *target = NULL;
201 struct wpa_bss_trans_info params;
202 struct wpa_bss_candidate_info *info = NULL;
203 struct neighbor_report *nei = wpa_s->wnm_neighbor_report_elements;
204 u8 *first_candidate_bssid = NULL, *pos;
205 unsigned int i;
206
207 params.mbo_transition_reason = wpa_s->wnm_mbo_transition_reason;
208 params.n_candidates = 0;
209 params.bssid = os_calloc(wpa_s->wnm_num_neighbor_report, ETH_ALEN);
210 if (!params.bssid)
211 return NULL;
212
213 pos = params.bssid;
214 for (i = 0; i < wpa_s->wnm_num_neighbor_report; nei++, i++) {
215 if (nei->is_first)
216 first_candidate_bssid = nei->bssid;
217 if (!nei->acceptable)
218 continue;
219 os_memcpy(pos, nei->bssid, ETH_ALEN);
220 pos += ETH_ALEN;
221 params.n_candidates++;
222 }
223
224 if (!params.n_candidates)
225 goto end;
226
227 #ifndef ESP_SUPPLICANT
228 info = wpa_drv_get_bss_trans_status(wpa_s, ¶ms);
229 #endif
230 if (!info) {
231 /* If failed to get candidate BSS transition status from driver,
232 * get the first acceptable candidate from wpa_supplicant.
233 */
234 target = wpa_bss_get_bssid(wpa_s, params.bssid);
235 goto end;
236 }
237
238 /* Get the first acceptable candidate from driver */
239 for (i = 0; i < info->num; i++) {
240 if (info->candidates[i].is_accept) {
241 target = wpa_bss_get_bssid(wpa_s,
242 info->candidates[i].bssid);
243 goto end;
244 }
245 }
246
247 /* If Disassociation Imminent is set and driver rejects all the
248 * candidate select first acceptable candidate which has
249 * rssi > disassoc_imminent_rssi_threshold
250 */
251 if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT) {
252 for (i = 0; i < info->num; i++) {
253 target = wpa_bss_get_bssid(wpa_s,
254 info->candidates[i].bssid);
255 #ifndef ESP_SUPPLICANT
256 if (target &&
257 (target->level <
258 wpa_s->conf->disassoc_imminent_rssi_threshold))
259 continue;
260 #else
261 if (target)
262 continue;
263 #endif
264 goto end;
265 }
266 }
267
268 /* While sending BTM reject use reason code of the first candidate
269 * received in BTM request frame
270 */
271 if (reason) {
272 for (i = 0; i < info->num; i++) {
273 if (first_candidate_bssid &&
274 os_memcmp(first_candidate_bssid,
275 info->candidates[i].bssid, ETH_ALEN) == 0)
276 {
277 *reason = info->candidates[i].reject_reason;
278 break;
279 }
280 }
281 }
282
283 target = NULL;
284
285 end:
286 os_free(params.bssid);
287 if (info) {
288 os_free(info->candidates);
289 os_free(info);
290 }
291 return target;
292 }
293 #endif /* CONFIG_MBO */
294
295
296
297 /* basic function to match candidate profile with current bss */
wpa_scan_res_match(struct wpa_supplicant * wpa_s,struct wpa_bss * current_bss,struct wpa_bss * target_bss)298 bool wpa_scan_res_match(struct wpa_supplicant *wpa_s,
299 struct wpa_bss *current_bss,
300 struct wpa_bss *target_bss)
301 {
302 if (current_bss->ssid_len != target_bss->ssid_len) {
303 wpa_printf(MSG_DEBUG, "WNM: ssid didn't match");
304 return false;
305 }
306 if (os_memcmp(current_bss->ssid, target_bss->ssid, current_bss->ssid_len) != 0) {
307 wpa_printf(MSG_DEBUG, "WNM: ssid didn't match");
308 return false;
309 }
310
311 /* TODO security Match */
312
313 return true;
314 }
315
316
317 static struct wpa_bss *
compare_scan_neighbor_results(struct wpa_supplicant * wpa_s,os_time_t age_secs,enum mbo_transition_reject_reason * reason)318 compare_scan_neighbor_results(struct wpa_supplicant *wpa_s, os_time_t age_secs,
319 enum mbo_transition_reject_reason *reason)
320 {
321 u8 i;
322 struct wpa_bss *bss = wpa_s->current_bss;
323 struct wpa_bss *target;
324
325 if (!bss)
326 return NULL;
327
328 wnm_clear_acceptable(wpa_s);
329
330 for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) {
331 struct neighbor_report *nei;
332
333 nei = &wpa_s->wnm_neighbor_report_elements[i];
334 if (nei->preference_present && nei->preference == 0) {
335 wpa_printf(MSG_DEBUG, "Skip excluded BSS " MACSTR,
336 MAC2STR(nei->bssid));
337 continue;
338 }
339
340 target = wpa_bss_get_bssid(wpa_s, nei->bssid);
341 if (!target) {
342 wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR
343 " (pref %d) not found in scan results",
344 MAC2STR(nei->bssid),
345 nei->preference_present ? nei->preference :
346 -1);
347 continue;
348 }
349
350 if (age_secs) {
351 struct os_reltime now;
352
353 if (os_get_reltime(&now) == 0 &&
354 os_time_expired(&now, &target->last_update,
355 age_secs)) {
356 wpa_printf(MSG_DEBUG,
357 "Candidate BSS is more than %ld seconds old",
358 age_secs);
359 continue;
360 }
361 }
362
363 if (bss->ssid_len != target->ssid_len ||
364 os_memcmp(bss->ssid, target->ssid, bss->ssid_len) != 0) {
365 /*
366 * TODO: Could consider allowing transition to another
367 * ESS if PMF was enabled for the association.
368 */
369 wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR
370 " (pref %d) in different ESS",
371 MAC2STR(nei->bssid),
372 nei->preference_present ? nei->preference :
373 -1);
374 continue;
375 }
376
377 if (wpa_s->current_bss &&
378 !wpa_scan_res_match(wpa_s, wpa_s->current_bss, target)) {
379 wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR
380 " (pref %d) does not match the current network profile",
381 MAC2STR(nei->bssid),
382 nei->preference_present ? nei->preference :
383 -1);
384 continue;
385 }
386
387 if (target->level < bss->level && target->level < -80) {
388 wpa_printf(MSG_DEBUG, "Candidate BSS " MACSTR
389 " (pref %d) does not have sufficient signal level (%d)",
390 MAC2STR(nei->bssid),
391 nei->preference_present ? nei->preference :
392 -1,
393 target->level);
394 continue;
395 }
396
397 nei->acceptable = 1;
398 }
399
400 #ifdef CONFIG_MBO
401 if (wpa_s->wnm_mbo_trans_reason_present)
402 target = get_mbo_transition_candidate(wpa_s, reason);
403 else
404 target = get_first_acceptable(wpa_s);
405 #else /* CONFIG_MBO */
406 target = get_first_acceptable(wpa_s);
407 #endif /* CONFIG_MBO */
408
409 if (target) {
410 wpa_printf(MSG_DEBUG,
411 "WNM: Found an acceptable preferred transition candidate BSS "
412 MACSTR " (RSSI %d)",
413 MAC2STR(target->bssid), target->level);
414 }
415
416 return target;
417 }
418
419
wpa_bss_ies_eq(struct wpa_bss * a,struct wpa_bss * b,u8 eid)420 static int wpa_bss_ies_eq(struct wpa_bss *a, struct wpa_bss *b, u8 eid)
421 {
422 const u8 *ie_a, *ie_b;
423
424 if (!a || !b)
425 return 0;
426
427 ie_a = wpa_bss_get_ie(a, eid);
428 ie_b = wpa_bss_get_ie(b, eid);
429
430 if (!ie_a || !ie_b || ie_a[1] != ie_b[1])
431 return 0;
432
433 return os_memcmp(ie_a, ie_b, ie_a[1]) == 0;
434 }
435
436
wnm_get_bss_info(struct wpa_supplicant * wpa_s,struct wpa_bss * bss)437 static u32 wnm_get_bss_info(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
438 {
439 u32 info = 0;
440
441 info |= NEI_REP_BSSID_INFO_AP_UNKNOWN_REACH;
442
443 /*
444 * Leave the security and key scope bits unset to indicate that the
445 * security information is not available.
446 */
447
448 if (bss->caps & WLAN_CAPABILITY_SPECTRUM_MGMT)
449 info |= NEI_REP_BSSID_INFO_SPECTRUM_MGMT;
450 if (bss->caps & WLAN_CAPABILITY_QOS)
451 info |= NEI_REP_BSSID_INFO_QOS;
452 if (bss->caps & WLAN_CAPABILITY_APSD)
453 info |= NEI_REP_BSSID_INFO_APSD;
454 if (bss->caps & WLAN_CAPABILITY_RADIO_MEASUREMENT)
455 info |= NEI_REP_BSSID_INFO_RM;
456 if (bss->caps & WLAN_CAPABILITY_DELAYED_BLOCK_ACK)
457 info |= NEI_REP_BSSID_INFO_DELAYED_BA;
458 if (bss->caps & WLAN_CAPABILITY_IMM_BLOCK_ACK)
459 info |= NEI_REP_BSSID_INFO_IMM_BA;
460 if (wpa_bss_ies_eq(bss, wpa_s->current_bss, WLAN_EID_MOBILITY_DOMAIN))
461 info |= NEI_REP_BSSID_INFO_MOBILITY_DOMAIN;
462 if (wpa_bss_ies_eq(bss, wpa_s->current_bss, WLAN_EID_HT_CAP))
463 info |= NEI_REP_BSSID_INFO_HT;
464
465 return info;
466 }
467
468
wnm_add_nei_rep(struct wpabuf ** buf,const u8 * bssid,u32 bss_info,u8 op_class,u8 chan,u8 phy_type,u8 pref)469 static int wnm_add_nei_rep(struct wpabuf **buf, const u8 *bssid,
470 u32 bss_info, u8 op_class, u8 chan, u8 phy_type,
471 u8 pref)
472 {
473 if (wpabuf_len(*buf) + 18 >
474 IEEE80211_MAX_MMPDU_SIZE - IEEE80211_HDRLEN) {
475 wpa_printf(MSG_DEBUG,
476 "WNM: No room in frame for Neighbor Report element");
477 return -1;
478 }
479
480 if (wpabuf_resize(buf, 18) < 0) {
481 wpa_printf(MSG_DEBUG,
482 "WNM: Failed to allocate memory for Neighbor Report element");
483 return -1;
484 }
485
486 wpabuf_put_u8(*buf, WLAN_EID_NEIGHBOR_REPORT);
487 /* length: 13 for basic neighbor report + 3 for preference subelement */
488 wpabuf_put_u8(*buf, 16);
489 wpabuf_put_data(*buf, bssid, ETH_ALEN);
490 wpabuf_put_le32(*buf, bss_info);
491 wpabuf_put_u8(*buf, op_class);
492 wpabuf_put_u8(*buf, chan);
493 wpabuf_put_u8(*buf, phy_type);
494 wpabuf_put_u8(*buf, WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE);
495 wpabuf_put_u8(*buf, 1);
496 wpabuf_put_u8(*buf, pref);
497 return 0;
498 }
499
wnm_nei_rep_add_bss(struct wpa_supplicant * wpa_s,struct wpa_bss * bss,struct wpabuf ** buf,u8 pref)500 static int wnm_nei_rep_add_bss(struct wpa_supplicant *wpa_s,
501 struct wpa_bss *bss, struct wpabuf **buf,
502 u8 pref)
503 {
504 const u8 *ie;
505 u8 op_class;
506 int sec_chan = 0;
507 enum phy_type phy_type;
508 u32 info;
509 struct ieee80211_ht_operation *ht_oper = NULL;
510
511 ie = wpa_bss_get_ie(bss, WLAN_EID_HT_OPERATION);
512 if (ie && ie[1] >= 2) {
513 ht_oper = (struct ieee80211_ht_operation *) (ie + 2);
514
515 if (ht_oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE)
516 sec_chan = 1;
517 else if (ht_oper->ht_param &
518 HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW)
519 sec_chan = -1;
520 }
521
522 op_class = get_operating_class(bss->channel, sec_chan);
523
524 phy_type = (sec_chan != 0) ? PHY_TYPE_HT : PHY_TYPE_ERP;
525
526 info = wnm_get_bss_info(wpa_s, bss);
527
528 return wnm_add_nei_rep(buf, bss->bssid, info, op_class, bss->channel, phy_type,
529 pref);
530 }
531
532
wnm_add_cand_list(struct wpa_supplicant * wpa_s,struct wpabuf ** buf)533 static void wnm_add_cand_list(struct wpa_supplicant *wpa_s, struct wpabuf **buf)
534 {
535 unsigned int i, pref = 255;
536 struct os_reltime now;
537
538 if (!wpa_s->current_bss)
539 return;
540
541 /*
542 * TODO: Define when scan results are no longer valid for the candidate
543 * list.
544 */
545 os_get_reltime(&now);
546 if (os_time_expired(&now, &wpa_s->last_scan, 10))
547 return;
548
549 wpa_printf(MSG_DEBUG,
550 "WNM: Add candidate list to BSS Transition Management Response frame");
551 for (i = 0; i < wpa_s->last_scan_res_used && pref; i++) {
552 struct wpa_bss *bss = wpa_s->last_scan_res[i];
553 int res;
554
555 if (wpa_scan_res_match(wpa_s, wpa_s->current_bss, bss)) {
556 res = wnm_nei_rep_add_bss(wpa_s, bss, buf, pref--);
557 if (res == -2)
558 continue; /* could not build entry for BSS */
559 if (res < 0)
560 break; /* no more room for candidates */
561 if (pref == 1)
562 break;
563 }
564 }
565
566 wpa_hexdump_buf(MSG_DEBUG,
567 "WNM: BSS Transition Management Response candidate list",
568 *buf);
569 }
570
571
572 #define BTM_RESP_MIN_SIZE 5 + ETH_ALEN
573
wnm_send_bss_transition_mgmt_resp(struct wpa_supplicant * wpa_s,u8 dialog_token,enum bss_trans_mgmt_status_code status,enum mbo_transition_reject_reason reason,u8 delay,const u8 * target_bssid)574 static void wnm_send_bss_transition_mgmt_resp(
575 struct wpa_supplicant *wpa_s, u8 dialog_token,
576 enum bss_trans_mgmt_status_code status,
577 enum mbo_transition_reject_reason reason,
578 u8 delay, const u8 *target_bssid)
579 {
580 struct wpabuf *buf;
581 int res;
582
583 if (!wpa_s->current_bss) {
584 wpa_printf(MSG_DEBUG,
585 "WNM: Current BSS not known - drop response");
586 return;
587 }
588 wpa_printf(MSG_DEBUG,
589 "WNM: Send BSS Transition Management Response to " MACSTR
590 " dialog_token=%u status=%u reason=%u delay=%d",
591 MAC2STR(wpa_s->current_bss->bssid), dialog_token, status, reason, delay);
592
593 buf = wpabuf_alloc(BTM_RESP_MIN_SIZE);
594 if (!buf) {
595 wpa_printf(MSG_DEBUG,
596 "WNM: Failed to allocate memory for BTM response");
597 return;
598 }
599
600 wpabuf_put_u8(buf, WLAN_ACTION_WNM);
601 wpabuf_put_u8(buf, WNM_BSS_TRANS_MGMT_RESP);
602 wpabuf_put_u8(buf, dialog_token);
603 wpabuf_put_u8(buf, status);
604 wpabuf_put_u8(buf, delay);
605 if (target_bssid) {
606 wpabuf_put_data(buf, target_bssid, ETH_ALEN);
607 } else if (status == WNM_BSS_TM_ACCEPT) {
608 /*
609 * P802.11-REVmc clarifies that the Target BSSID field is always
610 * present when status code is zero, so use a fake value here if
611 * no BSSID is yet known.
612 */
613 wpabuf_put_data(buf, "\0\0\0\0\0\0", ETH_ALEN);
614 }
615
616 if (status == WNM_BSS_TM_ACCEPT)
617 wnm_add_cand_list(wpa_s, &buf);
618
619 #ifdef CONFIG_MBO
620 if (status != WNM_BSS_TM_ACCEPT &&
621 wpa_bss_get_vendor_ie(wpa_s->current_bss, MBO_IE_VENDOR_TYPE)) {
622 u8 mbo[10];
623 size_t ret;
624
625 ret = wpas_mbo_ie_bss_trans_reject(wpa_s, mbo, sizeof(mbo),
626 reason);
627 if (ret) {
628 if (wpabuf_resize(&buf, ret) < 0) {
629 wpabuf_free(buf);
630 wpa_printf(MSG_DEBUG,
631 "WNM: Failed to allocate memory for MBO IE");
632 return;
633 }
634
635 wpabuf_put_data(buf, mbo, ret);
636 }
637 }
638 #endif /* CONFIG_MBO */
639
640 res = wpa_drv_send_action(wpa_s, 0, 0,
641 wpabuf_head_u8(buf), wpabuf_len(buf), 0);
642 if (res < 0) {
643 wpa_printf(MSG_DEBUG,
644 "WNM: Failed to send BSS Transition Management Response");
645 }
646
647 wpabuf_free(buf);
648 }
649
wnm_bss_tm_connect(struct wpa_supplicant * wpa_s,struct wpa_bss * bss,char * ssid,int after_new_scan)650 void wnm_bss_tm_connect(struct wpa_supplicant *wpa_s,
651 struct wpa_bss *bss, char *ssid,
652 int after_new_scan)
653 {
654 wpa_printf(MSG_DEBUG,
655 "WNM: Transition to BSS " MACSTR
656 " based on BSS Transition Management Request after_new_scan=%d)",
657 MAC2STR(bss->bssid), after_new_scan);
658
659 /* Send the BSS Management Response - Accept */
660 if (wpa_s->wnm_reply) {
661 wpa_s->wnm_reply = 0;
662 wpa_printf(MSG_DEBUG,
663 "WNM: Sending successful BSS Transition Management Response");
664 wnm_send_bss_transition_mgmt_resp(
665 wpa_s, wpa_s->wnm_dialog_token, WNM_BSS_TM_ACCEPT,
666 MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0,
667 bss->bssid);
668 }
669
670 if (bss == wpa_s->current_bss) {
671 wpa_printf(MSG_DEBUG,
672 "WNM: Already associated with the preferred candidate");
673 wnm_deallocate_memory(wpa_s);
674 return;
675 }
676
677 wpa_printf(MSG_DEBUG, "WNM: Issuing connect");
678
679 wpa_supplicant_connect(wpa_s, bss, ssid);
680 wnm_deallocate_memory(wpa_s);
681 }
682
683
wnm_scan_process(struct wpa_supplicant * wpa_s,int reply_on_fail)684 int wnm_scan_process(struct wpa_supplicant *wpa_s, int reply_on_fail)
685 {
686 struct wpa_bss *bss;
687 enum bss_trans_mgmt_status_code status = WNM_BSS_TM_REJECT_UNSPECIFIED;
688 enum mbo_transition_reject_reason reason =
689 MBO_TRANSITION_REJECT_REASON_UNSPECIFIED;
690
691 if (!wpa_s->wnm_neighbor_report_elements)
692 return 0;
693
694 wpa_dbg(wpa_s, MSG_DEBUG,
695 "WNM: Process scan results for BSS Transition Management");
696 if (os_reltime_before(&wpa_s->wnm_cand_valid_until,
697 &wpa_s->scan_trigger_time)) {
698 wpa_printf(MSG_DEBUG, "WNM: Previously stored BSS transition candidate list is not valid anymore - drop it");
699 wnm_deallocate_memory(wpa_s);
700 return 0;
701 }
702
703 /* Compare the Neighbor Report and scan results */
704 bss = compare_scan_neighbor_results(wpa_s, 0, &reason);
705 if (!bss) {
706 wpa_printf(MSG_DEBUG, "WNM: No BSS transition candidate match found");
707 status = WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES;
708 goto send_bss_resp_fail;
709 }
710
711 /* Associate to the network */
712 wnm_bss_tm_connect(wpa_s, bss, NULL, 1);
713 return 1;
714
715 send_bss_resp_fail:
716 if (!reply_on_fail)
717 return 0;
718
719 /* Send reject response for all the failures */
720
721 if (wpa_s->wnm_reply) {
722 wpa_s->wnm_reply = 0;
723 wnm_send_bss_transition_mgmt_resp(wpa_s,
724 wpa_s->wnm_dialog_token,
725 status, reason, 0, NULL);
726 }
727 wnm_deallocate_memory(wpa_s);
728
729 return 0;
730 }
731
732
cand_pref_compar(const void * a,const void * b)733 static int cand_pref_compar(const void *a, const void *b)
734 {
735 const struct neighbor_report *aa = a;
736 const struct neighbor_report *bb = b;
737
738 if (!aa->preference_present && !bb->preference_present)
739 return 0;
740 if (!aa->preference_present)
741 return 1;
742 if (!bb->preference_present)
743 return -1;
744 if (bb->preference > aa->preference)
745 return 1;
746 if (bb->preference < aa->preference)
747 return -1;
748 return 0;
749 }
750
751
wnm_sort_cand_list(struct wpa_supplicant * wpa_s)752 static void wnm_sort_cand_list(struct wpa_supplicant *wpa_s)
753 {
754 if (!wpa_s->wnm_neighbor_report_elements)
755 return;
756 qsort(wpa_s->wnm_neighbor_report_elements,
757 wpa_s->wnm_num_neighbor_report, sizeof(struct neighbor_report),
758 cand_pref_compar);
759 }
760
761
wnm_dump_cand_list(struct wpa_supplicant * wpa_s)762 static void wnm_dump_cand_list(struct wpa_supplicant *wpa_s)
763 {
764 #ifdef DEBUG_PRINT
765 unsigned int i;
766
767 wpa_printf(MSG_DEBUG, "WNM: BSS Transition Candidate List");
768 if (!wpa_s->wnm_neighbor_report_elements)
769 return;
770 for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) {
771 struct neighbor_report *nei;
772
773 nei = &wpa_s->wnm_neighbor_report_elements[i];
774 wpa_printf(MSG_DEBUG, "%u: " MACSTR
775 " info=0x%x op_class=%u chan=%u phy=%u pref=%d",
776 i, MAC2STR(nei->bssid), nei->bssid_info,
777 nei->regulatory_class,
778 nei->channel_number, nei->phy_type,
779 nei->preference_present ? nei->preference : -1);
780 }
781 #endif
782 }
783
wnm_set_scan_freqs(struct wpa_supplicant * wpa_s)784 static void wnm_set_scan_freqs(struct wpa_supplicant *wpa_s)
785 {
786 unsigned int i;
787 int num_chan = 0;
788 u8 chan = 0;
789
790 wpa_s->next_scan_chan = 0;
791 if (!wpa_s->wnm_neighbor_report_elements)
792 return;
793
794 for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) {
795 struct neighbor_report *nei;
796
797 nei = &wpa_s->wnm_neighbor_report_elements[i];
798 if (nei->channel_number <= 0) {
799 wpa_printf(MSG_DEBUG,
800 "WNM: Unknown neighbor operating channel_number for "
801 MACSTR " - scan all channels",
802 MAC2STR(nei->bssid));
803 return;
804 }
805 if (nei->channel_number != chan) {
806 chan = nei->channel_number;
807 num_chan++;
808 }
809 }
810
811 if (num_chan == 1) {
812 wpa_s->next_scan_chan = chan;
813 }
814 }
815
wnm_fetch_scan_results(struct wpa_supplicant * wpa_s)816 static int wnm_fetch_scan_results(struct wpa_supplicant *wpa_s)
817 {
818 /* ESP doesn't support this */
819 return 0;
820 }
821
ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant * wpa_s,const u8 * pos,const u8 * end,int reply)822 static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s,
823 const u8 *pos, const u8 *end,
824 int reply)
825 {
826 unsigned int beacon_int;
827 u8 valid_int;
828 #ifdef CONFIG_MBO
829 const u8 *vendor;
830 #endif /* CONFIG_MBO */
831 #ifdef ESP_SUPPLICANT
832 bool scan_required = false;
833 #endif
834
835 if (wpa_s->disable_mbo_oce || wpa_s->disable_btm)
836 return;
837
838 if (end - pos < 5)
839 return;
840
841 #ifdef CONFIG_MBO
842 wpa_s->wnm_mbo_trans_reason_present = 0;
843 wpa_s->wnm_mbo_transition_reason = 0;
844 #endif /* CONFIG_MBO */
845
846 if (wpa_s->current_bss)
847 beacon_int = wpa_s->current_bss->beacon_int;
848 else
849 beacon_int = 100; /* best guess */
850
851 wpa_s->wnm_dialog_token = pos[0];
852 wpa_s->wnm_mode = pos[1];
853 wpa_s->wnm_dissoc_timer = WPA_GET_LE16(pos + 2);
854 valid_int = pos[4];
855 wpa_s->wnm_reply = reply;
856
857 wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Request: "
858 "dialog_token=%u request_mode=0x%x "
859 "disassoc_timer=%u validity_interval=%u",
860 wpa_s->wnm_dialog_token, wpa_s->wnm_mode,
861 wpa_s->wnm_dissoc_timer, valid_int);
862
863 #if defined(CONFIG_MBO) && defined(CONFIG_TESTING_OPTIONS)
864 if (wpa_s->reject_btm_req_reason) {
865 wpa_printf(MSG_INFO,
866 "WNM: Testing - reject BSS Transition Management Request: reject_btm_req_reason=%d",
867 wpa_s->reject_btm_req_reason);
868 wnm_send_bss_transition_mgmt_resp(
869 wpa_s, wpa_s->wnm_dialog_token,
870 wpa_s->reject_btm_req_reason,
871 MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0, NULL);
872 return;
873 }
874 #endif /* CONFIG_MBO && CONFIG_TESTING_OPTIONS */
875
876 pos += 5;
877
878 if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) {
879 if (end - pos < 12) {
880 wpa_printf(MSG_DEBUG, "WNM: Too short BSS TM Request");
881 return;
882 }
883 os_memcpy(wpa_s->wnm_bss_termination_duration, pos, 12);
884 pos += 12; /* BSS Termination Duration */
885 }
886
887 if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT) {
888 char url[256];
889
890 if (end - pos < 1 || 1 + pos[0] > end - pos) {
891 wpa_printf(MSG_DEBUG, "WNM: Invalid BSS Transition "
892 "Management Request (URL)");
893 return;
894 }
895 os_memcpy(url, pos + 1, pos[0]);
896 url[pos[0]] = '\0';
897 pos += 1 + pos[0];
898
899 wpa_msg(wpa_s, MSG_DEBUG, "ESS_DISASSOC_IMMINENT %u %s",
900 wpa_s->wnm_dissoc_timer * beacon_int * 128 / 125, url);
901 }
902
903 if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT) {
904 wpa_msg(wpa_s, MSG_DEBUG, "WNM: Disassociation Imminent - "
905 "Disassociation Timer %u", wpa_s->wnm_dissoc_timer);
906 if (wpa_s->wnm_dissoc_timer) {
907 /* TODO: mark current BSS less preferred for
908 * selection */
909 #ifdef ESP_SUPPLICANT
910 os_memset(wpa_s->next_scan_bssid, 0, ETH_ALEN);
911 wpa_s->next_scan_chan = 0;
912 scan_required = true;
913 #else
914 wpa_printf(MSG_DEBUG, "Trying to find another BSS");
915 wpa_supplicant_req_scan(wpa_s, 0, 0);
916 #endif
917 }
918 }
919
920 #ifdef CONFIG_MBO
921 vendor = get_ie(pos, end - pos, WLAN_EID_VENDOR_SPECIFIC);
922 if (vendor)
923 wpas_mbo_ie_trans_req(wpa_s, vendor + 2, vendor[1]);
924 #endif /* CONFIG_MBO */
925
926 if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_PREF_CAND_LIST_INCLUDED) {
927 unsigned int valid_ms;
928
929 wpa_msg(wpa_s, MSG_DEBUG, "WNM: Preferred List Available");
930 wnm_deallocate_memory(wpa_s);
931 wpa_s->wnm_neighbor_report_elements = os_calloc(
932 WNM_MAX_NEIGHBOR_REPORT,
933 sizeof(struct neighbor_report));
934 if (wpa_s->wnm_neighbor_report_elements == NULL)
935 return;
936
937 while (end - pos >= 2 &&
938 wpa_s->wnm_num_neighbor_report < WNM_MAX_NEIGHBOR_REPORT)
939 {
940 u8 tag = *pos++;
941 u8 len = *pos++;
942
943 wpa_printf(MSG_DEBUG, "WNM: Neighbor report tag %u",
944 tag);
945 if (len > end - pos) {
946 wpa_printf(MSG_DEBUG, "WNM: Truncated request");
947 return;
948 }
949 if (tag == WLAN_EID_NEIGHBOR_REPORT) {
950 struct neighbor_report *rep;
951 rep = &wpa_s->wnm_neighbor_report_elements[
952 wpa_s->wnm_num_neighbor_report];
953 wnm_parse_neighbor_report(wpa_s, pos, len, rep);
954 wpa_s->wnm_num_neighbor_report++;
955 #ifdef CONFIG_MBO
956 if (wpa_s->wnm_mbo_trans_reason_present &&
957 wpa_s->wnm_num_neighbor_report == 1) {
958 rep->is_first = 1;
959 wpa_printf(MSG_DEBUG,
960 "WNM: First transition candidate is "
961 MACSTR, MAC2STR(rep->bssid));
962 }
963 #endif /* CONFIG_MBO */
964 }
965
966 pos += len;
967 }
968
969 if (!wpa_s->wnm_num_neighbor_report) {
970 wpa_printf(MSG_DEBUG,
971 "WNM: Candidate list included bit is set, but no candidates found");
972 wnm_send_bss_transition_mgmt_resp(
973 wpa_s, wpa_s->wnm_dialog_token,
974 WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES,
975 MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0,
976 NULL);
977 return;
978 }
979
980 wnm_sort_cand_list(wpa_s);
981 wnm_dump_cand_list(wpa_s);
982 valid_ms = valid_int * beacon_int * 128 / 125;
983 wpa_printf(MSG_DEBUG, "WNM: Candidate list valid for %u ms",
984 valid_ms);
985 os_get_reltime(&wpa_s->wnm_cand_valid_until);
986 wpa_s->wnm_cand_valid_until.sec += valid_ms / 1000;
987 wpa_s->wnm_cand_valid_until.usec += (valid_ms % 1000) * 1000;
988 wpa_s->wnm_cand_valid_until.sec +=
989 wpa_s->wnm_cand_valid_until.usec / 1000000;
990 wpa_s->wnm_cand_valid_until.usec %= 1000000;
991
992 /*
993 * Fetch the latest scan results from the kernel and check for
994 * candidates based on those results first. This can help in
995 * finding more up-to-date information should the driver has
996 * done some internal scanning operations after the last scan
997 * result update in wpa_supplicant.
998 */
999 if (wnm_fetch_scan_results(wpa_s) > 0)
1000 return;
1001
1002 /*
1003 * Try to use previously received scan results, if they are
1004 * recent enough to use for a connection.
1005 */
1006 if (wpa_s->last_scan_res_used > 0) {
1007 struct os_reltime now;
1008
1009 os_get_reltime(&now);
1010 if (!os_time_expired(&now, &wpa_s->last_scan, 10)) {
1011 wpa_printf(MSG_DEBUG,
1012 "WNM: Try to use recent scan results");
1013 if (wnm_scan_process(wpa_s, 0) > 0)
1014 return;
1015 wpa_printf(MSG_DEBUG,
1016 "WNM: No match in previous scan results - try a new scan");
1017 }
1018 }
1019 wnm_set_scan_freqs(wpa_s);
1020 if (wpa_s->wnm_num_neighbor_report == 1) {
1021 os_memcpy(wpa_s->next_scan_bssid,
1022 wpa_s->wnm_neighbor_report_elements[0].bssid,
1023 ETH_ALEN);
1024 wpa_printf(MSG_DEBUG,
1025 "WNM: Scan only for a specific BSSID since there is only a single candidate "
1026 MACSTR, MAC2STR(wpa_s->next_scan_bssid));
1027 }
1028 #ifdef ESP_SUPPLICANT
1029 scan_required = true;
1030 }
1031 if (scan_required) {
1032 wpa_printf(MSG_DEBUG, "Trying to find another BSS");
1033 #endif
1034 wpa_supplicant_req_scan(wpa_s, 0, 0);
1035 } else if (reply) {
1036 enum bss_trans_mgmt_status_code status;
1037 if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT)
1038 status = WNM_BSS_TM_ACCEPT;
1039 else {
1040 wpa_msg(wpa_s, MSG_DEBUG, "WNM: BSS Transition Management Request did not include candidates");
1041 status = WNM_BSS_TM_REJECT_UNSPECIFIED;
1042 }
1043 wpa_s->wnm_reply = 0;
1044 wnm_send_bss_transition_mgmt_resp(
1045 wpa_s, wpa_s->wnm_dialog_token, status,
1046 MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0, NULL);
1047 }
1048 }
1049
1050
1051 #define BTM_QUERY_MIN_SIZE 4
1052
wnm_send_bss_transition_mgmt_query(struct wpa_supplicant * wpa_s,u8 query_reason,const char * btm_candidates,int cand_list)1053 int wnm_send_bss_transition_mgmt_query(struct wpa_supplicant *wpa_s,
1054 u8 query_reason,
1055 const char *btm_candidates,
1056 int cand_list)
1057 {
1058 struct wpabuf *buf;
1059 int ret;
1060
1061 wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Query to "
1062 MACSTR " query_reason=%u%s",
1063 MAC2STR(wpa_s->current_bss->bssid), query_reason,
1064 cand_list ? " candidate list" : "");
1065
1066 buf = wpabuf_alloc(BTM_QUERY_MIN_SIZE);
1067 if (!buf)
1068 return -1;
1069
1070 wpabuf_put_u8(buf, WLAN_ACTION_WNM);
1071 wpabuf_put_u8(buf, WNM_BSS_TRANS_MGMT_QUERY);
1072 wpabuf_put_u8(buf, 1);
1073 wpabuf_put_u8(buf, query_reason);
1074
1075 if (cand_list)
1076 wnm_add_cand_list(wpa_s, &buf);
1077
1078 if (btm_candidates) {
1079 const size_t max_len = 1000;
1080
1081 ret = wpabuf_resize(&buf, max_len);
1082 if (ret < 0) {
1083 wpabuf_free(buf);
1084 return ret;
1085 }
1086
1087 ret = ieee802_11_parse_candidate_list(btm_candidates,
1088 wpabuf_put(buf, 0),
1089 max_len);
1090 if (ret < 0) {
1091 wpabuf_free(buf);
1092 return ret;
1093 }
1094
1095 wpabuf_put(buf, ret);
1096 }
1097
1098 ret = wpa_drv_send_action(wpa_s, 0, 0,
1099 wpabuf_head_u8(buf), wpabuf_len(buf), 0);
1100
1101 wpabuf_free(buf);
1102 return ret;
1103 }
1104
ieee802_11_rx_wnm_action(struct wpa_supplicant * wpa_s,u8 * sender,u8 * payload,size_t len)1105 void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s,
1106 u8 *sender, u8 *payload, size_t len)
1107 {
1108 const u8 *pos, *end;
1109 u8 act;
1110
1111 if (len < 2)
1112 return;
1113
1114 pos = payload;
1115 act = *pos++;
1116 end = payload + len;
1117
1118 wpa_printf(MSG_DEBUG, "WNM: RX action %u from " MACSTR,
1119 act, MAC2STR(sender));
1120
1121 switch (act) {
1122 case WNM_BSS_TRANS_MGMT_REQ:
1123 ieee802_11_rx_bss_trans_mgmt_req(wpa_s, pos, end,
1124 0x01);
1125 break;
1126 default:
1127 wpa_printf(MSG_ERROR, "WNM: Unknown request");
1128 break;
1129 }
1130 }
1131