|
@@ -19,46 +19,124 @@
|
|
|
#include "wext-compat.h"
|
|
|
#include "rdev-ops.h"
|
|
|
|
|
|
+/**
|
|
|
+ * DOC: BSS tree/list structure
|
|
|
+ *
|
|
|
+ * At the top level, the BSS list is kept in both a list in each
|
|
|
+ * registered device (@bss_list) as well as an RB-tree for faster
|
|
|
+ * lookup. In the RB-tree, entries can be looked up using their
|
|
|
+ * channel, MESHID, MESHCONF (for MBSSes) or channel, BSSID, SSID
|
|
|
+ * for other BSSes.
|
|
|
+ *
|
|
|
+ * Due to the possibility of hidden SSIDs, there's a second level
|
|
|
+ * structure, the "hidden_list" and "hidden_beacon_bss" pointer.
|
|
|
+ * The hidden_list connects all BSSes belonging to a single AP
|
|
|
+ * that has a hidden SSID, and connects beacon and probe response
|
|
|
+ * entries. For a probe response entry for a hidden SSID, the
|
|
|
+ * hidden_beacon_bss pointer points to the BSS struct holding the
|
|
|
+ * beacon's information.
|
|
|
+ *
|
|
|
+ * Reference counting is done for all these references except for
|
|
|
+ * the hidden_list, so that a beacon BSS struct that is otherwise
|
|
|
+ * not referenced has one reference for being on the bss_list and
|
|
|
+ * one for each probe response entry that points to it using the
|
|
|
+ * hidden_beacon_bss pointer. When a BSS struct that has such a
|
|
|
+ * pointer is get/put, the refcount update is also propagated to
|
|
|
+ * the referenced struct, this ensure that it cannot get removed
|
|
|
+ * while somebody is using the probe response version.
|
|
|
+ *
|
|
|
+ * Note that the hidden_beacon_bss pointer never changes, due to
|
|
|
+ * the reference counting. Therefore, no locking is needed for
|
|
|
+ * it.
|
|
|
+ *
|
|
|
+ * Also note that the hidden_beacon_bss pointer is only relevant
|
|
|
+ * if the driver uses something other than the IEs, e.g. private
|
|
|
+ * data stored stored in the BSS struct, since the beacon IEs are
|
|
|
+ * also linked into the probe response struct.
|
|
|
+ */
|
|
|
+
|
|
|
#define IEEE80211_SCAN_RESULT_EXPIRE (30 * HZ)
|
|
|
|
|
|
-static void bss_release(struct kref *ref)
|
|
|
+static void bss_free(struct cfg80211_internal_bss *bss)
|
|
|
{
|
|
|
struct cfg80211_bss_ies *ies;
|
|
|
- struct cfg80211_internal_bss *bss;
|
|
|
-
|
|
|
- bss = container_of(ref, struct cfg80211_internal_bss, ref);
|
|
|
|
|
|
if (WARN_ON(atomic_read(&bss->hold)))
|
|
|
return;
|
|
|
|
|
|
ies = (void *)rcu_access_pointer(bss->pub.beacon_ies);
|
|
|
- if (ies)
|
|
|
+ if (ies && !bss->pub.hidden_beacon_bss)
|
|
|
kfree_rcu(ies, rcu_head);
|
|
|
ies = (void *)rcu_access_pointer(bss->pub.proberesp_ies);
|
|
|
if (ies)
|
|
|
kfree_rcu(ies, rcu_head);
|
|
|
|
|
|
+ /*
|
|
|
+ * This happens when the module is removed, it doesn't
|
|
|
+ * really matter any more save for completeness
|
|
|
+ */
|
|
|
+ if (!list_empty(&bss->hidden_list))
|
|
|
+ list_del(&bss->hidden_list);
|
|
|
+
|
|
|
kfree(bss);
|
|
|
}
|
|
|
|
|
|
-static inline void bss_ref_get(struct cfg80211_internal_bss *bss)
|
|
|
+static inline void bss_ref_get(struct cfg80211_registered_device *dev,
|
|
|
+ struct cfg80211_internal_bss *bss)
|
|
|
{
|
|
|
- kref_get(&bss->ref);
|
|
|
+ lockdep_assert_held(&dev->bss_lock);
|
|
|
+
|
|
|
+ bss->refcount++;
|
|
|
+ if (bss->pub.hidden_beacon_bss) {
|
|
|
+ bss = container_of(bss->pub.hidden_beacon_bss,
|
|
|
+ struct cfg80211_internal_bss,
|
|
|
+ pub);
|
|
|
+ bss->refcount++;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-static inline void bss_ref_put(struct cfg80211_internal_bss *bss)
|
|
|
+static inline void bss_ref_put(struct cfg80211_registered_device *dev,
|
|
|
+ struct cfg80211_internal_bss *bss)
|
|
|
{
|
|
|
- kref_put(&bss->ref, bss_release);
|
|
|
+ lockdep_assert_held(&dev->bss_lock);
|
|
|
+
|
|
|
+ if (bss->pub.hidden_beacon_bss) {
|
|
|
+ struct cfg80211_internal_bss *hbss;
|
|
|
+ hbss = container_of(bss->pub.hidden_beacon_bss,
|
|
|
+ struct cfg80211_internal_bss,
|
|
|
+ pub);
|
|
|
+ hbss->refcount--;
|
|
|
+ if (hbss->refcount == 0)
|
|
|
+ bss_free(hbss);
|
|
|
+ }
|
|
|
+ bss->refcount--;
|
|
|
+ if (bss->refcount == 0)
|
|
|
+ bss_free(bss);
|
|
|
}
|
|
|
|
|
|
-static void __cfg80211_unlink_bss(struct cfg80211_registered_device *dev,
|
|
|
+static bool __cfg80211_unlink_bss(struct cfg80211_registered_device *dev,
|
|
|
struct cfg80211_internal_bss *bss)
|
|
|
{
|
|
|
lockdep_assert_held(&dev->bss_lock);
|
|
|
|
|
|
+ if (!list_empty(&bss->hidden_list)) {
|
|
|
+ /*
|
|
|
+ * don't remove the beacon entry if it has
|
|
|
+ * probe responses associated with it
|
|
|
+ */
|
|
|
+ if (!bss->pub.hidden_beacon_bss)
|
|
|
+ return false;
|
|
|
+ /*
|
|
|
+ * if it's a probe response entry break its
|
|
|
+ * link to the other entries in the group
|
|
|
+ */
|
|
|
+ list_del_init(&bss->hidden_list);
|
|
|
+ }
|
|
|
+
|
|
|
list_del_init(&bss->list);
|
|
|
rb_erase(&bss->rbn, &dev->bss_tree);
|
|
|
- bss_ref_put(bss);
|
|
|
+ bss_ref_put(dev, bss);
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
static void __cfg80211_bss_expire(struct cfg80211_registered_device *dev,
|
|
@@ -75,8 +153,8 @@ static void __cfg80211_bss_expire(struct cfg80211_registered_device *dev,
|
|
|
if (!time_after(expire_time, bss->ts))
|
|
|
continue;
|
|
|
|
|
|
- __cfg80211_unlink_bss(dev, bss);
|
|
|
- expired = true;
|
|
|
+ if (__cfg80211_unlink_bss(dev, bss))
|
|
|
+ expired = true;
|
|
|
}
|
|
|
|
|
|
if (expired)
|
|
@@ -466,7 +544,7 @@ struct cfg80211_bss *cfg80211_get_bss(struct wiphy *wiphy,
|
|
|
continue;
|
|
|
if (is_bss(&bss->pub, bssid, ssid, ssid_len)) {
|
|
|
res = bss;
|
|
|
- bss_ref_get(res);
|
|
|
+ bss_ref_get(dev, res);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
@@ -532,23 +610,67 @@ rb_find_bss(struct cfg80211_registered_device *dev,
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
-static void
|
|
|
-copy_hidden_ies(struct cfg80211_internal_bss *res,
|
|
|
- struct cfg80211_internal_bss *hidden)
|
|
|
+static bool cfg80211_combine_bsses(struct cfg80211_registered_device *dev,
|
|
|
+ struct cfg80211_internal_bss *new)
|
|
|
{
|
|
|
const struct cfg80211_bss_ies *ies;
|
|
|
+ struct cfg80211_internal_bss *bss;
|
|
|
+ const u8 *ie;
|
|
|
+ int i, ssidlen;
|
|
|
+ u8 fold = 0;
|
|
|
|
|
|
- if (rcu_access_pointer(res->pub.beacon_ies))
|
|
|
- return;
|
|
|
-
|
|
|
- ies = rcu_access_pointer(hidden->pub.beacon_ies);
|
|
|
+ ies = rcu_access_pointer(new->pub.beacon_ies);
|
|
|
if (WARN_ON(!ies))
|
|
|
- return;
|
|
|
+ return false;
|
|
|
|
|
|
- ies = kmemdup(ies, sizeof(*ies) + ies->len, GFP_ATOMIC);
|
|
|
- if (unlikely(!ies))
|
|
|
- return;
|
|
|
- rcu_assign_pointer(res->pub.beacon_ies, ies);
|
|
|
+ ie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len);
|
|
|
+ if (!ie) {
|
|
|
+ /* nothing to do */
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ ssidlen = ie[1];
|
|
|
+ for (i = 0; i < ssidlen; i++)
|
|
|
+ fold |= ie[2 + i];
|
|
|
+
|
|
|
+ if (fold) {
|
|
|
+ /* not a hidden SSID */
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* This is the bad part ... */
|
|
|
+
|
|
|
+ list_for_each_entry(bss, &dev->bss_list, list) {
|
|
|
+ if (!ether_addr_equal(bss->pub.bssid, new->pub.bssid))
|
|
|
+ continue;
|
|
|
+ if (bss->pub.channel != new->pub.channel)
|
|
|
+ continue;
|
|
|
+ if (rcu_access_pointer(bss->pub.beacon_ies))
|
|
|
+ continue;
|
|
|
+ ies = rcu_access_pointer(bss->pub.ies);
|
|
|
+ if (!ies)
|
|
|
+ continue;
|
|
|
+ ie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len);
|
|
|
+ if (!ie)
|
|
|
+ continue;
|
|
|
+ if (ssidlen && ie[1] != ssidlen)
|
|
|
+ continue;
|
|
|
+ /* that would be odd ... */
|
|
|
+ if (bss->pub.beacon_ies)
|
|
|
+ continue;
|
|
|
+ if (WARN_ON_ONCE(bss->pub.hidden_beacon_bss))
|
|
|
+ continue;
|
|
|
+ if (WARN_ON_ONCE(!list_empty(&bss->hidden_list)))
|
|
|
+ list_del(&bss->hidden_list);
|
|
|
+ /* combine them */
|
|
|
+ list_add(&bss->hidden_list, &new->hidden_list);
|
|
|
+ bss->pub.hidden_beacon_bss = &new->pub;
|
|
|
+ new->refcount += bss->refcount;
|
|
|
+ rcu_assign_pointer(bss->pub.beacon_ies,
|
|
|
+ new->pub.beacon_ies);
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
static struct cfg80211_internal_bss *
|
|
@@ -573,7 +695,6 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev,
|
|
|
|
|
|
if (found) {
|
|
|
found->pub.beacon_interval = tmp->pub.beacon_interval;
|
|
|
- found->pub.tsf = tmp->pub.tsf;
|
|
|
found->pub.signal = tmp->pub.signal;
|
|
|
found->pub.capability = tmp->pub.capability;
|
|
|
found->ts = tmp->ts;
|
|
@@ -594,6 +715,21 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev,
|
|
|
rcu_head);
|
|
|
} else if (rcu_access_pointer(tmp->pub.beacon_ies)) {
|
|
|
const struct cfg80211_bss_ies *old;
|
|
|
+ struct cfg80211_internal_bss *bss;
|
|
|
+
|
|
|
+ if (found->pub.hidden_beacon_bss &&
|
|
|
+ !list_empty(&found->hidden_list)) {
|
|
|
+ /*
|
|
|
+ * The found BSS struct is one of the probe
|
|
|
+ * response members of a group, but we're
|
|
|
+ * receiving a beacon (beacon_ies in the tmp
|
|
|
+ * bss is used). This can only mean that the
|
|
|
+ * AP changed its beacon from not having an
|
|
|
+ * SSID to showing it, which is confusing so
|
|
|
+ * drop this information.
|
|
|
+ */
|
|
|
+ goto drop;
|
|
|
+ }
|
|
|
|
|
|
old = rcu_access_pointer(found->pub.beacon_ies);
|
|
|
|
|
@@ -605,6 +741,18 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev,
|
|
|
rcu_assign_pointer(found->pub.ies,
|
|
|
tmp->pub.beacon_ies);
|
|
|
|
|
|
+ /* Assign beacon IEs to all sub entries */
|
|
|
+ list_for_each_entry(bss, &found->hidden_list,
|
|
|
+ hidden_list) {
|
|
|
+ const struct cfg80211_bss_ies *ies;
|
|
|
+
|
|
|
+ ies = rcu_access_pointer(bss->pub.beacon_ies);
|
|
|
+ WARN_ON(ies != old);
|
|
|
+
|
|
|
+ rcu_assign_pointer(bss->pub.beacon_ies,
|
|
|
+ tmp->pub.beacon_ies);
|
|
|
+ }
|
|
|
+
|
|
|
if (old)
|
|
|
kfree_rcu((struct cfg80211_bss_ies *)old,
|
|
|
rcu_head);
|
|
@@ -614,24 +762,6 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev,
|
|
|
struct cfg80211_internal_bss *hidden;
|
|
|
struct cfg80211_bss_ies *ies;
|
|
|
|
|
|
- /* 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_bss(dev, tmp, BSS_CMP_HIDE_ZLEN);
|
|
|
- if (hidden) {
|
|
|
- copy_hidden_ies(tmp, hidden);
|
|
|
- } else {
|
|
|
- hidden = rb_find_bss(dev, tmp, BSS_CMP_HIDE_NUL);
|
|
|
- if (hidden)
|
|
|
- copy_hidden_ies(tmp, hidden);
|
|
|
- }
|
|
|
-
|
|
|
/*
|
|
|
* create a copy -- the "res" variable that is passed in
|
|
|
* is allocated on the stack since it's not needed in the
|
|
@@ -646,21 +776,51 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev,
|
|
|
ies = (void *)rcu_dereference(tmp->pub.proberesp_ies);
|
|
|
if (ies)
|
|
|
kfree_rcu(ies, rcu_head);
|
|
|
- spin_unlock_bh(&dev->bss_lock);
|
|
|
- return NULL;
|
|
|
+ goto drop;
|
|
|
}
|
|
|
memcpy(new, tmp, sizeof(*new));
|
|
|
- kref_init(&new->ref);
|
|
|
+ new->refcount = 1;
|
|
|
+ INIT_LIST_HEAD(&new->hidden_list);
|
|
|
+
|
|
|
+ if (rcu_access_pointer(tmp->pub.proberesp_ies)) {
|
|
|
+ hidden = rb_find_bss(dev, tmp, BSS_CMP_HIDE_ZLEN);
|
|
|
+ if (!hidden)
|
|
|
+ hidden = rb_find_bss(dev, tmp,
|
|
|
+ BSS_CMP_HIDE_NUL);
|
|
|
+ if (hidden) {
|
|
|
+ new->pub.hidden_beacon_bss = &hidden->pub;
|
|
|
+ list_add(&new->hidden_list,
|
|
|
+ &hidden->hidden_list);
|
|
|
+ hidden->refcount++;
|
|
|
+ rcu_assign_pointer(new->pub.beacon_ies,
|
|
|
+ hidden->pub.beacon_ies);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ /*
|
|
|
+ * Ok so we found a beacon, and don't have an entry. If
|
|
|
+ * it's a beacon with hidden SSID, we might be in for an
|
|
|
+ * expensive search for any probe responses that should
|
|
|
+ * be grouped with this beacon for updates ...
|
|
|
+ */
|
|
|
+ if (!cfg80211_combine_bsses(dev, new)) {
|
|
|
+ kfree(new);
|
|
|
+ goto drop;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
list_add_tail(&new->list, &dev->bss_list);
|
|
|
rb_insert_bss(dev, new);
|
|
|
found = new;
|
|
|
}
|
|
|
|
|
|
dev->bss_generation++;
|
|
|
+ bss_ref_get(dev, found);
|
|
|
spin_unlock_bh(&dev->bss_lock);
|
|
|
|
|
|
- bss_ref_get(found);
|
|
|
return found;
|
|
|
+ drop:
|
|
|
+ spin_unlock_bh(&dev->bss_lock);
|
|
|
+ return NULL;
|
|
|
}
|
|
|
|
|
|
static struct ieee80211_channel *
|
|
@@ -719,7 +879,6 @@ cfg80211_inform_bss(struct wiphy *wiphy,
|
|
|
memcpy(tmp.pub.bssid, bssid, ETH_ALEN);
|
|
|
tmp.pub.channel = channel;
|
|
|
tmp.pub.signal = signal;
|
|
|
- tmp.pub.tsf = tsf;
|
|
|
tmp.pub.beacon_interval = beacon_interval;
|
|
|
tmp.pub.capability = capability;
|
|
|
/*
|
|
@@ -734,6 +893,7 @@ cfg80211_inform_bss(struct wiphy *wiphy,
|
|
|
if (!ies)
|
|
|
return NULL;
|
|
|
ies->len = ielen;
|
|
|
+ ies->tsf = tsf;
|
|
|
memcpy(ies->data, ie, ielen);
|
|
|
|
|
|
rcu_assign_pointer(tmp.pub.beacon_ies, ies);
|
|
@@ -790,6 +950,7 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy,
|
|
|
if (!ies)
|
|
|
return NULL;
|
|
|
ies->len = ielen;
|
|
|
+ ies->tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp);
|
|
|
memcpy(ies->data, mgmt->u.probe_resp.variable, ielen);
|
|
|
|
|
|
if (ieee80211_is_probe_resp(mgmt->frame_control))
|
|
@@ -801,7 +962,6 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy,
|
|
|
memcpy(tmp.pub.bssid, mgmt->bssid, ETH_ALEN);
|
|
|
tmp.pub.channel = channel;
|
|
|
tmp.pub.signal = signal;
|
|
|
- tmp.pub.tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp);
|
|
|
tmp.pub.beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int);
|
|
|
tmp.pub.capability = le16_to_cpu(mgmt->u.probe_resp.capab_info);
|
|
|
|
|
@@ -818,27 +978,35 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy,
|
|
|
}
|
|
|
EXPORT_SYMBOL(cfg80211_inform_bss_frame);
|
|
|
|
|
|
-void cfg80211_ref_bss(struct cfg80211_bss *pub)
|
|
|
+void cfg80211_ref_bss(struct wiphy *wiphy, struct cfg80211_bss *pub)
|
|
|
{
|
|
|
+ struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy);
|
|
|
struct cfg80211_internal_bss *bss;
|
|
|
|
|
|
if (!pub)
|
|
|
return;
|
|
|
|
|
|
bss = container_of(pub, struct cfg80211_internal_bss, pub);
|
|
|
- bss_ref_get(bss);
|
|
|
+
|
|
|
+ spin_lock_bh(&dev->bss_lock);
|
|
|
+ bss_ref_get(dev, bss);
|
|
|
+ spin_unlock_bh(&dev->bss_lock);
|
|
|
}
|
|
|
EXPORT_SYMBOL(cfg80211_ref_bss);
|
|
|
|
|
|
-void cfg80211_put_bss(struct cfg80211_bss *pub)
|
|
|
+void cfg80211_put_bss(struct wiphy *wiphy, struct cfg80211_bss *pub)
|
|
|
{
|
|
|
+ struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy);
|
|
|
struct cfg80211_internal_bss *bss;
|
|
|
|
|
|
if (!pub)
|
|
|
return;
|
|
|
|
|
|
bss = container_of(pub, struct cfg80211_internal_bss, pub);
|
|
|
- bss_ref_put(bss);
|
|
|
+
|
|
|
+ spin_lock_bh(&dev->bss_lock);
|
|
|
+ bss_ref_put(dev, bss);
|
|
|
+ spin_unlock_bh(&dev->bss_lock);
|
|
|
}
|
|
|
EXPORT_SYMBOL(cfg80211_put_bss);
|
|
|
|
|
@@ -854,8 +1022,8 @@ void cfg80211_unlink_bss(struct wiphy *wiphy, struct cfg80211_bss *pub)
|
|
|
|
|
|
spin_lock_bh(&dev->bss_lock);
|
|
|
if (!list_empty(&bss->list)) {
|
|
|
- __cfg80211_unlink_bss(dev, bss);
|
|
|
- dev->bss_generation++;
|
|
|
+ if (__cfg80211_unlink_bss(dev, bss))
|
|
|
+ dev->bss_generation++;
|
|
|
}
|
|
|
spin_unlock_bh(&dev->bss_lock);
|
|
|
}
|
|
@@ -1124,15 +1292,10 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info,
|
|
|
|
|
|
rcu_read_lock();
|
|
|
ies = rcu_dereference(bss->pub.ies);
|
|
|
- if (ies) {
|
|
|
- rem = ies->len;
|
|
|
- ie = ies->data;
|
|
|
- } else {
|
|
|
- rem = 0;
|
|
|
- ie = NULL;
|
|
|
- }
|
|
|
+ rem = ies->len;
|
|
|
+ ie = ies->data;
|
|
|
|
|
|
- while (ies && rem >= 2) {
|
|
|
+ while (rem >= 2) {
|
|
|
/* invalid data */
|
|
|
if (ie[1] > rem - 2)
|
|
|
break;
|
|
@@ -1245,7 +1408,7 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info,
|
|
|
if (buf) {
|
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
|
iwe.cmd = IWEVCUSTOM;
|
|
|
- sprintf(buf, "tsf=%016llx", (unsigned long long)(bss->pub.tsf));
|
|
|
+ sprintf(buf, "tsf=%016llx", (unsigned long long)(ies->tsf));
|
|
|
iwe.u.data.length = strlen(buf);
|
|
|
current_ev = iwe_stream_add_point(info, current_ev, end_buf,
|
|
|
&iwe, buf);
|