1357 lines · 33.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" #define IEEE80211_SCAN_RESULT_EXPIRE	(30 * HZ) void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak){	struct cfg80211_scan_request *request;	struct net_device *dev;#ifdef CONFIG_CFG80211_WEXT	union iwreq_data wrqu;#endif 	ASSERT_RDEV_LOCK(rdev); 	request = rdev->scan_req; 	if (!request)		return; 	dev = request->dev; 	/*	 * This must be before sending the other events!	 * Otherwise, wpa_supplicant gets completely confused with	 * wext events.	 */	cfg80211_sme_scan_done(dev); 	if (request->aborted)		nl80211_send_scan_aborted(rdev, dev);	else		nl80211_send_scan_done(rdev, dev); #ifdef CONFIG_CFG80211_WEXT	if (!request->aborted) {		memset(&wrqu, 0, sizeof(wrqu)); 		wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL);	}#endif 	dev_put(dev); 	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); 	cfg80211_lock_rdev(rdev);	___cfg80211_scan_done(rdev, false);	cfg80211_unlock_rdev(rdev);} void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted){	WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req); 	request->aborted = aborted;	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; 	rdev = container_of(wk, struct cfg80211_registered_device,			    sched_scan_results_wk); 	mutex_lock(&rdev->sched_scan_mtx); 	/* we don't have sched_scan_req anymore if the scan is stopping */	if (rdev->sched_scan_req)		nl80211_send_sched_scan_results(rdev,						rdev->sched_scan_req->dev); 	mutex_unlock(&rdev->sched_scan_mtx);} void cfg80211_sched_scan_results(struct wiphy *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); 	mutex_lock(&rdev->sched_scan_mtx);	__cfg80211_stop_sched_scan(rdev, true);	mutex_unlock(&rdev->sched_scan_mtx);}EXPORT_SYMBOL(cfg80211_sched_scan_stopped); int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,			       bool driver_initiated){	struct net_device *dev; 	lockdep_assert_held(&rdev->sched_scan_mtx); 	if (!rdev->sched_scan_req)		return -ENOENT; 	dev = rdev->sched_scan_req->dev; 	if (!driver_initiated) {		int err = rdev->ops->sched_scan_stop(&rdev->wiphy, 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;} static void bss_release(struct kref *ref){	struct cfg80211_internal_bss *bss; 	bss = container_of(ref, struct cfg80211_internal_bss, ref);	if (bss->pub.free_priv)		bss->pub.free_priv(&bss->pub); 	if (bss->beacon_ies_allocated)		kfree(bss->pub.beacon_ies);	if (bss->proberesp_ies_allocated)		kfree(bss->pub.proberesp_ies); 	BUG_ON(atomic_read(&bss->hold)); 	kfree(bss);} /* must hold dev->bss_lock! */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); 	list_for_each_entry(bss, &dev->bss_list, list) {		bss->ts -= age_jiffies;	}} /* must hold dev->bss_lock! */static void __cfg80211_unlink_bss(struct cfg80211_registered_device *dev,				  struct cfg80211_internal_bss *bss){	list_del_init(&bss->list);	rb_erase(&bss->rbn, &dev->bss_tree);	kref_put(&bss->ref, bss_release);} /* must hold dev->bss_lock! */void cfg80211_bss_expire(struct cfg80211_registered_device *dev){	struct cfg80211_internal_bss *bss, *tmp;	bool expired = false; 	list_for_each_entry_safe(bss, tmp, &dev->bss_list, list) {		if (atomic_read(&bss->hold))			continue;		if (!time_after(jiffies, bss->ts + IEEE80211_SCAN_RESULT_EXPIRE))			continue;		__cfg80211_unlink_bss(dev, bss);		expired = true;	} 	if (expired)		dev->bss_generation++;} 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; 		if (end - pos < sizeof(*ie))			return NULL; 		ie = (struct ieee80211_vendor_ie *)pos;		ie_oui = ie->oui[0] << 16 | ie->oui[1] << 8 | ie->oui[2];		if (ie_oui == oui && ie->oui_type == oui_type)			return pos; 		pos += 2 + ie->len;	}	return NULL;}EXPORT_SYMBOL(cfg80211_find_vendor_ie); static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2){	const u8 *ie1 = cfg80211_find_ie(num, ies1, len1);	const u8 *ie2 = cfg80211_find_ie(num, ies2, len2); 	/* equal if both missing */	if (!ie1 && !ie2)		return 0;	/* sort missing IE before (left of) present IE */	if (!ie1)		return -1;	if (!ie2)		return 1; 	/* sort by length first, then by contents */	if (ie1[1] != ie2[1])		return ie2[1] - ie1[1];	return memcmp(ie1 + 2, ie2 + 2, ie1[1]);} static bool is_bss(struct cfg80211_bss *a,		   const u8 *bssid,		   const u8 *ssid, size_t ssid_len){	const u8 *ssidie; 	if (bssid && !ether_addr_equal(a->bssid, bssid))		return false; 	if (!ssid)		return true; 	ssidie = cfg80211_find_ie(WLAN_EID_SSID,				  a->information_elements,				  a->len_information_elements);	if (!ssidie)		return false;	if (ssidie[1] != ssid_len)		return false;	return memcmp(ssidie + 2, ssid, ssid_len) == 0;} static bool is_mesh_bss(struct cfg80211_bss *a){	const u8 *ie; 	if (!WLAN_CAPABILITY_IS_STA_BSS(a->capability))		return false; 	ie = cfg80211_find_ie(WLAN_EID_MESH_ID,			      a->information_elements,			      a->len_information_elements);	if (!ie)		return false; 	ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG,			      a->information_elements,			      a->len_information_elements);	if (!ie)		return false; 	return true;} static bool is_mesh(struct cfg80211_bss *a,		    const u8 *meshid, size_t meshidlen,		    const u8 *meshcfg){	const u8 *ie; 	if (!WLAN_CAPABILITY_IS_STA_BSS(a->capability))		return false; 	ie = cfg80211_find_ie(WLAN_EID_MESH_ID,			      a->information_elements,			      a->len_information_elements);	if (!ie)		return false;	if (ie[1] != meshidlen)		return false;	if (memcmp(ie + 2, meshid, meshidlen))		return false; 	ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG,			      a->information_elements,			      a->len_information_elements);	if (!ie)		return false;	if (ie[1] != sizeof(struct ieee80211_meshconf_ie))		return false; 	/*	 * Ignore mesh capability (last two bytes of the IE) when	 * comparing since that may differ between stations taking	 * part in the same mesh.	 */	return memcmp(ie + 2, meshcfg,	    sizeof(struct ieee80211_meshconf_ie) - 2) == 0;} static int cmp_bss_core(struct cfg80211_bss *a,			struct cfg80211_bss *b){	int r; 	if (a->channel != b->channel)		return b->channel->center_freq - a->channel->center_freq; 	if (is_mesh_bss(a) && is_mesh_bss(b)) {		r = cmp_ies(WLAN_EID_MESH_ID,			    a->information_elements,			    a->len_information_elements,			    b->information_elements,			    b->len_information_elements);		if (r)			return r;		return cmp_ies(WLAN_EID_MESH_CONFIG,			       a->information_elements,			       a->len_information_elements,			       b->information_elements,			       b->len_information_elements);	} 	/*	 * we can't use compare_ether_addr here since we need a < > operator.	 * The binary return value of compare_ether_addr isn't enough	 */	return memcmp(a->bssid, b->bssid, sizeof(a->bssid));} static int cmp_bss(struct cfg80211_bss *a,		   struct cfg80211_bss *b){	int r; 	r = cmp_bss_core(a, b);	if (r)		return r; 	return cmp_ies(WLAN_EID_SSID,		       a->information_elements,		       a->len_information_elements,		       b->information_elements,		       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,				      const u8 *ssid, size_t ssid_len,				      u16 capa_mask, u16 capa_val){	struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy);	struct cfg80211_internal_bss *bss, *res = NULL;	unsigned long now = jiffies; 	spin_lock_bh(&dev->bss_lock); 	list_for_each_entry(bss, &dev->bss_list, list) {		if ((bss->pub.capability & capa_mask) != capa_val)			continue;		if (channel && bss->pub.channel != channel)			continue;		/* Don't get expired BSS structs */		if (time_after(now, bss->ts + IEEE80211_SCAN_RESULT_EXPIRE) &&		    !atomic_read(&bss->hold))			continue;		if (is_bss(&bss->pub, bssid, ssid, ssid_len)) {			res = bss;			kref_get(&res->ref);			break;		}	} 	spin_unlock_bh(&dev->bss_lock);	if (!res)		return NULL;	return &res->pub;}EXPORT_SYMBOL(cfg80211_get_bss); struct cfg80211_bss *cfg80211_get_mesh(struct wiphy *wiphy,				       struct ieee80211_channel *channel,				       const u8 *meshid, size_t meshidlen,				       const u8 *meshcfg){	struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy);	struct cfg80211_internal_bss *bss, *res = NULL; 	spin_lock_bh(&dev->bss_lock); 	list_for_each_entry(bss, &dev->bss_list, list) {		if (channel && bss->pub.channel != channel)			continue;		if (is_mesh(&bss->pub, meshid, meshidlen, meshcfg)) {			res = bss;			kref_get(&res->ref);

Lines 1–500 of 1357.