Merge branch 'moonshot' of ssh://moonshot.suchdamage.org:822/srv/git/libeap into...
[libeap.git] / wpa_supplicant / bss.c
index a981f89..dc978af 100644 (file)
 #include "common/ieee802_11_defs.h"
 #include "drivers/driver.h"
 #include "wpa_supplicant_i.h"
+#include "config.h"
 #include "notify.h"
+#include "scan.h"
 #include "bss.h"
 
 
-#ifndef WPA_BSS_MAX_COUNT
-#define WPA_BSS_MAX_COUNT 200
-#endif /* WPA_BSS_MAX_COUNT */
-
 /**
  * WPA_BSS_EXPIRATION_PERIOD - Period of expiration run in seconds
  */
  */
 #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_MODE_CHANGED_FLAG      BIT(3)
+#define WPA_BSS_WPAIE_CHANGED_FLAG     BIT(4)
+#define WPA_BSS_RSNIE_CHANGED_FLAG     BIT(5)
+#define WPA_BSS_WPS_CHANGED_FLAG       BIT(6)
+#define WPA_BSS_RATES_CHANGED_FLAG     BIT(7)
+#define WPA_BSS_IES_CHANGED_FLAG       BIT(8)
+
 
 static void wpa_bss_remove(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
 {
@@ -110,7 +118,7 @@ static void wpa_bss_add(struct wpa_supplicant *wpa_s,
 {
        struct wpa_bss *bss;
 
-       bss = os_zalloc(sizeof(*bss) + res->ie_len);
+       bss = os_zalloc(sizeof(*bss) + res->ie_len + res->beacon_ie_len);
        if (bss == NULL)
                return;
        bss->id = wpa_s->bss_next_id++;
@@ -119,7 +127,8 @@ static void wpa_bss_add(struct wpa_supplicant *wpa_s,
        os_memcpy(bss->ssid, ssid, ssid_len);
        bss->ssid_len = ssid_len;
        bss->ie_len = res->ie_len;
-       os_memcpy(bss + 1, res + 1, res->ie_len);
+       bss->beacon_ie_len = res->beacon_ie_len;
+       os_memcpy(bss + 1, res + 1, res->ie_len + res->beacon_ie_len);
 
        dl_list_add_tail(&wpa_s->bss, &bss->list);
        dl_list_add_tail(&wpa_s->bss_id, &bss->list_id);
@@ -127,7 +136,7 @@ static void wpa_bss_add(struct wpa_supplicant *wpa_s,
        wpa_printf(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_BSS_MAX_COUNT) {
+       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));
@@ -135,27 +144,173 @@ static void wpa_bss_add(struct wpa_supplicant *wpa_s,
 }
 
 
+static int are_ies_equal(const struct wpa_bss *old,
+                        const struct wpa_scan_res *new, u32 ie)
+{
+       const u8 *old_ie, *new_ie;
+       struct wpabuf *old_ie_buff = NULL;
+       struct wpabuf *new_ie_buff = NULL;
+       int new_ie_len, old_ie_len, ret, is_multi;
+
+       switch (ie) {
+       case WPA_IE_VENDOR_TYPE:
+               old_ie = wpa_bss_get_vendor_ie(old, ie);
+               new_ie = wpa_scan_get_vendor_ie(new, ie);
+               is_multi = 0;
+               break;
+       case WPS_IE_VENDOR_TYPE:
+               old_ie_buff = wpa_bss_get_vendor_ie_multi(old, ie);
+               new_ie_buff = wpa_scan_get_vendor_ie_multi(new, ie);
+               is_multi = 1;
+               break;
+       case WLAN_EID_RSN:
+       case WLAN_EID_SUPP_RATES:
+       case WLAN_EID_EXT_SUPP_RATES:
+               old_ie = wpa_bss_get_ie(old, ie);
+               new_ie = wpa_scan_get_ie(new, ie);
+               is_multi = 0;
+               break;
+       default:
+               wpa_printf(MSG_DEBUG, "bss: %s: cannot compare IEs", __func__);
+               return 0;
+       }
+
+       if (is_multi) {
+               /* in case of multiple IEs stored in buffer */
+               old_ie = old_ie_buff ? wpabuf_head_u8(old_ie_buff) : NULL;
+               new_ie = new_ie_buff ? wpabuf_head_u8(new_ie_buff) : NULL;
+               old_ie_len = old_ie_buff ? wpabuf_len(old_ie_buff) : 0;
+               new_ie_len = new_ie_buff ? wpabuf_len(new_ie_buff) : 0;
+       } else {
+               /* in case of single IE */
+               old_ie_len = old_ie ? old_ie[1] + 2 : 0;
+               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);
+
+       wpabuf_free(old_ie_buff);
+       wpabuf_free(new_ie_buff);
+
+       return ret;
+}
+
+
+static u32 wpa_bss_compare_res(const struct wpa_bss *old,
+                              const struct wpa_scan_res *new)
+{
+       u32 changes = 0;
+       int caps_diff = old->caps ^ new->caps;
+
+       if (old->freq != new->freq)
+               changes |= WPA_BSS_FREQ_CHANGED_FLAG;
+
+       if (old->level != new->level)
+               changes |= WPA_BSS_SIGNAL_CHANGED_FLAG;
+
+       if (caps_diff & IEEE80211_CAP_PRIVACY)
+               changes |= WPA_BSS_PRIVACY_CHANGED_FLAG;
+
+       if (caps_diff & IEEE80211_CAP_IBSS)
+               changes |= WPA_BSS_MODE_CHANGED_FLAG;
+
+       if (old->ie_len == new->ie_len &&
+           os_memcmp(old + 1, new + 1, old->ie_len) == 0)
+               return changes;
+       changes |= WPA_BSS_IES_CHANGED_FLAG;
+
+       if (!are_ies_equal(old, new, WPA_IE_VENDOR_TYPE))
+               changes |= WPA_BSS_WPAIE_CHANGED_FLAG;
+
+       if (!are_ies_equal(old, new, WLAN_EID_RSN))
+               changes |= WPA_BSS_RSNIE_CHANGED_FLAG;
+
+       if (!are_ies_equal(old, new, WPS_IE_VENDOR_TYPE))
+               changes |= WPA_BSS_WPS_CHANGED_FLAG;
+
+       if (!are_ies_equal(old, new, WLAN_EID_SUPP_RATES) ||
+           !are_ies_equal(old, new, WLAN_EID_EXT_SUPP_RATES))
+               changes |= WPA_BSS_RATES_CHANGED_FLAG;
+
+       return changes;
+}
+
+
+static void notify_bss_changes(struct wpa_supplicant *wpa_s, u32 changes,
+                              const struct wpa_bss *bss)
+{
+       if (changes & WPA_BSS_FREQ_CHANGED_FLAG)
+               wpas_notify_bss_freq_changed(wpa_s, bss->id);
+
+       if (changes & WPA_BSS_SIGNAL_CHANGED_FLAG)
+               wpas_notify_bss_signal_changed(wpa_s, bss->id);
+
+       if (changes & WPA_BSS_PRIVACY_CHANGED_FLAG)
+               wpas_notify_bss_privacy_changed(wpa_s, bss->id);
+
+       if (changes & WPA_BSS_MODE_CHANGED_FLAG)
+               wpas_notify_bss_mode_changed(wpa_s, bss->id);
+
+       if (changes & WPA_BSS_WPAIE_CHANGED_FLAG)
+               wpas_notify_bss_wpaie_changed(wpa_s, bss->id);
+
+       if (changes & WPA_BSS_RSNIE_CHANGED_FLAG)
+               wpas_notify_bss_rsnie_changed(wpa_s, bss->id);
+
+       if (changes & WPA_BSS_WPS_CHANGED_FLAG)
+               wpas_notify_bss_wps_changed(wpa_s, bss->id);
+
+       if (changes & WPA_BSS_IES_CHANGED_FLAG)
+               wpas_notify_bss_ies_changed(wpa_s, bss->id);
+
+       if (changes & WPA_BSS_RATES_CHANGED_FLAG)
+               wpas_notify_bss_rates_changed(wpa_s, bss->id);
+}
+
+
 static void wpa_bss_update(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
                           struct wpa_scan_res *res)
 {
+       u32 changes;
+
+       changes = wpa_bss_compare_res(bss, res);
        bss->scan_miss_count = 0;
        bss->last_update_idx = wpa_s->bss_update_idx;
        wpa_bss_copy_res(bss, res);
        /* Move the entry to the end of the list */
        dl_list_del(&bss->list);
-       if (bss->ie_len >= res->ie_len) {
-               os_memcpy(bss + 1, res + 1, res->ie_len);
+       if (bss->ie_len + bss->beacon_ie_len >=
+           res->ie_len + res->beacon_ie_len) {
+               os_memcpy(bss + 1, res + 1, res->ie_len + res->beacon_ie_len);
                bss->ie_len = res->ie_len;
+               bss->beacon_ie_len = res->beacon_ie_len;
        } else {
                struct wpa_bss *nbss;
-               nbss = os_realloc(bss, sizeof(*bss) + res->ie_len);
+               struct dl_list *prev = bss->list_id.prev;
+               dl_list_del(&bss->list_id);
+               nbss = os_realloc(bss, sizeof(*bss) + res->ie_len +
+                                 res->beacon_ie_len);
                if (nbss) {
                        bss = nbss;
-                       os_memcpy(bss + 1, res + 1, res->ie_len);
+                       os_memcpy(bss + 1, res + 1,
+                                 res->ie_len + res->beacon_ie_len);
                        bss->ie_len = res->ie_len;
+                       bss->beacon_ie_len = res->beacon_ie_len;
                }
+               dl_list_add(prev, &bss->list_id);
        }
        dl_list_add_tail(&wpa_s->bss, &bss->list);
+
+       notify_bss_changes(wpa_s, changes, 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;
 }
 
 
@@ -170,7 +325,7 @@ void wpa_bss_update_start(struct wpa_supplicant *wpa_s)
 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);
@@ -185,6 +340,11 @@ void wpa_bss_update_scan_res(struct wpa_supplicant *wpa_s,
                return;
        }
 
+       p2p = wpa_scan_get_vendor_ie(res, P2P_IE_VENDOR_TYPE);
+       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]);
@@ -245,6 +405,8 @@ void wpa_bss_update_end(struct wpa_supplicant *wpa_s, struct scan_info *info,
                return; /* do not expire entries without new scan */
 
        dl_list_for_each_safe(bss, n, &wpa_s->bss, struct wpa_bss, list) {
+               if (wpa_bss_in_use(wpa_s, bss))
+                       continue;
                if (!wpa_bss_included_in_scan(bss, info))
                        continue; /* expire only BSSes that were scanned */
                if (bss->last_update_idx < wpa_s->bss_update_idx)
@@ -271,9 +433,8 @@ static void wpa_bss_timeout(void *eloop_ctx, void *timeout_ctx)
        t.sec -= WPA_BSS_EXPIRATION_AGE;
 
        dl_list_for_each_safe(bss, n, &wpa_s->bss, struct wpa_bss, list) {
-               if (os_memcmp(bss->bssid, wpa_s->bssid, ETH_ALEN) == 0 ||
-                   os_memcmp(bss->bssid, wpa_s->pending_bssid, ETH_ALEN) == 0)
-                       continue; /* do not expire BSSes that are in use */
+               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",
@@ -421,3 +582,30 @@ int wpa_bss_get_max_rate(const struct wpa_bss *bss)
 
        return rate;
 }
+
+
+int wpa_bss_get_bit_rates(const struct wpa_bss *bss, u8 **rates)
+{
+       const u8 *ie, *ie2;
+       int i, j;
+       unsigned int len;
+       u8 *r;
+
+       ie = wpa_bss_get_ie(bss, WLAN_EID_SUPP_RATES);
+       ie2 = wpa_bss_get_ie(bss, WLAN_EID_EXT_SUPP_RATES);
+
+       len = (ie ? ie[1] : 0) + (ie2 ? ie2[1] : 0);
+
+       r = os_malloc(len);
+       if (!r)
+               return -1;
+
+       for (i = 0; ie && i < ie[1]; i++)
+               r[i] = ie[i + 2] & 0x7f;
+
+       for (j = 0; ie2 && j < ie2[1]; j++)
+               r[i + j] = ie2[j + 2] & 0x7f;
+
+       *rates = r;
+       return len;
+}