cfg80211: merge in beacon ies of hidden bss.
authorDmitry Tarnyagin <abi.dmitryt@gmail.com>
Fri, 4 Nov 2011 16:12:07 +0000 (17:12 +0100)
committerJohn W. Linville <linville@tuxdriver.com>
Fri, 11 Nov 2011 17:32:47 +0000 (12:32 -0500)
The problem with PSM when a hidden SSID was used was originally
reported by Juuso Oikarinen.

 - When generally scanning, the AP is getting a bss entry with
   a zero SSID.
 - When associating, a probe-req is sent to the AP with the SSID,
   and as a result a probe-response is received with the hidden
   SSID in place. As a consequence, a second bss entry is created
   for the AP, now with the real SSID.
 - After association, mac80211 executes ieee80211_recalc_ps(),
   but does not switch to powersave because the beacon-ies are missing.

As result, the STA does not ever enter PSM.

The patch merges in beacon ies of hidden bss from beacon to the probe
response, creating a consistent set of ies in place.

Patch is depended on "cfg80211: fix cmp_ies" made by Johannes.

Signed-off-by: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
net/wireless/scan.c

index dc23b31594e0f54758d0b59ffa5854c6e426e2e8..31119e32e092b6e2615a8653c768a5cd7531465b 100644 (file)
@@ -355,8 +355,8 @@ static bool is_mesh(struct cfg80211_bss *a,
            sizeof(struct ieee80211_meshconf_ie) - 2) == 0;
 }
 
-static int cmp_bss(struct cfg80211_bss *a,
-                  struct cfg80211_bss *b)
+static int cmp_bss_core(struct cfg80211_bss *a,
+                       struct cfg80211_bss *b)
 {
        int r;
 
@@ -378,7 +378,15 @@ static int cmp_bss(struct cfg80211_bss *a,
                               b->len_information_elements);
        }
 
-       r = memcmp(a->bssid, b->bssid, ETH_ALEN);
+       return memcmp(a->bssid, b->bssid, ETH_ALEN);
+}
+
+static int cmp_bss(struct cfg80211_bss *a,
+                  struct cfg80211_bss *b)
+{
+       int r;
+
+       r = cmp_bss_core(a, b);
        if (r)
                return r;
 
@@ -389,6 +397,52 @@ static int cmp_bss(struct cfg80211_bss *a,
                       b->len_information_elements);
 }
 
+static int cmp_hidden_bss(struct cfg80211_bss *a,
+                  struct cfg80211_bss *b)
+{
+       const u8 *ie1;
+       const u8 *ie2;
+       int i;
+       int r;
+
+       r = cmp_bss_core(a, b);
+       if (r)
+               return r;
+
+       ie1 = cfg80211_find_ie(WLAN_EID_SSID,
+                       a->information_elements,
+                       a->len_information_elements);
+       ie2 = cfg80211_find_ie(WLAN_EID_SSID,
+                       b->information_elements,
+                       b->len_information_elements);
+
+       /* Key comparator must use same algorithm in any rb-tree
+        * search function (order is important), otherwise ordering
+        * of items in the tree is broken and search gives incorrect
+        * results. This code uses same order as cmp_ies() does. */
+
+       /* sort missing IE before (left of) present IE */
+       if (!ie1)
+               return -1;
+       if (!ie2)
+               return 1;
+
+       /* zero-size SSID is used as an indication of the hidden bss */
+       if (!ie2[1])
+               return 0;
+
+       /* sort by length first, then by contents */
+       if (ie1[1] != ie2[1])
+               return ie2[1] - ie1[1];
+
+       /* zeroed SSID ie is another indication of a hidden bss */
+       for (i = 0; i < ie2[1]; i++)
+               if (ie2[i + 2])
+                       return -1;
+
+       return 0;
+}
+
 struct cfg80211_bss *cfg80211_get_bss(struct wiphy *wiphy,
                                      struct ieee80211_channel *channel,
                                      const u8 *bssid,
@@ -504,6 +558,48 @@ rb_find_bss(struct cfg80211_registered_device *dev,
        return NULL;
 }
 
+static struct cfg80211_internal_bss *
+rb_find_hidden_bss(struct cfg80211_registered_device *dev,
+           struct cfg80211_internal_bss *res)
+{
+       struct rb_node *n = dev->bss_tree.rb_node;
+       struct cfg80211_internal_bss *bss;
+       int r;
+
+       while (n) {
+               bss = rb_entry(n, struct cfg80211_internal_bss, rbn);
+               r = cmp_hidden_bss(&res->pub, &bss->pub);
+
+               if (r == 0)
+                       return bss;
+               else if (r < 0)
+                       n = n->rb_left;
+               else
+                       n = n->rb_right;
+       }
+
+       return NULL;
+}
+
+static void
+copy_hidden_ies(struct cfg80211_internal_bss *res,
+                struct cfg80211_internal_bss *hidden)
+{
+       if (unlikely(res->pub.beacon_ies))
+               return;
+       if (WARN_ON(!hidden->pub.beacon_ies))
+               return;
+
+       res->pub.beacon_ies = kmalloc(hidden->pub.len_beacon_ies, GFP_ATOMIC);
+       if (unlikely(!res->pub.beacon_ies))
+               return;
+
+       res->beacon_ies_allocated = true;
+       res->pub.len_beacon_ies = hidden->pub.len_beacon_ies;
+       memcpy(res->pub.beacon_ies, hidden->pub.beacon_ies,
+                       res->pub.len_beacon_ies);
+}
+
 static struct cfg80211_internal_bss *
 cfg80211_bss_update(struct cfg80211_registered_device *dev,
                    struct cfg80211_internal_bss *res)
@@ -607,6 +703,21 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev,
 
                kref_put(&res->ref, bss_release);
        } else {
+               struct cfg80211_internal_bss *hidden;
+
+               /* First check if the beacon is a probe response from
+                * a hidden bss. If so, copy beacon ies (with nullified
+                * ssid) into the probe response bss entry (with real ssid).
+                * It is required basically for PSM implementation
+                * (probe responses do not contain tim ie) */
+
+               /* TODO: The code is not trying to update existing probe
+                * response bss entries when beacon ies are
+                * getting changed. */
+               hidden = rb_find_hidden_bss(dev, res);
+               if (hidden)
+                       copy_hidden_ies(res, hidden);
+
                /* this "consumes" the reference */
                list_add_tail(&res->list, &dev->bss_list);
                rb_insert_bss(dev, res);