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