Maintain list of BSS entries in last scan result order
[mech_eap.git] / wpa_supplicant / bss.c
index e2ac230..434fb08 100644 (file)
@@ -2,14 +2,8 @@
  * BSS table
  * Copyright (c) 2009-2010, Jouni Malinen <j@w1.fi>
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * Alternatively, this software may be distributed under the terms of BSD
- * license.
- *
- * See README and COPYING for more details.
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
  */
 
 #include "utils/includes.h"
  */
 #define WPA_BSS_EXPIRATION_PERIOD 10
 
-/**
- * WPA_BSS_EXPIRATION_AGE - BSS entry age after which it can be expired
- *
- * This value control the time in seconds after which a BSS entry gets removed
- * if it has not been updated or is not in use.
- */
-#define WPA_BSS_EXPIRATION_AGE 180
-
-/**
- * WPA_BSS_EXPIRATION_SCAN_COUNT - Expire BSS after number of scans
- *
- * If the BSS entry has not been seen in this many scans, it will be removed.
- * Value 1 means that the entry is removed after the first scan without the
- * BSSID being seen. Larger values can be used to avoid BSS entries
- * disappearing if they are not visible in every scan (e.g., low signal quality
- * or interference).
- */
-#define WPA_BSS_EXPIRATION_SCAN_COUNT 2
-
 #define WPA_BSS_FREQ_CHANGED_FLAG      BIT(0)
 #define WPA_BSS_SIGNAL_CHANGED_FLAG    BIT(1)
 #define WPA_BSS_PRIVACY_CHANGED_FLAG   BIT(2)
 #define WPA_BSS_IES_CHANGED_FLAG       BIT(8)
 
 
-static void wpa_bss_remove(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
+static void wpa_bss_remove(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
+                          const char *reason)
 {
+       if (wpa_s->last_scan_res) {
+               unsigned int i;
+               for (i = 0; i < wpa_s->last_scan_res_used; i++) {
+                       if (wpa_s->last_scan_res[i] == bss) {
+                               os_memmove(&wpa_s->last_scan_res[i],
+                                          &wpa_s->last_scan_res[i + 1],
+                                          (wpa_s->last_scan_res_used - i - 1)
+                                          * sizeof(struct wpa_bss *));
+                               wpa_s->last_scan_res_used--;
+                               break;
+                       }
+               }
+       }
        dl_list_del(&bss->list);
        dl_list_del(&bss->list_id);
        wpa_s->num_bss--;
-       wpa_printf(MSG_DEBUG, "BSS: Remove id %u BSSID " MACSTR " SSID '%s'",
-                  bss->id, MAC2STR(bss->bssid),
-                  wpa_ssid_txt(bss->ssid, bss->ssid_len));
+       wpa_dbg(wpa_s, MSG_DEBUG, "BSS: Remove id %u BSSID " MACSTR
+               " SSID '%s' due to %s", bss->id, MAC2STR(bss->bssid),
+               wpa_ssid_txt(bss->ssid, bss->ssid_len), reason);
        wpas_notify_bss_removed(wpa_s, bss->bssid, bss->id);
+#ifdef CONFIG_INTERWORKING
+       wpabuf_free(bss->anqp_venue_name);
+       wpabuf_free(bss->anqp_network_auth_type);
+       wpabuf_free(bss->anqp_roaming_consortium);
+       wpabuf_free(bss->anqp_ip_addr_type_availability);
+       wpabuf_free(bss->anqp_nai_realm);
+       wpabuf_free(bss->anqp_3gpp);
+       wpabuf_free(bss->anqp_domain_name);
+#endif /* CONFIG_INTERWORKING */
+#ifdef CONFIG_HS20
+       wpabuf_free(bss->hs20_operator_friendly_name);
+       wpabuf_free(bss->hs20_wan_metrics);
+       wpabuf_free(bss->hs20_connection_capability);
+       wpabuf_free(bss->hs20_operating_class);
+#endif /* CONFIG_HS20 */
        os_free(bss);
 }
 
@@ -77,6 +81,8 @@ struct wpa_bss * wpa_bss_get(struct wpa_supplicant *wpa_s, const u8 *bssid,
                             const u8 *ssid, size_t ssid_len)
 {
        struct wpa_bss *bss;
+       if (!wpa_supplicant_filter_bssid_match(wpa_s, bssid))
+               return NULL;
        dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
                if (os_memcmp(bss->bssid, bssid, ETH_ALEN) == 0 &&
                    bss->ssid_len == ssid_len &&
@@ -112,15 +118,79 @@ static void wpa_bss_copy_res(struct wpa_bss *dst, struct wpa_scan_res *src)
 }
 
 
-static void wpa_bss_add(struct wpa_supplicant *wpa_s,
-                       const u8 *ssid, size_t ssid_len,
-                       struct wpa_scan_res *res)
+static int wpa_bss_known(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
+{
+       struct wpa_ssid *ssid;
+
+       for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
+               if (ssid->ssid == NULL || ssid->ssid_len == 0)
+                       continue;
+               if (ssid->ssid_len == bss->ssid_len &&
+                   os_memcmp(ssid->ssid, bss->ssid, ssid->ssid_len) == 0)
+                       return 1;
+       }
+
+       return 0;
+}
+
+
+static int wpa_bss_in_use(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
+{
+       return bss == wpa_s->current_bss ||
+               os_memcmp(bss->bssid, wpa_s->bssid, ETH_ALEN) == 0 ||
+               os_memcmp(bss->bssid, wpa_s->pending_bssid, ETH_ALEN) == 0;
+}
+
+
+static int wpa_bss_remove_oldest_unknown(struct wpa_supplicant *wpa_s)
+{
+       struct wpa_bss *bss;
+
+       dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
+               if (!wpa_bss_known(wpa_s, bss)) {
+                       wpa_bss_remove(wpa_s, bss, __func__);
+                       return 0;
+               }
+       }
+
+       return -1;
+}
+
+
+static int wpa_bss_remove_oldest(struct wpa_supplicant *wpa_s)
+{
+       struct wpa_bss *bss;
+
+       /*
+        * Remove the oldest entry that does not match with any configured
+        * network.
+        */
+       if (wpa_bss_remove_oldest_unknown(wpa_s) == 0)
+               return 0;
+
+       /*
+        * Remove the oldest entry that isn't currently in use.
+        */
+       dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
+               if (!wpa_bss_in_use(wpa_s, bss)) {
+                       wpa_bss_remove(wpa_s, bss, __func__);
+                       return 0;
+               }
+       }
+
+       return -1;
+}
+
+
+static struct wpa_bss * wpa_bss_add(struct wpa_supplicant *wpa_s,
+                                   const u8 *ssid, size_t ssid_len,
+                                   struct wpa_scan_res *res)
 {
        struct wpa_bss *bss;
 
        bss = os_zalloc(sizeof(*bss) + res->ie_len + res->beacon_ie_len);
        if (bss == NULL)
-               return;
+               return NULL;
        bss->id = wpa_s->bss_next_id++;
        bss->last_update_idx = wpa_s->bss_update_idx;
        wpa_bss_copy_res(bss, res);
@@ -133,14 +203,18 @@ static void wpa_bss_add(struct wpa_supplicant *wpa_s,
        dl_list_add_tail(&wpa_s->bss, &bss->list);
        dl_list_add_tail(&wpa_s->bss_id, &bss->list_id);
        wpa_s->num_bss++;
-       wpa_printf(MSG_DEBUG, "BSS: Add new id %u BSSID " MACSTR " SSID '%s'",
-                  bss->id, MAC2STR(bss->bssid), wpa_ssid_txt(ssid, ssid_len));
+       wpa_dbg(wpa_s, MSG_DEBUG, "BSS: Add new id %u BSSID " MACSTR
+               " SSID '%s'",
+               bss->id, MAC2STR(bss->bssid), wpa_ssid_txt(ssid, ssid_len));
        wpas_notify_bss_added(wpa_s, bss->bssid, bss->id);
-       if (wpa_s->num_bss > wpa_s->conf->bss_max_count) {
-               /* Remove the oldest entry */
-               wpa_bss_remove(wpa_s, dl_list_first(&wpa_s->bss,
-                                                   struct wpa_bss, list));
+       if (wpa_s->num_bss > wpa_s->conf->bss_max_count &&
+           wpa_bss_remove_oldest(wpa_s) != 0) {
+               wpa_printf(MSG_ERROR, "Increasing the MAX BSS count to %d "
+                          "because all BSSes are in use. We should normally "
+                          "not get here!", (int) wpa_s->num_bss);
+               wpa_s->conf->bss_max_count = wpa_s->num_bss;
        }
+       return bss;
 }
 
 
@@ -187,8 +261,11 @@ static int are_ies_equal(const struct wpa_bss *old,
                new_ie_len = new_ie ? new_ie[1] + 2 : 0;
        }
 
-       ret = (old_ie_len == new_ie_len &&
-              os_memcmp(old_ie, new_ie, old_ie_len) == 0);
+       if (!old_ie || !new_ie)
+               ret = !old_ie && !new_ie;
+       else
+               ret = (old_ie_len == new_ie_len &&
+                      os_memcmp(old_ie, new_ie, old_ie_len) == 0);
 
        wpabuf_free(old_ie_buff);
        wpabuf_free(new_ie_buff);
@@ -292,6 +369,8 @@ static void wpa_bss_update(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
                nbss = os_realloc(bss, sizeof(*bss) + res->ie_len +
                                  res->beacon_ie_len);
                if (nbss) {
+                       if (wpa_s->current_bss == bss)
+                               wpa_s->current_bss = nbss;
                        bss = nbss;
                        os_memcpy(bss + 1, res + 1,
                                  res->ie_len + res->beacon_ie_len);
@@ -306,47 +385,76 @@ static void wpa_bss_update(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
 }
 
 
-static int wpa_bss_in_use(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
-{
-       return bss == wpa_s->current_bss ||
-               os_memcmp(bss->bssid, wpa_s->bssid, ETH_ALEN) == 0 ||
-               os_memcmp(bss->bssid, wpa_s->pending_bssid, ETH_ALEN) == 0;
-}
-
-
 void wpa_bss_update_start(struct wpa_supplicant *wpa_s)
 {
        wpa_s->bss_update_idx++;
-       wpa_printf(MSG_DEBUG, "BSS: Start scan result update %u",
-                  wpa_s->bss_update_idx);
+       wpa_dbg(wpa_s, MSG_DEBUG, "BSS: Start scan result update %u",
+               wpa_s->bss_update_idx);
+       wpa_s->last_scan_res_used = 0;
 }
 
 
 void wpa_bss_update_scan_res(struct wpa_supplicant *wpa_s,
                             struct wpa_scan_res *res)
 {
-       const u8 *ssid;
+       const u8 *ssid, *p2p;
        struct wpa_bss *bss;
 
        ssid = wpa_scan_get_ie(res, WLAN_EID_SSID);
        if (ssid == NULL) {
-               wpa_printf(MSG_DEBUG, "BSS: No SSID IE included for " MACSTR,
-                          MAC2STR(res->bssid));
+               wpa_dbg(wpa_s, MSG_DEBUG, "BSS: No SSID IE included for "
+                       MACSTR, MAC2STR(res->bssid));
                return;
        }
        if (ssid[1] > 32) {
-               wpa_printf(MSG_DEBUG, "BSS: Too long SSID IE included for "
-                          MACSTR, MAC2STR(res->bssid));
+               wpa_dbg(wpa_s, MSG_DEBUG, "BSS: Too long SSID IE included for "
+                       MACSTR, MAC2STR(res->bssid));
                return;
        }
 
+       p2p = wpa_scan_get_vendor_ie(res, P2P_IE_VENDOR_TYPE);
+#ifdef CONFIG_P2P
+       if (p2p == NULL &&
+           wpa_s->p2p_group_interface != NOT_P2P_GROUP_INTERFACE) {
+               /*
+                * If it's a P2P specific interface, then don't update
+                * the scan result without a P2P IE.
+                */
+               wpa_printf(MSG_DEBUG, "BSS: No P2P IE - skipping BSS " MACSTR
+                          " update for P2P interface", MAC2STR(res->bssid));
+               return;
+       }
+#endif /* CONFIG_P2P */
+       if (p2p && ssid[1] == P2P_WILDCARD_SSID_LEN &&
+           os_memcmp(ssid + 2, P2P_WILDCARD_SSID, P2P_WILDCARD_SSID_LEN) == 0)
+               return; /* Skip P2P listen discovery results here */
+
        /* TODO: add option for ignoring BSSes we are not interested in
         * (to save memory) */
        bss = wpa_bss_get(wpa_s, res->bssid, ssid + 2, ssid[1]);
        if (bss == NULL)
-               wpa_bss_add(wpa_s, ssid + 2, ssid[1], res);
+               bss = wpa_bss_add(wpa_s, ssid + 2, ssid[1], res);
        else
                wpa_bss_update(wpa_s, bss, res);
+
+       if (bss == NULL)
+               return;
+       if (wpa_s->last_scan_res_used >= wpa_s->last_scan_res_size) {
+               struct wpa_bss **n;
+               unsigned int siz;
+               if (wpa_s->last_scan_res_size == 0)
+                       siz = 32;
+               else
+                       siz = wpa_s->last_scan_res_size * 2;
+               n = os_realloc_array(wpa_s->last_scan_res, siz,
+                                    sizeof(struct wpa_bss *));
+               if (n == NULL)
+                       return;
+               wpa_s->last_scan_res = n;
+               wpa_s->last_scan_res_size = siz;
+       }
+
+       wpa_s->last_scan_res[wpa_s->last_scan_res_used++] = bss;
 }
 
 
@@ -396,9 +504,26 @@ void wpa_bss_update_end(struct wpa_supplicant *wpa_s, struct scan_info *info,
 {
        struct wpa_bss *bss, *n;
 
+       wpa_s->last_scan_full = 0;
+       os_get_time(&wpa_s->last_scan);
        if (!new_scan)
                return; /* do not expire entries without new scan */
 
+       if (info && !info->aborted && !info->freqs) {
+               size_t i;
+               if (info->num_ssids == 0) {
+                       wpa_s->last_scan_full = 1;
+               } else {
+                       for (i = 0; i < info->num_ssids; i++) {
+                               if (info->ssids[i].ssid == NULL ||
+                                   info->ssids[i].ssid_len == 0) {
+                                       wpa_s->last_scan_full = 1;
+                                       break;
+                               }
+                       }
+               }
+       }
+
        dl_list_for_each_safe(bss, n, &wpa_s->bss, struct wpa_bss, list) {
                if (wpa_bss_in_use(wpa_s, bss))
                        continue;
@@ -406,18 +531,21 @@ void wpa_bss_update_end(struct wpa_supplicant *wpa_s, struct scan_info *info,
                        continue; /* expire only BSSes that were scanned */
                if (bss->last_update_idx < wpa_s->bss_update_idx)
                        bss->scan_miss_count++;
-               if (bss->scan_miss_count >= WPA_BSS_EXPIRATION_SCAN_COUNT) {
-                       wpa_printf(MSG_DEBUG, "BSS: Expire BSS %u due to no "
-                                  "match in scan", bss->id);
-                       wpa_bss_remove(wpa_s, bss);
+               if (bss->scan_miss_count >=
+                   wpa_s->conf->bss_expiration_scan_count) {
+                       wpa_bss_remove(wpa_s, bss, "no match in scan");
                }
        }
+
+       wpa_printf(MSG_DEBUG, "BSS: last_scan_res_used=%u/%u "
+                  "last_scan_full=%d",
+                  wpa_s->last_scan_res_used, wpa_s->last_scan_res_size,
+                  wpa_s->last_scan_full);
 }
 
 
-static void wpa_bss_timeout(void *eloop_ctx, void *timeout_ctx)
+void wpa_bss_flush_by_age(struct wpa_supplicant *wpa_s, int age)
 {
-       struct wpa_supplicant *wpa_s = eloop_ctx;
        struct wpa_bss *bss, *n;
        struct os_time t;
 
@@ -425,19 +553,25 @@ static void wpa_bss_timeout(void *eloop_ctx, void *timeout_ctx)
                return;
 
        os_get_time(&t);
-       t.sec -= WPA_BSS_EXPIRATION_AGE;
+       t.sec -= age;
 
        dl_list_for_each_safe(bss, n, &wpa_s->bss, struct wpa_bss, list) {
                if (wpa_bss_in_use(wpa_s, bss))
                        continue;
 
                if (os_time_before(&bss->last_update, &t)) {
-                       wpa_printf(MSG_DEBUG, "BSS: Expire BSS %u due to age",
-                                  bss->id);
-                       wpa_bss_remove(wpa_s, bss);
+                       wpa_bss_remove(wpa_s, bss, __func__);
                } else
                        break;
        }
+}
+
+
+static void wpa_bss_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+       struct wpa_supplicant *wpa_s = eloop_ctx;
+
+       wpa_bss_flush_by_age(wpa_s, wpa_s->conf->bss_expiration_age);
        eloop_register_timeout(WPA_BSS_EXPIRATION_PERIOD, 0,
                               wpa_bss_timeout, wpa_s, NULL);
 }
@@ -453,14 +587,25 @@ int wpa_bss_init(struct wpa_supplicant *wpa_s)
 }
 
 
-void wpa_bss_deinit(struct wpa_supplicant *wpa_s)
+void wpa_bss_flush(struct wpa_supplicant *wpa_s)
 {
        struct wpa_bss *bss, *n;
-       eloop_cancel_timeout(wpa_bss_timeout, wpa_s, NULL);
+
        if (wpa_s->bss.next == NULL)
                return; /* BSS table not yet initialized */
-       dl_list_for_each_safe(bss, n, &wpa_s->bss, struct wpa_bss, list)
-               wpa_bss_remove(wpa_s, bss);
+
+       dl_list_for_each_safe(bss, n, &wpa_s->bss, struct wpa_bss, list) {
+               if (wpa_bss_in_use(wpa_s, bss))
+                       continue;
+               wpa_bss_remove(wpa_s, bss, __func__);
+       }
+}
+
+
+void wpa_bss_deinit(struct wpa_supplicant *wpa_s)
+{
+       eloop_cancel_timeout(wpa_bss_timeout, wpa_s, NULL);
+       wpa_bss_flush(wpa_s);
 }
 
 
@@ -468,7 +613,9 @@ struct wpa_bss * wpa_bss_get_bssid(struct wpa_supplicant *wpa_s,
                                   const u8 *bssid)
 {
        struct wpa_bss *bss;
-       dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
+       if (!wpa_supplicant_filter_bssid_match(wpa_s, bssid))
+               return NULL;
+       dl_list_for_each_reverse(bss, &wpa_s->bss, struct wpa_bss, list) {
                if (os_memcmp(bss->bssid, bssid, ETH_ALEN) == 0)
                        return bss;
        }
@@ -476,6 +623,23 @@ struct wpa_bss * wpa_bss_get_bssid(struct wpa_supplicant *wpa_s,
 }
 
 
+#ifdef CONFIG_P2P
+struct wpa_bss * wpa_bss_get_p2p_dev_addr(struct wpa_supplicant *wpa_s,
+                                         const u8 *dev_addr)
+{
+       struct wpa_bss *bss;
+       dl_list_for_each_reverse(bss, &wpa_s->bss, struct wpa_bss, list) {
+               u8 addr[ETH_ALEN];
+               if (p2p_parse_dev_addr((const u8 *) (bss + 1), bss->ie_len,
+                                      addr) == 0 &&
+                   os_memcmp(addr, dev_addr, ETH_ALEN) == 0)
+                       return bss;
+       }
+       return NULL;
+}
+#endif /* CONFIG_P2P */
+
+
 struct wpa_bss * wpa_bss_get_id(struct wpa_supplicant *wpa_s, unsigned int id)
 {
        struct wpa_bss *bss;