1833 lines · 47.0 KB · C
RawDownload
/* * cfg80211 scan result handling * * Copyright 2008 Johannes Berg <johannes@sipsolutions.net> * Copyright 2013-2014  Intel Mobile Communications GmbH * Copyright 2016	Intel Deutschland GmbH */#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. */ /* * Limit the number of BSS entries stored in mac80211. Each one is * a bit over 4k at most, so this limits to roughly 4-5M of memory. * If somebody wants to really attack this though, they'd likely * use small beacons, and only one type of frame, limiting each of * the entries to a much smaller size (in order to generate more * entries in total, so overhead is bigger.) */static int bss_entries_limit = 1000;module_param(bss_entries_limit, int, 0644);MODULE_PARM_DESC(bss_entries_limit,                 "limit to number of scan BSS entries (per wiphy, default 1000)"); #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 *rdev,			       struct cfg80211_internal_bss *bss){	lockdep_assert_held(&rdev->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 *rdev,			       struct cfg80211_internal_bss *bss){	lockdep_assert_held(&rdev->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 *rdev,				  struct cfg80211_internal_bss *bss){	lockdep_assert_held(&rdev->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, &rdev->bss_tree);	rdev->bss_entries--;	WARN_ONCE((rdev->bss_entries == 0) ^ list_empty(&rdev->bss_list),		  "rdev bss entries[%d]/list[empty:%d] corruption\n",		  rdev->bss_entries, list_empty(&rdev->bss_list));	bss_ref_put(rdev, bss);	return true;} static void __cfg80211_bss_expire(struct cfg80211_registered_device *rdev,				  unsigned long expire_time){	struct cfg80211_internal_bss *bss, *tmp;	bool expired = false; 	lockdep_assert_held(&rdev->bss_lock); 	list_for_each_entry_safe(bss, tmp, &rdev->bss_list, list) {		if (atomic_read(&bss->hold))			continue;		if (!time_after(expire_time, bss->ts))			continue; 		if (__cfg80211_unlink_bss(rdev, bss))			expired = true;	} 	if (expired)		rdev->bss_generation++;} static bool cfg80211_bss_expire_oldest(struct cfg80211_registered_device *rdev){	struct cfg80211_internal_bss *bss, *oldest = NULL;	bool ret; 	lockdep_assert_held(&rdev->bss_lock); 	list_for_each_entry(bss, &rdev->bss_list, list) {		if (atomic_read(&bss->hold))			continue; 		if (!list_empty(&bss->hidden_list) &&		    !bss->pub.hidden_beacon_bss)			continue; 		if (oldest && time_before(oldest->ts, bss->ts))			continue;		oldest = bss;	} 	if (WARN_ON(!oldest))		return false; 	/*	 * The callers make sure to increase rdev->bss_generation if anything	 * gets removed (and a new entry added), so there's no need to also do	 * it here.	 */ 	ret = __cfg80211_unlink_bss(rdev, oldest);	WARN_ON(!ret);	return ret;} void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev,			   bool send_message){	struct cfg80211_scan_request *request;	struct wireless_dev *wdev;	struct sk_buff *msg;#ifdef CONFIG_CFG80211_WEXT	union iwreq_data wrqu;#endif 	ASSERT_RTNL(); 	if (rdev->scan_msg) {		nl80211_send_scan_msg(rdev, rdev->scan_msg);		rdev->scan_msg = NULL;		return;	} 	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->info.aborted &&	    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);	} 	msg = nl80211_build_scan_msg(rdev, wdev, request->info.aborted); #ifdef CONFIG_CFG80211_WEXT	if (wdev->netdev && !request->info.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;	kfree(request); 	if (!send_message)		rdev->scan_msg = msg;	else		nl80211_send_scan_msg(rdev, msg);} 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, true);	rtnl_unlock();} void cfg80211_scan_done(struct cfg80211_scan_request *request,			struct cfg80211_scan_info *info){	trace_cfg80211_scan_done(request, info);	WARN_ON(request != wiphy_to_rdev(request->wiphy)->scan_req); 	request->info = *info;	request->notified = true;	queue_work(cfg80211_wq, &wiphy_to_rdev(request->wiphy)->scan_done_wk);}EXPORT_SYMBOL(cfg80211_scan_done); void cfg80211_add_sched_scan_req(struct cfg80211_registered_device *rdev,				 struct cfg80211_sched_scan_request *req){	ASSERT_RTNL(); 	list_add_rcu(&req->list, &rdev->sched_scan_req_list);} static void cfg80211_del_sched_scan_req(struct cfg80211_registered_device *rdev,					struct cfg80211_sched_scan_request *req){	ASSERT_RTNL(); 	list_del_rcu(&req->list);	kfree_rcu(req, rcu_head);} static struct cfg80211_sched_scan_request *cfg80211_find_sched_scan_req(struct cfg80211_registered_device *rdev, u64 reqid){	struct cfg80211_sched_scan_request *pos; 	WARN_ON_ONCE(!rcu_read_lock_held() && !lockdep_rtnl_is_held()); 	list_for_each_entry_rcu(pos, &rdev->sched_scan_req_list, list) {		if (pos->reqid == reqid)			return pos;	}	return NULL;} /* * Determines if a scheduled scan request can be handled. When a legacy * scheduled scan is running no other scheduled scan is allowed regardless * whether the request is for legacy or multi-support scan. When a multi-support * scheduled scan is running a request for legacy scan is not allowed. In this * case a request for multi-support scan can be handled if resources are * available, ie. struct wiphy::max_sched_scan_reqs limit is not yet reached. */int cfg80211_sched_scan_req_possible(struct cfg80211_registered_device *rdev,				     bool want_multi){	struct cfg80211_sched_scan_request *pos;	int i = 0; 	list_for_each_entry(pos, &rdev->sched_scan_req_list, list) {		/* request id zero means legacy in progress */		if (!i && !pos->reqid)			return -EINPROGRESS;		i++;	} 	if (i) {		/* no legacy allowed when multi request(s) are active */		if (!want_multi)			return -EINPROGRESS; 		/* resource limit reached */		if (i == rdev->wiphy.max_sched_scan_reqs)			return -ENOSPC;	}	return 0;} void cfg80211_sched_scan_results_wk(struct work_struct *work){	struct cfg80211_registered_device *rdev;	struct cfg80211_sched_scan_request *req, *tmp; 	rdev = container_of(work, struct cfg80211_registered_device,			   sched_scan_res_wk); 	rtnl_lock();	list_for_each_entry_safe(req, tmp, &rdev->sched_scan_req_list, list) {		if (req->report_results) {			req->report_results = false;			if (req->flags & NL80211_SCAN_FLAG_FLUSH) {				/* flush entries from previous scans */				spin_lock_bh(&rdev->bss_lock);				__cfg80211_bss_expire(rdev, req->scan_start);				spin_unlock_bh(&rdev->bss_lock);				req->scan_start = jiffies;			}			nl80211_send_sched_scan(req,						NL80211_CMD_SCHED_SCAN_RESULTS);		}	}	rtnl_unlock();} void cfg80211_sched_scan_results(struct wiphy *wiphy, u64 reqid){	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);	struct cfg80211_sched_scan_request *request; 	trace_cfg80211_sched_scan_results(wiphy, reqid);	/* ignore if we're not scanning */ 	rcu_read_lock();	request = cfg80211_find_sched_scan_req(rdev, reqid);	if (request) {		request->report_results = true;		queue_work(cfg80211_wq, &rdev->sched_scan_res_wk);	}	rcu_read_unlock();}EXPORT_SYMBOL(cfg80211_sched_scan_results); void cfg80211_sched_scan_stopped_rtnl(struct wiphy *wiphy, u64 reqid){	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); 	ASSERT_RTNL(); 	trace_cfg80211_sched_scan_stopped(wiphy, reqid); 	__cfg80211_stop_sched_scan(rdev, reqid, true);}EXPORT_SYMBOL(cfg80211_sched_scan_stopped_rtnl); void cfg80211_sched_scan_stopped(struct wiphy *wiphy, u64 reqid){	rtnl_lock();	cfg80211_sched_scan_stopped_rtnl(wiphy, reqid);	rtnl_unlock();}EXPORT_SYMBOL(cfg80211_sched_scan_stopped); int cfg80211_stop_sched_scan_req(struct cfg80211_registered_device *rdev,				 struct cfg80211_sched_scan_request *req,				 bool driver_initiated){	ASSERT_RTNL(); 	if (!driver_initiated) {		int err = rdev_sched_scan_stop(rdev, req->dev, req->reqid);		if (err)			return err;	} 	nl80211_send_sched_scan(req, NL80211_CMD_SCHED_SCAN_STOPPED); 	cfg80211_del_sched_scan_req(rdev, req); 	return 0;} int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,			       u64 reqid, bool driver_initiated){	struct cfg80211_sched_scan_request *sched_scan_req; 	ASSERT_RTNL(); 	sched_scan_req = cfg80211_find_sched_scan_req(rdev, reqid);	if (!sched_scan_req)		return -ENOENT; 	return cfg80211_stop_sched_scan_req(rdev, sched_scan_req,					    driver_initiated);} void cfg80211_bss_age(struct cfg80211_registered_device *rdev,                      unsigned long age_secs){	struct cfg80211_internal_bss *bss;	unsigned long age_jiffies = msecs_to_jiffies(age_secs * MSEC_PER_SEC); 	spin_lock_bh(&rdev->bss_lock);	list_for_each_entry(bss, &rdev->bss_list, list)		bss->ts -= age_jiffies;	spin_unlock_bh(&rdev->bss_lock);} void cfg80211_bss_expire(struct cfg80211_registered_device *rdev){	__cfg80211_bss_expire(rdev, jiffies - IEEE80211_SCAN_RESULT_EXPIRE);} const u8 *cfg80211_find_ie_match(u8 eid, const u8 *ies, int len,				 const u8 *match, int match_len,				 int match_offset){	/* match_offset can't be smaller than 2, unless match_len is	 * zero, in which case match_offset must be zero as well.	 */	if (WARN_ON((match_len && match_offset < 2) ||		    (!match_len && match_offset)))		return NULL; 	while (len >= 2 && len >= ies[1] + 2) {		if ((ies[0] == eid) &&		    (ies[1] + 2 >= match_offset + match_len) &&		    !memcmp(ies + match_offset, match, match_len))			return ies; 		len -= ies[1] + 2;		ies += ies[1] + 2;

Lines 1–500 of 1833.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t