Change version number to v2.6 for the release
[mech_eap.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_range_rep_timeout_handler(void *eloop_data, void *user_ctx)
48 {
49         struct hostapd_data *hapd = eloop_data;
50
51         wpa_printf(MSG_DEBUG, "RRM: Range request (token %u) timed out",
52                    hapd->range_req_token);
53         hapd->range_req_active = 0;
54 }
55
56
57 static void hostapd_handle_range_report(struct hostapd_data *hapd, u8 token,
58                                         const u8 *pos, size_t len)
59 {
60         if (!hapd->range_req_active || hapd->range_req_token != token) {
61                 wpa_printf(MSG_DEBUG, "Unexpected range report, token %u",
62                            token);
63                 return;
64         }
65
66         hapd->range_req_active = 0;
67         eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd, NULL);
68         wpa_printf(MSG_DEBUG, "Range report token %u len %zu", token, len);
69 }
70
71
72 static void hostapd_handle_radio_msmt_report(struct hostapd_data *hapd,
73                                              const u8 *buf, size_t len)
74 {
75         const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf;
76         const u8 *pos, *ie, *end;
77         u8 token;
78
79         end = buf + len;
80         token = mgmt->u.action.u.rrm.dialog_token;
81         pos = mgmt->u.action.u.rrm.variable;
82
83         while ((ie = get_ie(pos, end - pos, WLAN_EID_MEASURE_REPORT))) {
84                 if (ie[1] < 5) {
85                         wpa_printf(MSG_DEBUG, "Bad Measurement Report element");
86                         break;
87                 }
88
89                 wpa_printf(MSG_DEBUG, "Measurement report type %u", ie[4]);
90
91                 switch (ie[4]) {
92                 case MEASURE_TYPE_LCI:
93                         hostapd_handle_lci_report(hapd, token, ie + 2, ie[1]);
94                         break;
95                 case MEASURE_TYPE_FTM_RANGE:
96                         hostapd_handle_range_report(hapd, token, ie + 2, ie[1]);
97                         break;
98                 default:
99                         wpa_printf(MSG_DEBUG,
100                                    "Measurement report type %u is not supported",
101                                    ie[4]);
102                         break;
103                 }
104
105                 pos = ie + ie[1] + 2;
106         }
107 }
108
109
110 static u16 hostapd_parse_location_lci_req_age(const u8 *buf, size_t len)
111 {
112         const u8 *subelem;
113
114         /* Range Request element + Location Subject + Maximum Age subelement */
115         if (len < 3 + 1 + 4)
116                 return 0;
117
118         /* Subelements are arranged as IEs */
119         subelem = get_ie(buf + 4, len - 4, LCI_REQ_SUBELEM_MAX_AGE);
120         if (subelem && subelem[1] == 2)
121                 return *(u16 *) (subelem + 2);
122
123         return 0;
124 }
125
126
127 static int hostapd_check_lci_age(struct hostapd_neighbor_entry *nr, u16 max_age)
128 {
129         struct os_time curr, diff;
130         unsigned long diff_l;
131
132         if (!max_age)
133                 return 0;
134
135         if (max_age == 0xffff)
136                 return 1;
137
138         if (os_get_time(&curr))
139                 return 0;
140
141         os_time_sub(&curr, &nr->lci_date, &diff);
142
143         /* avoid overflow */
144         if (diff.sec > 0xffff)
145                 return 0;
146
147         /* LCI age is calculated in 10th of a second units. */
148         diff_l = diff.sec * 10 + diff.usec / 100000;
149
150         return max_age > diff_l;
151 }
152
153
154 static size_t hostapd_neighbor_report_len(struct wpabuf *buf,
155                                           struct hostapd_neighbor_entry *nr,
156                                           int send_lci, int send_civic)
157 {
158         size_t len = 2 + wpabuf_len(nr->nr);
159
160         if (send_lci && nr->lci)
161                 len += 2 + wpabuf_len(nr->lci);
162
163         if (send_civic && nr->civic)
164                 len += 2 + wpabuf_len(nr->civic);
165
166         return len;
167 }
168
169
170 static void hostapd_send_nei_report_resp(struct hostapd_data *hapd,
171                                          const u8 *addr, u8 dialog_token,
172                                          struct wpa_ssid_value *ssid, u8 lci,
173                                          u8 civic, u16 lci_max_age)
174 {
175         struct hostapd_neighbor_entry *nr;
176         struct wpabuf *buf;
177         u8 *msmt_token;
178
179         /*
180          * The number and length of the Neighbor Report elements in a Neighbor
181          * Report frame is limited by the maximum allowed MMPDU size; + 3 bytes
182          * of RRM header.
183          */
184         buf = wpabuf_alloc(3 + IEEE80211_MAX_MMPDU_SIZE);
185         if (!buf)
186                 return;
187
188         wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
189         wpabuf_put_u8(buf, WLAN_RRM_NEIGHBOR_REPORT_RESPONSE);
190         wpabuf_put_u8(buf, dialog_token);
191
192         dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry,
193                          list) {
194                 int send_lci;
195                 size_t len;
196
197                 if (ssid->ssid_len != nr->ssid.ssid_len ||
198                     os_memcmp(ssid->ssid, nr->ssid.ssid, ssid->ssid_len) != 0)
199                         continue;
200
201                 send_lci = (lci != 0) && hostapd_check_lci_age(nr, lci_max_age);
202                 len = hostapd_neighbor_report_len(buf, nr, send_lci, civic);
203
204                 if (len - 2 > 0xff) {
205                         wpa_printf(MSG_DEBUG,
206                                    "NR entry for " MACSTR " exceeds 0xFF bytes",
207                                    MAC2STR(nr->bssid));
208                         continue;
209                 }
210
211                 if (len > wpabuf_tailroom(buf))
212                         break;
213
214                 wpabuf_put_u8(buf, WLAN_EID_NEIGHBOR_REPORT);
215                 wpabuf_put_u8(buf, len - 2);
216                 wpabuf_put_buf(buf, nr->nr);
217
218                 if (send_lci && nr->lci) {
219                         wpabuf_put_u8(buf, WLAN_EID_MEASURE_REPORT);
220                         wpabuf_put_u8(buf, wpabuf_len(nr->lci));
221                         /*
222                          * Override measurement token - the first byte of the
223                          * Measurement Report element.
224                          */
225                         msmt_token = wpabuf_put(buf, 0);
226                         wpabuf_put_buf(buf, nr->lci);
227                         *msmt_token = lci;
228                 }
229
230                 if (civic && nr->civic) {
231                         wpabuf_put_u8(buf, WLAN_EID_MEASURE_REPORT);
232                         wpabuf_put_u8(buf, wpabuf_len(nr->civic));
233                         /*
234                          * Override measurement token - the first byte of the
235                          * Measurement Report element.
236                          */
237                         msmt_token = wpabuf_put(buf, 0);
238                         wpabuf_put_buf(buf, nr->civic);
239                         *msmt_token = civic;
240                 }
241         }
242
243         hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr,
244                                 wpabuf_head(buf), wpabuf_len(buf));
245         wpabuf_free(buf);
246 }
247
248
249 static void hostapd_handle_nei_report_req(struct hostapd_data *hapd,
250                                           const u8 *buf, size_t len)
251 {
252         const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf;
253         const u8 *pos, *ie, *end;
254         struct wpa_ssid_value ssid = {
255                 .ssid_len = 0
256         };
257         u8 token;
258         u8 lci = 0, civic = 0; /* Measurement tokens */
259         u16 lci_max_age = 0;
260
261         if (!(hapd->conf->radio_measurements[0] &
262               WLAN_RRM_CAPS_NEIGHBOR_REPORT))
263                 return;
264
265         end = buf + len;
266
267         token = mgmt->u.action.u.rrm.dialog_token;
268         pos = mgmt->u.action.u.rrm.variable;
269         len = end - pos;
270
271         ie = get_ie(pos, len, WLAN_EID_SSID);
272         if (ie && ie[1] && ie[1] <= SSID_MAX_LEN) {
273                 ssid.ssid_len = ie[1];
274                 os_memcpy(ssid.ssid, ie + 2, ssid.ssid_len);
275         } else {
276                 ssid.ssid_len = hapd->conf->ssid.ssid_len;
277                 os_memcpy(ssid.ssid, hapd->conf->ssid.ssid, ssid.ssid_len);
278         }
279
280         while ((ie = get_ie(pos, len, WLAN_EID_MEASURE_REQUEST))) {
281                 if (ie[1] < 3)
282                         break;
283
284                 wpa_printf(MSG_DEBUG,
285                            "Neighbor report request, measure type %u",
286                            ie[4]);
287
288                 switch (ie[4]) { /* Measurement Type */
289                 case MEASURE_TYPE_LCI:
290                         lci = ie[2]; /* Measurement Token */
291                         lci_max_age = hostapd_parse_location_lci_req_age(ie + 2,
292                                                                          ie[1]);
293                         break;
294                 case MEASURE_TYPE_LOCATION_CIVIC:
295                         civic = ie[2]; /* Measurement token */
296                         break;
297                 }
298
299                 pos = ie + ie[1] + 2;
300                 len = end - pos;
301         }
302
303         hostapd_send_nei_report_resp(hapd, mgmt->sa, token, &ssid, lci, civic,
304                                      lci_max_age);
305 }
306
307
308 void hostapd_handle_radio_measurement(struct hostapd_data *hapd,
309                                       const u8 *buf, size_t len)
310 {
311         const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf;
312
313         /*
314          * Check for enough bytes: header + (1B)Category + (1B)Action +
315          * (1B)Dialog Token.
316          */
317         if (len < IEEE80211_HDRLEN + 3)
318                 return;
319
320         wpa_printf(MSG_DEBUG, "Radio measurement frame, action %u from " MACSTR,
321                    mgmt->u.action.u.rrm.action, MAC2STR(mgmt->sa));
322
323         switch (mgmt->u.action.u.rrm.action) {
324         case WLAN_RRM_RADIO_MEASUREMENT_REPORT:
325                 hostapd_handle_radio_msmt_report(hapd, buf, len);
326                 break;
327         case WLAN_RRM_NEIGHBOR_REPORT_REQUEST:
328                 hostapd_handle_nei_report_req(hapd, buf, len);
329                 break;
330         default:
331                 wpa_printf(MSG_DEBUG, "RRM action %u is not supported",
332                            mgmt->u.action.u.rrm.action);
333                 break;
334         }
335 }
336
337
338 int hostapd_send_lci_req(struct hostapd_data *hapd, const u8 *addr)
339 {
340         struct wpabuf *buf;
341         struct sta_info *sta = ap_get_sta(hapd, addr);
342         int ret;
343
344         if (!sta) {
345                 wpa_printf(MSG_INFO,
346                            "Request LCI: Destination address is not in station list");
347                 return -1;
348         }
349
350         if (!(sta->flags & WLAN_STA_AUTHORIZED)) {
351                 wpa_printf(MSG_INFO,
352                            "Request LCI: Destination address is not connected");
353                 return -1;
354         }
355
356         if (!(sta->rrm_enabled_capa[1] & WLAN_RRM_CAPS_LCI_MEASUREMENT)) {
357                 wpa_printf(MSG_INFO,
358                            "Request LCI: Station does not support LCI in RRM");
359                 return -1;
360         }
361
362         if (hapd->lci_req_active) {
363                 wpa_printf(MSG_DEBUG,
364                            "Request LCI: LCI request is already in process, overriding");
365                 hapd->lci_req_active = 0;
366                 eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd,
367                                      NULL);
368         }
369
370         /* Measurement request (5) + Measurement element with LCI (10) */
371         buf = wpabuf_alloc(5 + 10);
372         if (!buf)
373                 return -1;
374
375         hapd->lci_req_token++;
376         /* For wraparounds - the token must be nonzero */
377         if (!hapd->lci_req_token)
378                 hapd->lci_req_token++;
379
380         wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
381         wpabuf_put_u8(buf, WLAN_RRM_RADIO_MEASUREMENT_REQUEST);
382         wpabuf_put_u8(buf, hapd->lci_req_token);
383         wpabuf_put_le16(buf, 0); /* Number of repetitions */
384
385         wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
386         wpabuf_put_u8(buf, 3 + 1 + 4);
387
388         wpabuf_put_u8(buf, 1); /* Measurement Token */
389         /*
390          * Parallel and Enable bits are 0, Duration, Request, and Report are
391          * reserved.
392          */
393         wpabuf_put_u8(buf, 0);
394         wpabuf_put_u8(buf, MEASURE_TYPE_LCI);
395
396         wpabuf_put_u8(buf, LOCATION_SUBJECT_REMOTE);
397
398         wpabuf_put_u8(buf, LCI_REQ_SUBELEM_MAX_AGE);
399         wpabuf_put_u8(buf, 2);
400         wpabuf_put_le16(buf, 0xffff);
401
402         ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr,
403                                       wpabuf_head(buf), wpabuf_len(buf));
404         wpabuf_free(buf);
405         if (ret)
406                 return ret;
407
408         hapd->lci_req_active = 1;
409
410         eloop_register_timeout(HOSTAPD_RRM_REQUEST_TIMEOUT, 0,
411                                hostapd_lci_rep_timeout_handler, hapd, NULL);
412
413         return 0;
414 }
415
416
417 int hostapd_send_range_req(struct hostapd_data *hapd, const u8 *addr,
418                            u16 random_interval, u8 min_ap,
419                            const u8 *responders, unsigned int n_responders)
420 {
421         struct wpabuf *buf;
422         struct sta_info *sta;
423         u8 *len;
424         unsigned int i;
425         int ret;
426
427         wpa_printf(MSG_DEBUG, "Request range: dest addr " MACSTR
428                    " rand interval %u min AP %u n_responders %u", MAC2STR(addr),
429                    random_interval, min_ap, n_responders);
430
431         if (min_ap == 0 || min_ap > n_responders) {
432                 wpa_printf(MSG_INFO, "Request range: Wrong min AP count");
433                 return -1;
434         }
435
436         sta = ap_get_sta(hapd, addr);
437         if (!sta || !(sta->flags & WLAN_STA_AUTHORIZED)) {
438                 wpa_printf(MSG_INFO,
439                            "Request range: Destination address is not connected");
440                 return -1;
441         }
442
443         if (!(sta->rrm_enabled_capa[4] & WLAN_RRM_CAPS_FTM_RANGE_REPORT)) {
444                 wpa_printf(MSG_ERROR,
445                            "Request range: Destination station does not support FTM range report in RRM");
446                 return -1;
447         }
448
449         if (hapd->range_req_active) {
450                 wpa_printf(MSG_DEBUG,
451                            "Request range: Range request is already in process; overriding");
452                 hapd->range_req_active = 0;
453                 eloop_register_timeout(HOSTAPD_RRM_REQUEST_TIMEOUT, 0,
454                                        hostapd_range_rep_timeout_handler, hapd,
455                                        NULL);
456         }
457
458         /* Action + measurement type + token + reps + EID + len = 7 */
459         buf = wpabuf_alloc(7 + 255);
460         if (!buf)
461                 return -1;
462
463         hapd->range_req_token++;
464         if (!hapd->range_req_token) /* For wraparounds */
465                 hapd->range_req_token++;
466
467         /* IEEE P802.11-REVmc/D5.0, 9.6.7.2 */
468         wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
469         wpabuf_put_u8(buf, WLAN_RRM_RADIO_MEASUREMENT_REQUEST);
470         wpabuf_put_u8(buf, hapd->range_req_token); /* Dialog Token */
471         wpabuf_put_le16(buf, 0); /* Number of Repetitions */
472
473         /* IEEE P802.11-REVmc/D5.0, 9.4.2.21 */
474         wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
475         len = wpabuf_put(buf, 1); /* Length will be set later */
476
477         wpabuf_put_u8(buf, 1); /* Measurement Token */
478         /*
479          * Parallel and Enable bits are 0; Duration, Request, and Report are
480          * reserved.
481          */
482         wpabuf_put_u8(buf, 0); /* Measurement Request Mode */
483         wpabuf_put_u8(buf, MEASURE_TYPE_FTM_RANGE); /* Measurement Type */
484
485         /* IEEE P802.11-REVmc/D5.0, 9.4.2.21.19 */
486         wpabuf_put_le16(buf, random_interval); /* Randomization Interval */
487         wpabuf_put_u8(buf, min_ap); /* Minimum AP Count */
488
489         /* FTM Range Subelements */
490
491         /*
492          * Taking the neighbor report part of the range request from neighbor
493          * database instead of requesting the separate bits of data from the
494          * user.
495          */
496         for (i = 0; i < n_responders; i++) {
497                 struct hostapd_neighbor_entry *nr;
498
499                 nr = hostapd_neighbor_get(hapd, responders + ETH_ALEN * i,
500                                           NULL);
501                 if (!nr) {
502                         wpa_printf(MSG_INFO, "Missing neighbor report for "
503                                    MACSTR, MAC2STR(responders + ETH_ALEN * i));
504                         wpabuf_free(buf);
505                         return -1;
506                 }
507
508                 if (wpabuf_tailroom(buf) < 2 + wpabuf_len(nr->nr)) {
509                         wpa_printf(MSG_ERROR, "Too long range request");
510                         wpabuf_free(buf);
511                         return -1;
512                 }
513
514                 wpabuf_put_u8(buf, WLAN_EID_NEIGHBOR_REPORT);
515                 wpabuf_put_u8(buf, wpabuf_len(nr->nr));
516                 wpabuf_put_buf(buf, nr->nr);
517         }
518
519         /* Action + measurement type + token + reps + EID + len = 7 */
520         *len = wpabuf_len(buf) - 7;
521
522         ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr,
523                                       wpabuf_head(buf), wpabuf_len(buf));
524         wpabuf_free(buf);
525         if (ret)
526                 return ret;
527
528         hapd->range_req_active = 1;
529
530         eloop_register_timeout(HOSTAPD_RRM_REQUEST_TIMEOUT, 0,
531                                hostapd_range_rep_timeout_handler, hapd, NULL);
532
533         return 0;
534 }
535
536
537 void hostapd_clean_rrm(struct hostapd_data *hapd)
538 {
539         hostpad_free_neighbor_db(hapd);
540         eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd, NULL);
541         hapd->lci_req_active = 0;
542         eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd, NULL);
543         hapd->range_req_active = 0;
544 }