1510 lines · 38.5 KB · C
RawDownload
/* * cfg80211 scan result handling * * Copyright 2008 Johannes Berg <johannes@sipsolutions.net> */#include <linux/kernel.h>#include <linux/slab.h>#include <linux/module.h>#include <linux/netdevice.h>#include <linux/wireless.h>#include <linux/nl80211.h>#include <linux/etherdevice.h>#include <net/arp.h>#include <net/cfg80211.h>#include <net/cfg80211-wext.h>#include <net/iw_handler.h>#include "core.h"#include "nl80211.h"#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_free(struct cfg80211_internal_bss *bss){	struct cfg80211_bss_ies *ies; 	if (WARN_ON(atomic_read(&bss->hold)))		return; 	ies = (void *)rcu_access_pointer(bss->pub.beacon_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_registered_device *dev,			       struct cfg80211_internal_bss *bss){	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_registered_device *dev,			       struct cfg80211_internal_bss *bss){	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 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(dev, bss);	return true;} static void __cfg80211_bss_expire(struct cfg80211_registered_device *dev,				  unsigned long expire_time){	struct cfg80211_internal_bss *bss, *tmp;	bool expired = false; 	lockdep_assert_held(&dev->bss_lock); 	list_for_each_entry_safe(bss, tmp, &dev->bss_list, list) {		if (atomic_read(&bss->hold))			continue;		if (!time_after(expire_time, bss->ts))			continue; 		if (__cfg80211_unlink_bss(dev, bss))			expired = true;	} 	if (expired)		dev->bss_generation++;} void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak){	struct cfg80211_scan_request *request;	struct wireless_dev *wdev;#ifdef CONFIG_CFG80211_WEXT	union iwreq_data wrqu;#endif 	ASSERT_RTNL(); 	request = rdev->scan_req; 	if (!request)		return; 	wdev = request->wdev; 	/*	 * This must be before sending the other events!	 * Otherwise, wpa_supplicant gets completely confused with	 * wext events.	 */	if (wdev->netdev)		cfg80211_sme_scan_done(wdev->netdev); 	if (request->aborted) {		nl80211_send_scan_aborted(rdev, wdev);	} else {		if (request->flags & NL80211_SCAN_FLAG_FLUSH) {			/* flush entries from previous scans */			spin_lock_bh(&rdev->bss_lock);			__cfg80211_bss_expire(rdev, request->scan_start);			spin_unlock_bh(&rdev->bss_lock);		}		nl80211_send_scan_done(rdev, wdev);	} #ifdef CONFIG_CFG80211_WEXT	if (wdev->netdev && !request->aborted) {		memset(&wrqu, 0, sizeof(wrqu)); 		wireless_send_event(wdev->netdev, SIOCGIWSCAN, &wrqu, NULL);	}#endif 	if (wdev->netdev)		dev_put(wdev->netdev); 	rdev->scan_req = NULL; 	/*	 * OK. If this is invoked with "leak" then we can't	 * free this ... but we've cleaned it up anyway. The	 * driver failed to call the scan_done callback, so	 * all bets are off, it might still be trying to use	 * the scan request or not ... if it accesses the dev	 * in there (it shouldn't anyway) then it may crash.	 */	if (!leak)		kfree(request);} void __cfg80211_scan_done(struct work_struct *wk){	struct cfg80211_registered_device *rdev; 	rdev = container_of(wk, struct cfg80211_registered_device,			    scan_done_wk); 	rtnl_lock();	___cfg80211_scan_done(rdev, false);	rtnl_unlock();} void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted){	trace_cfg80211_scan_done(request, aborted);	WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req); 	request->aborted = aborted;	request->notified = true;	queue_work(cfg80211_wq, &wiphy_to_dev(request->wiphy)->scan_done_wk);}EXPORT_SYMBOL(cfg80211_scan_done); void __cfg80211_sched_scan_results(struct work_struct *wk){	struct cfg80211_registered_device *rdev;	struct cfg80211_sched_scan_request *request; 	rdev = container_of(wk, struct cfg80211_registered_device,			    sched_scan_results_wk); 	rtnl_lock(); 	request = rdev->sched_scan_req; 	/* we don't have sched_scan_req anymore if the scan is stopping */	if (request) {		if (request->flags & NL80211_SCAN_FLAG_FLUSH) {			/* flush entries from previous scans */			spin_lock_bh(&rdev->bss_lock);			__cfg80211_bss_expire(rdev, request->scan_start);			spin_unlock_bh(&rdev->bss_lock);			request->scan_start =				jiffies + msecs_to_jiffies(request->interval);		}		nl80211_send_sched_scan_results(rdev, request->dev);	} 	rtnl_unlock();} void cfg80211_sched_scan_results(struct wiphy *wiphy){	trace_cfg80211_sched_scan_results(wiphy);	/* ignore if we're not scanning */	if (wiphy_to_dev(wiphy)->sched_scan_req)		queue_work(cfg80211_wq,			   &wiphy_to_dev(wiphy)->sched_scan_results_wk);}EXPORT_SYMBOL(cfg80211_sched_scan_results); void cfg80211_sched_scan_stopped(struct wiphy *wiphy){	struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy); 	trace_cfg80211_sched_scan_stopped(wiphy); 	rtnl_lock();	__cfg80211_stop_sched_scan(rdev, true);	rtnl_unlock();}EXPORT_SYMBOL(cfg80211_sched_scan_stopped); int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,			       bool driver_initiated){	struct net_device *dev; 	ASSERT_RTNL(); 	if (!rdev->sched_scan_req)		return -ENOENT; 	dev = rdev->sched_scan_req->dev; 	if (!driver_initiated) {		int err = rdev_sched_scan_stop(rdev, dev);		if (err)			return err;	} 	nl80211_send_sched_scan(rdev, dev, NL80211_CMD_SCHED_SCAN_STOPPED); 	kfree(rdev->sched_scan_req);	rdev->sched_scan_req = NULL; 	return 0;} void cfg80211_bss_age(struct cfg80211_registered_device *dev,                      unsigned long age_secs){	struct cfg80211_internal_bss *bss;	unsigned long age_jiffies = msecs_to_jiffies(age_secs * MSEC_PER_SEC); 	spin_lock_bh(&dev->bss_lock);	list_for_each_entry(bss, &dev->bss_list, list)		bss->ts -= age_jiffies;	spin_unlock_bh(&dev->bss_lock);} void cfg80211_bss_expire(struct cfg80211_registered_device *dev){	__cfg80211_bss_expire(dev, jiffies - IEEE80211_SCAN_RESULT_EXPIRE);} const u8 *cfg80211_find_ie(u8 eid, const u8 *ies, int len){	while (len > 2 && ies[0] != eid) {		len -= ies[1] + 2;		ies += ies[1] + 2;	}	if (len < 2)		return NULL;	if (len < 2 + ies[1])		return NULL;	return ies;}EXPORT_SYMBOL(cfg80211_find_ie); const u8 *cfg80211_find_vendor_ie(unsigned int oui, u8 oui_type,				  const u8 *ies, int len){	struct ieee80211_vendor_ie *ie;	const u8 *pos = ies, *end = ies + len;	int ie_oui; 	while (pos < end) {		pos = cfg80211_find_ie(WLAN_EID_VENDOR_SPECIFIC, pos,				       end - pos);		if (!pos)			return NULL; 		ie = (struct ieee80211_vendor_ie *)pos; 		/* make sure we can access ie->len */		BUILD_BUG_ON(offsetof(struct ieee80211_vendor_ie, len) != 1); 		if (ie->len < sizeof(*ie))			goto cont; 		ie_oui = ie->oui[0] << 16 | ie->oui[1] << 8 | ie->oui[2];		if (ie_oui == oui && ie->oui_type == oui_type)			return pos;cont:		pos += 2 + ie->len;	}	return NULL;}EXPORT_SYMBOL(cfg80211_find_vendor_ie); static bool is_bss(struct cfg80211_bss *a, const u8 *bssid,		   const u8 *ssid, size_t ssid_len){	const struct cfg80211_bss_ies *ies;	const u8 *ssidie; 	if (bssid && !ether_addr_equal(a->bssid, bssid))		return false; 	if (!ssid)		return true; 	ies = rcu_access_pointer(a->ies);	if (!ies)		return false;	ssidie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len);	if (!ssidie)		return false;	if (ssidie[1] != ssid_len)		return false;	return memcmp(ssidie + 2, ssid, ssid_len) == 0;} /** * enum bss_compare_mode - BSS compare mode * @BSS_CMP_REGULAR: regular compare mode (for insertion and normal find) * @BSS_CMP_HIDE_ZLEN: find hidden SSID with zero-length mode * @BSS_CMP_HIDE_NUL: find hidden SSID with NUL-ed out mode */enum bss_compare_mode {	BSS_CMP_REGULAR,	BSS_CMP_HIDE_ZLEN,	BSS_CMP_HIDE_NUL,}; static int cmp_bss(struct cfg80211_bss *a,		   struct cfg80211_bss *b,		   enum bss_compare_mode mode){	const struct cfg80211_bss_ies *a_ies, *b_ies;	const u8 *ie1 = NULL;	const u8 *ie2 = NULL;	int i, r; 	if (a->channel != b->channel)		return b->channel->center_freq - a->channel->center_freq; 	a_ies = rcu_access_pointer(a->ies);	if (!a_ies)		return -1;	b_ies = rcu_access_pointer(b->ies);	if (!b_ies)		return 1; 	if (WLAN_CAPABILITY_IS_STA_BSS(a->capability))		ie1 = cfg80211_find_ie(WLAN_EID_MESH_ID,				       a_ies->data, a_ies->len);	if (WLAN_CAPABILITY_IS_STA_BSS(b->capability))		ie2 = cfg80211_find_ie(WLAN_EID_MESH_ID,				       b_ies->data, b_ies->len);	if (ie1 && ie2) {		int mesh_id_cmp; 		if (ie1[1] == ie2[1])			mesh_id_cmp = memcmp(ie1 + 2, ie2 + 2, ie1[1]);		else			mesh_id_cmp = ie2[1] - ie1[1]; 		ie1 = cfg80211_find_ie(WLAN_EID_MESH_CONFIG,				       a_ies->data, a_ies->len);		ie2 = cfg80211_find_ie(WLAN_EID_MESH_CONFIG,				       b_ies->data, b_ies->len);		if (ie1 && ie2) {			if (mesh_id_cmp)				return mesh_id_cmp;			if (ie1[1] != ie2[1])				return ie2[1] - ie1[1];			return memcmp(ie1 + 2, ie2 + 2, ie1[1]);		}	} 	r = memcmp(a->bssid, b->bssid, sizeof(a->bssid));	if (r)		return r; 	ie1 = cfg80211_find_ie(WLAN_EID_SSID, a_ies->data, a_ies->len);	ie2 = cfg80211_find_ie(WLAN_EID_SSID, b_ies->data, b_ies->len); 	if (!ie1 && !ie2)		return 0; 	/*	 * Note that with "hide_ssid", the function returns a match if	 * the already-present BSS ("b") is a hidden SSID beacon for	 * the new BSS ("a").	 */ 	/* sort missing IE before (left of) present IE */	if (!ie1)		return -1;	if (!ie2)		return 1; 	switch (mode) {	case BSS_CMP_HIDE_ZLEN:		/*		 * In ZLEN mode we assume the BSS entry we're		 * looking for has a zero-length SSID. So if		 * the one we're looking at right now has that,		 * return 0. Otherwise, return the difference		 * in length, but since we're looking for the		 * 0-length it's really equivalent to returning		 * the length of the one we're looking at.		 *

Lines 1–500 of 1510.