a09fc1ccf8
In case that the ssid list is longer than max number of ssids, ths scheduled scan aborted after timeout and rescheduled again with shorter timeout and longer interval. In case that the timeout is shorter than the interval, only one scan iteration will be run. In this case, go back to the initial values. Signed-hostap: David Spinadel <david.spinadel@intel.com>
1610 lines
42 KiB
C
1610 lines
42 KiB
C
/*
|
|
* WPA Supplicant - Scanning
|
|
* Copyright (c) 2003-2012, Jouni Malinen <j@w1.fi>
|
|
*
|
|
* This software may be distributed under the terms of the BSD license.
|
|
* See README for more details.
|
|
*/
|
|
|
|
#include "utils/includes.h"
|
|
|
|
#include "utils/common.h"
|
|
#include "utils/eloop.h"
|
|
#include "common/ieee802_11_defs.h"
|
|
#include "common/wpa_ctrl.h"
|
|
#include "config.h"
|
|
#include "wpa_supplicant_i.h"
|
|
#include "driver_i.h"
|
|
#include "wps_supplicant.h"
|
|
#include "p2p_supplicant.h"
|
|
#include "p2p/p2p.h"
|
|
#include "hs20_supplicant.h"
|
|
#include "notify.h"
|
|
#include "bss.h"
|
|
#include "scan.h"
|
|
|
|
|
|
static void wpa_supplicant_gen_assoc_event(struct wpa_supplicant *wpa_s)
|
|
{
|
|
struct wpa_ssid *ssid;
|
|
union wpa_event_data data;
|
|
|
|
ssid = wpa_supplicant_get_ssid(wpa_s);
|
|
if (ssid == NULL)
|
|
return;
|
|
|
|
if (wpa_s->current_ssid == NULL) {
|
|
wpa_s->current_ssid = ssid;
|
|
if (wpa_s->current_ssid != NULL)
|
|
wpas_notify_network_changed(wpa_s);
|
|
}
|
|
wpa_supplicant_initiate_eapol(wpa_s);
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Already associated with a configured "
|
|
"network - generating associated event");
|
|
os_memset(&data, 0, sizeof(data));
|
|
wpa_supplicant_event(wpa_s, EVENT_ASSOC, &data);
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_WPS
|
|
static int wpas_wps_in_use(struct wpa_supplicant *wpa_s,
|
|
enum wps_request_type *req_type)
|
|
{
|
|
struct wpa_ssid *ssid;
|
|
int wps = 0;
|
|
|
|
for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
|
|
if (!(ssid->key_mgmt & WPA_KEY_MGMT_WPS))
|
|
continue;
|
|
|
|
wps = 1;
|
|
*req_type = wpas_wps_get_req_type(ssid);
|
|
if (!ssid->eap.phase1)
|
|
continue;
|
|
|
|
if (os_strstr(ssid->eap.phase1, "pbc=1"))
|
|
return 2;
|
|
}
|
|
|
|
#ifdef CONFIG_P2P
|
|
if (!wpa_s->global->p2p_disabled && wpa_s->global->p2p &&
|
|
!wpa_s->conf->p2p_disabled) {
|
|
wpa_s->wps->dev.p2p = 1;
|
|
if (!wps) {
|
|
wps = 1;
|
|
*req_type = WPS_REQ_ENROLLEE_INFO;
|
|
}
|
|
}
|
|
#endif /* CONFIG_P2P */
|
|
|
|
return wps;
|
|
}
|
|
#endif /* CONFIG_WPS */
|
|
|
|
|
|
/**
|
|
* wpa_supplicant_enabled_networks - Check whether there are enabled networks
|
|
* @wpa_s: Pointer to wpa_supplicant data
|
|
* Returns: 0 if no networks are enabled, >0 if networks are enabled
|
|
*
|
|
* This function is used to figure out whether any networks (or Interworking
|
|
* with enabled credentials and auto_interworking) are present in the current
|
|
* configuration.
|
|
*/
|
|
int wpa_supplicant_enabled_networks(struct wpa_supplicant *wpa_s)
|
|
{
|
|
struct wpa_ssid *ssid = wpa_s->conf->ssid;
|
|
int count = 0, disabled = 0;
|
|
while (ssid) {
|
|
if (!wpas_network_disabled(wpa_s, ssid))
|
|
count++;
|
|
else
|
|
disabled++;
|
|
ssid = ssid->next;
|
|
}
|
|
if (wpa_s->conf->cred && wpa_s->conf->interworking &&
|
|
wpa_s->conf->auto_interworking)
|
|
count++;
|
|
if (count == 0 && disabled > 0) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "No enabled networks (%d disabled "
|
|
"networks)", disabled);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
static void wpa_supplicant_assoc_try(struct wpa_supplicant *wpa_s,
|
|
struct wpa_ssid *ssid)
|
|
{
|
|
while (ssid) {
|
|
if (!wpas_network_disabled(wpa_s, ssid))
|
|
break;
|
|
ssid = ssid->next;
|
|
}
|
|
|
|
/* ap_scan=2 mode - try to associate with each SSID. */
|
|
if (ssid == NULL) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "wpa_supplicant_assoc_try: Reached "
|
|
"end of scan list - go back to beginning");
|
|
wpa_s->prev_scan_ssid = WILDCARD_SSID_SCAN;
|
|
wpa_supplicant_req_scan(wpa_s, 0, 0);
|
|
return;
|
|
}
|
|
if (ssid->next) {
|
|
/* Continue from the next SSID on the next attempt. */
|
|
wpa_s->prev_scan_ssid = ssid;
|
|
} else {
|
|
/* Start from the beginning of the SSID list. */
|
|
wpa_s->prev_scan_ssid = WILDCARD_SSID_SCAN;
|
|
}
|
|
wpa_supplicant_associate(wpa_s, NULL, ssid);
|
|
}
|
|
|
|
|
|
static int int_array_len(const int *a)
|
|
{
|
|
int i;
|
|
for (i = 0; a && a[i]; i++)
|
|
;
|
|
return i;
|
|
}
|
|
|
|
|
|
static void int_array_concat(int **res, const int *a)
|
|
{
|
|
int reslen, alen, i;
|
|
int *n;
|
|
|
|
reslen = int_array_len(*res);
|
|
alen = int_array_len(a);
|
|
|
|
n = os_realloc_array(*res, reslen + alen + 1, sizeof(int));
|
|
if (n == NULL) {
|
|
os_free(*res);
|
|
*res = NULL;
|
|
return;
|
|
}
|
|
for (i = 0; i <= alen; i++)
|
|
n[reslen + i] = a[i];
|
|
*res = n;
|
|
}
|
|
|
|
|
|
static int freq_cmp(const void *a, const void *b)
|
|
{
|
|
int _a = *(int *) a;
|
|
int _b = *(int *) b;
|
|
|
|
if (_a == 0)
|
|
return 1;
|
|
if (_b == 0)
|
|
return -1;
|
|
return _a - _b;
|
|
}
|
|
|
|
|
|
static void int_array_sort_unique(int *a)
|
|
{
|
|
int alen;
|
|
int i, j;
|
|
|
|
if (a == NULL)
|
|
return;
|
|
|
|
alen = int_array_len(a);
|
|
qsort(a, alen, sizeof(int), freq_cmp);
|
|
|
|
i = 0;
|
|
j = 1;
|
|
while (a[i] && a[j]) {
|
|
if (a[i] == a[j]) {
|
|
j++;
|
|
continue;
|
|
}
|
|
a[++i] = a[j++];
|
|
}
|
|
if (a[i])
|
|
i++;
|
|
a[i] = 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_supplicant_trigger_scan - Request driver to start a scan
|
|
* @wpa_s: Pointer to wpa_supplicant data
|
|
* @params: Scan parameters
|
|
* Returns: 0 on success, -1 on failure
|
|
*/
|
|
int wpa_supplicant_trigger_scan(struct wpa_supplicant *wpa_s,
|
|
struct wpa_driver_scan_params *params)
|
|
{
|
|
int ret;
|
|
|
|
wpa_supplicant_notify_scanning(wpa_s, 1);
|
|
|
|
ret = wpa_drv_scan(wpa_s, params);
|
|
if (ret) {
|
|
wpa_supplicant_notify_scanning(wpa_s, 0);
|
|
wpas_notify_scan_done(wpa_s, 0);
|
|
} else {
|
|
wpa_s->scan_runs++;
|
|
wpa_s->normal_scans++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
wpa_supplicant_delayed_sched_scan_timeout(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct wpa_supplicant *wpa_s = eloop_ctx;
|
|
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Starting delayed sched scan");
|
|
|
|
if (wpa_supplicant_req_sched_scan(wpa_s))
|
|
wpa_supplicant_req_scan(wpa_s, 0, 0);
|
|
}
|
|
|
|
|
|
static void
|
|
wpa_supplicant_sched_scan_timeout(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct wpa_supplicant *wpa_s = eloop_ctx;
|
|
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Sched scan timeout - stopping it");
|
|
|
|
wpa_s->sched_scan_timed_out = 1;
|
|
wpa_supplicant_cancel_sched_scan(wpa_s);
|
|
}
|
|
|
|
|
|
static int
|
|
wpa_supplicant_start_sched_scan(struct wpa_supplicant *wpa_s,
|
|
struct wpa_driver_scan_params *params,
|
|
int interval)
|
|
{
|
|
int ret;
|
|
|
|
wpa_supplicant_notify_scanning(wpa_s, 1);
|
|
ret = wpa_drv_sched_scan(wpa_s, params, interval * 1000);
|
|
if (ret)
|
|
wpa_supplicant_notify_scanning(wpa_s, 0);
|
|
else
|
|
wpa_s->sched_scanning = 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int wpa_supplicant_stop_sched_scan(struct wpa_supplicant *wpa_s)
|
|
{
|
|
int ret;
|
|
|
|
ret = wpa_drv_stop_sched_scan(wpa_s);
|
|
if (ret) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "stopping sched_scan failed!");
|
|
/* TODO: what to do if stopping fails? */
|
|
return -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static struct wpa_driver_scan_filter *
|
|
wpa_supplicant_build_filter_ssids(struct wpa_config *conf, size_t *num_ssids)
|
|
{
|
|
struct wpa_driver_scan_filter *ssids;
|
|
struct wpa_ssid *ssid;
|
|
size_t count;
|
|
|
|
*num_ssids = 0;
|
|
if (!conf->filter_ssids)
|
|
return NULL;
|
|
|
|
for (count = 0, ssid = conf->ssid; ssid; ssid = ssid->next) {
|
|
if (ssid->ssid && ssid->ssid_len)
|
|
count++;
|
|
}
|
|
if (count == 0)
|
|
return NULL;
|
|
ssids = os_zalloc(count * sizeof(struct wpa_driver_scan_filter));
|
|
if (ssids == NULL)
|
|
return NULL;
|
|
|
|
for (ssid = conf->ssid; ssid; ssid = ssid->next) {
|
|
if (!ssid->ssid || !ssid->ssid_len)
|
|
continue;
|
|
os_memcpy(ssids[*num_ssids].ssid, ssid->ssid, ssid->ssid_len);
|
|
ssids[*num_ssids].ssid_len = ssid->ssid_len;
|
|
(*num_ssids)++;
|
|
}
|
|
|
|
return ssids;
|
|
}
|
|
|
|
|
|
static void wpa_supplicant_optimize_freqs(
|
|
struct wpa_supplicant *wpa_s, struct wpa_driver_scan_params *params)
|
|
{
|
|
#ifdef CONFIG_P2P
|
|
if (params->freqs == NULL && wpa_s->p2p_in_provisioning &&
|
|
wpa_s->go_params) {
|
|
/* Optimize provisioning state scan based on GO information */
|
|
if (wpa_s->p2p_in_provisioning < 5 &&
|
|
wpa_s->go_params->freq > 0) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Scan only GO "
|
|
"preferred frequency %d MHz",
|
|
wpa_s->go_params->freq);
|
|
params->freqs = os_zalloc(2 * sizeof(int));
|
|
if (params->freqs)
|
|
params->freqs[0] = wpa_s->go_params->freq;
|
|
} else if (wpa_s->p2p_in_provisioning < 8 &&
|
|
wpa_s->go_params->freq_list[0]) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Scan only common "
|
|
"channels");
|
|
int_array_concat(¶ms->freqs,
|
|
wpa_s->go_params->freq_list);
|
|
if (params->freqs)
|
|
int_array_sort_unique(params->freqs);
|
|
}
|
|
wpa_s->p2p_in_provisioning++;
|
|
}
|
|
#endif /* CONFIG_P2P */
|
|
|
|
#ifdef CONFIG_WPS
|
|
if (params->freqs == NULL && wpa_s->after_wps && wpa_s->wps_freq) {
|
|
/*
|
|
* Optimize post-provisioning scan based on channel used
|
|
* during provisioning.
|
|
*/
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "WPS: Scan only frequency %u MHz "
|
|
"that was used during provisioning", wpa_s->wps_freq);
|
|
params->freqs = os_zalloc(2 * sizeof(int));
|
|
if (params->freqs)
|
|
params->freqs[0] = wpa_s->wps_freq;
|
|
wpa_s->after_wps--;
|
|
}
|
|
|
|
if (params->freqs == NULL && wpa_s->known_wps_freq && wpa_s->wps_freq)
|
|
{
|
|
/* Optimize provisioning scan based on already known channel */
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "WPS: Scan only frequency %u MHz",
|
|
wpa_s->wps_freq);
|
|
params->freqs = os_zalloc(2 * sizeof(int));
|
|
if (params->freqs)
|
|
params->freqs[0] = wpa_s->wps_freq;
|
|
wpa_s->known_wps_freq = 0; /* only do this once */
|
|
}
|
|
#endif /* CONFIG_WPS */
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_INTERWORKING
|
|
static void wpas_add_interworking_elements(struct wpa_supplicant *wpa_s,
|
|
struct wpabuf *buf)
|
|
{
|
|
if (wpa_s->conf->interworking == 0)
|
|
return;
|
|
|
|
wpabuf_put_u8(buf, WLAN_EID_EXT_CAPAB);
|
|
wpabuf_put_u8(buf, 4);
|
|
wpabuf_put_u8(buf, 0x00);
|
|
wpabuf_put_u8(buf, 0x00);
|
|
wpabuf_put_u8(buf, 0x00);
|
|
wpabuf_put_u8(buf, 0x80); /* Bit 31 - Interworking */
|
|
|
|
wpabuf_put_u8(buf, WLAN_EID_INTERWORKING);
|
|
wpabuf_put_u8(buf, is_zero_ether_addr(wpa_s->conf->hessid) ? 1 :
|
|
1 + ETH_ALEN);
|
|
wpabuf_put_u8(buf, wpa_s->conf->access_network_type);
|
|
/* No Venue Info */
|
|
if (!is_zero_ether_addr(wpa_s->conf->hessid))
|
|
wpabuf_put_data(buf, wpa_s->conf->hessid, ETH_ALEN);
|
|
}
|
|
#endif /* CONFIG_INTERWORKING */
|
|
|
|
|
|
static struct wpabuf * wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s)
|
|
{
|
|
struct wpabuf *extra_ie = NULL;
|
|
#ifdef CONFIG_WPS
|
|
int wps = 0;
|
|
enum wps_request_type req_type = WPS_REQ_ENROLLEE_INFO;
|
|
#endif /* CONFIG_WPS */
|
|
|
|
#ifdef CONFIG_INTERWORKING
|
|
if (wpa_s->conf->interworking &&
|
|
wpabuf_resize(&extra_ie, 100) == 0)
|
|
wpas_add_interworking_elements(wpa_s, extra_ie);
|
|
#endif /* CONFIG_INTERWORKING */
|
|
|
|
#ifdef CONFIG_WPS
|
|
wps = wpas_wps_in_use(wpa_s, &req_type);
|
|
|
|
if (wps) {
|
|
struct wpabuf *wps_ie;
|
|
wps_ie = wps_build_probe_req_ie(wps == 2 ? DEV_PW_PUSHBUTTON :
|
|
DEV_PW_DEFAULT,
|
|
&wpa_s->wps->dev,
|
|
wpa_s->wps->uuid, req_type,
|
|
0, NULL);
|
|
if (wps_ie) {
|
|
if (wpabuf_resize(&extra_ie, wpabuf_len(wps_ie)) == 0)
|
|
wpabuf_put_buf(extra_ie, wps_ie);
|
|
wpabuf_free(wps_ie);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_P2P
|
|
if (wps) {
|
|
size_t ielen = p2p_scan_ie_buf_len(wpa_s->global->p2p);
|
|
if (wpabuf_resize(&extra_ie, ielen) == 0)
|
|
wpas_p2p_scan_ie(wpa_s, extra_ie);
|
|
}
|
|
#endif /* CONFIG_P2P */
|
|
|
|
#endif /* CONFIG_WPS */
|
|
|
|
return extra_ie;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_P2P
|
|
|
|
/*
|
|
* Check whether there are any enabled networks or credentials that could be
|
|
* used for a non-P2P connection.
|
|
*/
|
|
static int non_p2p_network_enabled(struct wpa_supplicant *wpa_s)
|
|
{
|
|
struct wpa_ssid *ssid;
|
|
|
|
for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
|
|
if (wpas_network_disabled(wpa_s, ssid))
|
|
continue;
|
|
if (!ssid->p2p_group)
|
|
return 1;
|
|
}
|
|
|
|
if (wpa_s->conf->cred && wpa_s->conf->interworking &&
|
|
wpa_s->conf->auto_interworking)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Find the operating frequency of any other virtual interface that is using
|
|
* the same radio concurrently.
|
|
*/
|
|
static int shared_vif_oper_freq(struct wpa_supplicant *wpa_s)
|
|
{
|
|
const char *rn, *rn2;
|
|
struct wpa_supplicant *ifs;
|
|
u8 bssid[ETH_ALEN];
|
|
|
|
if (!wpa_s->driver->get_radio_name)
|
|
return -1;
|
|
|
|
rn = wpa_s->driver->get_radio_name(wpa_s->drv_priv);
|
|
if (rn == NULL || rn[0] == '\0')
|
|
return -1;
|
|
|
|
for (ifs = wpa_s->global->ifaces; ifs; ifs = ifs->next) {
|
|
if (ifs == wpa_s || !ifs->driver->get_radio_name)
|
|
continue;
|
|
|
|
rn2 = ifs->driver->get_radio_name(ifs->drv_priv);
|
|
if (!rn2 || os_strcmp(rn, rn2) != 0)
|
|
continue;
|
|
|
|
if (ifs->current_ssid == NULL || ifs->assoc_freq == 0)
|
|
continue;
|
|
|
|
if (ifs->current_ssid->mode == WPAS_MODE_AP ||
|
|
ifs->current_ssid->mode == WPAS_MODE_P2P_GO)
|
|
return ifs->current_ssid->frequency;
|
|
if (wpa_drv_get_bssid(ifs, bssid) == 0)
|
|
return ifs->assoc_freq;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* CONFIG_P2P */
|
|
|
|
|
|
static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
|
|
{
|
|
struct wpa_supplicant *wpa_s = eloop_ctx;
|
|
struct wpa_ssid *ssid;
|
|
enum scan_req_type scan_req = NORMAL_SCAN_REQ;
|
|
int ret;
|
|
struct wpabuf *extra_ie = NULL;
|
|
struct wpa_driver_scan_params params;
|
|
struct wpa_driver_scan_params *scan_params;
|
|
size_t max_ssids;
|
|
enum wpa_states prev_state;
|
|
|
|
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Skip scan - interface disabled");
|
|
return;
|
|
}
|
|
|
|
if (wpa_s->disconnected && wpa_s->scan_req == NORMAL_SCAN_REQ) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Disconnected - do not scan");
|
|
wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
|
|
return;
|
|
}
|
|
|
|
if (!wpa_supplicant_enabled_networks(wpa_s) &&
|
|
wpa_s->scan_req == NORMAL_SCAN_REQ) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "No enabled networks - do not scan");
|
|
wpa_supplicant_set_state(wpa_s, WPA_INACTIVE);
|
|
#ifdef CONFIG_P2P
|
|
wpa_s->sta_scan_pending = 0;
|
|
#endif /* CONFIG_P2P */
|
|
return;
|
|
}
|
|
|
|
if (wpa_s->conf->ap_scan != 0 &&
|
|
(wpa_s->drv_flags & WPA_DRIVER_FLAGS_WIRED)) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Using wired authentication - "
|
|
"overriding ap_scan configuration");
|
|
wpa_s->conf->ap_scan = 0;
|
|
wpas_notify_ap_scan_changed(wpa_s);
|
|
}
|
|
|
|
if (wpa_s->conf->ap_scan == 0) {
|
|
wpa_supplicant_gen_assoc_event(wpa_s);
|
|
return;
|
|
}
|
|
|
|
#ifdef CONFIG_P2P
|
|
if (wpas_p2p_in_progress(wpa_s)) {
|
|
if (wpa_s->sta_scan_pending &&
|
|
wpas_p2p_in_progress(wpa_s) == 2 &&
|
|
wpa_s->global->p2p_cb_on_scan_complete) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Process pending station "
|
|
"mode scan during P2P search");
|
|
} else {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Delay station mode scan "
|
|
"while P2P operation is in progress");
|
|
wpa_s->sta_scan_pending = 1;
|
|
wpa_supplicant_req_scan(wpa_s, 5, 0);
|
|
return;
|
|
}
|
|
}
|
|
#endif /* CONFIG_P2P */
|
|
|
|
if (wpa_s->conf->ap_scan == 2)
|
|
max_ssids = 1;
|
|
else {
|
|
max_ssids = wpa_s->max_scan_ssids;
|
|
if (max_ssids > WPAS_MAX_SCAN_SSIDS)
|
|
max_ssids = WPAS_MAX_SCAN_SSIDS;
|
|
}
|
|
|
|
scan_req = wpa_s->scan_req;
|
|
wpa_s->scan_req = NORMAL_SCAN_REQ;
|
|
|
|
os_memset(¶ms, 0, sizeof(params));
|
|
|
|
prev_state = wpa_s->wpa_state;
|
|
if (wpa_s->wpa_state == WPA_DISCONNECTED ||
|
|
wpa_s->wpa_state == WPA_INACTIVE)
|
|
wpa_supplicant_set_state(wpa_s, WPA_SCANNING);
|
|
|
|
/*
|
|
* If autoscan has set its own scanning parameters
|
|
*/
|
|
if (wpa_s->autoscan_params != NULL) {
|
|
scan_params = wpa_s->autoscan_params;
|
|
goto scan;
|
|
}
|
|
|
|
if (scan_req != MANUAL_SCAN_REQ && wpa_s->connect_without_scan) {
|
|
for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
|
|
if (ssid == wpa_s->connect_without_scan)
|
|
break;
|
|
}
|
|
wpa_s->connect_without_scan = NULL;
|
|
if (ssid) {
|
|
wpa_printf(MSG_DEBUG, "Start a pre-selected network "
|
|
"without scan step");
|
|
wpa_supplicant_associate(wpa_s, NULL, ssid);
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_P2P
|
|
if ((wpa_s->p2p_in_provisioning || wpa_s->show_group_started) &&
|
|
wpa_s->go_params) {
|
|
wpa_printf(MSG_DEBUG, "P2P: Use specific SSID for scan during "
|
|
"P2P group formation");
|
|
params.ssids[0].ssid = wpa_s->go_params->ssid;
|
|
params.ssids[0].ssid_len = wpa_s->go_params->ssid_len;
|
|
params.num_ssids = 1;
|
|
goto ssid_list_set;
|
|
}
|
|
#endif /* CONFIG_P2P */
|
|
|
|
/* Find the starting point from which to continue scanning */
|
|
ssid = wpa_s->conf->ssid;
|
|
if (wpa_s->prev_scan_ssid != WILDCARD_SSID_SCAN) {
|
|
while (ssid) {
|
|
if (ssid == wpa_s->prev_scan_ssid) {
|
|
ssid = ssid->next;
|
|
break;
|
|
}
|
|
ssid = ssid->next;
|
|
}
|
|
}
|
|
|
|
if (scan_req != MANUAL_SCAN_REQ && wpa_s->conf->ap_scan == 2) {
|
|
wpa_s->connect_without_scan = NULL;
|
|
wpa_s->prev_scan_wildcard = 0;
|
|
wpa_supplicant_assoc_try(wpa_s, ssid);
|
|
return;
|
|
} else if (wpa_s->conf->ap_scan == 2) {
|
|
/*
|
|
* User-initiated scan request in ap_scan == 2; scan with
|
|
* wildcard SSID.
|
|
*/
|
|
ssid = NULL;
|
|
} else {
|
|
struct wpa_ssid *start = ssid, *tssid;
|
|
int freqs_set = 0;
|
|
if (ssid == NULL && max_ssids > 1)
|
|
ssid = wpa_s->conf->ssid;
|
|
while (ssid) {
|
|
if (!wpas_network_disabled(wpa_s, ssid) &&
|
|
ssid->scan_ssid) {
|
|
wpa_hexdump_ascii(MSG_DEBUG, "Scan SSID",
|
|
ssid->ssid, ssid->ssid_len);
|
|
params.ssids[params.num_ssids].ssid =
|
|
ssid->ssid;
|
|
params.ssids[params.num_ssids].ssid_len =
|
|
ssid->ssid_len;
|
|
params.num_ssids++;
|
|
if (params.num_ssids + 1 >= max_ssids)
|
|
break;
|
|
}
|
|
ssid = ssid->next;
|
|
if (ssid == start)
|
|
break;
|
|
if (ssid == NULL && max_ssids > 1 &&
|
|
start != wpa_s->conf->ssid)
|
|
ssid = wpa_s->conf->ssid;
|
|
}
|
|
|
|
for (tssid = wpa_s->conf->ssid; tssid; tssid = tssid->next) {
|
|
if (wpas_network_disabled(wpa_s, tssid))
|
|
continue;
|
|
if ((params.freqs || !freqs_set) && tssid->scan_freq) {
|
|
int_array_concat(¶ms.freqs,
|
|
tssid->scan_freq);
|
|
} else {
|
|
os_free(params.freqs);
|
|
params.freqs = NULL;
|
|
}
|
|
freqs_set = 1;
|
|
}
|
|
int_array_sort_unique(params.freqs);
|
|
}
|
|
|
|
if (ssid && max_ssids == 1) {
|
|
/*
|
|
* If the driver is limited to 1 SSID at a time interleave
|
|
* wildcard SSID scans with specific SSID scans to avoid
|
|
* waiting a long time for a wildcard scan.
|
|
*/
|
|
if (!wpa_s->prev_scan_wildcard) {
|
|
params.ssids[0].ssid = NULL;
|
|
params.ssids[0].ssid_len = 0;
|
|
wpa_s->prev_scan_wildcard = 1;
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Starting AP scan for "
|
|
"wildcard SSID (Interleave with specific)");
|
|
} else {
|
|
wpa_s->prev_scan_ssid = ssid;
|
|
wpa_s->prev_scan_wildcard = 0;
|
|
wpa_dbg(wpa_s, MSG_DEBUG,
|
|
"Starting AP scan for specific SSID: %s",
|
|
wpa_ssid_txt(ssid->ssid, ssid->ssid_len));
|
|
}
|
|
} else if (ssid) {
|
|
/* max_ssids > 1 */
|
|
|
|
wpa_s->prev_scan_ssid = ssid;
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Include wildcard SSID in "
|
|
"the scan request");
|
|
params.num_ssids++;
|
|
} else {
|
|
wpa_s->prev_scan_ssid = WILDCARD_SSID_SCAN;
|
|
params.num_ssids++;
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Starting AP scan for wildcard "
|
|
"SSID");
|
|
}
|
|
#ifdef CONFIG_P2P
|
|
ssid_list_set:
|
|
#endif /* CONFIG_P2P */
|
|
|
|
wpa_supplicant_optimize_freqs(wpa_s, ¶ms);
|
|
extra_ie = wpa_supplicant_extra_ies(wpa_s);
|
|
|
|
#ifdef CONFIG_HS20
|
|
if (wpa_s->conf->hs20 && wpabuf_resize(&extra_ie, 7) == 0)
|
|
wpas_hs20_add_indication(extra_ie);
|
|
#endif /* CONFIG_HS20 */
|
|
|
|
if (params.freqs == NULL && wpa_s->next_scan_freqs) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Optimize scan based on previously "
|
|
"generated frequency list");
|
|
params.freqs = wpa_s->next_scan_freqs;
|
|
} else
|
|
os_free(wpa_s->next_scan_freqs);
|
|
wpa_s->next_scan_freqs = NULL;
|
|
|
|
params.filter_ssids = wpa_supplicant_build_filter_ssids(
|
|
wpa_s->conf, ¶ms.num_filter_ssids);
|
|
if (extra_ie) {
|
|
params.extra_ies = wpabuf_head(extra_ie);
|
|
params.extra_ies_len = wpabuf_len(extra_ie);
|
|
}
|
|
|
|
#ifdef CONFIG_P2P
|
|
if (wpa_s->p2p_in_provisioning ||
|
|
(wpa_s->show_group_started && wpa_s->go_params)) {
|
|
/*
|
|
* The interface may not yet be in P2P mode, so we have to
|
|
* explicitly request P2P probe to disable CCK rates.
|
|
*/
|
|
params.p2p_probe = 1;
|
|
}
|
|
#endif /* CONFIG_P2P */
|
|
|
|
scan_params = ¶ms;
|
|
|
|
scan:
|
|
#ifdef CONFIG_P2P
|
|
/*
|
|
* If the driver does not support multi-channel concurrency and a
|
|
* virtual interface that shares the same radio with the wpa_s interface
|
|
* is operating there may not be need to scan other channels apart from
|
|
* the current operating channel on the other virtual interface. Filter
|
|
* out other channels in case we are trying to find a connection for a
|
|
* station interface when we are not configured to prefer station
|
|
* connection and a concurrent operation is already in process.
|
|
*/
|
|
if (wpa_s->scan_for_connection && scan_req == NORMAL_SCAN_REQ &&
|
|
!scan_params->freqs && !params.freqs &&
|
|
wpas_is_p2p_prioritized(wpa_s) &&
|
|
!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_MULTI_CHANNEL_CONCURRENT) &&
|
|
wpa_s->p2p_group_interface == NOT_P2P_GROUP_INTERFACE &&
|
|
non_p2p_network_enabled(wpa_s)) {
|
|
int freq = shared_vif_oper_freq(wpa_s);
|
|
if (freq > 0) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Scan only the current "
|
|
"operating channel (%d MHz) since driver does "
|
|
"not support multi-channel concurrency", freq);
|
|
params.freqs = os_zalloc(sizeof(int) * 2);
|
|
if (params.freqs)
|
|
params.freqs[0] = freq;
|
|
scan_params->freqs = params.freqs;
|
|
}
|
|
}
|
|
#endif /* CONFIG_P2P */
|
|
|
|
ret = wpa_supplicant_trigger_scan(wpa_s, scan_params);
|
|
|
|
wpabuf_free(extra_ie);
|
|
os_free(params.freqs);
|
|
os_free(params.filter_ssids);
|
|
|
|
if (ret) {
|
|
wpa_msg(wpa_s, MSG_WARNING, "Failed to initiate AP scan");
|
|
if (prev_state != wpa_s->wpa_state)
|
|
wpa_supplicant_set_state(wpa_s, prev_state);
|
|
/* Restore scan_req since we will try to scan again */
|
|
wpa_s->scan_req = scan_req;
|
|
wpa_supplicant_req_scan(wpa_s, 1, 0);
|
|
} else {
|
|
wpa_s->scan_for_connection = 0;
|
|
}
|
|
}
|
|
|
|
|
|
void wpa_supplicant_update_scan_int(struct wpa_supplicant *wpa_s, int sec)
|
|
{
|
|
struct os_time remaining, new_int;
|
|
int cancelled;
|
|
|
|
cancelled = eloop_cancel_timeout_one(wpa_supplicant_scan, wpa_s, NULL,
|
|
&remaining);
|
|
|
|
new_int.sec = sec;
|
|
new_int.usec = 0;
|
|
if (cancelled && os_time_before(&remaining, &new_int)) {
|
|
new_int.sec = remaining.sec;
|
|
new_int.usec = remaining.usec;
|
|
}
|
|
|
|
eloop_register_timeout(new_int.sec, new_int.usec, wpa_supplicant_scan,
|
|
wpa_s, NULL);
|
|
wpa_s->scan_interval = sec;
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_supplicant_req_scan - Schedule a scan for neighboring access points
|
|
* @wpa_s: Pointer to wpa_supplicant data
|
|
* @sec: Number of seconds after which to scan
|
|
* @usec: Number of microseconds after which to scan
|
|
*
|
|
* This function is used to schedule a scan for neighboring access points after
|
|
* the specified time.
|
|
*/
|
|
void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec)
|
|
{
|
|
/* If there's at least one network that should be specifically scanned
|
|
* then don't cancel the scan and reschedule. Some drivers do
|
|
* background scanning which generates frequent scan results, and that
|
|
* causes the specific SSID scan to get continually pushed back and
|
|
* never happen, which causes hidden APs to never get probe-scanned.
|
|
*/
|
|
if (eloop_is_timeout_registered(wpa_supplicant_scan, wpa_s, NULL) &&
|
|
wpa_s->conf->ap_scan == 1) {
|
|
struct wpa_ssid *ssid = wpa_s->conf->ssid;
|
|
|
|
while (ssid) {
|
|
if (!wpas_network_disabled(wpa_s, ssid) &&
|
|
ssid->scan_ssid)
|
|
break;
|
|
ssid = ssid->next;
|
|
}
|
|
if (ssid) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Not rescheduling scan to "
|
|
"ensure that specific SSID scans occur");
|
|
return;
|
|
}
|
|
}
|
|
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Setting scan request: %d sec %d usec",
|
|
sec, usec);
|
|
eloop_cancel_timeout(wpa_supplicant_scan, wpa_s, NULL);
|
|
eloop_register_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_supplicant_delayed_sched_scan - Request a delayed scheduled scan
|
|
* @wpa_s: Pointer to wpa_supplicant data
|
|
* @sec: Number of seconds after which to scan
|
|
* @usec: Number of microseconds after which to scan
|
|
* Returns: 0 on success or -1 otherwise
|
|
*
|
|
* This function is used to schedule periodic scans for neighboring
|
|
* access points after the specified time.
|
|
*/
|
|
int wpa_supplicant_delayed_sched_scan(struct wpa_supplicant *wpa_s,
|
|
int sec, int usec)
|
|
{
|
|
if (!wpa_s->sched_scan_supported)
|
|
return -1;
|
|
|
|
eloop_register_timeout(sec, usec,
|
|
wpa_supplicant_delayed_sched_scan_timeout,
|
|
wpa_s, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_supplicant_req_sched_scan - Start a periodic scheduled scan
|
|
* @wpa_s: Pointer to wpa_supplicant data
|
|
* Returns: 0 is sched_scan was started or -1 otherwise
|
|
*
|
|
* This function is used to schedule periodic scans for neighboring
|
|
* access points repeating the scan continuously.
|
|
*/
|
|
int wpa_supplicant_req_sched_scan(struct wpa_supplicant *wpa_s)
|
|
{
|
|
struct wpa_driver_scan_params params;
|
|
struct wpa_driver_scan_params *scan_params;
|
|
enum wpa_states prev_state;
|
|
struct wpa_ssid *ssid = NULL;
|
|
struct wpabuf *extra_ie = NULL;
|
|
int ret;
|
|
unsigned int max_sched_scan_ssids;
|
|
int wildcard = 0;
|
|
int need_ssids;
|
|
|
|
if (!wpa_s->sched_scan_supported)
|
|
return -1;
|
|
|
|
if (wpa_s->max_sched_scan_ssids > WPAS_MAX_SCAN_SSIDS)
|
|
max_sched_scan_ssids = WPAS_MAX_SCAN_SSIDS;
|
|
else
|
|
max_sched_scan_ssids = wpa_s->max_sched_scan_ssids;
|
|
if (max_sched_scan_ssids < 1 || wpa_s->conf->disable_scan_offload)
|
|
return -1;
|
|
|
|
if (wpa_s->sched_scanning) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Already sched scanning");
|
|
return 0;
|
|
}
|
|
|
|
need_ssids = 0;
|
|
for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
|
|
if (!wpas_network_disabled(wpa_s, ssid) && !ssid->scan_ssid) {
|
|
/* Use wildcard SSID to find this network */
|
|
wildcard = 1;
|
|
} else if (!wpas_network_disabled(wpa_s, ssid) &&
|
|
ssid->ssid_len)
|
|
need_ssids++;
|
|
|
|
#ifdef CONFIG_WPS
|
|
if (!wpas_network_disabled(wpa_s, ssid) &&
|
|
ssid->key_mgmt == WPA_KEY_MGMT_WPS) {
|
|
/*
|
|
* Normal scan is more reliable and faster for WPS
|
|
* operations and since these are for short periods of
|
|
* time, the benefit of trying to use sched_scan would
|
|
* be limited.
|
|
*/
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Use normal scan instead of "
|
|
"sched_scan for WPS");
|
|
return -1;
|
|
}
|
|
#endif /* CONFIG_WPS */
|
|
}
|
|
if (wildcard)
|
|
need_ssids++;
|
|
|
|
if (wpa_s->normal_scans < 3 &&
|
|
(need_ssids <= wpa_s->max_scan_ssids ||
|
|
wpa_s->max_scan_ssids >= (int) max_sched_scan_ssids)) {
|
|
/*
|
|
* When normal scan can speed up operations, use that for the
|
|
* first operations before starting the sched_scan to allow
|
|
* user space sleep more. We do this only if the normal scan
|
|
* has functionality that is suitable for this or if the
|
|
* sched_scan does not have better support for multiple SSIDs.
|
|
*/
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Use normal scan instead of "
|
|
"sched_scan for initial scans (normal_scans=%d)",
|
|
wpa_s->normal_scans);
|
|
return -1;
|
|
}
|
|
|
|
os_memset(¶ms, 0, sizeof(params));
|
|
|
|
/* If we can't allocate space for the filters, we just don't filter */
|
|
params.filter_ssids = os_zalloc(wpa_s->max_match_sets *
|
|
sizeof(struct wpa_driver_scan_filter));
|
|
|
|
prev_state = wpa_s->wpa_state;
|
|
if (wpa_s->wpa_state == WPA_DISCONNECTED ||
|
|
wpa_s->wpa_state == WPA_INACTIVE)
|
|
wpa_supplicant_set_state(wpa_s, WPA_SCANNING);
|
|
|
|
if (wpa_s->autoscan_params != NULL) {
|
|
scan_params = wpa_s->autoscan_params;
|
|
goto scan;
|
|
}
|
|
|
|
/* Find the starting point from which to continue scanning */
|
|
ssid = wpa_s->conf->ssid;
|
|
if (wpa_s->prev_sched_ssid) {
|
|
while (ssid) {
|
|
if (ssid == wpa_s->prev_sched_ssid) {
|
|
ssid = ssid->next;
|
|
break;
|
|
}
|
|
ssid = ssid->next;
|
|
}
|
|
}
|
|
|
|
if (!ssid || !wpa_s->prev_sched_ssid) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Beginning of SSID list");
|
|
|
|
if (wpa_s->sched_scan_interval == 0)
|
|
wpa_s->sched_scan_interval = 10;
|
|
wpa_s->sched_scan_timeout = max_sched_scan_ssids * 2;
|
|
wpa_s->first_sched_scan = 1;
|
|
ssid = wpa_s->conf->ssid;
|
|
wpa_s->prev_sched_ssid = ssid;
|
|
}
|
|
|
|
if (wildcard) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Add wildcard SSID to sched_scan");
|
|
params.num_ssids++;
|
|
}
|
|
|
|
while (ssid) {
|
|
if (wpas_network_disabled(wpa_s, ssid))
|
|
goto next;
|
|
|
|
if (params.num_filter_ssids < wpa_s->max_match_sets &&
|
|
params.filter_ssids && ssid->ssid && ssid->ssid_len) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "add to filter ssid: %s",
|
|
wpa_ssid_txt(ssid->ssid, ssid->ssid_len));
|
|
os_memcpy(params.filter_ssids[params.num_filter_ssids].ssid,
|
|
ssid->ssid, ssid->ssid_len);
|
|
params.filter_ssids[params.num_filter_ssids].ssid_len =
|
|
ssid->ssid_len;
|
|
params.num_filter_ssids++;
|
|
} else if (params.filter_ssids && ssid->ssid && ssid->ssid_len)
|
|
{
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Not enough room for SSID "
|
|
"filter for sched_scan - drop filter");
|
|
os_free(params.filter_ssids);
|
|
params.filter_ssids = NULL;
|
|
params.num_filter_ssids = 0;
|
|
}
|
|
|
|
if (ssid->scan_ssid && ssid->ssid && ssid->ssid_len) {
|
|
if (params.num_ssids == max_sched_scan_ssids)
|
|
break; /* only room for broadcast SSID */
|
|
wpa_dbg(wpa_s, MSG_DEBUG,
|
|
"add to active scan ssid: %s",
|
|
wpa_ssid_txt(ssid->ssid, ssid->ssid_len));
|
|
params.ssids[params.num_ssids].ssid =
|
|
ssid->ssid;
|
|
params.ssids[params.num_ssids].ssid_len =
|
|
ssid->ssid_len;
|
|
params.num_ssids++;
|
|
if (params.num_ssids >= max_sched_scan_ssids) {
|
|
wpa_s->prev_sched_ssid = ssid;
|
|
do {
|
|
ssid = ssid->next;
|
|
} while (ssid &&
|
|
(wpas_network_disabled(wpa_s, ssid) ||
|
|
!ssid->scan_ssid));
|
|
break;
|
|
}
|
|
}
|
|
|
|
next:
|
|
wpa_s->prev_sched_ssid = ssid;
|
|
ssid = ssid->next;
|
|
}
|
|
|
|
if (params.num_filter_ssids == 0) {
|
|
os_free(params.filter_ssids);
|
|
params.filter_ssids = NULL;
|
|
}
|
|
|
|
extra_ie = wpa_supplicant_extra_ies(wpa_s);
|
|
if (extra_ie) {
|
|
params.extra_ies = wpabuf_head(extra_ie);
|
|
params.extra_ies_len = wpabuf_len(extra_ie);
|
|
}
|
|
|
|
scan_params = ¶ms;
|
|
|
|
scan:
|
|
if (ssid || !wpa_s->first_sched_scan) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG,
|
|
"Starting sched scan: interval %d timeout %d",
|
|
wpa_s->sched_scan_interval, wpa_s->sched_scan_timeout);
|
|
} else {
|
|
wpa_dbg(wpa_s, MSG_DEBUG,
|
|
"Starting sched scan: interval %d (no timeout)",
|
|
wpa_s->sched_scan_interval);
|
|
}
|
|
|
|
ret = wpa_supplicant_start_sched_scan(wpa_s, scan_params,
|
|
wpa_s->sched_scan_interval);
|
|
wpabuf_free(extra_ie);
|
|
os_free(params.filter_ssids);
|
|
if (ret) {
|
|
wpa_msg(wpa_s, MSG_WARNING, "Failed to initiate sched scan");
|
|
if (prev_state != wpa_s->wpa_state)
|
|
wpa_supplicant_set_state(wpa_s, prev_state);
|
|
return ret;
|
|
}
|
|
|
|
/* If we have more SSIDs to scan, add a timeout so we scan them too */
|
|
if (ssid || !wpa_s->first_sched_scan) {
|
|
wpa_s->sched_scan_timed_out = 0;
|
|
eloop_register_timeout(wpa_s->sched_scan_timeout, 0,
|
|
wpa_supplicant_sched_scan_timeout,
|
|
wpa_s, NULL);
|
|
wpa_s->first_sched_scan = 0;
|
|
wpa_s->sched_scan_timeout /= 2;
|
|
wpa_s->sched_scan_interval *= 2;
|
|
if (wpa_s->sched_scan_timeout < wpa_s->sched_scan_interval) {
|
|
wpa_s->sched_scan_interval = 10;
|
|
wpa_s->sched_scan_timeout = max_sched_scan_ssids * 2;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_supplicant_cancel_scan - Cancel a scheduled scan request
|
|
* @wpa_s: Pointer to wpa_supplicant data
|
|
*
|
|
* This function is used to cancel a scan request scheduled with
|
|
* wpa_supplicant_req_scan().
|
|
*/
|
|
void wpa_supplicant_cancel_scan(struct wpa_supplicant *wpa_s)
|
|
{
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Cancelling scan request");
|
|
eloop_cancel_timeout(wpa_supplicant_scan, wpa_s, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_supplicant_cancel_sched_scan - Stop running scheduled scans
|
|
* @wpa_s: Pointer to wpa_supplicant data
|
|
*
|
|
* This function is used to stop a periodic scheduled scan.
|
|
*/
|
|
void wpa_supplicant_cancel_sched_scan(struct wpa_supplicant *wpa_s)
|
|
{
|
|
if (!wpa_s->sched_scanning)
|
|
return;
|
|
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Cancelling sched scan");
|
|
eloop_cancel_timeout(wpa_supplicant_sched_scan_timeout, wpa_s, NULL);
|
|
wpa_supplicant_stop_sched_scan(wpa_s);
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_supplicant_notify_scanning - Indicate possible scan state change
|
|
* @wpa_s: Pointer to wpa_supplicant data
|
|
* @scanning: Whether scanning is currently in progress
|
|
*
|
|
* This function is to generate scanning notifycations. It is called whenever
|
|
* there may have been a change in scanning (scan started, completed, stopped).
|
|
* wpas_notify_scanning() is called whenever the scanning state changed from the
|
|
* previously notified state.
|
|
*/
|
|
void wpa_supplicant_notify_scanning(struct wpa_supplicant *wpa_s,
|
|
int scanning)
|
|
{
|
|
if (wpa_s->scanning != scanning) {
|
|
wpa_s->scanning = scanning;
|
|
wpas_notify_scanning(wpa_s);
|
|
}
|
|
}
|
|
|
|
|
|
static int wpa_scan_get_max_rate(const struct wpa_scan_res *res)
|
|
{
|
|
int rate = 0;
|
|
const u8 *ie;
|
|
int i;
|
|
|
|
ie = wpa_scan_get_ie(res, WLAN_EID_SUPP_RATES);
|
|
for (i = 0; ie && i < ie[1]; i++) {
|
|
if ((ie[i + 2] & 0x7f) > rate)
|
|
rate = ie[i + 2] & 0x7f;
|
|
}
|
|
|
|
ie = wpa_scan_get_ie(res, WLAN_EID_EXT_SUPP_RATES);
|
|
for (i = 0; ie && i < ie[1]; i++) {
|
|
if ((ie[i + 2] & 0x7f) > rate)
|
|
rate = ie[i + 2] & 0x7f;
|
|
}
|
|
|
|
return rate;
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_scan_get_ie - Fetch a specified information element from a scan result
|
|
* @res: Scan result entry
|
|
* @ie: Information element identitifier (WLAN_EID_*)
|
|
* Returns: Pointer to the information element (id field) or %NULL if not found
|
|
*
|
|
* This function returns the first matching information element in the scan
|
|
* result.
|
|
*/
|
|
const u8 * wpa_scan_get_ie(const struct wpa_scan_res *res, u8 ie)
|
|
{
|
|
const u8 *end, *pos;
|
|
|
|
pos = (const u8 *) (res + 1);
|
|
end = pos + res->ie_len;
|
|
|
|
while (pos + 1 < end) {
|
|
if (pos + 2 + pos[1] > end)
|
|
break;
|
|
if (pos[0] == ie)
|
|
return pos;
|
|
pos += 2 + pos[1];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_scan_get_vendor_ie - Fetch vendor information element from a scan result
|
|
* @res: Scan result entry
|
|
* @vendor_type: Vendor type (four octets starting the IE payload)
|
|
* Returns: Pointer to the information element (id field) or %NULL if not found
|
|
*
|
|
* This function returns the first matching information element in the scan
|
|
* result.
|
|
*/
|
|
const u8 * wpa_scan_get_vendor_ie(const struct wpa_scan_res *res,
|
|
u32 vendor_type)
|
|
{
|
|
const u8 *end, *pos;
|
|
|
|
pos = (const u8 *) (res + 1);
|
|
end = pos + res->ie_len;
|
|
|
|
while (pos + 1 < end) {
|
|
if (pos + 2 + pos[1] > end)
|
|
break;
|
|
if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 &&
|
|
vendor_type == WPA_GET_BE32(&pos[2]))
|
|
return pos;
|
|
pos += 2 + pos[1];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_scan_get_vendor_ie_multi - Fetch vendor IE data from a scan result
|
|
* @res: Scan result entry
|
|
* @vendor_type: Vendor type (four octets starting the IE payload)
|
|
* Returns: Pointer to the information element payload or %NULL if not found
|
|
*
|
|
* This function returns concatenated payload of possibly fragmented vendor
|
|
* specific information elements in the scan result. The caller is responsible
|
|
* for freeing the returned buffer.
|
|
*/
|
|
struct wpabuf * wpa_scan_get_vendor_ie_multi(const struct wpa_scan_res *res,
|
|
u32 vendor_type)
|
|
{
|
|
struct wpabuf *buf;
|
|
const u8 *end, *pos;
|
|
|
|
buf = wpabuf_alloc(res->ie_len);
|
|
if (buf == NULL)
|
|
return NULL;
|
|
|
|
pos = (const u8 *) (res + 1);
|
|
end = pos + res->ie_len;
|
|
|
|
while (pos + 1 < end) {
|
|
if (pos + 2 + pos[1] > end)
|
|
break;
|
|
if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 &&
|
|
vendor_type == WPA_GET_BE32(&pos[2]))
|
|
wpabuf_put_data(buf, pos + 2 + 4, pos[1] - 4);
|
|
pos += 2 + pos[1];
|
|
}
|
|
|
|
if (wpabuf_len(buf) == 0) {
|
|
wpabuf_free(buf);
|
|
buf = NULL;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
/*
|
|
* Channels with a great SNR can operate at full rate. What is a great SNR?
|
|
* This doc https://supportforums.cisco.com/docs/DOC-12954 says, "the general
|
|
* rule of thumb is that any SNR above 20 is good." This one
|
|
* http://www.cisco.com/en/US/tech/tk722/tk809/technologies_q_and_a_item09186a00805e9a96.shtml#qa23
|
|
* recommends 25 as a minimum SNR for 54 Mbps data rate. 30 is chosen here as a
|
|
* conservative value.
|
|
*/
|
|
#define GREAT_SNR 30
|
|
|
|
/* Compare function for sorting scan results. Return >0 if @b is considered
|
|
* better. */
|
|
static int wpa_scan_result_compar(const void *a, const void *b)
|
|
{
|
|
#define IS_5GHZ(n) (n > 4000)
|
|
#define MIN(a,b) a < b ? a : b
|
|
struct wpa_scan_res **_wa = (void *) a;
|
|
struct wpa_scan_res **_wb = (void *) b;
|
|
struct wpa_scan_res *wa = *_wa;
|
|
struct wpa_scan_res *wb = *_wb;
|
|
int wpa_a, wpa_b, maxrate_a, maxrate_b;
|
|
int snr_a, snr_b;
|
|
|
|
/* WPA/WPA2 support preferred */
|
|
wpa_a = wpa_scan_get_vendor_ie(wa, WPA_IE_VENDOR_TYPE) != NULL ||
|
|
wpa_scan_get_ie(wa, WLAN_EID_RSN) != NULL;
|
|
wpa_b = wpa_scan_get_vendor_ie(wb, WPA_IE_VENDOR_TYPE) != NULL ||
|
|
wpa_scan_get_ie(wb, WLAN_EID_RSN) != NULL;
|
|
|
|
if (wpa_b && !wpa_a)
|
|
return 1;
|
|
if (!wpa_b && wpa_a)
|
|
return -1;
|
|
|
|
/* privacy support preferred */
|
|
if ((wa->caps & IEEE80211_CAP_PRIVACY) == 0 &&
|
|
(wb->caps & IEEE80211_CAP_PRIVACY))
|
|
return 1;
|
|
if ((wa->caps & IEEE80211_CAP_PRIVACY) &&
|
|
(wb->caps & IEEE80211_CAP_PRIVACY) == 0)
|
|
return -1;
|
|
|
|
if ((wa->flags & wb->flags & WPA_SCAN_LEVEL_DBM) &&
|
|
!((wa->flags | wb->flags) & WPA_SCAN_NOISE_INVALID)) {
|
|
snr_a = MIN(wa->level - wa->noise, GREAT_SNR);
|
|
snr_b = MIN(wb->level - wb->noise, GREAT_SNR);
|
|
} else {
|
|
/* Not suitable information to calculate SNR, so use level */
|
|
snr_a = wa->level;
|
|
snr_b = wb->level;
|
|
}
|
|
|
|
/* best/max rate preferred if SNR close enough */
|
|
if ((snr_a && snr_b && abs(snr_b - snr_a) < 5) ||
|
|
(wa->qual && wb->qual && abs(wb->qual - wa->qual) < 10)) {
|
|
maxrate_a = wpa_scan_get_max_rate(wa);
|
|
maxrate_b = wpa_scan_get_max_rate(wb);
|
|
if (maxrate_a != maxrate_b)
|
|
return maxrate_b - maxrate_a;
|
|
if (IS_5GHZ(wa->freq) ^ IS_5GHZ(wb->freq))
|
|
return IS_5GHZ(wa->freq) ? -1 : 1;
|
|
}
|
|
|
|
/* use freq for channel preference */
|
|
|
|
/* all things being equal, use SNR; if SNRs are
|
|
* identical, use quality values since some drivers may only report
|
|
* that value and leave the signal level zero */
|
|
if (snr_b == snr_a)
|
|
return wb->qual - wa->qual;
|
|
return snr_b - snr_a;
|
|
#undef MIN
|
|
#undef IS_5GHZ
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_WPS
|
|
/* Compare function for sorting scan results when searching a WPS AP for
|
|
* provisioning. Return >0 if @b is considered better. */
|
|
static int wpa_scan_result_wps_compar(const void *a, const void *b)
|
|
{
|
|
struct wpa_scan_res **_wa = (void *) a;
|
|
struct wpa_scan_res **_wb = (void *) b;
|
|
struct wpa_scan_res *wa = *_wa;
|
|
struct wpa_scan_res *wb = *_wb;
|
|
int uses_wps_a, uses_wps_b;
|
|
struct wpabuf *wps_a, *wps_b;
|
|
int res;
|
|
|
|
/* Optimization - check WPS IE existence before allocated memory and
|
|
* doing full reassembly. */
|
|
uses_wps_a = wpa_scan_get_vendor_ie(wa, WPS_IE_VENDOR_TYPE) != NULL;
|
|
uses_wps_b = wpa_scan_get_vendor_ie(wb, WPS_IE_VENDOR_TYPE) != NULL;
|
|
if (uses_wps_a && !uses_wps_b)
|
|
return -1;
|
|
if (!uses_wps_a && uses_wps_b)
|
|
return 1;
|
|
|
|
if (uses_wps_a && uses_wps_b) {
|
|
wps_a = wpa_scan_get_vendor_ie_multi(wa, WPS_IE_VENDOR_TYPE);
|
|
wps_b = wpa_scan_get_vendor_ie_multi(wb, WPS_IE_VENDOR_TYPE);
|
|
res = wps_ap_priority_compar(wps_a, wps_b);
|
|
wpabuf_free(wps_a);
|
|
wpabuf_free(wps_b);
|
|
if (res)
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Do not use current AP security policy as a sorting criteria during
|
|
* WPS provisioning step since the AP may get reconfigured at the
|
|
* completion of provisioning.
|
|
*/
|
|
|
|
/* all things being equal, use signal level; if signal levels are
|
|
* identical, use quality values since some drivers may only report
|
|
* that value and leave the signal level zero */
|
|
if (wb->level == wa->level)
|
|
return wb->qual - wa->qual;
|
|
return wb->level - wa->level;
|
|
}
|
|
#endif /* CONFIG_WPS */
|
|
|
|
|
|
static void dump_scan_res(struct wpa_scan_results *scan_res)
|
|
{
|
|
#ifndef CONFIG_NO_STDOUT_DEBUG
|
|
size_t i;
|
|
|
|
if (scan_res->res == NULL || scan_res->num == 0)
|
|
return;
|
|
|
|
wpa_printf(MSG_EXCESSIVE, "Sorted scan results");
|
|
|
|
for (i = 0; i < scan_res->num; i++) {
|
|
struct wpa_scan_res *r = scan_res->res[i];
|
|
u8 *pos;
|
|
if ((r->flags & (WPA_SCAN_LEVEL_DBM | WPA_SCAN_NOISE_INVALID))
|
|
== WPA_SCAN_LEVEL_DBM) {
|
|
int snr = r->level - r->noise;
|
|
wpa_printf(MSG_EXCESSIVE, MACSTR " freq=%d qual=%d "
|
|
"noise=%d level=%d snr=%d%s flags=0x%x "
|
|
"age=%u",
|
|
MAC2STR(r->bssid), r->freq, r->qual,
|
|
r->noise, r->level, snr,
|
|
snr >= GREAT_SNR ? "*" : "", r->flags,
|
|
r->age);
|
|
} else {
|
|
wpa_printf(MSG_EXCESSIVE, MACSTR " freq=%d qual=%d "
|
|
"noise=%d level=%d flags=0x%x age=%u",
|
|
MAC2STR(r->bssid), r->freq, r->qual,
|
|
r->noise, r->level, r->flags, r->age);
|
|
}
|
|
pos = (u8 *) (r + 1);
|
|
if (r->ie_len)
|
|
wpa_hexdump(MSG_EXCESSIVE, "IEs", pos, r->ie_len);
|
|
pos += r->ie_len;
|
|
if (r->beacon_ie_len)
|
|
wpa_hexdump(MSG_EXCESSIVE, "Beacon IEs",
|
|
pos, r->beacon_ie_len);
|
|
}
|
|
#endif /* CONFIG_NO_STDOUT_DEBUG */
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_supplicant_filter_bssid_match - Is the specified BSSID allowed
|
|
* @wpa_s: Pointer to wpa_supplicant data
|
|
* @bssid: BSSID to check
|
|
* Returns: 0 if the BSSID is filtered or 1 if not
|
|
*
|
|
* This function is used to filter out specific BSSIDs from scan reslts mainly
|
|
* for testing purposes (SET bssid_filter ctrl_iface command).
|
|
*/
|
|
int wpa_supplicant_filter_bssid_match(struct wpa_supplicant *wpa_s,
|
|
const u8 *bssid)
|
|
{
|
|
size_t i;
|
|
|
|
if (wpa_s->bssid_filter == NULL)
|
|
return 1;
|
|
|
|
for (i = 0; i < wpa_s->bssid_filter_count; i++) {
|
|
if (os_memcmp(wpa_s->bssid_filter + i * ETH_ALEN, bssid,
|
|
ETH_ALEN) == 0)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void filter_scan_res(struct wpa_supplicant *wpa_s,
|
|
struct wpa_scan_results *res)
|
|
{
|
|
size_t i, j;
|
|
|
|
if (wpa_s->bssid_filter == NULL)
|
|
return;
|
|
|
|
for (i = 0, j = 0; i < res->num; i++) {
|
|
if (wpa_supplicant_filter_bssid_match(wpa_s,
|
|
res->res[i]->bssid)) {
|
|
res->res[j++] = res->res[i];
|
|
} else {
|
|
os_free(res->res[i]);
|
|
res->res[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (res->num != j) {
|
|
wpa_printf(MSG_DEBUG, "Filtered out %d scan results",
|
|
(int) (res->num - j));
|
|
res->num = j;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_supplicant_get_scan_results - Get scan results
|
|
* @wpa_s: Pointer to wpa_supplicant data
|
|
* @info: Information about what was scanned or %NULL if not available
|
|
* @new_scan: Whether a new scan was performed
|
|
* Returns: Scan results, %NULL on failure
|
|
*
|
|
* This function request the current scan results from the driver and updates
|
|
* the local BSS list wpa_s->bss. The caller is responsible for freeing the
|
|
* results with wpa_scan_results_free().
|
|
*/
|
|
struct wpa_scan_results *
|
|
wpa_supplicant_get_scan_results(struct wpa_supplicant *wpa_s,
|
|
struct scan_info *info, int new_scan)
|
|
{
|
|
struct wpa_scan_results *scan_res;
|
|
size_t i;
|
|
int (*compar)(const void *, const void *) = wpa_scan_result_compar;
|
|
|
|
scan_res = wpa_drv_get_scan_results2(wpa_s);
|
|
if (scan_res == NULL) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Failed to get scan results");
|
|
return NULL;
|
|
}
|
|
if (scan_res->fetch_time.sec == 0) {
|
|
/*
|
|
* Make sure we have a valid timestamp if the driver wrapper
|
|
* does not set this.
|
|
*/
|
|
os_get_time(&scan_res->fetch_time);
|
|
}
|
|
filter_scan_res(wpa_s, scan_res);
|
|
|
|
#ifdef CONFIG_WPS
|
|
if (wpas_wps_in_progress(wpa_s)) {
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "WPS: Order scan results with WPS "
|
|
"provisioning rules");
|
|
compar = wpa_scan_result_wps_compar;
|
|
}
|
|
#endif /* CONFIG_WPS */
|
|
|
|
qsort(scan_res->res, scan_res->num, sizeof(struct wpa_scan_res *),
|
|
compar);
|
|
dump_scan_res(scan_res);
|
|
|
|
wpa_bss_update_start(wpa_s);
|
|
for (i = 0; i < scan_res->num; i++)
|
|
wpa_bss_update_scan_res(wpa_s, scan_res->res[i],
|
|
&scan_res->fetch_time);
|
|
wpa_bss_update_end(wpa_s, info, new_scan);
|
|
|
|
return scan_res;
|
|
}
|
|
|
|
|
|
/**
|
|
* wpa_supplicant_update_scan_results - Update scan results from the driver
|
|
* @wpa_s: Pointer to wpa_supplicant data
|
|
* Returns: 0 on success, -1 on failure
|
|
*
|
|
* This function updates the BSS table within wpa_supplicant based on the
|
|
* currently available scan results from the driver without requesting a new
|
|
* scan. This is used in cases where the driver indicates an association
|
|
* (including roaming within ESS) and wpa_supplicant does not yet have the
|
|
* needed information to complete the connection (e.g., to perform validation
|
|
* steps in 4-way handshake).
|
|
*/
|
|
int wpa_supplicant_update_scan_results(struct wpa_supplicant *wpa_s)
|
|
{
|
|
struct wpa_scan_results *scan_res;
|
|
scan_res = wpa_supplicant_get_scan_results(wpa_s, NULL, 0);
|
|
if (scan_res == NULL)
|
|
return -1;
|
|
wpa_scan_results_free(scan_res);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* scan_only_handler - Reports scan results
|
|
*/
|
|
void scan_only_handler(struct wpa_supplicant *wpa_s,
|
|
struct wpa_scan_results *scan_res)
|
|
{
|
|
wpa_dbg(wpa_s, MSG_DEBUG, "Scan-only results received");
|
|
wpa_msg_ctrl(wpa_s, MSG_INFO, WPA_EVENT_SCAN_RESULTS);
|
|
wpas_notify_scan_results(wpa_s);
|
|
wpas_notify_scan_done(wpa_s, 1);
|
|
}
|