]> git.ipfire.org Git - thirdparty/hostap.git/blame - wpa_supplicant/rrm.c
wpa_supplicant: Refactor Radio Measurement Request handling
[thirdparty/hostap.git] / wpa_supplicant / rrm.c
CommitLineData
ec493469
AS
1/*
2 * wpa_supplicant - Radio Measurements
3 * Copyright (c) 2003-2016, Jouni Malinen <j@w1.fi>
4 *
5 * This software may be distributed under the terms of the BSD license.
6 * See README for more details.
7 */
8
9#include "includes.h"
10
11#include "utils/common.h"
12#include "utils/eloop.h"
13#include "common/ieee802_11_common.h"
14#include "wpa_supplicant_i.h"
15#include "driver_i.h"
16#include "bss.h"
17
18
19static void wpas_rrm_neighbor_rep_timeout_handler(void *data, void *user_ctx)
20{
21 struct rrm_data *rrm = data;
22
23 if (!rrm->notify_neighbor_rep) {
24 wpa_printf(MSG_ERROR,
25 "RRM: Unexpected neighbor report timeout");
26 return;
27 }
28
29 wpa_printf(MSG_DEBUG, "RRM: Notifying neighbor report - NONE");
30 rrm->notify_neighbor_rep(rrm->neighbor_rep_cb_ctx, NULL);
31
32 rrm->notify_neighbor_rep = NULL;
33 rrm->neighbor_rep_cb_ctx = NULL;
34}
35
36
37/*
38 * wpas_rrm_reset - Clear and reset all RRM data in wpa_supplicant
39 * @wpa_s: Pointer to wpa_supplicant
40 */
41void wpas_rrm_reset(struct wpa_supplicant *wpa_s)
42{
43 wpa_s->rrm.rrm_used = 0;
44
45 eloop_cancel_timeout(wpas_rrm_neighbor_rep_timeout_handler, &wpa_s->rrm,
46 NULL);
47 if (wpa_s->rrm.notify_neighbor_rep)
48 wpas_rrm_neighbor_rep_timeout_handler(&wpa_s->rrm, NULL);
49 wpa_s->rrm.next_neighbor_rep_token = 1;
50}
51
52
53/*
54 * wpas_rrm_process_neighbor_rep - Handle incoming neighbor report
55 * @wpa_s: Pointer to wpa_supplicant
56 * @report: Neighbor report buffer, prefixed by a 1-byte dialog token
57 * @report_len: Length of neighbor report buffer
58 */
59void wpas_rrm_process_neighbor_rep(struct wpa_supplicant *wpa_s,
60 const u8 *report, size_t report_len)
61{
62 struct wpabuf *neighbor_rep;
63
64 wpa_hexdump(MSG_DEBUG, "RRM: New Neighbor Report", report, report_len);
65 if (report_len < 1)
66 return;
67
68 if (report[0] != wpa_s->rrm.next_neighbor_rep_token - 1) {
69 wpa_printf(MSG_DEBUG,
70 "RRM: Discarding neighbor report with token %d (expected %d)",
71 report[0], wpa_s->rrm.next_neighbor_rep_token - 1);
72 return;
73 }
74
75 eloop_cancel_timeout(wpas_rrm_neighbor_rep_timeout_handler, &wpa_s->rrm,
76 NULL);
77
78 if (!wpa_s->rrm.notify_neighbor_rep) {
79 wpa_printf(MSG_ERROR, "RRM: Unexpected neighbor report");
80 return;
81 }
82
83 /* skipping the first byte, which is only an id (dialog token) */
84 neighbor_rep = wpabuf_alloc(report_len - 1);
85 if (neighbor_rep == NULL)
86 return;
87 wpabuf_put_data(neighbor_rep, report + 1, report_len - 1);
88 wpa_printf(MSG_DEBUG, "RRM: Notifying neighbor report (token = %d)",
89 report[0]);
90 wpa_s->rrm.notify_neighbor_rep(wpa_s->rrm.neighbor_rep_cb_ctx,
91 neighbor_rep);
92 wpa_s->rrm.notify_neighbor_rep = NULL;
93 wpa_s->rrm.neighbor_rep_cb_ctx = NULL;
94}
95
96
97#if defined(__CYGWIN__) || defined(CONFIG_NATIVE_WINDOWS)
98/* Workaround different, undefined for Windows, error codes used here */
99#define ENOTCONN -1
100#define EOPNOTSUPP -1
101#define ECANCELED -1
102#endif
103
104/* Measurement Request element + Location Subject + Maximum Age subelement */
105#define MEASURE_REQUEST_LCI_LEN (3 + 1 + 4)
106/* Measurement Request element + Location Civic Request */
107#define MEASURE_REQUEST_CIVIC_LEN (3 + 5)
108
109
110/**
111 * wpas_rrm_send_neighbor_rep_request - Request a neighbor report from our AP
112 * @wpa_s: Pointer to wpa_supplicant
113 * @ssid: if not null, this is sent in the request. Otherwise, no SSID IE
114 * is sent in the request.
115 * @lci: if set, neighbor request will include LCI request
116 * @civic: if set, neighbor request will include civic location request
117 * @cb: Callback function to be called once the requested report arrives, or
118 * timed out after RRM_NEIGHBOR_REPORT_TIMEOUT seconds.
119 * In the former case, 'neighbor_rep' is a newly allocated wpabuf, and it's
120 * the requester's responsibility to free it.
121 * In the latter case NULL will be sent in 'neighbor_rep'.
122 * @cb_ctx: Context value to send the callback function
123 * Returns: 0 in case of success, negative error code otherwise
124 *
125 * In case there is a previous request which has not been answered yet, the
126 * new request fails. The caller may retry after RRM_NEIGHBOR_REPORT_TIMEOUT.
127 * Request must contain a callback function.
128 */
129int wpas_rrm_send_neighbor_rep_request(struct wpa_supplicant *wpa_s,
130 const struct wpa_ssid_value *ssid,
131 int lci, int civic,
132 void (*cb)(void *ctx,
133 struct wpabuf *neighbor_rep),
134 void *cb_ctx)
135{
136 struct wpabuf *buf;
137 const u8 *rrm_ie;
138
139 if (wpa_s->wpa_state != WPA_COMPLETED || wpa_s->current_ssid == NULL) {
140 wpa_printf(MSG_DEBUG, "RRM: No connection, no RRM.");
141 return -ENOTCONN;
142 }
143
144 if (!wpa_s->rrm.rrm_used) {
145 wpa_printf(MSG_DEBUG, "RRM: No RRM in current connection.");
146 return -EOPNOTSUPP;
147 }
148
149 rrm_ie = wpa_bss_get_ie(wpa_s->current_bss,
150 WLAN_EID_RRM_ENABLED_CAPABILITIES);
151 if (!rrm_ie || !(wpa_s->current_bss->caps & IEEE80211_CAP_RRM) ||
152 !(rrm_ie[2] & WLAN_RRM_CAPS_NEIGHBOR_REPORT)) {
153 wpa_printf(MSG_DEBUG,
154 "RRM: No network support for Neighbor Report.");
155 return -EOPNOTSUPP;
156 }
157
158 if (!cb) {
159 wpa_printf(MSG_DEBUG,
160 "RRM: Neighbor Report request must provide a callback.");
161 return -EINVAL;
162 }
163
164 /* Refuse if there's a live request */
165 if (wpa_s->rrm.notify_neighbor_rep) {
166 wpa_printf(MSG_DEBUG,
167 "RRM: Currently handling previous Neighbor Report.");
168 return -EBUSY;
169 }
170
171 /* 3 = action category + action code + dialog token */
172 buf = wpabuf_alloc(3 + (ssid ? 2 + ssid->ssid_len : 0) +
173 (lci ? 2 + MEASURE_REQUEST_LCI_LEN : 0) +
174 (civic ? 2 + MEASURE_REQUEST_CIVIC_LEN : 0));
175 if (buf == NULL) {
176 wpa_printf(MSG_DEBUG,
177 "RRM: Failed to allocate Neighbor Report Request");
178 return -ENOMEM;
179 }
180
181 wpa_printf(MSG_DEBUG, "RRM: Neighbor report request (for %s), token=%d",
182 (ssid ? wpa_ssid_txt(ssid->ssid, ssid->ssid_len) : ""),
183 wpa_s->rrm.next_neighbor_rep_token);
184
185 wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
186 wpabuf_put_u8(buf, WLAN_RRM_NEIGHBOR_REPORT_REQUEST);
187 wpabuf_put_u8(buf, wpa_s->rrm.next_neighbor_rep_token);
188 if (ssid) {
189 wpabuf_put_u8(buf, WLAN_EID_SSID);
190 wpabuf_put_u8(buf, ssid->ssid_len);
191 wpabuf_put_data(buf, ssid->ssid, ssid->ssid_len);
192 }
193
194 if (lci) {
195 /* IEEE P802.11-REVmc/D5.0 9.4.2.21 */
196 wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
197 wpabuf_put_u8(buf, MEASURE_REQUEST_LCI_LEN);
198
199 /*
200 * Measurement token; nonzero number that is unique among the
201 * Measurement Request elements in a particular frame.
202 */
203 wpabuf_put_u8(buf, 1); /* Measurement Token */
204
205 /*
206 * Parallel, Enable, Request, and Report bits are 0, Duration is
207 * reserved.
208 */
209 wpabuf_put_u8(buf, 0); /* Measurement Request Mode */
210 wpabuf_put_u8(buf, MEASURE_TYPE_LCI); /* Measurement Type */
211
212 /* IEEE P802.11-REVmc/D5.0 9.4.2.21.10 - LCI request */
213 /* Location Subject */
214 wpabuf_put_u8(buf, LOCATION_SUBJECT_REMOTE);
215
216 /* Optional Subelements */
217 /*
218 * IEEE P802.11-REVmc/D5.0 Figure 9-170
219 * The Maximum Age subelement is required, otherwise the AP can
220 * send only data that was determined after receiving the
221 * request. Setting it here to unlimited age.
222 */
223 wpabuf_put_u8(buf, LCI_REQ_SUBELEM_MAX_AGE);
224 wpabuf_put_u8(buf, 2);
225 wpabuf_put_le16(buf, 0xffff);
226 }
227
228 if (civic) {
229 /* IEEE P802.11-REVmc/D5.0 9.4.2.21 */
230 wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
231 wpabuf_put_u8(buf, MEASURE_REQUEST_CIVIC_LEN);
232
233 /*
234 * Measurement token; nonzero number that is unique among the
235 * Measurement Request elements in a particular frame.
236 */
237 wpabuf_put_u8(buf, 2); /* Measurement Token */
238
239 /*
240 * Parallel, Enable, Request, and Report bits are 0, Duration is
241 * reserved.
242 */
243 wpabuf_put_u8(buf, 0); /* Measurement Request Mode */
244 /* Measurement Type */
245 wpabuf_put_u8(buf, MEASURE_TYPE_LOCATION_CIVIC);
246
247 /* IEEE P802.11-REVmc/D5.0 9.4.2.21.14:
248 * Location Civic request */
249 /* Location Subject */
250 wpabuf_put_u8(buf, LOCATION_SUBJECT_REMOTE);
251 wpabuf_put_u8(buf, 0); /* Civic Location Type: IETF RFC 4776 */
252 /* Location Service Interval Units: Seconds */
253 wpabuf_put_u8(buf, 0);
254 /* Location Service Interval: 0 - Only one report is requested
255 */
256 wpabuf_put_le16(buf, 0);
257 /* No optional subelements */
258 }
259
260 wpa_s->rrm.next_neighbor_rep_token++;
261
262 if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
263 wpa_s->own_addr, wpa_s->bssid,
264 wpabuf_head(buf), wpabuf_len(buf), 0) < 0) {
265 wpa_printf(MSG_DEBUG,
266 "RRM: Failed to send Neighbor Report Request");
267 wpabuf_free(buf);
268 return -ECANCELED;
269 }
270
271 wpa_s->rrm.neighbor_rep_cb_ctx = cb_ctx;
272 wpa_s->rrm.notify_neighbor_rep = cb;
273 eloop_register_timeout(RRM_NEIGHBOR_REPORT_TIMEOUT, 0,
274 wpas_rrm_neighbor_rep_timeout_handler,
275 &wpa_s->rrm, NULL);
276
277 wpabuf_free(buf);
278 return 0;
279}
280
281
282static struct wpabuf * wpas_rrm_build_lci_report(struct wpa_supplicant *wpa_s,
283 const u8 *request, size_t len,
284 struct wpabuf *report)
285{
286 u8 token, type, subject;
287 u16 max_age = 0;
288 struct os_reltime t, diff;
289 unsigned long diff_l;
290 u8 *ptoken;
291 const u8 *subelem;
292
293 if (!wpa_s->lci || len < 3 + 4)
294 return report;
295
296 token = *request++;
297 /* Measurement request mode isn't used */
298 request++;
299 type = *request++;
300 subject = *request++;
301 len -= 4;
302
303 wpa_printf(MSG_DEBUG,
304 "Measurement request token %u type %u location subject %u",
305 token, type, subject);
306
307 if (type != MEASURE_TYPE_LCI || subject != LOCATION_SUBJECT_REMOTE) {
308 wpa_printf(MSG_INFO,
309 "Not building LCI report - bad type or location subject");
310 return report;
311 }
312
313 /* Subelements are formatted exactly like elements */
314 subelem = get_ie(request, len, LCI_REQ_SUBELEM_MAX_AGE);
315 if (subelem && subelem[1] == 2)
316 max_age = WPA_GET_LE16(subelem + 2);
317
318 if (os_get_reltime(&t))
319 return report;
320
321 os_reltime_sub(&t, &wpa_s->lci_time, &diff);
322 /* LCI age is calculated in 10th of a second units. */
323 diff_l = diff.sec * 10 + diff.usec / 100000;
324
325 if (max_age != 0xffff && max_age < diff_l)
326 return report;
327
328 if (wpabuf_resize(&report, 2 + wpabuf_len(wpa_s->lci)))
329 return report;
330
331 wpabuf_put_u8(report, WLAN_EID_MEASURE_REPORT);
332 wpabuf_put_u8(report, wpabuf_len(wpa_s->lci));
333 /* We'll override user's measurement token */
334 ptoken = wpabuf_put(report, 0);
335 wpabuf_put_buf(report, wpa_s->lci);
336 *ptoken = token;
337
338 return report;
339}
340
341
9664ab8b
AS
342static int
343wpas_rrm_handle_msr_req_element(
344 struct wpa_supplicant *wpa_s,
345 const struct rrm_measurement_request_element *req,
346 struct wpabuf **buf)
ec493469 347{
9664ab8b
AS
348 wpa_printf(MSG_DEBUG, "Measurement request type %d token %d",
349 req->type, req->token);
350
351 switch (req->type) {
352 case MEASURE_TYPE_LCI:
353 *buf = wpas_rrm_build_lci_report(wpa_s, &req->token, req->len,
354 *buf);
355 break;
356 default:
ec493469 357 wpa_printf(MSG_INFO,
9664ab8b
AS
358 "RRM: Unsupported radio measurement type %u",
359 req->type);
360 break;
ec493469
AS
361 }
362
9664ab8b
AS
363 return 0;
364}
ec493469 365
ec493469 366
9664ab8b
AS
367static struct wpabuf *
368wpas_rrm_process_msr_req_elems(struct wpa_supplicant *wpa_s, const u8 *pos,
369 size_t len)
370{
371 struct wpabuf *buf = NULL;
ec493469 372
9664ab8b 373 while (len) {
332bf5d3 374 const struct rrm_measurement_request_element *req;
9664ab8b 375 int res;
ec493469 376
9664ab8b 377 if (len < 2) {
332bf5d3
AS
378 wpa_printf(MSG_DEBUG, "RRM: Truncated element");
379 goto out;
380 }
381
9664ab8b 382 req = (const struct rrm_measurement_request_element *) pos;
332bf5d3
AS
383 if (req->eid != WLAN_EID_MEASURE_REQUEST) {
384 wpa_printf(MSG_DEBUG,
385 "RRM: Expected Measurement Request element, but EID is %u",
386 req->eid);
387 goto out;
388 }
389
390 if (req->len < 3) {
391 wpa_printf(MSG_DEBUG, "RRM: Element length too short");
392 goto out;
393 }
332bf5d3 394
9664ab8b 395 if (req->len > len - 2) {
332bf5d3
AS
396 wpa_printf(MSG_DEBUG, "RRM: Element length too long");
397 goto out;
398 }
ec493469 399
9664ab8b
AS
400 res = wpas_rrm_handle_msr_req_element(wpa_s, req, &buf);
401 if (res < 0)
402 goto out;
ec493469 403
9664ab8b
AS
404 pos += req->len + 2;
405 len -= req->len + 2;
ec493469
AS
406 }
407
9664ab8b
AS
408 return buf;
409
410out:
411 wpabuf_free(buf);
412 return NULL;
413}
414
415
416void wpas_rrm_handle_radio_measurement_request(struct wpa_supplicant *wpa_s,
417 const u8 *src,
418 const u8 *frame, size_t len)
419{
420 struct wpabuf *buf, *report;
421 u8 token;
422
423 if (wpa_s->wpa_state != WPA_COMPLETED) {
424 wpa_printf(MSG_INFO,
425 "RRM: Ignoring radio measurement request: Not associated");
426 return;
427 }
428
429 if (!wpa_s->rrm.rrm_used) {
430 wpa_printf(MSG_INFO,
431 "RRM: Ignoring radio measurement request: Not RRM network");
432 return;
433 }
434
435 if (len < 3) {
436 wpa_printf(MSG_INFO,
437 "RRM: Ignoring too short radio measurement request");
438 return;
439 }
440
441 token = *frame;
442
443 /* Number of repetitions is not supported */
444
445 report = wpas_rrm_process_msr_req_elems(wpa_s, frame + 3, len - 3);
ec493469
AS
446 if (!report)
447 return;
448
449 buf = wpabuf_alloc(3 + wpabuf_len(report));
9664ab8b
AS
450 if (!buf) {
451 wpabuf_free(report);
452 return;
453 }
ec493469
AS
454
455 wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
456 wpabuf_put_u8(buf, WLAN_RRM_RADIO_MEASUREMENT_REPORT);
457 wpabuf_put_u8(buf, token);
458
459 wpabuf_put_buf(buf, report);
ec493469
AS
460
461 if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, src,
462 wpa_s->own_addr, wpa_s->bssid,
463 wpabuf_head(buf), wpabuf_len(buf), 0)) {
464 wpa_printf(MSG_ERROR,
465 "RRM: Radio measurement report failed: Sending Action frame failed");
466 }
467 wpabuf_free(buf);
332bf5d3 468 wpabuf_free(report);
ec493469
AS
469}
470
471
472void wpas_rrm_handle_link_measurement_request(struct wpa_supplicant *wpa_s,
473 const u8 *src,
474 const u8 *frame, size_t len,
475 int rssi)
476{
477 struct wpabuf *buf;
478 const struct rrm_link_measurement_request *req;
479 struct rrm_link_measurement_report report;
480
481 if (wpa_s->wpa_state != WPA_COMPLETED) {
482 wpa_printf(MSG_INFO,
483 "RRM: Ignoring link measurement request. Not associated");
484 return;
485 }
486
487 if (!wpa_s->rrm.rrm_used) {
488 wpa_printf(MSG_INFO,
489 "RRM: Ignoring link measurement request. Not RRM network");
490 return;
491 }
492
493 if (!(wpa_s->drv_rrm_flags & WPA_DRIVER_FLAGS_TX_POWER_INSERTION)) {
494 wpa_printf(MSG_INFO,
495 "RRM: Measurement report failed. TX power insertion not supported");
496 return;
497 }
498
499 req = (const struct rrm_link_measurement_request *) frame;
500 if (len < sizeof(*req)) {
501 wpa_printf(MSG_INFO,
502 "RRM: Link measurement report failed. Request too short");
503 return;
504 }
505
506 os_memset(&report, 0, sizeof(report));
507 report.tpc.eid = WLAN_EID_TPC_REPORT;
508 report.tpc.len = 2;
509 report.rsni = 255; /* 255 indicates that RSNI is not available */
510 report.dialog_token = req->dialog_token;
511
512 /*
513 * It's possible to estimate RCPI based on RSSI in dBm. This
514 * calculation will not reflect the correct value for high rates,
515 * but it's good enough for Action frames which are transmitted
516 * with up to 24 Mbps rates.
517 */
518 if (!rssi)
519 report.rcpi = 255; /* not available */
520 else if (rssi < -110)
521 report.rcpi = 0;
522 else if (rssi > 0)
523 report.rcpi = 220;
524 else
525 report.rcpi = (rssi + 110) * 2;
526
527 /* action_category + action_code */
528 buf = wpabuf_alloc(2 + sizeof(report));
529 if (buf == NULL) {
530 wpa_printf(MSG_ERROR,
531 "RRM: Link measurement report failed. Buffer allocation failed");
532 return;
533 }
534
535 wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
536 wpabuf_put_u8(buf, WLAN_RRM_LINK_MEASUREMENT_REPORT);
537 wpabuf_put_data(buf, &report, sizeof(report));
538 wpa_hexdump(MSG_DEBUG, "RRM: Link measurement report:",
539 wpabuf_head(buf), wpabuf_len(buf));
540
541 if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, src,
542 wpa_s->own_addr, wpa_s->bssid,
543 wpabuf_head(buf), wpabuf_len(buf), 0)) {
544 wpa_printf(MSG_ERROR,
545 "RRM: Link measurement report failed. Send action failed");
546 }
547 wpabuf_free(buf);
548}