diff options
Diffstat (limited to 'net/wireless')
-rw-r--r-- | net/wireless/core.h | 4 | ||||
-rw-r--r-- | net/wireless/scan.c | 273 |
2 files changed, 223 insertions, 54 deletions
diff --git a/net/wireless/core.h b/net/wireless/core.h index 8396f7671c8d..37d70dc2fe82 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h | |||
@@ -8,7 +8,6 @@ | |||
8 | #include <linux/mutex.h> | 8 | #include <linux/mutex.h> |
9 | #include <linux/list.h> | 9 | #include <linux/list.h> |
10 | #include <linux/netdevice.h> | 10 | #include <linux/netdevice.h> |
11 | #include <linux/kref.h> | ||
12 | #include <linux/rbtree.h> | 11 | #include <linux/rbtree.h> |
13 | #include <linux/debugfs.h> | 12 | #include <linux/debugfs.h> |
14 | #include <linux/rfkill.h> | 13 | #include <linux/rfkill.h> |
@@ -124,9 +123,10 @@ static inline void assert_cfg80211_lock(void) | |||
124 | 123 | ||
125 | struct cfg80211_internal_bss { | 124 | struct cfg80211_internal_bss { |
126 | struct list_head list; | 125 | struct list_head list; |
126 | struct list_head hidden_list; | ||
127 | struct rb_node rbn; | 127 | struct rb_node rbn; |
128 | unsigned long ts; | 128 | unsigned long ts; |
129 | struct kref ref; | 129 | unsigned long refcount; |
130 | atomic_t hold; | 130 | atomic_t hold; |
131 | 131 | ||
132 | /* must be last because of priv member */ | 132 | /* must be last because of priv member */ |
diff --git a/net/wireless/scan.c b/net/wireless/scan.c index dacb44ac2f74..5e0983d60428 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c | |||
@@ -19,46 +19,124 @@ | |||
19 | #include "wext-compat.h" | 19 | #include "wext-compat.h" |
20 | #include "rdev-ops.h" | 20 | #include "rdev-ops.h" |
21 | 21 | ||
22 | /** | ||
23 | * DOC: BSS tree/list structure | ||
24 | * | ||
25 | * At the top level, the BSS list is kept in both a list in each | ||
26 | * registered device (@bss_list) as well as an RB-tree for faster | ||
27 | * lookup. In the RB-tree, entries can be looked up using their | ||
28 | * channel, MESHID, MESHCONF (for MBSSes) or channel, BSSID, SSID | ||
29 | * for other BSSes. | ||
30 | * | ||
31 | * Due to the possibility of hidden SSIDs, there's a second level | ||
32 | * structure, the "hidden_list" and "hidden_beacon_bss" pointer. | ||
33 | * The hidden_list connects all BSSes belonging to a single AP | ||
34 | * that has a hidden SSID, and connects beacon and probe response | ||
35 | * entries. For a probe response entry for a hidden SSID, the | ||
36 | * hidden_beacon_bss pointer points to the BSS struct holding the | ||
37 | * beacon's information. | ||
38 | * | ||
39 | * Reference counting is done for all these references except for | ||
40 | * the hidden_list, so that a beacon BSS struct that is otherwise | ||
41 | * not referenced has one reference for being on the bss_list and | ||
42 | * one for each probe response entry that points to it using the | ||
43 | * hidden_beacon_bss pointer. When a BSS struct that has such a | ||
44 | * pointer is get/put, the refcount update is also propagated to | ||
45 | * the referenced struct, this ensure that it cannot get removed | ||
46 | * while somebody is using the probe response version. | ||
47 | * | ||
48 | * Note that the hidden_beacon_bss pointer never changes, due to | ||
49 | * the reference counting. Therefore, no locking is needed for | ||
50 | * it. | ||
51 | * | ||
52 | * Also note that the hidden_beacon_bss pointer is only relevant | ||
53 | * if the driver uses something other than the IEs, e.g. private | ||
54 | * data stored stored in the BSS struct, since the beacon IEs are | ||
55 | * also linked into the probe response struct. | ||
56 | */ | ||
57 | |||
22 | #define IEEE80211_SCAN_RESULT_EXPIRE (30 * HZ) | 58 | #define IEEE80211_SCAN_RESULT_EXPIRE (30 * HZ) |
23 | 59 | ||
24 | static void bss_release(struct kref *ref) | 60 | static void bss_free(struct cfg80211_internal_bss *bss) |
25 | { | 61 | { |
26 | struct cfg80211_bss_ies *ies; | 62 | struct cfg80211_bss_ies *ies; |
27 | struct cfg80211_internal_bss *bss; | ||
28 | |||
29 | bss = container_of(ref, struct cfg80211_internal_bss, ref); | ||
30 | 63 | ||
31 | if (WARN_ON(atomic_read(&bss->hold))) | 64 | if (WARN_ON(atomic_read(&bss->hold))) |
32 | return; | 65 | return; |
33 | 66 | ||
34 | ies = (void *)rcu_access_pointer(bss->pub.beacon_ies); | 67 | ies = (void *)rcu_access_pointer(bss->pub.beacon_ies); |
35 | if (ies) | 68 | if (ies && !bss->pub.hidden_beacon_bss) |
36 | kfree_rcu(ies, rcu_head); | 69 | kfree_rcu(ies, rcu_head); |
37 | ies = (void *)rcu_access_pointer(bss->pub.proberesp_ies); | 70 | ies = (void *)rcu_access_pointer(bss->pub.proberesp_ies); |
38 | if (ies) | 71 | if (ies) |
39 | kfree_rcu(ies, rcu_head); | 72 | kfree_rcu(ies, rcu_head); |
40 | 73 | ||
74 | /* | ||
75 | * This happens when the module is removed, it doesn't | ||
76 | * really matter any more save for completeness | ||
77 | */ | ||
78 | if (!list_empty(&bss->hidden_list)) | ||
79 | list_del(&bss->hidden_list); | ||
80 | |||
41 | kfree(bss); | 81 | kfree(bss); |
42 | } | 82 | } |
43 | 83 | ||
44 | static inline void bss_ref_get(struct cfg80211_internal_bss *bss) | 84 | static inline void bss_ref_get(struct cfg80211_registered_device *dev, |
85 | struct cfg80211_internal_bss *bss) | ||
45 | { | 86 | { |
46 | kref_get(&bss->ref); | 87 | lockdep_assert_held(&dev->bss_lock); |
88 | |||
89 | bss->refcount++; | ||
90 | if (bss->pub.hidden_beacon_bss) { | ||
91 | bss = container_of(bss->pub.hidden_beacon_bss, | ||
92 | struct cfg80211_internal_bss, | ||
93 | pub); | ||
94 | bss->refcount++; | ||
95 | } | ||
47 | } | 96 | } |
48 | 97 | ||
49 | static inline void bss_ref_put(struct cfg80211_internal_bss *bss) | 98 | static inline void bss_ref_put(struct cfg80211_registered_device *dev, |
99 | struct cfg80211_internal_bss *bss) | ||
50 | { | 100 | { |
51 | kref_put(&bss->ref, bss_release); | 101 | lockdep_assert_held(&dev->bss_lock); |
102 | |||
103 | if (bss->pub.hidden_beacon_bss) { | ||
104 | struct cfg80211_internal_bss *hbss; | ||
105 | hbss = container_of(bss->pub.hidden_beacon_bss, | ||
106 | struct cfg80211_internal_bss, | ||
107 | pub); | ||
108 | hbss->refcount--; | ||
109 | if (hbss->refcount == 0) | ||
110 | bss_free(hbss); | ||
111 | } | ||
112 | bss->refcount--; | ||
113 | if (bss->refcount == 0) | ||
114 | bss_free(bss); | ||
52 | } | 115 | } |
53 | 116 | ||
54 | static void __cfg80211_unlink_bss(struct cfg80211_registered_device *dev, | 117 | static bool __cfg80211_unlink_bss(struct cfg80211_registered_device *dev, |
55 | struct cfg80211_internal_bss *bss) | 118 | struct cfg80211_internal_bss *bss) |
56 | { | 119 | { |
57 | lockdep_assert_held(&dev->bss_lock); | 120 | lockdep_assert_held(&dev->bss_lock); |
58 | 121 | ||
122 | if (!list_empty(&bss->hidden_list)) { | ||
123 | /* | ||
124 | * don't remove the beacon entry if it has | ||
125 | * probe responses associated with it | ||
126 | */ | ||
127 | if (!bss->pub.hidden_beacon_bss) | ||
128 | return false; | ||
129 | /* | ||
130 | * if it's a probe response entry break its | ||
131 | * link to the other entries in the group | ||
132 | */ | ||
133 | list_del_init(&bss->hidden_list); | ||
134 | } | ||
135 | |||
59 | list_del_init(&bss->list); | 136 | list_del_init(&bss->list); |
60 | rb_erase(&bss->rbn, &dev->bss_tree); | 137 | rb_erase(&bss->rbn, &dev->bss_tree); |
61 | bss_ref_put(bss); | 138 | bss_ref_put(dev, bss); |
139 | return true; | ||
62 | } | 140 | } |
63 | 141 | ||
64 | static void __cfg80211_bss_expire(struct cfg80211_registered_device *dev, | 142 | static void __cfg80211_bss_expire(struct cfg80211_registered_device *dev, |
@@ -75,8 +153,8 @@ static void __cfg80211_bss_expire(struct cfg80211_registered_device *dev, | |||
75 | if (!time_after(expire_time, bss->ts)) | 153 | if (!time_after(expire_time, bss->ts)) |
76 | continue; | 154 | continue; |
77 | 155 | ||
78 | __cfg80211_unlink_bss(dev, bss); | 156 | if (__cfg80211_unlink_bss(dev, bss)) |
79 | expired = true; | 157 | expired = true; |
80 | } | 158 | } |
81 | 159 | ||
82 | if (expired) | 160 | if (expired) |
@@ -466,7 +544,7 @@ struct cfg80211_bss *cfg80211_get_bss(struct wiphy *wiphy, | |||
466 | continue; | 544 | continue; |
467 | if (is_bss(&bss->pub, bssid, ssid, ssid_len)) { | 545 | if (is_bss(&bss->pub, bssid, ssid, ssid_len)) { |
468 | res = bss; | 546 | res = bss; |
469 | bss_ref_get(res); | 547 | bss_ref_get(dev, res); |
470 | break; | 548 | break; |
471 | } | 549 | } |
472 | } | 550 | } |
@@ -532,23 +610,67 @@ rb_find_bss(struct cfg80211_registered_device *dev, | |||
532 | return NULL; | 610 | return NULL; |
533 | } | 611 | } |
534 | 612 | ||
535 | static void | 613 | static bool cfg80211_combine_bsses(struct cfg80211_registered_device *dev, |
536 | copy_hidden_ies(struct cfg80211_internal_bss *res, | 614 | struct cfg80211_internal_bss *new) |
537 | struct cfg80211_internal_bss *hidden) | ||
538 | { | 615 | { |
539 | const struct cfg80211_bss_ies *ies; | 616 | const struct cfg80211_bss_ies *ies; |
617 | struct cfg80211_internal_bss *bss; | ||
618 | const u8 *ie; | ||
619 | int i, ssidlen; | ||
620 | u8 fold = 0; | ||
540 | 621 | ||
541 | if (rcu_access_pointer(res->pub.beacon_ies)) | 622 | ies = rcu_access_pointer(new->pub.beacon_ies); |
542 | return; | ||
543 | |||
544 | ies = rcu_access_pointer(hidden->pub.beacon_ies); | ||
545 | if (WARN_ON(!ies)) | 623 | if (WARN_ON(!ies)) |
546 | return; | 624 | return false; |
547 | 625 | ||
548 | ies = kmemdup(ies, sizeof(*ies) + ies->len, GFP_ATOMIC); | 626 | ie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len); |
549 | if (unlikely(!ies)) | 627 | if (!ie) { |
550 | return; | 628 | /* nothing to do */ |
551 | rcu_assign_pointer(res->pub.beacon_ies, ies); | 629 | return true; |
630 | } | ||
631 | |||
632 | ssidlen = ie[1]; | ||
633 | for (i = 0; i < ssidlen; i++) | ||
634 | fold |= ie[2 + i]; | ||
635 | |||
636 | if (fold) { | ||
637 | /* not a hidden SSID */ | ||
638 | return true; | ||
639 | } | ||
640 | |||
641 | /* This is the bad part ... */ | ||
642 | |||
643 | list_for_each_entry(bss, &dev->bss_list, list) { | ||
644 | if (!ether_addr_equal(bss->pub.bssid, new->pub.bssid)) | ||
645 | continue; | ||
646 | if (bss->pub.channel != new->pub.channel) | ||
647 | continue; | ||
648 | if (rcu_access_pointer(bss->pub.beacon_ies)) | ||
649 | continue; | ||
650 | ies = rcu_access_pointer(bss->pub.ies); | ||
651 | if (!ies) | ||
652 | continue; | ||
653 | ie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len); | ||
654 | if (!ie) | ||
655 | continue; | ||
656 | if (ssidlen && ie[1] != ssidlen) | ||
657 | continue; | ||
658 | /* that would be odd ... */ | ||
659 | if (bss->pub.beacon_ies) | ||
660 | continue; | ||
661 | if (WARN_ON_ONCE(bss->pub.hidden_beacon_bss)) | ||
662 | continue; | ||
663 | if (WARN_ON_ONCE(!list_empty(&bss->hidden_list))) | ||
664 | list_del(&bss->hidden_list); | ||
665 | /* combine them */ | ||
666 | list_add(&bss->hidden_list, &new->hidden_list); | ||
667 | bss->pub.hidden_beacon_bss = &new->pub; | ||
668 | new->refcount += bss->refcount; | ||
669 | rcu_assign_pointer(bss->pub.beacon_ies, | ||
670 | new->pub.beacon_ies); | ||
671 | } | ||
672 | |||
673 | return true; | ||
552 | } | 674 | } |
553 | 675 | ||
554 | static struct cfg80211_internal_bss * | 676 | static struct cfg80211_internal_bss * |
@@ -594,6 +716,21 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev, | |||
594 | rcu_head); | 716 | rcu_head); |
595 | } else if (rcu_access_pointer(tmp->pub.beacon_ies)) { | 717 | } else if (rcu_access_pointer(tmp->pub.beacon_ies)) { |
596 | const struct cfg80211_bss_ies *old; | 718 | const struct cfg80211_bss_ies *old; |
719 | struct cfg80211_internal_bss *bss; | ||
720 | |||
721 | if (found->pub.hidden_beacon_bss && | ||
722 | !list_empty(&found->hidden_list)) { | ||
723 | /* | ||
724 | * The found BSS struct is one of the probe | ||
725 | * response members of a group, but we're | ||
726 | * receiving a beacon (beacon_ies in the tmp | ||
727 | * bss is used). This can only mean that the | ||
728 | * AP changed its beacon from not having an | ||
729 | * SSID to showing it, which is confusing so | ||
730 | * drop this information. | ||
731 | */ | ||
732 | goto drop; | ||
733 | } | ||
597 | 734 | ||
598 | old = rcu_access_pointer(found->pub.beacon_ies); | 735 | old = rcu_access_pointer(found->pub.beacon_ies); |
599 | 736 | ||
@@ -605,6 +742,18 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev, | |||
605 | rcu_assign_pointer(found->pub.ies, | 742 | rcu_assign_pointer(found->pub.ies, |
606 | tmp->pub.beacon_ies); | 743 | tmp->pub.beacon_ies); |
607 | 744 | ||
745 | /* Assign beacon IEs to all sub entries */ | ||
746 | list_for_each_entry(bss, &found->hidden_list, | ||
747 | hidden_list) { | ||
748 | const struct cfg80211_bss_ies *ies; | ||
749 | |||
750 | ies = rcu_access_pointer(bss->pub.beacon_ies); | ||
751 | WARN_ON(ies != old); | ||
752 | |||
753 | rcu_assign_pointer(bss->pub.beacon_ies, | ||
754 | tmp->pub.beacon_ies); | ||
755 | } | ||
756 | |||
608 | if (old) | 757 | if (old) |
609 | kfree_rcu((struct cfg80211_bss_ies *)old, | 758 | kfree_rcu((struct cfg80211_bss_ies *)old, |
610 | rcu_head); | 759 | rcu_head); |
@@ -614,24 +763,6 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev, | |||
614 | struct cfg80211_internal_bss *hidden; | 763 | struct cfg80211_internal_bss *hidden; |
615 | struct cfg80211_bss_ies *ies; | 764 | struct cfg80211_bss_ies *ies; |
616 | 765 | ||
617 | /* First check if the beacon is a probe response from | ||
618 | * a hidden bss. If so, copy beacon ies (with nullified | ||
619 | * ssid) into the probe response bss entry (with real ssid). | ||
620 | * It is required basically for PSM implementation | ||
621 | * (probe responses do not contain tim ie) */ | ||
622 | |||
623 | /* TODO: The code is not trying to update existing probe | ||
624 | * response bss entries when beacon ies are | ||
625 | * getting changed. */ | ||
626 | hidden = rb_find_bss(dev, tmp, BSS_CMP_HIDE_ZLEN); | ||
627 | if (hidden) { | ||
628 | copy_hidden_ies(tmp, hidden); | ||
629 | } else { | ||
630 | hidden = rb_find_bss(dev, tmp, BSS_CMP_HIDE_NUL); | ||
631 | if (hidden) | ||
632 | copy_hidden_ies(tmp, hidden); | ||
633 | } | ||
634 | |||
635 | /* | 766 | /* |
636 | * create a copy -- the "res" variable that is passed in | 767 | * create a copy -- the "res" variable that is passed in |
637 | * is allocated on the stack since it's not needed in the | 768 | * is allocated on the stack since it's not needed in the |
@@ -646,21 +777,51 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev, | |||
646 | ies = (void *)rcu_dereference(tmp->pub.proberesp_ies); | 777 | ies = (void *)rcu_dereference(tmp->pub.proberesp_ies); |
647 | if (ies) | 778 | if (ies) |
648 | kfree_rcu(ies, rcu_head); | 779 | kfree_rcu(ies, rcu_head); |
649 | spin_unlock_bh(&dev->bss_lock); | 780 | goto drop; |
650 | return NULL; | ||
651 | } | 781 | } |
652 | memcpy(new, tmp, sizeof(*new)); | 782 | memcpy(new, tmp, sizeof(*new)); |
653 | kref_init(&new->ref); | 783 | new->refcount = 1; |
784 | INIT_LIST_HEAD(&new->hidden_list); | ||
785 | |||
786 | if (rcu_access_pointer(tmp->pub.proberesp_ies)) { | ||
787 | hidden = rb_find_bss(dev, tmp, BSS_CMP_HIDE_ZLEN); | ||
788 | if (!hidden) | ||
789 | hidden = rb_find_bss(dev, tmp, | ||
790 | BSS_CMP_HIDE_NUL); | ||
791 | if (hidden) { | ||
792 | new->pub.hidden_beacon_bss = &hidden->pub; | ||
793 | list_add(&new->hidden_list, | ||
794 | &hidden->hidden_list); | ||
795 | hidden->refcount++; | ||
796 | rcu_assign_pointer(new->pub.beacon_ies, | ||
797 | hidden->pub.beacon_ies); | ||
798 | } | ||
799 | } else { | ||
800 | /* | ||
801 | * Ok so we found a beacon, and don't have an entry. If | ||
802 | * it's a beacon with hidden SSID, we might be in for an | ||
803 | * expensive search for any probe responses that should | ||
804 | * be grouped with this beacon for updates ... | ||
805 | */ | ||
806 | if (!cfg80211_combine_bsses(dev, new)) { | ||
807 | kfree(new); | ||
808 | goto drop; | ||
809 | } | ||
810 | } | ||
811 | |||
654 | list_add_tail(&new->list, &dev->bss_list); | 812 | list_add_tail(&new->list, &dev->bss_list); |
655 | rb_insert_bss(dev, new); | 813 | rb_insert_bss(dev, new); |
656 | found = new; | 814 | found = new; |
657 | } | 815 | } |
658 | 816 | ||
659 | dev->bss_generation++; | 817 | dev->bss_generation++; |
818 | bss_ref_get(dev, found); | ||
660 | spin_unlock_bh(&dev->bss_lock); | 819 | spin_unlock_bh(&dev->bss_lock); |
661 | 820 | ||
662 | bss_ref_get(found); | ||
663 | return found; | 821 | return found; |
822 | drop: | ||
823 | spin_unlock_bh(&dev->bss_lock); | ||
824 | return NULL; | ||
664 | } | 825 | } |
665 | 826 | ||
666 | static struct ieee80211_channel * | 827 | static struct ieee80211_channel * |
@@ -820,25 +981,33 @@ EXPORT_SYMBOL(cfg80211_inform_bss_frame); | |||
820 | 981 | ||
821 | void cfg80211_ref_bss(struct wiphy *wiphy, struct cfg80211_bss *pub) | 982 | void cfg80211_ref_bss(struct wiphy *wiphy, struct cfg80211_bss *pub) |
822 | { | 983 | { |
984 | struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy); | ||
823 | struct cfg80211_internal_bss *bss; | 985 | struct cfg80211_internal_bss *bss; |
824 | 986 | ||
825 | if (!pub) | 987 | if (!pub) |
826 | return; | 988 | return; |
827 | 989 | ||
828 | bss = container_of(pub, struct cfg80211_internal_bss, pub); | 990 | bss = container_of(pub, struct cfg80211_internal_bss, pub); |
829 | bss_ref_get(bss); | 991 | |
992 | spin_lock_bh(&dev->bss_lock); | ||
993 | bss_ref_get(dev, bss); | ||
994 | spin_unlock_bh(&dev->bss_lock); | ||
830 | } | 995 | } |
831 | EXPORT_SYMBOL(cfg80211_ref_bss); | 996 | EXPORT_SYMBOL(cfg80211_ref_bss); |
832 | 997 | ||
833 | void cfg80211_put_bss(struct wiphy *wiphy, struct cfg80211_bss *pub) | 998 | void cfg80211_put_bss(struct wiphy *wiphy, struct cfg80211_bss *pub) |
834 | { | 999 | { |
1000 | struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy); | ||
835 | struct cfg80211_internal_bss *bss; | 1001 | struct cfg80211_internal_bss *bss; |
836 | 1002 | ||
837 | if (!pub) | 1003 | if (!pub) |
838 | return; | 1004 | return; |
839 | 1005 | ||
840 | bss = container_of(pub, struct cfg80211_internal_bss, pub); | 1006 | bss = container_of(pub, struct cfg80211_internal_bss, pub); |
841 | bss_ref_put(bss); | 1007 | |
1008 | spin_lock_bh(&dev->bss_lock); | ||
1009 | bss_ref_put(dev, bss); | ||
1010 | spin_unlock_bh(&dev->bss_lock); | ||
842 | } | 1011 | } |
843 | EXPORT_SYMBOL(cfg80211_put_bss); | 1012 | EXPORT_SYMBOL(cfg80211_put_bss); |
844 | 1013 | ||
@@ -854,8 +1023,8 @@ void cfg80211_unlink_bss(struct wiphy *wiphy, struct cfg80211_bss *pub) | |||
854 | 1023 | ||
855 | spin_lock_bh(&dev->bss_lock); | 1024 | spin_lock_bh(&dev->bss_lock); |
856 | if (!list_empty(&bss->list)) { | 1025 | if (!list_empty(&bss->list)) { |
857 | __cfg80211_unlink_bss(dev, bss); | 1026 | if (__cfg80211_unlink_bss(dev, bss)) |
858 | dev->bss_generation++; | 1027 | dev->bss_generation++; |
859 | } | 1028 | } |
860 | spin_unlock_bh(&dev->bss_lock); | 1029 | spin_unlock_bh(&dev->bss_lock); |
861 | } | 1030 | } |