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