3d3193e09fe2d0a81b652b1aa586f91af66cdd26
[mech_eap.git] / wpa_supplicant / bgscan_learn.c
1 /*
2  * WPA Supplicant - background scan and roaming module: learn
3  * Copyright (c) 2009-2010, Jouni Malinen <j@w1.fi>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 as
7  * published by the Free Software Foundation.
8  *
9  * Alternatively, this software may be distributed under the terms of BSD
10  * license.
11  *
12  * See README and COPYING for more details.
13  */
14
15 #include "includes.h"
16
17 #include "common.h"
18 #include "eloop.h"
19 #include "list.h"
20 #include "common/ieee802_11_defs.h"
21 #include "drivers/driver.h"
22 #include "config_ssid.h"
23 #include "wpa_supplicant_i.h"
24 #include "driver_i.h"
25 #include "scan.h"
26 #include "bgscan.h"
27
28 struct bgscan_learn_bss {
29         struct dl_list list;
30         u8 bssid[ETH_ALEN];
31         int freq;
32         u8 *neigh; /* num_neigh * ETH_ALEN buffer */
33         size_t num_neigh;
34 };
35
36 struct bgscan_learn_data {
37         struct wpa_supplicant *wpa_s;
38         const struct wpa_ssid *ssid;
39         int scan_interval;
40         int signal_threshold;
41         int short_interval; /* use if signal < threshold */
42         int long_interval; /* use if signal > threshold */
43         struct os_time last_bgscan;
44         char *fname;
45         struct dl_list bss;
46         int *supp_freqs;
47         int probe_idx;
48 };
49
50
51 static void bss_free(struct bgscan_learn_bss *bss)
52 {
53         os_free(bss->neigh);
54         os_free(bss);
55 }
56
57
58 static int bssid_in_array(u8 *array, size_t array_len, const u8 *bssid)
59 {
60         size_t i;
61
62         if (array == NULL || array_len == 0)
63                 return 0;
64
65         for (i = 0; i < array_len; i++) {
66                 if (os_memcmp(array + i * ETH_ALEN, bssid, ETH_ALEN) == 0)
67                         return 1;
68         }
69
70         return 0;
71 }
72
73
74 static void bgscan_learn_add_neighbor(struct bgscan_learn_bss *bss,
75                                       const u8 *bssid)
76 {
77         u8 *n;
78
79         if (os_memcmp(bss->bssid, bssid, ETH_ALEN) == 0)
80                 return;
81         if (bssid_in_array(bss->neigh, bss->num_neigh, bssid))
82                 return;
83
84         n = os_realloc(bss->neigh, (bss->num_neigh + 1) * ETH_ALEN);
85         if (n == NULL)
86                 return;
87
88         os_memcpy(n + bss->num_neigh * ETH_ALEN, bssid, ETH_ALEN);
89         bss->neigh = n;
90         bss->num_neigh++;
91         printf("JKM: add neighbor %p\n", bss);
92 }
93
94
95 static struct bgscan_learn_bss * bgscan_learn_get_bss(
96         struct bgscan_learn_data *data, const u8 *bssid)
97 {
98         struct bgscan_learn_bss *bss;
99
100         dl_list_for_each(bss, &data->bss, struct bgscan_learn_bss, list) {
101                 if (os_memcmp(bss->bssid, bssid, ETH_ALEN) == 0)
102                         return bss;
103         }
104         return NULL;
105 }
106
107
108 static int bgscan_learn_load(struct bgscan_learn_data *data)
109 {
110         FILE *f;
111         char buf[128];
112         struct bgscan_learn_bss *bss;
113
114         if (data->fname == NULL)
115                 return 0;
116
117         f = fopen(data->fname, "r");
118         if (f == NULL)
119                 return 0;
120
121         wpa_printf(MSG_DEBUG, "bgscan learn: Loading data from %s",
122                    data->fname);
123
124         if (fgets(buf, sizeof(buf), f) == NULL ||
125             os_strncmp(buf, "wpa_supplicant-bgscan-learn\n", 28) != 0) {
126                 wpa_printf(MSG_INFO, "bgscan learn: Invalid data file %s",
127                            data->fname);
128                 fclose(f);
129                 return -1;
130         }
131
132         while (fgets(buf, sizeof(buf), f)) {
133                 if (os_strncmp(buf, "BSS ", 4) == 0) {
134                         bss = os_zalloc(sizeof(*bss));
135                         if (!bss)
136                                 continue;
137                         if (hwaddr_aton(buf + 4, bss->bssid) < 0) {
138                                 bss_free(bss);
139                                 continue;
140                         }
141                         bss->freq = atoi(buf + 4 + 18);
142                         dl_list_add(&data->bss, &bss->list);
143                         wpa_printf(MSG_DEBUG, "bgscan learn: Loaded BSS "
144                                    "entry: " MACSTR " freq=%d",
145                                    MAC2STR(bss->bssid), bss->freq);
146                 }
147
148                 if (os_strncmp(buf, "NEIGHBOR ", 9) == 0) {
149                         u8 addr[ETH_ALEN];
150
151                         if (hwaddr_aton(buf + 9, addr) < 0)
152                                 continue;
153                         bss = bgscan_learn_get_bss(data, addr);
154                         if (bss == NULL)
155                                 continue;
156                         if (hwaddr_aton(buf + 9 + 18, addr) < 0)
157                                 continue;
158
159                         bgscan_learn_add_neighbor(bss, addr);
160                 }
161         }
162
163         fclose(f);
164         return 0;
165 }
166
167
168 static void bgscan_learn_save(struct bgscan_learn_data *data)
169 {
170         FILE *f;
171         struct bgscan_learn_bss *bss;
172
173         if (data->fname == NULL)
174                 return;
175
176         wpa_printf(MSG_DEBUG, "bgscan learn: Saving data to %s",
177                    data->fname);
178
179         f = fopen(data->fname, "w");
180         if (f == NULL)
181                 return;
182         fprintf(f, "wpa_supplicant-bgscan-learn\n");
183
184         dl_list_for_each(bss, &data->bss, struct bgscan_learn_bss, list) {
185                 fprintf(f, "BSS " MACSTR " %d\n",
186                         MAC2STR(bss->bssid), bss->freq);
187         }
188
189         dl_list_for_each(bss, &data->bss, struct bgscan_learn_bss, list) {
190                 size_t i;
191                 for (i = 0; i < bss->num_neigh; i++) {
192                         fprintf(f, "NEIGHBOR " MACSTR " " MACSTR "\n",
193                                 MAC2STR(bss->bssid),
194                                 MAC2STR(bss->neigh + i * ETH_ALEN));
195                 }
196         }
197
198         fclose(f);
199 }
200
201
202 static int in_array(int *array, int val)
203 {
204         int i;
205
206         if (array == NULL)
207                 return 0;
208
209         for (i = 0; array[i]; i++) {
210                 if (array[i] == val)
211                         return 1;
212         }
213
214         return 0;
215 }
216
217
218 static int * bgscan_learn_get_freqs(struct bgscan_learn_data *data,
219                                     size_t *count)
220 {
221         struct bgscan_learn_bss *bss;
222         int *freqs = NULL, *n;
223
224         *count = 0;
225
226         dl_list_for_each(bss, &data->bss, struct bgscan_learn_bss, list) {
227                 if (in_array(freqs, bss->freq))
228                         continue;
229                 n = os_realloc(freqs, (*count + 2) * sizeof(int));
230                 if (n == NULL)
231                         return freqs;
232                 freqs = n;
233                 freqs[*count] = bss->freq;
234                 (*count)++;
235                 freqs[*count] = 0;
236         }
237
238         return freqs;
239 }
240
241
242 static int * bgscan_learn_get_probe_freq(struct bgscan_learn_data *data,
243                                          int *freqs, size_t count)
244 {
245         int idx, *n;
246
247         if (data->supp_freqs == NULL)
248                 return freqs;
249
250         idx = data->probe_idx + 1;
251         while (idx != data->probe_idx) {
252                 if (data->supp_freqs[idx] == 0)
253                         idx = 0;
254                 if (!in_array(freqs, data->supp_freqs[idx])) {
255                         wpa_printf(MSG_DEBUG, "bgscan learn: Probe new freq "
256                                    "%u", data->supp_freqs[idx]);
257                         data->probe_idx = idx;
258                         n = os_realloc(freqs, (count + 2) * sizeof(int));
259                         if (n == NULL)
260                                 return freqs;
261                         freqs = n;
262                         freqs[count] = data->supp_freqs[idx];
263                         count++;
264                         freqs[count] = 0;
265                         break;
266                 }
267
268                 idx++;
269         }
270
271         return freqs;
272 }
273
274
275 static void bgscan_learn_timeout(void *eloop_ctx, void *timeout_ctx)
276 {
277         struct bgscan_learn_data *data = eloop_ctx;
278         struct wpa_supplicant *wpa_s = data->wpa_s;
279         struct wpa_driver_scan_params params;
280         int *freqs = NULL;
281         size_t count, i;
282         char msg[100], *pos;
283
284         os_memset(&params, 0, sizeof(params));
285         params.num_ssids = 1;
286         params.ssids[0].ssid = data->ssid->ssid;
287         params.ssids[0].ssid_len = data->ssid->ssid_len;
288         if (data->ssid->scan_freq)
289                 params.freqs = data->ssid->scan_freq;
290         else {
291                 freqs = bgscan_learn_get_freqs(data, &count);
292                 wpa_printf(MSG_DEBUG, "bgscan learn: BSSes in this ESS have "
293                            "been seen on %u channels", (unsigned int) count);
294                 freqs = bgscan_learn_get_probe_freq(data, freqs, count);
295
296                 msg[0] = '\0';
297                 pos = msg;
298                 for (i = 0; freqs && freqs[i]; i++) {
299                         int ret;
300                         ret = os_snprintf(pos, msg + sizeof(msg) - pos, " %d",
301                                           freqs[i]);
302                         if (ret < 0 || ret >= msg + sizeof(msg) - pos)
303                                 break;
304                         pos += ret;
305                 }
306                 pos[0] = '\0';
307                 wpa_printf(MSG_DEBUG, "bgscan learn: Scanning frequencies:%s",
308                            msg);
309                 params.freqs = freqs;
310         }
311
312         wpa_printf(MSG_DEBUG, "bgscan learn: Request a background scan");
313         if (wpa_supplicant_trigger_scan(wpa_s, &params)) {
314                 wpa_printf(MSG_DEBUG, "bgscan learn: Failed to trigger scan");
315                 eloop_register_timeout(data->scan_interval, 0,
316                                        bgscan_learn_timeout, data, NULL);
317         } else
318                 os_get_time(&data->last_bgscan);
319         os_free(freqs);
320 }
321
322
323 static int bgscan_learn_get_params(struct bgscan_learn_data *data,
324                                    const char *params)
325 {
326         const char *pos;
327
328         if (params == NULL)
329                 return 0;
330
331         data->short_interval = atoi(params);
332
333         pos = os_strchr(params, ':');
334         if (pos == NULL)
335                 return 0;
336         pos++;
337         data->signal_threshold = atoi(pos);
338         pos = os_strchr(pos, ':');
339         if (pos == NULL) {
340                 wpa_printf(MSG_ERROR, "bgscan learn: Missing scan interval "
341                            "for high signal");
342                 return -1;
343         }
344         pos++;
345         data->long_interval = atoi(pos);
346         pos = os_strchr(pos, ':');
347         if (pos) {
348                 pos++;
349                 data->fname = os_strdup(pos);
350         }
351
352         return 0;
353 }
354
355
356 static int * bgscan_learn_get_supp_freqs(struct wpa_supplicant *wpa_s)
357 {
358         struct hostapd_hw_modes *modes;
359         u16 num_modes, flags;
360         int i, j, *freqs = NULL, *n;
361         size_t count = 0;
362
363         modes = wpa_drv_get_hw_feature_data(wpa_s, &num_modes, &flags);
364         if (!modes)
365                 return NULL;
366
367         for (i = 0; i < num_modes; i++) {
368                 for (j = 0; j < modes[i].num_channels; j++) {
369                         if (modes[i].channels[j].flag & HOSTAPD_CHAN_DISABLED)
370                                 continue;
371                         n = os_realloc(freqs, (count + 2) * sizeof(int));
372                         if (!n)
373                                 continue;
374
375                         freqs = n;
376                         freqs[count] = modes[i].channels[j].freq;
377                         count++;
378                         freqs[count] = 0;
379                 }
380                 os_free(modes[i].channels);
381                 os_free(modes[i].rates);
382         }
383         os_free(modes);
384
385         return freqs;
386 }
387
388
389 static void * bgscan_learn_init(struct wpa_supplicant *wpa_s,
390                                 const char *params,
391                                 const struct wpa_ssid *ssid)
392 {
393         struct bgscan_learn_data *data;
394
395         data = os_zalloc(sizeof(*data));
396         if (data == NULL)
397                 return NULL;
398         dl_list_init(&data->bss);
399         data->wpa_s = wpa_s;
400         data->ssid = ssid;
401         if (bgscan_learn_get_params(data, params) < 0) {
402                 os_free(data->fname);
403                 os_free(data);
404                 return NULL;
405         }
406         if (data->short_interval <= 0)
407                 data->short_interval = 30;
408         if (data->long_interval <= 0)
409                 data->long_interval = 30;
410
411         if (bgscan_learn_load(data) < 0) {
412                 os_free(data->fname);
413                 os_free(data);
414                 return NULL;
415         }
416
417         wpa_printf(MSG_DEBUG, "bgscan learn: Signal strength threshold %d  "
418                    "Short bgscan interval %d  Long bgscan interval %d",
419                    data->signal_threshold, data->short_interval,
420                    data->long_interval);
421
422         if (data->signal_threshold &&
423             wpa_drv_signal_monitor(wpa_s, data->signal_threshold, 4) < 0) {
424                 wpa_printf(MSG_ERROR, "bgscan learn: Failed to enable "
425                            "signal strength monitoring");
426         }
427
428         data->supp_freqs = bgscan_learn_get_supp_freqs(wpa_s);
429         data->scan_interval = data->short_interval;
430         eloop_register_timeout(data->scan_interval, 0, bgscan_learn_timeout,
431                                data, NULL);
432         return data;
433 }
434
435
436 static void bgscan_learn_deinit(void *priv)
437 {
438         struct bgscan_learn_data *data = priv;
439         struct bgscan_learn_bss *bss, *n;
440
441         bgscan_learn_save(data);
442         eloop_cancel_timeout(bgscan_learn_timeout, data, NULL);
443         if (data->signal_threshold)
444                 wpa_drv_signal_monitor(data->wpa_s, 0, 0);
445         os_free(data->fname);
446         dl_list_for_each_safe(bss, n, &data->bss, struct bgscan_learn_bss,
447                               list) {
448                 dl_list_del(&bss->list);
449                 bss_free(bss);
450         }
451         os_free(data->supp_freqs);
452         os_free(data);
453 }
454
455
456 static int bgscan_learn_bss_match(struct bgscan_learn_data *data,
457                                   struct wpa_scan_res *bss)
458 {
459         const u8 *ie;
460
461         ie = wpa_scan_get_ie(bss, WLAN_EID_SSID);
462         if (ie == NULL)
463                 return 0;
464
465         if (data->ssid->ssid_len != ie[1] ||
466             os_memcmp(data->ssid->ssid, ie + 2, ie[1]) != 0)
467                 return 0; /* SSID mismatch */
468
469         return 1;
470 }
471
472
473 static int bgscan_learn_notify_scan(void *priv,
474                                     struct wpa_scan_results *scan_res)
475 {
476         struct bgscan_learn_data *data = priv;
477         size_t i, j;
478 #define MAX_BSS 50
479         u8 bssid[MAX_BSS * ETH_ALEN];
480         size_t num_bssid = 0;
481
482         wpa_printf(MSG_DEBUG, "bgscan learn: scan result notification");
483
484         eloop_cancel_timeout(bgscan_learn_timeout, data, NULL);
485         eloop_register_timeout(data->scan_interval, 0, bgscan_learn_timeout,
486                                data, NULL);
487
488         for (i = 0; i < scan_res->num; i++) {
489                 struct wpa_scan_res *res = scan_res->res[i];
490                 if (!bgscan_learn_bss_match(data, res))
491                         continue;
492
493                 if (num_bssid < MAX_BSS) {
494                         os_memcpy(bssid + num_bssid * ETH_ALEN, res->bssid,
495                                   ETH_ALEN);
496                         num_bssid++;
497                 }
498         }
499         wpa_printf(MSG_DEBUG, "bgscan learn: %u matching BSSes in scan "
500                    "results", (unsigned int) num_bssid);
501
502         for (i = 0; i < scan_res->num; i++) {
503                 struct wpa_scan_res *res = scan_res->res[i];
504                 struct bgscan_learn_bss *bss;
505
506                 if (!bgscan_learn_bss_match(data, res))
507                         continue;
508
509                 bss = bgscan_learn_get_bss(data, res->bssid);
510                 if (bss && bss->freq != res->freq) {
511                         wpa_printf(MSG_DEBUG, "bgscan learn: Update BSS "
512                            MACSTR " freq %d -> %d",
513                                    MAC2STR(res->bssid), bss->freq, res->freq);
514                         bss->freq = res->freq;
515                 } else if (!bss) {
516                         wpa_printf(MSG_DEBUG, "bgscan learn: Add BSS " MACSTR
517                                    " freq=%d", MAC2STR(res->bssid), res->freq);
518                         bss = os_zalloc(sizeof(*bss));
519                         if (!bss)
520                                 continue;
521                         os_memcpy(bss->bssid, res->bssid, ETH_ALEN);
522                         bss->freq = res->freq;
523                         dl_list_add(&data->bss, &bss->list);
524                 }
525
526                 for (j = 0; j < num_bssid; j++) {
527                         u8 *addr = bssid + j * ETH_ALEN;
528                         bgscan_learn_add_neighbor(bss, addr);
529                 }
530         }
531
532         /*
533          * A more advanced bgscan could process scan results internally, select
534          * the BSS and request roam if needed. This sample uses the existing
535          * BSS/ESS selection routine. Change this to return 1 if selection is
536          * done inside the bgscan module.
537          */
538
539         return 0;
540 }
541
542
543 static void bgscan_learn_notify_beacon_loss(void *priv)
544 {
545         wpa_printf(MSG_DEBUG, "bgscan learn: beacon loss");
546         /* TODO: speed up background scanning */
547 }
548
549
550 static void bgscan_learn_notify_signal_change(void *priv, int above)
551 {
552         struct bgscan_learn_data *data = priv;
553
554         if (data->short_interval == data->long_interval ||
555             data->signal_threshold == 0)
556                 return;
557
558         wpa_printf(MSG_DEBUG, "bgscan learn: signal level changed "
559                    "(above=%d)", above);
560         if (data->scan_interval == data->long_interval && !above) {
561                 wpa_printf(MSG_DEBUG, "bgscan learn: Trigger immediate scan "
562                            "and start using short bgscan interval");
563                 data->scan_interval = data->short_interval;
564                 eloop_cancel_timeout(bgscan_learn_timeout, data, NULL);
565                 eloop_register_timeout(0, 0, bgscan_learn_timeout, data,
566                                        NULL);
567         } else if (data->scan_interval == data->short_interval && above) {
568                 wpa_printf(MSG_DEBUG, "bgscan learn: Start using long bgscan "
569                            "interval");
570                 data->scan_interval = data->long_interval;
571                 eloop_cancel_timeout(bgscan_learn_timeout, data, NULL);
572                 eloop_register_timeout(data->scan_interval, 0,
573                                        bgscan_learn_timeout, data, NULL);
574         } else if (!above) {
575                 struct os_time now;
576                 /*
577                  * Signal dropped further 4 dB. Request a new scan if we have
578                  * not yet scanned in a while.
579                  */
580                 os_get_time(&now);
581                 if (now.sec > data->last_bgscan.sec + 10) {
582                         wpa_printf(MSG_DEBUG, "bgscan learn: Trigger "
583                                    "immediate scan");
584                         eloop_cancel_timeout(bgscan_learn_timeout, data,
585                                              NULL);
586                         eloop_register_timeout(0, 0, bgscan_learn_timeout,
587                                                data, NULL);
588                 }
589         }
590 }
591
592
593 const struct bgscan_ops bgscan_learn_ops = {
594         .name = "learn",
595         .init = bgscan_learn_init,
596         .deinit = bgscan_learn_deinit,
597         .notify_scan = bgscan_learn_notify_scan,
598         .notify_beacon_loss = bgscan_learn_notify_beacon_loss,
599         .notify_signal_change = bgscan_learn_notify_signal_change,
600 };