]> git.ipfire.org Git - thirdparty/hostap.git/blob - src/ap/rrm.c
8548ea406b5aacf2d3230a7c3425661a9896a8b1
[thirdparty/hostap.git] / src / ap / rrm.c
1 /*
2 * hostapd / Radio Measurement (RRM)
3 * Copyright(c) 2013 - 2016 Intel Mobile Communications GmbH.
4 * Copyright(c) 2011 - 2016 Intel Corporation. All rights reserved.
5 *
6 * This software may be distributed under the terms of the BSD license.
7 * See README for more details.
8 */
9
10 #include "utils/includes.h"
11
12 #include "utils/common.h"
13 #include "hostapd.h"
14 #include "ap_drv_ops.h"
15 #include "sta_info.h"
16 #include "eloop.h"
17 #include "neighbor_db.h"
18 #include "rrm.h"
19
20 #define HOSTAPD_RRM_REQUEST_TIMEOUT 5
21
22
23 static void hostapd_lci_rep_timeout_handler(void *eloop_data, void *user_ctx)
24 {
25 struct hostapd_data *hapd = eloop_data;
26
27 wpa_printf(MSG_DEBUG, "RRM: LCI request (token %u) timed out",
28 hapd->lci_req_token);
29 hapd->lci_req_active = 0;
30 }
31
32
33 static void hostapd_handle_lci_report(struct hostapd_data *hapd, u8 token,
34 const u8 *pos, size_t len)
35 {
36 if (!hapd->lci_req_active || hapd->lci_req_token != token) {
37 wpa_printf(MSG_DEBUG, "Unexpected LCI report, token %u", token);
38 return;
39 }
40
41 hapd->lci_req_active = 0;
42 eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd, NULL);
43 wpa_printf(MSG_DEBUG, "LCI report token %u len %zu", token, len);
44 }
45
46
47 static void hostapd_handle_radio_msmt_report(struct hostapd_data *hapd,
48 const u8 *buf, size_t len)
49 {
50 const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf;
51 const u8 *pos, *ie, *end;
52 u8 token;
53
54 end = buf + len;
55 token = mgmt->u.action.u.rrm.dialog_token;
56 pos = mgmt->u.action.u.rrm.variable;
57
58 while ((ie = get_ie(pos, end - pos, WLAN_EID_MEASURE_REPORT))) {
59 if (ie[1] < 5) {
60 wpa_printf(MSG_DEBUG, "Bad Measurement Report element");
61 break;
62 }
63
64 wpa_printf(MSG_DEBUG, "Measurement report type %u", ie[4]);
65
66 switch (ie[4]) {
67 case MEASURE_TYPE_LCI:
68 hostapd_handle_lci_report(hapd, token, ie + 2, ie[1]);
69 break;
70 default:
71 wpa_printf(MSG_DEBUG,
72 "Measurement report type %u is not supported",
73 ie[4]);
74 break;
75 }
76
77 pos = ie + ie[1] + 2;
78 }
79 }
80
81
82 static u16 hostapd_parse_location_lci_req_age(const u8 *buf, size_t len)
83 {
84 const u8 *subelem;
85
86 /* Range Request element + Location Subject + Maximum Age subelement */
87 if (len < 3 + 1 + 4)
88 return 0;
89
90 /* Subelements are arranged as IEs */
91 subelem = get_ie(buf + 4, len - 4, LCI_REQ_SUBELEM_MAX_AGE);
92 if (subelem && subelem[1] == 2)
93 return *(u16 *) (subelem + 2);
94
95 return 0;
96 }
97
98
99 static int hostapd_check_lci_age(struct hostapd_neighbor_entry *nr, u16 max_age)
100 {
101 struct os_time curr, diff;
102 unsigned long diff_l;
103
104 if (!max_age)
105 return 0;
106
107 if (max_age == 0xffff)
108 return 1;
109
110 if (os_get_time(&curr))
111 return 0;
112
113 os_time_sub(&curr, &nr->lci_date, &diff);
114
115 /* avoid overflow */
116 if (diff.sec > 0xffff)
117 return 0;
118
119 /* LCI age is calculated in 10th of a second units. */
120 diff_l = diff.sec * 10 + diff.usec / 100000;
121
122 return max_age > diff_l;
123 }
124
125
126 static size_t hostapd_neighbor_report_len(struct wpabuf *buf,
127 struct hostapd_neighbor_entry *nr,
128 int send_lci, int send_civic)
129 {
130 size_t len = 2 + wpabuf_len(nr->nr);
131
132 if (send_lci && nr->lci)
133 len += 2 + wpabuf_len(nr->lci);
134
135 if (send_civic && nr->civic)
136 len += 2 + wpabuf_len(nr->civic);
137
138 return len;
139 }
140
141
142 static void hostapd_send_nei_report_resp(struct hostapd_data *hapd,
143 const u8 *addr, u8 dialog_token,
144 struct wpa_ssid_value *ssid, u8 lci,
145 u8 civic, u16 lci_max_age)
146 {
147 struct hostapd_neighbor_entry *nr;
148 struct wpabuf *buf;
149 u8 *msmt_token;
150
151 /*
152 * The number and length of the Neighbor Report elements in a Neighbor
153 * Report frame is limited by the maximum allowed MMPDU size; + 3 bytes
154 * of RRM header.
155 */
156 buf = wpabuf_alloc(3 + IEEE80211_MAX_MMPDU_SIZE);
157 if (!buf)
158 return;
159
160 wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
161 wpabuf_put_u8(buf, WLAN_RRM_NEIGHBOR_REPORT_RESPONSE);
162 wpabuf_put_u8(buf, dialog_token);
163
164 dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry,
165 list) {
166 int send_lci;
167 size_t len;
168
169 if (ssid->ssid_len != nr->ssid.ssid_len ||
170 os_memcmp(ssid->ssid, nr->ssid.ssid, ssid->ssid_len) != 0)
171 continue;
172
173 send_lci = (lci != 0) && hostapd_check_lci_age(nr, lci_max_age);
174 len = hostapd_neighbor_report_len(buf, nr, send_lci, civic);
175
176 if (len - 2 > 0xff) {
177 wpa_printf(MSG_DEBUG,
178 "NR entry for " MACSTR " exceeds 0xFF bytes",
179 MAC2STR(nr->bssid));
180 continue;
181 }
182
183 if (len > wpabuf_tailroom(buf))
184 break;
185
186 wpabuf_put_u8(buf, WLAN_EID_NEIGHBOR_REPORT);
187 wpabuf_put_u8(buf, len - 2);
188 wpabuf_put_buf(buf, nr->nr);
189
190 if (send_lci && nr->lci) {
191 wpabuf_put_u8(buf, WLAN_EID_MEASURE_REPORT);
192 wpabuf_put_u8(buf, wpabuf_len(nr->lci));
193 /*
194 * Override measurement token - the first byte of the
195 * Measurement Report element.
196 */
197 msmt_token = wpabuf_put(buf, 0);
198 wpabuf_put_buf(buf, nr->lci);
199 *msmt_token = lci;
200 }
201
202 if (civic && nr->civic) {
203 wpabuf_put_u8(buf, WLAN_EID_MEASURE_REPORT);
204 wpabuf_put_u8(buf, wpabuf_len(nr->civic));
205 /*
206 * Override measurement token - the first byte of the
207 * Measurement Report element.
208 */
209 msmt_token = wpabuf_put(buf, 0);
210 wpabuf_put_buf(buf, nr->civic);
211 *msmt_token = civic;
212 }
213 }
214
215 hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr,
216 wpabuf_head(buf), wpabuf_len(buf));
217 wpabuf_free(buf);
218 }
219
220
221 static void hostapd_handle_nei_report_req(struct hostapd_data *hapd,
222 const u8 *buf, size_t len)
223 {
224 const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf;
225 const u8 *pos, *ie, *end;
226 struct wpa_ssid_value ssid = {
227 .ssid_len = 0
228 };
229 u8 token;
230 u8 lci = 0, civic = 0; /* Measurement tokens */
231 u16 lci_max_age = 0;
232
233 if (!(hapd->conf->radio_measurements[0] &
234 WLAN_RRM_CAPS_NEIGHBOR_REPORT))
235 return;
236
237 end = buf + len;
238
239 token = mgmt->u.action.u.rrm.dialog_token;
240 pos = mgmt->u.action.u.rrm.variable;
241 len = end - pos;
242
243 ie = get_ie(pos, len, WLAN_EID_SSID);
244 if (ie && ie[1] && ie[1] <= SSID_MAX_LEN) {
245 ssid.ssid_len = ie[1];
246 os_memcpy(ssid.ssid, ie + 2, ssid.ssid_len);
247 } else {
248 ssid.ssid_len = hapd->conf->ssid.ssid_len;
249 os_memcpy(ssid.ssid, hapd->conf->ssid.ssid, ssid.ssid_len);
250 }
251
252 while ((ie = get_ie(pos, len, WLAN_EID_MEASURE_REQUEST))) {
253 if (ie[1] < 3)
254 break;
255
256 wpa_printf(MSG_DEBUG,
257 "Neighbor report request, measure type %u",
258 ie[4]);
259
260 switch (ie[4]) { /* Measurement Type */
261 case MEASURE_TYPE_LCI:
262 lci = ie[2]; /* Measurement Token */
263 lci_max_age = hostapd_parse_location_lci_req_age(ie + 2,
264 ie[1]);
265 break;
266 case MEASURE_TYPE_LOCATION_CIVIC:
267 civic = ie[2]; /* Measurement token */
268 break;
269 }
270
271 pos = ie + ie[1] + 2;
272 len = end - pos;
273 }
274
275 hostapd_send_nei_report_resp(hapd, mgmt->sa, token, &ssid, lci, civic,
276 lci_max_age);
277 }
278
279
280 void hostapd_handle_radio_measurement(struct hostapd_data *hapd,
281 const u8 *buf, size_t len)
282 {
283 const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf;
284
285 /*
286 * Check for enough bytes: header + (1B)Category + (1B)Action +
287 * (1B)Dialog Token.
288 */
289 if (len < IEEE80211_HDRLEN + 3)
290 return;
291
292 wpa_printf(MSG_DEBUG, "Radio measurement frame, action %u from " MACSTR,
293 mgmt->u.action.u.rrm.action, MAC2STR(mgmt->sa));
294
295 switch (mgmt->u.action.u.rrm.action) {
296 case WLAN_RRM_RADIO_MEASUREMENT_REPORT:
297 hostapd_handle_radio_msmt_report(hapd, buf, len);
298 break;
299 case WLAN_RRM_NEIGHBOR_REPORT_REQUEST:
300 hostapd_handle_nei_report_req(hapd, buf, len);
301 break;
302 default:
303 wpa_printf(MSG_DEBUG, "RRM action %u is not supported",
304 mgmt->u.action.u.rrm.action);
305 break;
306 }
307 }
308
309
310 int hostapd_send_lci_req(struct hostapd_data *hapd, const u8 *addr)
311 {
312 struct wpabuf *buf;
313 struct sta_info *sta = ap_get_sta(hapd, addr);
314 int ret;
315
316 if (!sta) {
317 wpa_printf(MSG_INFO,
318 "Request LCI: Destination address is not in station list");
319 return -1;
320 }
321
322 if (!(sta->flags & WLAN_STA_AUTHORIZED)) {
323 wpa_printf(MSG_INFO,
324 "Request LCI: Destination address is not connected");
325 return -1;
326 }
327
328 if (!(sta->rrm_enabled_capa[1] & WLAN_RRM_CAPS_LCI_MEASUREMENT)) {
329 wpa_printf(MSG_INFO,
330 "Request LCI: Station does not support LCI in RRM");
331 return -1;
332 }
333
334 if (hapd->lci_req_active) {
335 wpa_printf(MSG_DEBUG,
336 "Request LCI: LCI request is already in process, overriding");
337 hapd->lci_req_active = 0;
338 eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd,
339 NULL);
340 }
341
342 /* Measurement request (5) + Measurement element with LCI (10) */
343 buf = wpabuf_alloc(5 + 10);
344 if (!buf)
345 return -1;
346
347 hapd->lci_req_token++;
348 /* For wraparounds - the token must be nonzero */
349 if (!hapd->lci_req_token)
350 hapd->lci_req_token++;
351
352 wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
353 wpabuf_put_u8(buf, WLAN_RRM_RADIO_MEASUREMENT_REQUEST);
354 wpabuf_put_u8(buf, hapd->lci_req_token);
355 wpabuf_put_le16(buf, 0); /* Number of repetitions */
356
357 wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
358 wpabuf_put_u8(buf, 3 + 1 + 4);
359
360 wpabuf_put_u8(buf, 1); /* Measurement Token */
361 /*
362 * Parallel and Enable bits are 0, Duration, Request, and Report are
363 * reserved.
364 */
365 wpabuf_put_u8(buf, 0);
366 wpabuf_put_u8(buf, MEASURE_TYPE_LCI);
367
368 wpabuf_put_u8(buf, LOCATION_SUBJECT_REMOTE);
369
370 wpabuf_put_u8(buf, LCI_REQ_SUBELEM_MAX_AGE);
371 wpabuf_put_u8(buf, 2);
372 wpabuf_put_le16(buf, 0xffff);
373
374 ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr,
375 wpabuf_head(buf), wpabuf_len(buf));
376 wpabuf_free(buf);
377 if (ret)
378 return ret;
379
380 hapd->lci_req_active = 1;
381
382 eloop_register_timeout(HOSTAPD_RRM_REQUEST_TIMEOUT, 0,
383 hostapd_lci_rep_timeout_handler, hapd, NULL);
384
385 return 0;
386 }
387
388
389 void hostapd_clean_rrm(struct hostapd_data *hapd)
390 {
391 hostpad_free_neighbor_db(hapd);
392 eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd, NULL);
393 hapd->lci_req_active = 0;
394 }