1*36ebd06eSchristos /*
2*36ebd06eSchristos * hostapd - MBO
3*36ebd06eSchristos * Copyright (c) 2016, Qualcomm Atheros, Inc.
4*36ebd06eSchristos *
5*36ebd06eSchristos * This software may be distributed under the terms of the BSD license.
6*36ebd06eSchristos * See README for more details.
7*36ebd06eSchristos */
8*36ebd06eSchristos
9*36ebd06eSchristos #include "utils/includes.h"
10*36ebd06eSchristos
11*36ebd06eSchristos #include "utils/common.h"
12*36ebd06eSchristos #include "common/ieee802_11_defs.h"
13*36ebd06eSchristos #include "common/ieee802_11_common.h"
14*36ebd06eSchristos #include "hostapd.h"
15*36ebd06eSchristos #include "sta_info.h"
16*36ebd06eSchristos #include "mbo_ap.h"
17*36ebd06eSchristos
18*36ebd06eSchristos
mbo_ap_sta_free(struct sta_info * sta)19*36ebd06eSchristos void mbo_ap_sta_free(struct sta_info *sta)
20*36ebd06eSchristos {
21*36ebd06eSchristos struct mbo_non_pref_chan_info *info, *prev;
22*36ebd06eSchristos
23*36ebd06eSchristos info = sta->non_pref_chan;
24*36ebd06eSchristos sta->non_pref_chan = NULL;
25*36ebd06eSchristos while (info) {
26*36ebd06eSchristos prev = info;
27*36ebd06eSchristos info = info->next;
28*36ebd06eSchristos os_free(prev);
29*36ebd06eSchristos }
30*36ebd06eSchristos }
31*36ebd06eSchristos
32*36ebd06eSchristos
mbo_ap_parse_non_pref_chan(struct sta_info * sta,const u8 * buf,size_t len)33*36ebd06eSchristos static void mbo_ap_parse_non_pref_chan(struct sta_info *sta,
34*36ebd06eSchristos const u8 *buf, size_t len)
35*36ebd06eSchristos {
36*36ebd06eSchristos struct mbo_non_pref_chan_info *info, *tmp;
37*36ebd06eSchristos char channels[200], *pos, *end;
38*36ebd06eSchristos size_t num_chan, i;
39*36ebd06eSchristos int ret;
40*36ebd06eSchristos
41*36ebd06eSchristos if (len <= 3)
42*36ebd06eSchristos return; /* Not enough room for any channels */
43*36ebd06eSchristos
44*36ebd06eSchristos num_chan = len - 3;
45*36ebd06eSchristos info = os_zalloc(sizeof(*info) + num_chan);
46*36ebd06eSchristos if (!info)
47*36ebd06eSchristos return;
48*36ebd06eSchristos info->op_class = buf[0];
49*36ebd06eSchristos info->pref = buf[len - 2];
50*36ebd06eSchristos info->reason_code = buf[len - 1];
51*36ebd06eSchristos info->num_channels = num_chan;
52*36ebd06eSchristos buf++;
53*36ebd06eSchristos os_memcpy(info->channels, buf, num_chan);
54*36ebd06eSchristos if (!sta->non_pref_chan) {
55*36ebd06eSchristos sta->non_pref_chan = info;
56*36ebd06eSchristos } else {
57*36ebd06eSchristos tmp = sta->non_pref_chan;
58*36ebd06eSchristos while (tmp->next)
59*36ebd06eSchristos tmp = tmp->next;
60*36ebd06eSchristos tmp->next = info;
61*36ebd06eSchristos }
62*36ebd06eSchristos
63*36ebd06eSchristos pos = channels;
64*36ebd06eSchristos end = pos + sizeof(channels);
65*36ebd06eSchristos *pos = '\0';
66*36ebd06eSchristos for (i = 0; i < num_chan; i++) {
67*36ebd06eSchristos ret = os_snprintf(pos, end - pos, "%s%u",
68*36ebd06eSchristos i == 0 ? "" : " ", buf[i]);
69*36ebd06eSchristos if (os_snprintf_error(end - pos, ret)) {
70*36ebd06eSchristos *pos = '\0';
71*36ebd06eSchristos break;
72*36ebd06eSchristos }
73*36ebd06eSchristos pos += ret;
74*36ebd06eSchristos }
75*36ebd06eSchristos
76*36ebd06eSchristos wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR
77*36ebd06eSchristos " non-preferred channel list (op class %u, pref %u, reason code %u, channels %s)",
78*36ebd06eSchristos MAC2STR(sta->addr), info->op_class, info->pref,
79*36ebd06eSchristos info->reason_code, channels);
80*36ebd06eSchristos }
81*36ebd06eSchristos
82*36ebd06eSchristos
mbo_ap_check_sta_assoc(struct hostapd_data * hapd,struct sta_info * sta,struct ieee802_11_elems * elems)83*36ebd06eSchristos void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta,
84*36ebd06eSchristos struct ieee802_11_elems *elems)
85*36ebd06eSchristos {
86*36ebd06eSchristos const u8 *pos, *attr, *end;
87*36ebd06eSchristos size_t len;
88*36ebd06eSchristos
89*36ebd06eSchristos if (!hapd->conf->mbo_enabled || !elems->mbo)
90*36ebd06eSchristos return;
91*36ebd06eSchristos
92*36ebd06eSchristos pos = elems->mbo + 4;
93*36ebd06eSchristos len = elems->mbo_len - 4;
94*36ebd06eSchristos wpa_hexdump(MSG_DEBUG, "MBO: Association Request attributes", pos, len);
95*36ebd06eSchristos
96*36ebd06eSchristos attr = get_ie(pos, len, MBO_ATTR_ID_CELL_DATA_CAPA);
97*36ebd06eSchristos if (attr && attr[1] >= 1)
98*36ebd06eSchristos sta->cell_capa = attr[2];
99*36ebd06eSchristos
100*36ebd06eSchristos mbo_ap_sta_free(sta);
101*36ebd06eSchristos end = pos + len;
102*36ebd06eSchristos while (end - pos > 1) {
103*36ebd06eSchristos u8 ie_len = pos[1];
104*36ebd06eSchristos
105*36ebd06eSchristos if (2 + ie_len > end - pos)
106*36ebd06eSchristos break;
107*36ebd06eSchristos
108*36ebd06eSchristos if (pos[0] == MBO_ATTR_ID_NON_PREF_CHAN_REPORT)
109*36ebd06eSchristos mbo_ap_parse_non_pref_chan(sta, pos + 2, ie_len);
110*36ebd06eSchristos pos += 2 + pos[1];
111*36ebd06eSchristos }
112*36ebd06eSchristos }
113*36ebd06eSchristos
114*36ebd06eSchristos
mbo_ap_get_info(struct sta_info * sta,char * buf,size_t buflen)115*36ebd06eSchristos int mbo_ap_get_info(struct sta_info *sta, char *buf, size_t buflen)
116*36ebd06eSchristos {
117*36ebd06eSchristos char *pos = buf, *end = buf + buflen;
118*36ebd06eSchristos int ret;
119*36ebd06eSchristos struct mbo_non_pref_chan_info *info;
120*36ebd06eSchristos u8 i;
121*36ebd06eSchristos unsigned int count = 0;
122*36ebd06eSchristos
123*36ebd06eSchristos if (!sta->cell_capa)
124*36ebd06eSchristos return 0;
125*36ebd06eSchristos
126*36ebd06eSchristos ret = os_snprintf(pos, end - pos, "mbo_cell_capa=%u\n", sta->cell_capa);
127*36ebd06eSchristos if (os_snprintf_error(end - pos, ret))
128*36ebd06eSchristos return pos - buf;
129*36ebd06eSchristos pos += ret;
130*36ebd06eSchristos
131*36ebd06eSchristos for (info = sta->non_pref_chan; info; info = info->next) {
132*36ebd06eSchristos char *pos2 = pos;
133*36ebd06eSchristos
134*36ebd06eSchristos ret = os_snprintf(pos2, end - pos2,
135*36ebd06eSchristos "non_pref_chan[%u]=%u:%u:%u:",
136*36ebd06eSchristos count, info->op_class, info->pref,
137*36ebd06eSchristos info->reason_code);
138*36ebd06eSchristos count++;
139*36ebd06eSchristos if (os_snprintf_error(end - pos2, ret))
140*36ebd06eSchristos break;
141*36ebd06eSchristos pos2 += ret;
142*36ebd06eSchristos
143*36ebd06eSchristos for (i = 0; i < info->num_channels; i++) {
144*36ebd06eSchristos ret = os_snprintf(pos2, end - pos2, "%u%s",
145*36ebd06eSchristos info->channels[i],
146*36ebd06eSchristos i + 1 < info->num_channels ?
147*36ebd06eSchristos "," : "");
148*36ebd06eSchristos if (os_snprintf_error(end - pos2, ret)) {
149*36ebd06eSchristos pos2 = NULL;
150*36ebd06eSchristos break;
151*36ebd06eSchristos }
152*36ebd06eSchristos pos2 += ret;
153*36ebd06eSchristos }
154*36ebd06eSchristos
155*36ebd06eSchristos if (!pos2)
156*36ebd06eSchristos break;
157*36ebd06eSchristos ret = os_snprintf(pos2, end - pos2, "\n");
158*36ebd06eSchristos if (os_snprintf_error(end - pos2, ret))
159*36ebd06eSchristos break;
160*36ebd06eSchristos pos2 += ret;
161*36ebd06eSchristos pos = pos2;
162*36ebd06eSchristos }
163*36ebd06eSchristos
164*36ebd06eSchristos return pos - buf;
165*36ebd06eSchristos }
166*36ebd06eSchristos
167*36ebd06eSchristos
mbo_ap_wnm_notif_req_cell_capa(struct sta_info * sta,const u8 * buf,size_t len)168*36ebd06eSchristos static void mbo_ap_wnm_notif_req_cell_capa(struct sta_info *sta,
169*36ebd06eSchristos const u8 *buf, size_t len)
170*36ebd06eSchristos {
171*36ebd06eSchristos if (len < 1)
172*36ebd06eSchristos return;
173*36ebd06eSchristos wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR
174*36ebd06eSchristos " updated cellular data capability: %u",
175*36ebd06eSchristos MAC2STR(sta->addr), buf[0]);
176*36ebd06eSchristos sta->cell_capa = buf[0];
177*36ebd06eSchristos }
178*36ebd06eSchristos
179*36ebd06eSchristos
mbo_ap_wnm_notif_req_elem(struct sta_info * sta,u8 type,const u8 * buf,size_t len,int * first_non_pref_chan)180*36ebd06eSchristos static void mbo_ap_wnm_notif_req_elem(struct sta_info *sta, u8 type,
181*36ebd06eSchristos const u8 *buf, size_t len,
182*36ebd06eSchristos int *first_non_pref_chan)
183*36ebd06eSchristos {
184*36ebd06eSchristos switch (type) {
185*36ebd06eSchristos case WFA_WNM_NOTIF_SUBELEM_NON_PREF_CHAN_REPORT:
186*36ebd06eSchristos if (*first_non_pref_chan) {
187*36ebd06eSchristos /*
188*36ebd06eSchristos * Need to free the previously stored entries now to
189*36ebd06eSchristos * allow the update to replace all entries.
190*36ebd06eSchristos */
191*36ebd06eSchristos *first_non_pref_chan = 0;
192*36ebd06eSchristos mbo_ap_sta_free(sta);
193*36ebd06eSchristos }
194*36ebd06eSchristos mbo_ap_parse_non_pref_chan(sta, buf, len);
195*36ebd06eSchristos break;
196*36ebd06eSchristos case WFA_WNM_NOTIF_SUBELEM_CELL_DATA_CAPA:
197*36ebd06eSchristos mbo_ap_wnm_notif_req_cell_capa(sta, buf, len);
198*36ebd06eSchristos break;
199*36ebd06eSchristos default:
200*36ebd06eSchristos wpa_printf(MSG_DEBUG,
201*36ebd06eSchristos "MBO: Ignore unknown WNM Notification WFA subelement %u",
202*36ebd06eSchristos type);
203*36ebd06eSchristos break;
204*36ebd06eSchristos }
205*36ebd06eSchristos }
206*36ebd06eSchristos
207*36ebd06eSchristos
mbo_ap_wnm_notification_req(struct hostapd_data * hapd,const u8 * addr,const u8 * buf,size_t len)208*36ebd06eSchristos void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr,
209*36ebd06eSchristos const u8 *buf, size_t len)
210*36ebd06eSchristos {
211*36ebd06eSchristos const u8 *pos, *end;
212*36ebd06eSchristos u8 ie_len;
213*36ebd06eSchristos struct sta_info *sta;
214*36ebd06eSchristos int first_non_pref_chan = 1;
215*36ebd06eSchristos
216*36ebd06eSchristos if (!hapd->conf->mbo_enabled)
217*36ebd06eSchristos return;
218*36ebd06eSchristos
219*36ebd06eSchristos sta = ap_get_sta(hapd, addr);
220*36ebd06eSchristos if (!sta)
221*36ebd06eSchristos return;
222*36ebd06eSchristos
223*36ebd06eSchristos pos = buf;
224*36ebd06eSchristos end = buf + len;
225*36ebd06eSchristos
226*36ebd06eSchristos while (end - pos > 1) {
227*36ebd06eSchristos ie_len = pos[1];
228*36ebd06eSchristos
229*36ebd06eSchristos if (2 + ie_len > end - pos)
230*36ebd06eSchristos break;
231*36ebd06eSchristos
232*36ebd06eSchristos if (pos[0] == WLAN_EID_VENDOR_SPECIFIC &&
233*36ebd06eSchristos ie_len >= 4 && WPA_GET_BE24(pos + 2) == OUI_WFA)
234*36ebd06eSchristos mbo_ap_wnm_notif_req_elem(sta, pos[5],
235*36ebd06eSchristos pos + 6, ie_len - 4,
236*36ebd06eSchristos &first_non_pref_chan);
237*36ebd06eSchristos else
238*36ebd06eSchristos wpa_printf(MSG_DEBUG,
239*36ebd06eSchristos "MBO: Ignore unknown WNM Notification element %u (len=%u)",
240*36ebd06eSchristos pos[0], pos[1]);
241*36ebd06eSchristos
242*36ebd06eSchristos pos += 2 + pos[1];
243*36ebd06eSchristos }
244*36ebd06eSchristos }
245