diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 76dbc9d62..189e3a627 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -694,6 +694,8 @@ struct wpa_driver_capa { unsigned int flags; int max_scan_ssids; + int max_sched_scan_ssids; + int sched_scan_supported; /** * max_remain_on_chan - Maximum remain-on-channel duration in msec @@ -2455,6 +2457,35 @@ struct wpa_driver_ops { * the station gets added by FT-over-DS. */ int (*add_sta_node)(void *priv, const u8 *addr, u16 auth_alg); + + /** + * sched_scan - Request the driver to initiate scheduled scan + * @priv: Private driver interface data + * @params: Scan parameters + * @interval: Interval between scan cycles in milliseconds + * Returns: 0 on success, -1 on failure + * + * This operation should be used for scheduled scan offload to + * the hardware. Every time scan results are available, the + * driver should report scan results event for wpa_supplicant + * which will eventually request the results with + * wpa_driver_get_scan_results2(). This operation is optional + * and if not provided or if it returns -1, we fall back to + * normal host-scheduled scans. + */ + int (*sched_scan)(void *priv, struct wpa_driver_scan_params *params, + u32 interval); + + /** + * stop_sched_scan - Request the driver to stop a scheduled scan + * @priv: Private driver interface data + * Returns: 0 on success, -1 on failure + * + * This should cause the scheduled scan to be stopped and + * results should stop being sent. Must be supported if + * sched_scan is supported. + */ + int (*stop_sched_scan)(void *priv); }; @@ -2867,7 +2898,12 @@ enum wpa_event_type { * completed Group Key Handshake while the host (including * wpa_supplicant was sleeping). */ - EVENT_DRIVER_GTK_REKEY + EVENT_DRIVER_GTK_REKEY, + + /** + * EVENT_SCHED_SCAN_STOPPED - Scheduled scan was stopped + */ + EVENT_SCHED_SCAN_STOPPED }; diff --git a/src/drivers/driver_ndis.c b/src/drivers/driver_ndis.c index 19db3e1ec..0410577df 100644 --- a/src/drivers/driver_ndis.c +++ b/src/drivers/driver_ndis.c @@ -3331,5 +3331,7 @@ const struct wpa_driver_ops wpa_driver_ndis_ops = { NULL /* sta_assoc */, NULL /* sta_auth */, NULL /* add_tspec */, - NULL /* add_sta_node */ + NULL /* add_sta_node */, + NULL /* sched_scan */, + NULL /* stop_sched_scan */ }; diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index 79fdddd76..2f439fc0a 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -79,6 +79,23 @@ static inline int wpa_drv_scan(struct wpa_supplicant *wpa_s, return -1; } +static inline int wpa_drv_sched_scan(struct wpa_supplicant *wpa_s, + struct wpa_driver_scan_params *params, + u32 interval) +{ + if (wpa_s->driver->sched_scan) + return wpa_s->driver->sched_scan(wpa_s->drv_priv, + params, interval); + return -1; +} + +static inline int wpa_drv_stop_sched_scan(struct wpa_supplicant *wpa_s) +{ + if (wpa_s->driver->stop_sched_scan) + return wpa_s->driver->stop_sched_scan(wpa_s->drv_priv); + return -1; +} + static inline struct wpa_scan_results * wpa_drv_get_scan_results2( struct wpa_supplicant *wpa_s) { diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 7d886e71f..08ed7be14 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -2263,6 +2263,17 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, wpa_sm_update_replay_ctr(wpa_s->wpa, data->driver_gtk_rekey.replay_ctr); break; + case EVENT_SCHED_SCAN_STOPPED: + wpa_s->sched_scanning = 0; + wpa_supplicant_notify_scanning(wpa_s, 0); + + /* + * If we timed out, start a new sched scan to continue + * searching for more SSIDs. + */ + if (wpa_s->sched_scan_timed_out) + wpa_supplicant_req_sched_scan(wpa_s); + break; default: wpa_msg(wpa_s, MSG_INFO, "Unknown event %d", event); break; diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c index c504028bb..c73f2be3b 100644 --- a/wpa_supplicant/scan.c +++ b/wpa_supplicant/scan.c @@ -215,6 +215,54 @@ int wpa_supplicant_trigger_scan(struct wpa_supplicant *wpa_s, } +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; + + if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_USER_SPACE_MLME) + return -1; + + 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) { @@ -566,6 +614,130 @@ void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec) } +/** + * wpa_supplicant_req_sched_scan - Start a periodic scheduled scan + * @wpa_s: Pointer to wpa_supplicant data + * + * 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; + enum wpa_states prev_state; + struct wpa_ssid *ssid; + struct wpabuf *wps_ie = NULL; + int ret; + int use_wildcard = 0; + unsigned int max_sched_scan_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 (wpa_s->sched_scanning) + return 0; + + 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); + + /* 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"); + + wpa_s->sched_scan_interval = 2; + 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; + } + + while (ssid) { + if (ssid->disabled) { + wpa_s->prev_sched_ssid = ssid; + ssid = ssid->next; + continue; + } + + if (!ssid->scan_ssid) + use_wildcard = 1; + else { + 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_sched_scan_ssids) { + wpa_s->prev_sched_ssid = ssid; + break; + } + } + wpa_s->prev_sched_ssid = ssid; + ssid = ssid->next; + } + + if (ssid || use_wildcard) { + wpa_dbg(wpa_s, MSG_DEBUG, "Include wildcard SSID in " + "the sched scan request"); + params.num_ssids++; + } else { + wpa_dbg(wpa_s, MSG_DEBUG, "ssid %p - list ended", ssid); + } + + if (!params.num_ssids) + return 0; + + if (wpa_s->wps) + wps_ie = wpa_supplicant_extra_ies(wpa_s, ¶ms); + + wpa_dbg(wpa_s, MSG_DEBUG, + "Starting sched scan: interval %d timeout %d", + wpa_s->sched_scan_interval, wpa_s->sched_scan_timeout); + + ret = wpa_supplicant_start_sched_scan(wpa_s, ¶ms, + wpa_s->sched_scan_interval); + wpabuf_free(wps_ie); + 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; + } + + return 0; +} + + /** * wpa_supplicant_cancel_scan - Cancel a scheduled scan request * @wpa_s: Pointer to wpa_supplicant data @@ -580,6 +752,23 @@ void wpa_supplicant_cancel_scan(struct wpa_supplicant *wpa_s) } +/** + * 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); +} + + void wpa_supplicant_notify_scanning(struct wpa_supplicant *wpa_s, int scanning) { diff --git a/wpa_supplicant/scan.h b/wpa_supplicant/scan.h index 025b81520..f9d21b056 100644 --- a/wpa_supplicant/scan.h +++ b/wpa_supplicant/scan.h @@ -17,7 +17,9 @@ int wpa_supplicant_enabled_networks(struct wpa_config *conf); void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec); +int wpa_supplicant_req_sched_scan(struct wpa_supplicant *wpa_s); void wpa_supplicant_cancel_scan(struct wpa_supplicant *wpa_s); +void wpa_supplicant_cancel_sched_scan(struct wpa_supplicant *wpa_s); void wpa_supplicant_notify_scanning(struct wpa_supplicant *wpa_s, int scanning); struct wpa_driver_scan_params; diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 3cf62e801..3d1fa9bae 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -2114,6 +2114,7 @@ static struct wpa_supplicant * wpa_supplicant_alloc(void) wpa_s->scan_interval = 5; wpa_s->new_connection = 1; wpa_s->parent = wpa_s; + wpa_s->sched_scanning = 0; return wpa_s; } @@ -2278,6 +2279,8 @@ next_driver: return -1; } wpa_s->max_scan_ssids = capa.max_scan_ssids; + wpa_s->max_sched_scan_ssids = capa.max_sched_scan_ssids; + wpa_s->sched_scan_supported = capa.sched_scan_supported; wpa_s->max_remain_on_chan = capa.max_remain_on_chan; wpa_s->max_stations = capa.max_stations; } diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 866649bc8..8395686d8 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -387,6 +387,12 @@ struct wpa_supplicant { */ #define WILDCARD_SSID_SCAN ((struct wpa_ssid *) 1) + struct wpa_ssid *prev_sched_ssid; /* last SSID used in sched scan */ + int sched_scan_timeout; + int sched_scan_interval; + int first_sched_scan; + int sched_scan_timed_out; + void (*scan_res_handler)(struct wpa_supplicant *wpa_s, struct wpa_scan_results *scan_res); struct dl_list bss; /* struct wpa_bss::list */ @@ -405,6 +411,7 @@ struct wpa_supplicant { enum wpa_states wpa_state; int scanning; + int sched_scanning; int new_connection; int reassociated_connection; @@ -428,6 +435,8 @@ struct wpa_supplicant { struct wpa_client_mlme mlme; unsigned int drv_flags; int max_scan_ssids; + int max_sched_scan_ssids; + int sched_scan_supported; unsigned int max_remain_on_chan; unsigned int max_stations;