mesh: Implement mesh scanning
[mech_eap.git] / wpa_supplicant / scan.c
index c61bd55..b95182e 100644 (file)
@@ -22,6 +22,7 @@
 #include "notify.h"
 #include "bss.h"
 #include "scan.h"
+#include "mesh.h"
 
 
 static void wpa_supplicant_gen_assoc_event(struct wpa_supplicant *wpa_s)
@@ -148,7 +149,20 @@ static void wpas_trigger_scan_cb(struct wpa_radio_work *work, int deinit)
        int ret;
 
        if (deinit) {
-               wpa_scan_free_params(params);
+               if (!work->started) {
+                       wpa_scan_free_params(params);
+                       return;
+               }
+               wpa_supplicant_notify_scanning(wpa_s, 0);
+               wpas_notify_scan_done(wpa_s, 0);
+               wpa_s->scan_work = NULL;
+               return;
+       }
+
+       if (wpas_update_random_addr_disassoc(wpa_s) < 0) {
+               wpa_msg(wpa_s, MSG_INFO,
+                       "Failed to assign random MAC address for a scan");
+               radio_work_done(work);
                return;
        }
 
@@ -320,6 +334,32 @@ static void wpa_supplicant_optimize_freqs(
                }
                wpa_s->p2p_in_provisioning++;
        }
+
+       if (params->freqs == NULL && wpa_s->p2p_in_invitation) {
+               /*
+                * Optimize scan based on GO information during persistent
+                * group reinvocation
+                */
+               if (wpa_s->p2p_in_invitation < 5 &&
+                   wpa_s->p2p_invite_go_freq > 0) {
+                       wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Scan only GO preferred frequency %d MHz during invitation",
+                               wpa_s->p2p_invite_go_freq);
+                       params->freqs = os_zalloc(2 * sizeof(int));
+                       if (params->freqs)
+                               params->freqs[0] = wpa_s->p2p_invite_go_freq;
+               }
+               wpa_s->p2p_in_invitation++;
+               if (wpa_s->p2p_in_invitation > 20) {
+                       /*
+                        * This should not really happen since the variable is
+                        * cleared on group removal, but if it does happen, make
+                        * sure we do not get stuck in special invitation scan
+                        * mode.
+                        */
+                       wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Clear p2p_in_invitation");
+                       wpa_s->p2p_in_invitation = 0;
+               }
+       }
 #endif /* CONFIG_P2P */
 
 #ifdef CONFIG_WPS
@@ -359,11 +399,17 @@ static void wpas_add_interworking_elements(struct wpa_supplicant *wpa_s,
                return;
 
        wpabuf_put_u8(buf, WLAN_EID_EXT_CAPAB);
-       wpabuf_put_u8(buf, 4);
+       wpabuf_put_u8(buf, 6);
        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, 0x00);
+#ifdef CONFIG_HS20
+       wpabuf_put_u8(buf, 0x40); /* Bit 46 - WNM-Notification */
+#else /* CONFIG_HS20 */
+       wpabuf_put_u8(buf, 0x00);
+#endif /* CONFIG_HS20 */
 
        wpabuf_put_u8(buf, WLAN_EID_INTERWORKING);
        wpabuf_put_u8(buf, is_zero_ether_addr(wpa_s->conf->hessid) ? 1 :
@@ -415,11 +461,13 @@ static struct wpabuf * wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s)
        }
 #endif /* CONFIG_P2P */
 
+       wpa_supplicant_mesh_add_scan_ie(wpa_s, &extra_ie);
+
 #endif /* CONFIG_WPS */
 
 #ifdef CONFIG_HS20
        if (wpa_s->conf->hs20 && wpabuf_resize(&extra_ie, 7) == 0)
-               wpas_hs20_add_indication(extra_ie);
+               wpas_hs20_add_indication(extra_ie, -1);
 #endif /* CONFIG_HS20 */
 
        return extra_ie;
@@ -510,6 +558,47 @@ static void wpa_setband_scan_freqs(struct wpa_supplicant *wpa_s,
 }
 
 
+static void wpa_set_scan_ssids(struct wpa_supplicant *wpa_s,
+                              struct wpa_driver_scan_params *params,
+                              size_t max_ssids)
+{
+       unsigned int i;
+       struct wpa_ssid *ssid;
+
+       for (i = 0; i < wpa_s->scan_id_count; i++) {
+               unsigned int j;
+
+               ssid = wpa_config_get_network(wpa_s->conf, wpa_s->scan_id[i]);
+               if (!ssid || !ssid->scan_ssid)
+                       continue;
+
+               for (j = 0; j < params->num_ssids; j++) {
+                       if (params->ssids[j].ssid_len == ssid->ssid_len &&
+                           params->ssids[j].ssid &&
+                           os_memcmp(params->ssids[j].ssid, ssid->ssid,
+                                     ssid->ssid_len) == 0)
+                               break;
+               }
+               if (j < params->num_ssids)
+                       continue; /* already in the list */
+
+               if (params->num_ssids + 1 > max_ssids) {
+                       wpa_printf(MSG_DEBUG,
+                                  "Over max scan SSIDs for manual request");
+                       break;
+               }
+
+               wpa_printf(MSG_DEBUG, "Scan SSID (manual request): %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++;
+       }
+
+       wpa_s->scan_id_count = 0;
+}
+
+
 static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
 {
        struct wpa_supplicant *wpa_s = eloop_ctx;
@@ -521,6 +610,11 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
        size_t max_ssids;
        enum wpa_states prev_state;
 
+       if (wpa_s->pno || wpa_s->pno_sched_pending) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Skip scan - PNO is in progress");
+               return;
+       }
+
        if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Skip scan - interface disabled");
                return;
@@ -542,17 +636,6 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                return;
        }
 
-       if (wpa_s->external_scan_running) {
-               struct os_reltime now, diff;
-               os_get_reltime(&now);
-               os_reltime_sub(&now, &wpa_s->scan_start_time, &diff);
-               if (diff.sec < 30) {
-                       wpa_dbg(wpa_s, MSG_DEBUG, "Externally triggered scan running - Reschedule the incoming scan req");
-                       wpa_supplicant_req_scan(wpa_s, 1, 0);
-                       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");
@@ -573,13 +656,11 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                return;
        }
 
-#ifdef CONFIG_P2P
        if (wpas_p2p_in_progress(wpa_s)) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Delay station mode scan while P2P operation is in progress");
                wpa_supplicant_req_scan(wpa_s, 5, 0);
                return;
        }
-#endif /* CONFIG_P2P */
 
        if (wpa_s->conf->ap_scan == 2)
                max_ssids = 1;
@@ -633,6 +714,19 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                params.num_ssids = 1;
                goto ssid_list_set;
        }
+
+       if (wpa_s->p2p_in_invitation) {
+               if (wpa_s->current_ssid) {
+                       wpa_printf(MSG_DEBUG, "P2P: Use specific SSID for scan during invitation");
+                       params.ssids[0].ssid = wpa_s->current_ssid->ssid;
+                       params.ssids[0].ssid_len =
+                               wpa_s->current_ssid->ssid_len;
+                       params.num_ssids = 1;
+               } else {
+                       wpa_printf(MSG_DEBUG, "P2P: No specific SSID known for scan during invitation");
+               }
+               goto ssid_list_set;
+       }
 #endif /* CONFIG_P2P */
 
        /* Find the starting point from which to continue scanning */
@@ -659,6 +753,36 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                 * wildcard SSID.
                 */
                ssid = NULL;
+       } else if (wpa_s->reattach && wpa_s->current_ssid != NULL) {
+               /*
+                * Perform single-channel single-SSID scan for
+                * reassociate-to-same-BSS operation.
+                */
+               /* Setup SSID */
+               ssid = wpa_s->current_ssid;
+               wpa_hexdump_ascii(MSG_DEBUG, "Scan SSID",
+                                 ssid->ssid, ssid->ssid_len);
+               params.ssids[0].ssid = ssid->ssid;
+               params.ssids[0].ssid_len = ssid->ssid_len;
+               params.num_ssids = 1;
+
+               /*
+                * Allocate memory for frequency array, allocate one extra
+                * slot for the zero-terminator.
+                */
+               params.freqs = os_malloc(sizeof(int) * 2);
+               if (params.freqs == NULL) {
+                       wpa_dbg(wpa_s, MSG_ERROR, "Memory allocation failed");
+                       return;
+               }
+               params.freqs[0] = wpa_s->assoc_freq;
+               params.freqs[1] = 0;
+
+               /*
+                * Reset the reattach flag so that we fall back to full scan if
+                * this scan fails.
+                */
+               wpa_s->reattach = 0;
        } else {
                struct wpa_ssid *start = ssid, *tssid;
                int freqs_set = 0;
@@ -685,6 +809,10 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                                ssid = wpa_s->conf->ssid;
                }
 
+               if (wpa_s->scan_id_count &&
+                   wpa_s->last_scan_req == MANUAL_SCAN_REQ)
+                       wpa_set_scan_ssids(wpa_s, &params, max_ssids);
+
                for (tssid = wpa_s->conf->ssid; tssid; tssid = tssid->next) {
                        if (wpas_network_disabled(wpa_s, tssid))
                                continue;
@@ -795,7 +923,7 @@ ssid_list_set:
        }
 
 #ifdef CONFIG_P2P
-       if (wpa_s->p2p_in_provisioning ||
+       if (wpa_s->p2p_in_provisioning || wpa_s->p2p_in_invitation ||
            (wpa_s->show_group_started && wpa_s->go_params)) {
                /*
                 * The interface may not yet be in P2P mode, so we have to
@@ -899,17 +1027,19 @@ void wpa_supplicant_update_scan_int(struct wpa_supplicant *wpa_s, int sec)
  */
 void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec)
 {
-       if (eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL))
-       {
-               wpa_dbg(wpa_s, MSG_DEBUG, "Rescheduling scan request: %d sec %d usec",
+       int res = eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s,
+                                       NULL);
+       if (res == 1) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Rescheduling scan request: %d.%06d sec",
                        sec, usec);
-               return;
+       } else if (res == 0) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Ignore new scan request for %d.%06d sec since an earlier request is scheduled to trigger sooner",
+                       sec, usec);
+       } else {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Setting scan request: %d.%06d sec",
+                       sec, usec);
+               eloop_register_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL);
        }
-
-       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);
 }
 
 
@@ -1121,6 +1251,16 @@ int wpa_supplicant_req_sched_scan(struct wpa_supplicant *wpa_s)
                params.extra_ies_len = wpabuf_len(extra_ie);
        }
 
+       if (wpa_s->conf->filter_rssi)
+               params.filter_rssi = wpa_s->conf->filter_rssi;
+
+       /* See if user specified frequencies. If so, scan only those. */
+       if (wpa_s->conf->freq_list && !params.freqs) {
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "Optimize scan based on conf->freq_list");
+               int_array_concat(&params.freqs, wpa_s->conf->freq_list);
+       }
+
        scan_params = &params;
 
 scan:
@@ -1766,18 +1906,20 @@ wpa_scan_clone_params(const struct wpa_driver_scan_params *src)
        }
 
        if (src->filter_ssids) {
-               params->filter_ssids = os_malloc(sizeof(params->filter_ssids) *
+               params->filter_ssids = os_malloc(sizeof(*params->filter_ssids) *
                                                 src->num_filter_ssids);
                if (params->filter_ssids == NULL)
                        goto failed;
                os_memcpy(params->filter_ssids, src->filter_ssids,
-                         sizeof(params->filter_ssids) * src->num_filter_ssids);
+                         sizeof(*params->filter_ssids) *
+                         src->num_filter_ssids);
                params->num_filter_ssids = src->num_filter_ssids;
        }
 
        params->filter_rssi = src->filter_rssi;
        params->p2p_probe = src->p2p_probe;
        params->only_new_results = src->only_new_results;
+       params->low_priority = src->low_priority;
 
        return params;
 
@@ -1801,3 +1943,132 @@ void wpa_scan_free_params(struct wpa_driver_scan_params *params)
        os_free(params->filter_ssids);
        os_free(params);
 }
+
+
+int wpas_start_pno(struct wpa_supplicant *wpa_s)
+{
+       int ret, interval;
+       size_t i, num_ssid, num_match_ssid;
+       struct wpa_ssid *ssid;
+       struct wpa_driver_scan_params params;
+
+       if (!wpa_s->sched_scan_supported)
+               return -1;
+
+       if (wpa_s->pno || wpa_s->pno_sched_pending)
+               return 0;
+
+       if ((wpa_s->wpa_state > WPA_SCANNING) &&
+           (wpa_s->wpa_state <= WPA_COMPLETED)) {
+               wpa_printf(MSG_ERROR, "PNO: In assoc process");
+               return -EAGAIN;
+       }
+
+       if (wpa_s->wpa_state == WPA_SCANNING) {
+               wpa_supplicant_cancel_scan(wpa_s);
+               if (wpa_s->sched_scanning) {
+                       wpa_printf(MSG_DEBUG, "Schedule PNO on completion of "
+                                  "ongoing sched scan");
+                       wpa_supplicant_cancel_sched_scan(wpa_s);
+                       wpa_s->pno_sched_pending = 1;
+                       return 0;
+               }
+       }
+
+       os_memset(&params, 0, sizeof(params));
+
+       num_ssid = num_match_ssid = 0;
+       ssid = wpa_s->conf->ssid;
+       while (ssid) {
+               if (!wpas_network_disabled(wpa_s, ssid)) {
+                       num_match_ssid++;
+                       if (ssid->scan_ssid)
+                               num_ssid++;
+               }
+               ssid = ssid->next;
+       }
+
+       if (num_match_ssid == 0) {
+               wpa_printf(MSG_DEBUG, "PNO: No configured SSIDs");
+               return -1;
+       }
+
+       if (num_match_ssid > num_ssid) {
+               params.num_ssids++; /* wildcard */
+               num_ssid++;
+       }
+
+       if (num_ssid > WPAS_MAX_SCAN_SSIDS) {
+               wpa_printf(MSG_DEBUG, "PNO: Use only the first %u SSIDs from "
+                          "%u", WPAS_MAX_SCAN_SSIDS, (unsigned int) num_ssid);
+               num_ssid = WPAS_MAX_SCAN_SSIDS;
+       }
+
+       if (num_match_ssid > wpa_s->max_match_sets) {
+               num_match_ssid = wpa_s->max_match_sets;
+               wpa_dbg(wpa_s, MSG_DEBUG, "PNO: Too many SSIDs to match");
+       }
+       params.filter_ssids = os_calloc(num_match_ssid,
+                                       sizeof(struct wpa_driver_scan_filter));
+       if (params.filter_ssids == NULL)
+               return -1;
+       i = 0;
+       ssid = wpa_s->conf->ssid;
+       while (ssid) {
+               if (!wpas_network_disabled(wpa_s, ssid)) {
+                       if (ssid->scan_ssid && params.num_ssids < num_ssid) {
+                               params.ssids[params.num_ssids].ssid =
+                                       ssid->ssid;
+                               params.ssids[params.num_ssids].ssid_len =
+                                        ssid->ssid_len;
+                               params.num_ssids++;
+                       }
+                       os_memcpy(params.filter_ssids[i].ssid, ssid->ssid,
+                                 ssid->ssid_len);
+                       params.filter_ssids[i].ssid_len = ssid->ssid_len;
+                       params.num_filter_ssids++;
+                       i++;
+                       if (i == num_match_ssid)
+                               break;
+               }
+               ssid = ssid->next;
+       }
+
+       if (wpa_s->conf->filter_rssi)
+               params.filter_rssi = wpa_s->conf->filter_rssi;
+
+       interval = wpa_s->conf->sched_scan_interval ?
+               wpa_s->conf->sched_scan_interval : 10;
+
+       if (params.freqs == NULL && wpa_s->manual_sched_scan_freqs) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Limit sched scan to specified channels");
+               params.freqs = wpa_s->manual_sched_scan_freqs;
+       }
+
+       ret = wpa_supplicant_start_sched_scan(wpa_s, &params, interval);
+       os_free(params.filter_ssids);
+       if (ret == 0)
+               wpa_s->pno = 1;
+       else
+               wpa_msg(wpa_s, MSG_ERROR, "Failed to schedule PNO");
+       return ret;
+}
+
+
+int wpas_stop_pno(struct wpa_supplicant *wpa_s)
+{
+       int ret = 0;
+
+       if (!wpa_s->pno)
+               return 0;
+
+       ret = wpa_supplicant_stop_sched_scan(wpa_s);
+
+       wpa_s->pno = 0;
+       wpa_s->pno_sched_pending = 0;
+
+       if (wpa_s->wpa_state == WPA_SCANNING)
+               wpa_supplicant_req_scan(wpa_s, 0, 0);
+
+       return ret;
+}