8548ea406b5aacf2d3230a7c3425661a9896a8b1
[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_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 }