]> git.ipfire.org Git - thirdparty/hostap.git/blame - wpa_supplicant/rrm.c
wpa_supplicant: Move RRM implementation to a dedicated file
[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
342void wpas_rrm_handle_radio_measurement_request(struct wpa_supplicant *wpa_s,
343 const u8 *src,
344 const u8 *frame, size_t len)
345{
346 struct wpabuf *buf, *report;
347 u8 token;
348 const u8 *ie, *end;
349
350 if (wpa_s->wpa_state != WPA_COMPLETED) {
351 wpa_printf(MSG_INFO,
352 "RRM: Ignoring radio measurement request: Not associated");
353 return;
354 }
355
356 if (!wpa_s->rrm.rrm_used) {
357 wpa_printf(MSG_INFO,
358 "RRM: Ignoring radio measurement request: Not RRM network");
359 return;
360 }
361
362 if (len < 3) {
363 wpa_printf(MSG_INFO,
364 "RRM: Ignoring too short radio measurement request");
365 return;
366 }
367
368 end = frame + len;
369
370 token = *frame++;
371
372 /* Ignore number of repetitions because it's not used in LCI request */
373 frame += 2;
374
375 report = NULL;
376 while ((ie = get_ie(frame, end - frame, WLAN_EID_MEASURE_REQUEST)) &&
377 ie[1] >= 3) {
378 u8 msmt_type;
379
380 msmt_type = ie[4];
381 wpa_printf(MSG_DEBUG, "RRM request %d", msmt_type);
382
383 switch (msmt_type) {
384 case MEASURE_TYPE_LCI:
385 report = wpas_rrm_build_lci_report(wpa_s, ie + 2, ie[1],
386 report);
387 break;
388 default:
389 wpa_printf(MSG_INFO,
390 "RRM: Unsupported radio measurement request %d",
391 msmt_type);
392 break;
393 }
394
395 frame = ie + ie[1] + 2;
396 }
397
398 if (!report)
399 return;
400
401 buf = wpabuf_alloc(3 + wpabuf_len(report));
402 if (!buf) {
403 wpabuf_free(report);
404 return;
405 }
406
407 wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
408 wpabuf_put_u8(buf, WLAN_RRM_RADIO_MEASUREMENT_REPORT);
409 wpabuf_put_u8(buf, token);
410
411 wpabuf_put_buf(buf, report);
412 wpabuf_free(report);
413
414 if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, src,
415 wpa_s->own_addr, wpa_s->bssid,
416 wpabuf_head(buf), wpabuf_len(buf), 0)) {
417 wpa_printf(MSG_ERROR,
418 "RRM: Radio measurement report failed: Sending Action frame failed");
419 }
420 wpabuf_free(buf);
421}
422
423
424void wpas_rrm_handle_link_measurement_request(struct wpa_supplicant *wpa_s,
425 const u8 *src,
426 const u8 *frame, size_t len,
427 int rssi)
428{
429 struct wpabuf *buf;
430 const struct rrm_link_measurement_request *req;
431 struct rrm_link_measurement_report report;
432
433 if (wpa_s->wpa_state != WPA_COMPLETED) {
434 wpa_printf(MSG_INFO,
435 "RRM: Ignoring link measurement request. Not associated");
436 return;
437 }
438
439 if (!wpa_s->rrm.rrm_used) {
440 wpa_printf(MSG_INFO,
441 "RRM: Ignoring link measurement request. Not RRM network");
442 return;
443 }
444
445 if (!(wpa_s->drv_rrm_flags & WPA_DRIVER_FLAGS_TX_POWER_INSERTION)) {
446 wpa_printf(MSG_INFO,
447 "RRM: Measurement report failed. TX power insertion not supported");
448 return;
449 }
450
451 req = (const struct rrm_link_measurement_request *) frame;
452 if (len < sizeof(*req)) {
453 wpa_printf(MSG_INFO,
454 "RRM: Link measurement report failed. Request too short");
455 return;
456 }
457
458 os_memset(&report, 0, sizeof(report));
459 report.tpc.eid = WLAN_EID_TPC_REPORT;
460 report.tpc.len = 2;
461 report.rsni = 255; /* 255 indicates that RSNI is not available */
462 report.dialog_token = req->dialog_token;
463
464 /*
465 * It's possible to estimate RCPI based on RSSI in dBm. This
466 * calculation will not reflect the correct value for high rates,
467 * but it's good enough for Action frames which are transmitted
468 * with up to 24 Mbps rates.
469 */
470 if (!rssi)
471 report.rcpi = 255; /* not available */
472 else if (rssi < -110)
473 report.rcpi = 0;
474 else if (rssi > 0)
475 report.rcpi = 220;
476 else
477 report.rcpi = (rssi + 110) * 2;
478
479 /* action_category + action_code */
480 buf = wpabuf_alloc(2 + sizeof(report));
481 if (buf == NULL) {
482 wpa_printf(MSG_ERROR,
483 "RRM: Link measurement report failed. Buffer allocation failed");
484 return;
485 }
486
487 wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
488 wpabuf_put_u8(buf, WLAN_RRM_LINK_MEASUREMENT_REPORT);
489 wpabuf_put_data(buf, &report, sizeof(report));
490 wpa_hexdump(MSG_DEBUG, "RRM: Link measurement report:",
491 wpabuf_head(buf), wpabuf_len(buf));
492
493 if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, src,
494 wpa_s->own_addr, wpa_s->bssid,
495 wpabuf_head(buf), wpabuf_len(buf), 0)) {
496 wpa_printf(MSG_ERROR,
497 "RRM: Link measurement report failed. Send action failed");
498 }
499 wpabuf_free(buf);
500}