]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/wpa/wpa_supplicant/mbo.c
MFV r344063:
[FreeBSD/FreeBSD.git] / contrib / wpa / wpa_supplicant / mbo.c
1 /*
2  * wpa_supplicant - MBO
3  *
4  * Copyright(c) 2015 Intel Deutschland GmbH
5  * Contact Information:
6  * Intel Linux Wireless <ilw@linux.intel.com>
7  * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
8  *
9  * This software may be distributed under the terms of the BSD license.
10  * See README for more details.
11  */
12
13 #include "utils/includes.h"
14
15 #include "utils/common.h"
16 #include "common/ieee802_11_defs.h"
17 #include "common/gas.h"
18 #include "config.h"
19 #include "wpa_supplicant_i.h"
20 #include "driver_i.h"
21 #include "bss.h"
22 #include "scan.h"
23
24 /* type + length + oui + oui type */
25 #define MBO_IE_HEADER 6
26
27
28 static int wpas_mbo_validate_non_pref_chan(u8 oper_class, u8 chan, u8 reason)
29 {
30         if (reason > MBO_NON_PREF_CHAN_REASON_INT_INTERFERENCE)
31                 return -1;
32
33         /* Only checking the validity of the channel and oper_class */
34         if (ieee80211_chan_to_freq(NULL, oper_class, chan) == -1)
35                 return -1;
36
37         return 0;
38 }
39
40
41 const u8 * mbo_attr_from_mbo_ie(const u8 *mbo_ie, enum mbo_attr_id attr)
42 {
43         const u8 *mbo;
44         u8 ie_len = mbo_ie[1];
45
46         if (ie_len < MBO_IE_HEADER - 2)
47                 return NULL;
48         mbo = mbo_ie + MBO_IE_HEADER;
49
50         return get_ie(mbo, 2 + ie_len - MBO_IE_HEADER, attr);
51 }
52
53
54 const u8 * wpas_mbo_get_bss_attr(struct wpa_bss *bss, enum mbo_attr_id attr)
55 {
56         const u8 *mbo, *end;
57
58         if (!bss)
59                 return NULL;
60
61         mbo = wpa_bss_get_vendor_ie(bss, MBO_IE_VENDOR_TYPE);
62         if (!mbo)
63                 return NULL;
64
65         end = mbo + 2 + mbo[1];
66         mbo += MBO_IE_HEADER;
67
68         return get_ie(mbo, end - mbo, attr);
69 }
70
71
72 static void wpas_mbo_non_pref_chan_attr_body(struct wpa_supplicant *wpa_s,
73                                              struct wpabuf *mbo,
74                                              u8 start, u8 end)
75 {
76         u8 i;
77
78         wpabuf_put_u8(mbo, wpa_s->non_pref_chan[start].oper_class);
79
80         for (i = start; i < end; i++)
81                 wpabuf_put_u8(mbo, wpa_s->non_pref_chan[i].chan);
82
83         wpabuf_put_u8(mbo, wpa_s->non_pref_chan[start].preference);
84         wpabuf_put_u8(mbo, wpa_s->non_pref_chan[start].reason);
85 }
86
87
88 static void wpas_mbo_non_pref_chan_attr(struct wpa_supplicant *wpa_s,
89                                         struct wpabuf *mbo, u8 start, u8 end)
90 {
91         size_t size = end - start + 3;
92
93         if (size + 2 > wpabuf_tailroom(mbo))
94                 return;
95
96         wpabuf_put_u8(mbo, MBO_ATTR_ID_NON_PREF_CHAN_REPORT);
97         wpabuf_put_u8(mbo, size); /* Length */
98
99         wpas_mbo_non_pref_chan_attr_body(wpa_s, mbo, start, end);
100 }
101
102
103 static void wpas_mbo_non_pref_chan_subelem_hdr(struct wpabuf *mbo, u8 len)
104 {
105         wpabuf_put_u8(mbo, WLAN_EID_VENDOR_SPECIFIC);
106         wpabuf_put_u8(mbo, len); /* Length */
107         wpabuf_put_be24(mbo, OUI_WFA);
108         wpabuf_put_u8(mbo, MBO_ATTR_ID_NON_PREF_CHAN_REPORT);
109 }
110
111
112 static void wpas_mbo_non_pref_chan_subelement(struct wpa_supplicant *wpa_s,
113                                               struct wpabuf *mbo, u8 start,
114                                               u8 end)
115 {
116         size_t size = end - start + 7;
117
118         if (size + 2 > wpabuf_tailroom(mbo))
119                 return;
120
121         wpas_mbo_non_pref_chan_subelem_hdr(mbo, size);
122         wpas_mbo_non_pref_chan_attr_body(wpa_s, mbo, start, end);
123 }
124
125
126 static void wpas_mbo_non_pref_chan_attrs(struct wpa_supplicant *wpa_s,
127                                          struct wpabuf *mbo, int subelement)
128 {
129         u8 i, start = 0;
130         struct wpa_mbo_non_pref_channel *start_pref;
131
132         if (!wpa_s->non_pref_chan || !wpa_s->non_pref_chan_num) {
133                 if (subelement)
134                         wpas_mbo_non_pref_chan_subelem_hdr(mbo, 4);
135                 return;
136         }
137         start_pref = &wpa_s->non_pref_chan[0];
138
139         for (i = 1; i <= wpa_s->non_pref_chan_num; i++) {
140                 struct wpa_mbo_non_pref_channel *non_pref = NULL;
141
142                 if (i < wpa_s->non_pref_chan_num)
143                         non_pref = &wpa_s->non_pref_chan[i];
144                 if (!non_pref ||
145                     non_pref->oper_class != start_pref->oper_class ||
146                     non_pref->reason != start_pref->reason ||
147                     non_pref->preference != start_pref->preference) {
148                         if (subelement)
149                                 wpas_mbo_non_pref_chan_subelement(wpa_s, mbo,
150                                                                   start, i);
151                         else
152                                 wpas_mbo_non_pref_chan_attr(wpa_s, mbo, start,
153                                                             i);
154
155                         if (!non_pref)
156                                 return;
157
158                         start = i;
159                         start_pref = non_pref;
160                 }
161         }
162 }
163
164
165 int wpas_mbo_ie(struct wpa_supplicant *wpa_s, u8 *buf, size_t len,
166                 int add_oce_capa)
167 {
168         struct wpabuf *mbo;
169         int res;
170
171         if (len < MBO_IE_HEADER + 3 + 7 +
172             ((wpa_s->enable_oce & OCE_STA) ? 3 : 0))
173                 return 0;
174
175         /* Leave room for the MBO IE header */
176         mbo = wpabuf_alloc(len - MBO_IE_HEADER);
177         if (!mbo)
178                 return 0;
179
180         /* Add non-preferred channels attribute */
181         wpas_mbo_non_pref_chan_attrs(wpa_s, mbo, 0);
182
183         /*
184          * Send cellular capabilities attribute even if AP does not advertise
185          * cellular capabilities.
186          */
187         wpabuf_put_u8(mbo, MBO_ATTR_ID_CELL_DATA_CAPA);
188         wpabuf_put_u8(mbo, 1);
189         wpabuf_put_u8(mbo, wpa_s->conf->mbo_cell_capa);
190
191         /* Add OCE capability indication attribute if OCE is enabled */
192         if ((wpa_s->enable_oce & OCE_STA) && add_oce_capa) {
193                 wpabuf_put_u8(mbo, OCE_ATTR_ID_CAPA_IND);
194                 wpabuf_put_u8(mbo, 1);
195                 wpabuf_put_u8(mbo, OCE_RELEASE);
196         }
197
198         res = mbo_add_ie(buf, len, wpabuf_head_u8(mbo), wpabuf_len(mbo));
199         if (!res)
200                 wpa_printf(MSG_ERROR, "Failed to add MBO/OCE IE");
201
202         wpabuf_free(mbo);
203         return res;
204 }
205
206
207 static void wpas_mbo_send_wnm_notification(struct wpa_supplicant *wpa_s,
208                                            const u8 *data, size_t len)
209 {
210         struct wpabuf *buf;
211         int res;
212
213         /*
214          * Send WNM-Notification Request frame only in case of a change in
215          * non-preferred channels list during association, if the AP supports
216          * MBO.
217          */
218         if (wpa_s->wpa_state != WPA_COMPLETED || !wpa_s->current_bss ||
219             !wpa_bss_get_vendor_ie(wpa_s->current_bss, MBO_IE_VENDOR_TYPE))
220                 return;
221
222         buf = wpabuf_alloc(4 + len);
223         if (!buf)
224                 return;
225
226         wpabuf_put_u8(buf, WLAN_ACTION_WNM);
227         wpabuf_put_u8(buf, WNM_NOTIFICATION_REQ);
228         wpa_s->mbo_wnm_token++;
229         if (wpa_s->mbo_wnm_token == 0)
230                 wpa_s->mbo_wnm_token++;
231         wpabuf_put_u8(buf, wpa_s->mbo_wnm_token);
232         wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); /* Type */
233
234         wpabuf_put_data(buf, data, len);
235
236         res = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
237                                   wpa_s->own_addr, wpa_s->bssid,
238                                   wpabuf_head(buf), wpabuf_len(buf), 0);
239         if (res < 0)
240                 wpa_printf(MSG_DEBUG,
241                            "Failed to send WNM-Notification Request frame with non-preferred channel list");
242
243         wpabuf_free(buf);
244 }
245
246
247 static void wpas_mbo_non_pref_chan_changed(struct wpa_supplicant *wpa_s)
248 {
249         struct wpabuf *buf;
250
251         buf = wpabuf_alloc(512);
252         if (!buf)
253                 return;
254
255         wpas_mbo_non_pref_chan_attrs(wpa_s, buf, 1);
256         wpas_mbo_send_wnm_notification(wpa_s, wpabuf_head_u8(buf),
257                                        wpabuf_len(buf));
258         wpabuf_free(buf);
259 }
260
261
262 static int wpa_non_pref_chan_is_eq(struct wpa_mbo_non_pref_channel *a,
263                                    struct wpa_mbo_non_pref_channel *b)
264 {
265         return a->oper_class == b->oper_class && a->chan == b->chan;
266 }
267
268
269 /*
270  * wpa_non_pref_chan_cmp - Compare two channels for sorting
271  *
272  * In MBO IE non-preferred channel subelement we can put many channels in an
273  * attribute if they are in the same operating class and have the same
274  * preference and reason. To make it easy for the functions that build
275  * the IE attributes and WNM Request subelements, save the channels sorted
276  * by their oper_class and reason.
277  */
278 static int wpa_non_pref_chan_cmp(const void *_a, const void *_b)
279 {
280         const struct wpa_mbo_non_pref_channel *a = _a, *b = _b;
281
282         if (a->oper_class != b->oper_class)
283                 return a->oper_class - b->oper_class;
284         if (a->reason != b->reason)
285                 return a->reason - b->reason;
286         return a->preference - b->preference;
287 }
288
289
290 int wpas_mbo_update_non_pref_chan(struct wpa_supplicant *wpa_s,
291                                   const char *non_pref_chan)
292 {
293         char *cmd, *token, *context = NULL;
294         struct wpa_mbo_non_pref_channel *chans = NULL, *tmp_chans;
295         size_t num = 0, size = 0;
296         unsigned i;
297
298         wpa_printf(MSG_DEBUG, "MBO: Update non-preferred channels, non_pref_chan=%s",
299                    non_pref_chan ? non_pref_chan : "N/A");
300
301         /*
302          * The shortest channel configuration is 7 characters - 3 colons and
303          * 4 values.
304          */
305         if (!non_pref_chan || os_strlen(non_pref_chan) < 7)
306                 goto update;
307
308         cmd = os_strdup(non_pref_chan);
309         if (!cmd)
310                 return -1;
311
312         while ((token = str_token(cmd, " ", &context))) {
313                 struct wpa_mbo_non_pref_channel *chan;
314                 int ret;
315                 unsigned int _oper_class;
316                 unsigned int _chan;
317                 unsigned int _preference;
318                 unsigned int _reason;
319
320                 if (num == size) {
321                         size = size ? size * 2 : 1;
322                         tmp_chans = os_realloc_array(chans, size,
323                                                      sizeof(*chans));
324                         if (!tmp_chans) {
325                                 wpa_printf(MSG_ERROR,
326                                            "Couldn't reallocate non_pref_chan");
327                                 goto fail;
328                         }
329                         chans = tmp_chans;
330                 }
331
332                 chan = &chans[num];
333
334                 ret = sscanf(token, "%u:%u:%u:%u", &_oper_class,
335                              &_chan, &_preference, &_reason);
336                 if (ret != 4 ||
337                     _oper_class > 255 || _chan > 255 ||
338                     _preference > 255 || _reason > 65535 ) {
339                         wpa_printf(MSG_ERROR, "Invalid non-pref chan input %s",
340                                    token);
341                         goto fail;
342                 }
343                 chan->oper_class = _oper_class;
344                 chan->chan = _chan;
345                 chan->preference = _preference;
346                 chan->reason = _reason;
347
348                 if (wpas_mbo_validate_non_pref_chan(chan->oper_class,
349                                                     chan->chan, chan->reason)) {
350                         wpa_printf(MSG_ERROR,
351                                    "Invalid non_pref_chan: oper class %d chan %d reason %d",
352                                    chan->oper_class, chan->chan, chan->reason);
353                         goto fail;
354                 }
355
356                 for (i = 0; i < num; i++)
357                         if (wpa_non_pref_chan_is_eq(chan, &chans[i]))
358                                 break;
359                 if (i != num) {
360                         wpa_printf(MSG_ERROR,
361                                    "oper class %d chan %d is duplicated",
362                                    chan->oper_class, chan->chan);
363                         goto fail;
364                 }
365
366                 num++;
367         }
368
369         os_free(cmd);
370
371         if (chans) {
372                 qsort(chans, num, sizeof(struct wpa_mbo_non_pref_channel),
373                       wpa_non_pref_chan_cmp);
374         }
375
376 update:
377         os_free(wpa_s->non_pref_chan);
378         wpa_s->non_pref_chan = chans;
379         wpa_s->non_pref_chan_num = num;
380         wpas_mbo_non_pref_chan_changed(wpa_s);
381
382         return 0;
383
384 fail:
385         os_free(chans);
386         os_free(cmd);
387         return -1;
388 }
389
390
391 void wpas_mbo_scan_ie(struct wpa_supplicant *wpa_s, struct wpabuf *ie)
392 {
393         u8 *len;
394
395         wpabuf_put_u8(ie, WLAN_EID_VENDOR_SPECIFIC);
396         len = wpabuf_put(ie, 1);
397
398         wpabuf_put_be24(ie, OUI_WFA);
399         wpabuf_put_u8(ie, MBO_OUI_TYPE);
400
401         wpabuf_put_u8(ie, MBO_ATTR_ID_CELL_DATA_CAPA);
402         wpabuf_put_u8(ie, 1);
403         wpabuf_put_u8(ie, wpa_s->conf->mbo_cell_capa);
404         if (wpa_s->enable_oce & OCE_STA) {
405                 wpabuf_put_u8(ie, OCE_ATTR_ID_CAPA_IND);
406                 wpabuf_put_u8(ie, 1);
407                 wpabuf_put_u8(ie, OCE_RELEASE);
408         }
409         *len = (u8 *) wpabuf_put(ie, 0) - len - 1;
410 }
411
412
413 void wpas_mbo_ie_trans_req(struct wpa_supplicant *wpa_s, const u8 *mbo_ie,
414                            size_t len)
415 {
416         const u8 *pos, *cell_pref = NULL;
417         u8 id, elen;
418         u16 disallowed_sec = 0;
419
420         if (len <= 4 || WPA_GET_BE24(mbo_ie) != OUI_WFA ||
421             mbo_ie[3] != MBO_OUI_TYPE)
422                 return;
423
424         pos = mbo_ie + 4;
425         len -= 4;
426
427         while (len >= 2) {
428                 id = *pos++;
429                 elen = *pos++;
430                 len -= 2;
431
432                 if (elen > len)
433                         goto fail;
434
435                 switch (id) {
436                 case MBO_ATTR_ID_CELL_DATA_PREF:
437                         if (elen != 1)
438                                 goto fail;
439
440                         if (wpa_s->conf->mbo_cell_capa ==
441                             MBO_CELL_CAPA_AVAILABLE)
442                                 cell_pref = pos;
443                         else
444                                 wpa_printf(MSG_DEBUG,
445                                            "MBO: Station does not support Cellular data connection");
446                         break;
447                 case MBO_ATTR_ID_TRANSITION_REASON:
448                         if (elen != 1)
449                                 goto fail;
450
451                         wpa_s->wnm_mbo_trans_reason_present = 1;
452                         wpa_s->wnm_mbo_transition_reason = *pos;
453                         break;
454                 case MBO_ATTR_ID_ASSOC_RETRY_DELAY:
455                         if (elen != 2)
456                                 goto fail;
457
458                         if (wpa_s->wnm_mode &
459                             WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) {
460                                 wpa_printf(MSG_DEBUG,
461                                            "MBO: Unexpected association retry delay, BSS is terminating");
462                                 goto fail;
463                         } else if (wpa_s->wnm_mode &
464                                    WNM_BSS_TM_REQ_DISASSOC_IMMINENT) {
465                                 disallowed_sec = WPA_GET_LE16(pos);
466                                 wpa_printf(MSG_DEBUG,
467                                            "MBO: Association retry delay: %u",
468                                            disallowed_sec);
469                         } else {
470                                 wpa_printf(MSG_DEBUG,
471                                            "MBO: Association retry delay attribute not in disassoc imminent mode");
472                         }
473
474                         break;
475                 case MBO_ATTR_ID_AP_CAPA_IND:
476                 case MBO_ATTR_ID_NON_PREF_CHAN_REPORT:
477                 case MBO_ATTR_ID_CELL_DATA_CAPA:
478                 case MBO_ATTR_ID_ASSOC_DISALLOW:
479                 case MBO_ATTR_ID_TRANSITION_REJECT_REASON:
480                         wpa_printf(MSG_DEBUG,
481                                    "MBO: Attribute %d should not be included in BTM Request frame",
482                                    id);
483                         break;
484                 default:
485                         wpa_printf(MSG_DEBUG, "MBO: Unknown attribute id %u",
486                                    id);
487                         return;
488                 }
489
490                 pos += elen;
491                 len -= elen;
492         }
493
494         if (cell_pref)
495                 wpa_msg(wpa_s, MSG_INFO, MBO_CELL_PREFERENCE "preference=%u",
496                         *cell_pref);
497
498         if (wpa_s->wnm_mbo_trans_reason_present)
499                 wpa_msg(wpa_s, MSG_INFO, MBO_TRANSITION_REASON "reason=%u",
500                         wpa_s->wnm_mbo_transition_reason);
501
502         if (disallowed_sec && wpa_s->current_bss)
503                 wpa_bss_tmp_disallow(wpa_s, wpa_s->current_bss->bssid,
504                                      disallowed_sec);
505
506         return;
507 fail:
508         wpa_printf(MSG_DEBUG, "MBO IE parsing failed (id=%u len=%u left=%zu)",
509                    id, elen, len);
510 }
511
512
513 size_t wpas_mbo_ie_bss_trans_reject(struct wpa_supplicant *wpa_s, u8 *pos,
514                                     size_t len,
515                                     enum mbo_transition_reject_reason reason)
516 {
517         u8 reject_attr[3];
518
519         reject_attr[0] = MBO_ATTR_ID_TRANSITION_REJECT_REASON;
520         reject_attr[1] = 1;
521         reject_attr[2] = reason;
522
523         return mbo_add_ie(pos, len, reject_attr, sizeof(reject_attr));
524 }
525
526
527 void wpas_mbo_update_cell_capa(struct wpa_supplicant *wpa_s, u8 mbo_cell_capa)
528 {
529         u8 cell_capa[7];
530
531         if (wpa_s->conf->mbo_cell_capa == mbo_cell_capa) {
532                 wpa_printf(MSG_DEBUG,
533                            "MBO: Cellular capability already set to %u",
534                            mbo_cell_capa);
535                 return;
536         }
537
538         wpa_s->conf->mbo_cell_capa = mbo_cell_capa;
539
540         cell_capa[0] = WLAN_EID_VENDOR_SPECIFIC;
541         cell_capa[1] = 5; /* Length */
542         WPA_PUT_BE24(cell_capa + 2, OUI_WFA);
543         cell_capa[5] = MBO_ATTR_ID_CELL_DATA_CAPA;
544         cell_capa[6] = mbo_cell_capa;
545
546         wpas_mbo_send_wnm_notification(wpa_s, cell_capa, 7);
547         wpa_supplicant_set_default_scan_ies(wpa_s);
548 }
549
550
551 struct wpabuf * mbo_build_anqp_buf(struct wpa_supplicant *wpa_s,
552                                    struct wpa_bss *bss, u32 mbo_subtypes)
553 {
554         struct wpabuf *anqp_buf;
555         u8 *len_pos;
556         u8 i;
557
558         if (!wpa_bss_get_vendor_ie(bss, MBO_IE_VENDOR_TYPE)) {
559                 wpa_printf(MSG_INFO, "MBO: " MACSTR
560                            " does not support MBO - cannot request MBO ANQP elements from it",
561                            MAC2STR(bss->bssid));
562                 return NULL;
563         }
564
565         /* Allocate size for the maximum case - all MBO subtypes are set */
566         anqp_buf = wpabuf_alloc(9 + MAX_MBO_ANQP_SUBTYPE);
567         if (!anqp_buf)
568                 return NULL;
569
570         len_pos = gas_anqp_add_element(anqp_buf, ANQP_VENDOR_SPECIFIC);
571         wpabuf_put_be24(anqp_buf, OUI_WFA);
572         wpabuf_put_u8(anqp_buf, MBO_ANQP_OUI_TYPE);
573
574         wpabuf_put_u8(anqp_buf, MBO_ANQP_SUBTYPE_QUERY_LIST);
575
576         /* The first valid MBO subtype is 1 */
577         for (i = 1; i <= MAX_MBO_ANQP_SUBTYPE; i++) {
578                 if (mbo_subtypes & BIT(i))
579                         wpabuf_put_u8(anqp_buf, i);
580         }
581
582         gas_anqp_set_element_len(anqp_buf, len_pos);
583
584         return anqp_buf;
585 }
586
587
588 void mbo_parse_rx_anqp_resp(struct wpa_supplicant *wpa_s,
589                             struct wpa_bss *bss, const u8 *sa,
590                             const u8 *data, size_t slen)
591 {
592         const u8 *pos = data;
593         u8 subtype;
594
595         if (slen < 1)
596                 return;
597
598         subtype = *pos++;
599         slen--;
600
601         switch (subtype) {
602         case MBO_ANQP_SUBTYPE_CELL_CONN_PREF:
603                 if (slen < 1)
604                         break;
605                 wpa_msg(wpa_s, MSG_INFO, RX_MBO_ANQP MACSTR
606                         " cell_conn_pref=%u", MAC2STR(sa), *pos);
607                 break;
608         default:
609                 wpa_printf(MSG_DEBUG, "MBO: Unsupported ANQP subtype %u",
610                            subtype);
611                 break;
612         }
613 }