forked from Minki/linux
01a0ac417c
When we lose a scan, cfg80211 tries to clean up after the driver. However, it currently does this too early, it does this in GOING_DOWN already instead of DOWN, so it may happen with mac80211. Besides fixing this, also make it more robust by leaking the scan request so if the driver later actually finishes the scan, it won't crash. Also check in ___cfg80211_scan_done whether a scan request is still pending and exit if not. Reported-by: Felix Fietkau <nbd@openwrt.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Tested-by: Felix Fietkau <nbd@openwrt.org> Signed-off-by: John W. Linville <linville@tuxdriver.com>
991 lines
24 KiB
C
991 lines
24 KiB
C
/*
|
|
* cfg80211 scan result handling
|
|
*
|
|
* Copyright 2008 Johannes Berg <johannes@sipsolutions.net>
|
|
*/
|
|
#include <linux/kernel.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/iw_handler.h>
|
|
#include "core.h"
|
|
#include "nl80211.h"
|
|
#include "wext-compat.h"
|
|
|
|
#define IEEE80211_SCAN_RESULT_EXPIRE (15 * HZ)
|
|
|
|
void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak)
|
|
{
|
|
struct cfg80211_scan_request *request;
|
|
struct net_device *dev;
|
|
#ifdef CONFIG_WIRELESS_EXT
|
|
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_WIRELESS_EXT
|
|
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;
|
|
schedule_work(&wiphy_to_dev(request->wiphy)->scan_done_wk);
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_scan_done);
|
|
|
|
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->ies_allocated)
|
|
kfree(bss->pub.information_elements);
|
|
|
|
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! */
|
|
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;
|
|
list_del(&bss->list);
|
|
rb_erase(&bss->rbn, &dev->bss_tree);
|
|
kref_put(&bss->ref, bss_release);
|
|
expired = true;
|
|
}
|
|
|
|
if (expired)
|
|
dev->bss_generation++;
|
|
}
|
|
|
|
static u8 *find_ie(u8 num, u8 *ies, size_t len)
|
|
{
|
|
while (len > 2 && ies[0] != num) {
|
|
len -= ies[1] + 2;
|
|
ies += ies[1] + 2;
|
|
}
|
|
if (len < 2)
|
|
return NULL;
|
|
if (len < 2 + ies[1])
|
|
return NULL;
|
|
return ies;
|
|
}
|
|
|
|
static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2)
|
|
{
|
|
const u8 *ie1 = find_ie(num, ies1, len1);
|
|
const u8 *ie2 = find_ie(num, ies2, len2);
|
|
int r;
|
|
|
|
if (!ie1 && !ie2)
|
|
return 0;
|
|
if (!ie1 || !ie2)
|
|
return -1;
|
|
|
|
r = memcmp(ie1 + 2, ie2 + 2, min(ie1[1], ie2[1]));
|
|
if (r == 0 && ie1[1] != ie2[1])
|
|
return ie2[1] - ie1[1];
|
|
return r;
|
|
}
|
|
|
|
static bool is_bss(struct cfg80211_bss *a,
|
|
const u8 *bssid,
|
|
const u8 *ssid, size_t ssid_len)
|
|
{
|
|
const u8 *ssidie;
|
|
|
|
if (bssid && compare_ether_addr(a->bssid, bssid))
|
|
return false;
|
|
|
|
if (!ssid)
|
|
return true;
|
|
|
|
ssidie = 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(struct cfg80211_bss *a,
|
|
const u8 *meshid, size_t meshidlen,
|
|
const u8 *meshcfg)
|
|
{
|
|
const u8 *ie;
|
|
|
|
if (!is_zero_ether_addr(a->bssid))
|
|
return false;
|
|
|
|
ie = 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 = find_ie(WLAN_EID_MESH_CONFIG,
|
|
a->information_elements,
|
|
a->len_information_elements);
|
|
if (!ie)
|
|
return false;
|
|
if (ie[1] != IEEE80211_MESH_CONFIG_LEN)
|
|
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, IEEE80211_MESH_CONFIG_LEN - 2) == 0;
|
|
}
|
|
|
|
static int cmp_bss(struct cfg80211_bss *a,
|
|
struct cfg80211_bss *b)
|
|
{
|
|
int r;
|
|
|
|
if (a->channel != b->channel)
|
|
return b->channel->center_freq - a->channel->center_freq;
|
|
|
|
r = memcmp(a->bssid, b->bssid, ETH_ALEN);
|
|
if (r)
|
|
return r;
|
|
|
|
if (is_zero_ether_addr(a->bssid)) {
|
|
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);
|
|
}
|
|
|
|
return cmp_ies(WLAN_EID_SSID,
|
|
a->information_elements,
|
|
a->len_information_elements,
|
|
b->information_elements,
|
|
b->len_information_elements);
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
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);
|
|
break;
|
|
}
|
|
}
|
|
|
|
spin_unlock_bh(&dev->bss_lock);
|
|
if (!res)
|
|
return NULL;
|
|
return &res->pub;
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_get_mesh);
|
|
|
|
|
|
static void rb_insert_bss(struct cfg80211_registered_device *dev,
|
|
struct cfg80211_internal_bss *bss)
|
|
{
|
|
struct rb_node **p = &dev->bss_tree.rb_node;
|
|
struct rb_node *parent = NULL;
|
|
struct cfg80211_internal_bss *tbss;
|
|
int cmp;
|
|
|
|
while (*p) {
|
|
parent = *p;
|
|
tbss = rb_entry(parent, struct cfg80211_internal_bss, rbn);
|
|
|
|
cmp = cmp_bss(&bss->pub, &tbss->pub);
|
|
|
|
if (WARN_ON(!cmp)) {
|
|
/* will sort of leak this BSS */
|
|
return;
|
|
}
|
|
|
|
if (cmp < 0)
|
|
p = &(*p)->rb_left;
|
|
else
|
|
p = &(*p)->rb_right;
|
|
}
|
|
|
|
rb_link_node(&bss->rbn, parent, p);
|
|
rb_insert_color(&bss->rbn, &dev->bss_tree);
|
|
}
|
|
|
|
static struct cfg80211_internal_bss *
|
|
rb_find_bss(struct cfg80211_registered_device *dev,
|
|
struct cfg80211_internal_bss *res)
|
|
{
|
|
struct rb_node *n = dev->bss_tree.rb_node;
|
|
struct cfg80211_internal_bss *bss;
|
|
int r;
|
|
|
|
while (n) {
|
|
bss = rb_entry(n, struct cfg80211_internal_bss, rbn);
|
|
r = cmp_bss(&res->pub, &bss->pub);
|
|
|
|
if (r == 0)
|
|
return bss;
|
|
else if (r < 0)
|
|
n = n->rb_left;
|
|
else
|
|
n = n->rb_right;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct cfg80211_internal_bss *
|
|
cfg80211_bss_update(struct cfg80211_registered_device *dev,
|
|
struct cfg80211_internal_bss *res,
|
|
bool overwrite)
|
|
{
|
|
struct cfg80211_internal_bss *found = NULL;
|
|
const u8 *meshid, *meshcfg;
|
|
|
|
/*
|
|
* The reference to "res" is donated to this function.
|
|
*/
|
|
|
|
if (WARN_ON(!res->pub.channel)) {
|
|
kref_put(&res->ref, bss_release);
|
|
return NULL;
|
|
}
|
|
|
|
res->ts = jiffies;
|
|
|
|
if (is_zero_ether_addr(res->pub.bssid)) {
|
|
/* must be mesh, verify */
|
|
meshid = find_ie(WLAN_EID_MESH_ID, res->pub.information_elements,
|
|
res->pub.len_information_elements);
|
|
meshcfg = find_ie(WLAN_EID_MESH_CONFIG,
|
|
res->pub.information_elements,
|
|
res->pub.len_information_elements);
|
|
if (!meshid || !meshcfg ||
|
|
meshcfg[1] != IEEE80211_MESH_CONFIG_LEN) {
|
|
/* bogus mesh */
|
|
kref_put(&res->ref, bss_release);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
spin_lock_bh(&dev->bss_lock);
|
|
|
|
found = rb_find_bss(dev, res);
|
|
|
|
if (found) {
|
|
found->pub.beacon_interval = res->pub.beacon_interval;
|
|
found->pub.tsf = res->pub.tsf;
|
|
found->pub.signal = res->pub.signal;
|
|
found->pub.capability = res->pub.capability;
|
|
found->ts = res->ts;
|
|
|
|
/* overwrite IEs */
|
|
if (overwrite) {
|
|
size_t used = dev->wiphy.bss_priv_size + sizeof(*res);
|
|
size_t ielen = res->pub.len_information_elements;
|
|
|
|
if (!found->ies_allocated && ksize(found) >= used + ielen) {
|
|
memcpy(found->pub.information_elements,
|
|
res->pub.information_elements, ielen);
|
|
found->pub.len_information_elements = ielen;
|
|
} else {
|
|
u8 *ies = found->pub.information_elements;
|
|
|
|
if (found->ies_allocated)
|
|
ies = krealloc(ies, ielen, GFP_ATOMIC);
|
|
else
|
|
ies = kmalloc(ielen, GFP_ATOMIC);
|
|
|
|
if (ies) {
|
|
memcpy(ies, res->pub.information_elements, ielen);
|
|
found->ies_allocated = true;
|
|
found->pub.information_elements = ies;
|
|
found->pub.len_information_elements = ielen;
|
|
}
|
|
}
|
|
}
|
|
|
|
kref_put(&res->ref, bss_release);
|
|
} else {
|
|
/* this "consumes" the reference */
|
|
list_add_tail(&res->list, &dev->bss_list);
|
|
rb_insert_bss(dev, res);
|
|
found = res;
|
|
}
|
|
|
|
dev->bss_generation++;
|
|
spin_unlock_bh(&dev->bss_lock);
|
|
|
|
kref_get(&found->ref);
|
|
return found;
|
|
}
|
|
|
|
struct cfg80211_bss*
|
|
cfg80211_inform_bss(struct wiphy *wiphy,
|
|
struct ieee80211_channel *channel,
|
|
const u8 *bssid,
|
|
u64 timestamp, u16 capability, u16 beacon_interval,
|
|
const u8 *ie, size_t ielen,
|
|
s32 signal, gfp_t gfp)
|
|
{
|
|
struct cfg80211_internal_bss *res;
|
|
size_t privsz;
|
|
|
|
if (WARN_ON(!wiphy))
|
|
return NULL;
|
|
|
|
privsz = wiphy->bss_priv_size;
|
|
|
|
if (WARN_ON(wiphy->signal_type == NL80211_BSS_SIGNAL_UNSPEC &&
|
|
(signal < 0 || signal > 100)))
|
|
return NULL;
|
|
|
|
res = kzalloc(sizeof(*res) + privsz + ielen, gfp);
|
|
if (!res)
|
|
return NULL;
|
|
|
|
memcpy(res->pub.bssid, bssid, ETH_ALEN);
|
|
res->pub.channel = channel;
|
|
res->pub.signal = signal;
|
|
res->pub.tsf = timestamp;
|
|
res->pub.beacon_interval = beacon_interval;
|
|
res->pub.capability = capability;
|
|
/* point to after the private area */
|
|
res->pub.information_elements = (u8 *)res + sizeof(*res) + privsz;
|
|
memcpy(res->pub.information_elements, ie, ielen);
|
|
res->pub.len_information_elements = ielen;
|
|
|
|
kref_init(&res->ref);
|
|
|
|
res = cfg80211_bss_update(wiphy_to_dev(wiphy), res, 0);
|
|
if (!res)
|
|
return NULL;
|
|
|
|
if (res->pub.capability & WLAN_CAPABILITY_ESS)
|
|
regulatory_hint_found_beacon(wiphy, channel, gfp);
|
|
|
|
/* cfg80211_bss_update gives us a referenced result */
|
|
return &res->pub;
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_inform_bss);
|
|
|
|
struct cfg80211_bss *
|
|
cfg80211_inform_bss_frame(struct wiphy *wiphy,
|
|
struct ieee80211_channel *channel,
|
|
struct ieee80211_mgmt *mgmt, size_t len,
|
|
s32 signal, gfp_t gfp)
|
|
{
|
|
struct cfg80211_internal_bss *res;
|
|
size_t ielen = len - offsetof(struct ieee80211_mgmt,
|
|
u.probe_resp.variable);
|
|
bool overwrite;
|
|
size_t privsz = wiphy->bss_priv_size;
|
|
|
|
if (WARN_ON(wiphy->signal_type == NL80211_BSS_SIGNAL_UNSPEC &&
|
|
(signal < 0 || signal > 100)))
|
|
return NULL;
|
|
|
|
if (WARN_ON(!mgmt || !wiphy ||
|
|
len < offsetof(struct ieee80211_mgmt, u.probe_resp.variable)))
|
|
return NULL;
|
|
|
|
res = kzalloc(sizeof(*res) + privsz + ielen, gfp);
|
|
if (!res)
|
|
return NULL;
|
|
|
|
memcpy(res->pub.bssid, mgmt->bssid, ETH_ALEN);
|
|
res->pub.channel = channel;
|
|
res->pub.signal = signal;
|
|
res->pub.tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp);
|
|
res->pub.beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int);
|
|
res->pub.capability = le16_to_cpu(mgmt->u.probe_resp.capab_info);
|
|
/* point to after the private area */
|
|
res->pub.information_elements = (u8 *)res + sizeof(*res) + privsz;
|
|
memcpy(res->pub.information_elements, mgmt->u.probe_resp.variable, ielen);
|
|
res->pub.len_information_elements = ielen;
|
|
|
|
kref_init(&res->ref);
|
|
|
|
overwrite = ieee80211_is_probe_resp(mgmt->frame_control);
|
|
|
|
res = cfg80211_bss_update(wiphy_to_dev(wiphy), res, overwrite);
|
|
if (!res)
|
|
return NULL;
|
|
|
|
if (res->pub.capability & WLAN_CAPABILITY_ESS)
|
|
regulatory_hint_found_beacon(wiphy, channel, gfp);
|
|
|
|
/* cfg80211_bss_update gives us a referenced result */
|
|
return &res->pub;
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_inform_bss_frame);
|
|
|
|
void cfg80211_put_bss(struct cfg80211_bss *pub)
|
|
{
|
|
struct cfg80211_internal_bss *bss;
|
|
|
|
if (!pub)
|
|
return;
|
|
|
|
bss = container_of(pub, struct cfg80211_internal_bss, pub);
|
|
kref_put(&bss->ref, bss_release);
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_put_bss);
|
|
|
|
void cfg80211_unlink_bss(struct wiphy *wiphy, struct cfg80211_bss *pub)
|
|
{
|
|
struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy);
|
|
struct cfg80211_internal_bss *bss;
|
|
|
|
if (WARN_ON(!pub))
|
|
return;
|
|
|
|
bss = container_of(pub, struct cfg80211_internal_bss, pub);
|
|
|
|
spin_lock_bh(&dev->bss_lock);
|
|
|
|
list_del(&bss->list);
|
|
dev->bss_generation++;
|
|
rb_erase(&bss->rbn, &dev->bss_tree);
|
|
|
|
spin_unlock_bh(&dev->bss_lock);
|
|
|
|
kref_put(&bss->ref, bss_release);
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_unlink_bss);
|
|
|
|
#ifdef CONFIG_WIRELESS_EXT
|
|
int cfg80211_wext_siwscan(struct net_device *dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *wrqu, char *extra)
|
|
{
|
|
struct cfg80211_registered_device *rdev;
|
|
struct wiphy *wiphy;
|
|
struct iw_scan_req *wreq = NULL;
|
|
struct cfg80211_scan_request *creq;
|
|
int i, err, n_channels = 0;
|
|
enum ieee80211_band band;
|
|
|
|
if (!netif_running(dev))
|
|
return -ENETDOWN;
|
|
|
|
rdev = cfg80211_get_dev_from_ifindex(dev_net(dev), dev->ifindex);
|
|
|
|
if (IS_ERR(rdev))
|
|
return PTR_ERR(rdev);
|
|
|
|
if (rdev->scan_req) {
|
|
err = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
wiphy = &rdev->wiphy;
|
|
|
|
for (band = 0; band < IEEE80211_NUM_BANDS; band++)
|
|
if (wiphy->bands[band])
|
|
n_channels += wiphy->bands[band]->n_channels;
|
|
|
|
creq = kzalloc(sizeof(*creq) + sizeof(struct cfg80211_ssid) +
|
|
n_channels * sizeof(void *),
|
|
GFP_ATOMIC);
|
|
if (!creq) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
creq->wiphy = wiphy;
|
|
creq->dev = dev;
|
|
/* SSIDs come after channels */
|
|
creq->ssids = (void *)&creq->channels[n_channels];
|
|
creq->n_channels = n_channels;
|
|
creq->n_ssids = 1;
|
|
|
|
/* all channels */
|
|
i = 0;
|
|
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
|
|
int j;
|
|
if (!wiphy->bands[band])
|
|
continue;
|
|
for (j = 0; j < wiphy->bands[band]->n_channels; j++) {
|
|
creq->channels[i] = &wiphy->bands[band]->channels[j];
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/* translate scan request */
|
|
if (wrqu->data.length == sizeof(struct iw_scan_req)) {
|
|
wreq = (struct iw_scan_req *)extra;
|
|
|
|
if (wrqu->data.flags & IW_SCAN_THIS_ESSID) {
|
|
if (wreq->essid_len > IEEE80211_MAX_SSID_LEN)
|
|
return -EINVAL;
|
|
memcpy(creq->ssids[0].ssid, wreq->essid, wreq->essid_len);
|
|
creq->ssids[0].ssid_len = wreq->essid_len;
|
|
}
|
|
if (wreq->scan_type == IW_SCAN_TYPE_PASSIVE)
|
|
creq->n_ssids = 0;
|
|
}
|
|
|
|
rdev->scan_req = creq;
|
|
err = rdev->ops->scan(wiphy, dev, creq);
|
|
if (err) {
|
|
rdev->scan_req = NULL;
|
|
kfree(creq);
|
|
} else {
|
|
nl80211_send_scan_start(rdev, dev);
|
|
dev_hold(dev);
|
|
}
|
|
out:
|
|
cfg80211_unlock_rdev(rdev);
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cfg80211_wext_siwscan);
|
|
|
|
static void ieee80211_scan_add_ies(struct iw_request_info *info,
|
|
struct cfg80211_bss *bss,
|
|
char **current_ev, char *end_buf)
|
|
{
|
|
u8 *pos, *end, *next;
|
|
struct iw_event iwe;
|
|
|
|
if (!bss->information_elements ||
|
|
!bss->len_information_elements)
|
|
return;
|
|
|
|
/*
|
|
* If needed, fragment the IEs buffer (at IE boundaries) into short
|
|
* enough fragments to fit into IW_GENERIC_IE_MAX octet messages.
|
|
*/
|
|
pos = bss->information_elements;
|
|
end = pos + bss->len_information_elements;
|
|
|
|
while (end - pos > IW_GENERIC_IE_MAX) {
|
|
next = pos + 2 + pos[1];
|
|
while (next + 2 + next[1] - pos < IW_GENERIC_IE_MAX)
|
|
next = next + 2 + next[1];
|
|
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = IWEVGENIE;
|
|
iwe.u.data.length = next - pos;
|
|
*current_ev = iwe_stream_add_point(info, *current_ev,
|
|
end_buf, &iwe, pos);
|
|
|
|
pos = next;
|
|
}
|
|
|
|
if (end > pos) {
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = IWEVGENIE;
|
|
iwe.u.data.length = end - pos;
|
|
*current_ev = iwe_stream_add_point(info, *current_ev,
|
|
end_buf, &iwe, pos);
|
|
}
|
|
}
|
|
|
|
static inline unsigned int elapsed_jiffies_msecs(unsigned long start)
|
|
{
|
|
unsigned long end = jiffies;
|
|
|
|
if (end >= start)
|
|
return jiffies_to_msecs(end - start);
|
|
|
|
return jiffies_to_msecs(end + (MAX_JIFFY_OFFSET - start) + 1);
|
|
}
|
|
|
|
static char *
|
|
ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info,
|
|
struct cfg80211_internal_bss *bss, char *current_ev,
|
|
char *end_buf)
|
|
{
|
|
struct iw_event iwe;
|
|
u8 *buf, *cfg, *p;
|
|
u8 *ie = bss->pub.information_elements;
|
|
int rem = bss->pub.len_information_elements, i, sig;
|
|
bool ismesh = false;
|
|
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = SIOCGIWAP;
|
|
iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
|
|
memcpy(iwe.u.ap_addr.sa_data, bss->pub.bssid, ETH_ALEN);
|
|
current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe,
|
|
IW_EV_ADDR_LEN);
|
|
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = SIOCGIWFREQ;
|
|
iwe.u.freq.m = ieee80211_frequency_to_channel(bss->pub.channel->center_freq);
|
|
iwe.u.freq.e = 0;
|
|
current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe,
|
|
IW_EV_FREQ_LEN);
|
|
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = SIOCGIWFREQ;
|
|
iwe.u.freq.m = bss->pub.channel->center_freq;
|
|
iwe.u.freq.e = 6;
|
|
current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe,
|
|
IW_EV_FREQ_LEN);
|
|
|
|
if (wiphy->signal_type != CFG80211_SIGNAL_TYPE_NONE) {
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = IWEVQUAL;
|
|
iwe.u.qual.updated = IW_QUAL_LEVEL_UPDATED |
|
|
IW_QUAL_NOISE_INVALID |
|
|
IW_QUAL_QUAL_UPDATED;
|
|
switch (wiphy->signal_type) {
|
|
case CFG80211_SIGNAL_TYPE_MBM:
|
|
sig = bss->pub.signal / 100;
|
|
iwe.u.qual.level = sig;
|
|
iwe.u.qual.updated |= IW_QUAL_DBM;
|
|
if (sig < -110) /* rather bad */
|
|
sig = -110;
|
|
else if (sig > -40) /* perfect */
|
|
sig = -40;
|
|
/* will give a range of 0 .. 70 */
|
|
iwe.u.qual.qual = sig + 110;
|
|
break;
|
|
case CFG80211_SIGNAL_TYPE_UNSPEC:
|
|
iwe.u.qual.level = bss->pub.signal;
|
|
/* will give range 0 .. 100 */
|
|
iwe.u.qual.qual = bss->pub.signal;
|
|
break;
|
|
default:
|
|
/* not reached */
|
|
break;
|
|
}
|
|
current_ev = iwe_stream_add_event(info, current_ev, end_buf,
|
|
&iwe, IW_EV_QUAL_LEN);
|
|
}
|
|
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = SIOCGIWENCODE;
|
|
if (bss->pub.capability & WLAN_CAPABILITY_PRIVACY)
|
|
iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
|
|
else
|
|
iwe.u.data.flags = IW_ENCODE_DISABLED;
|
|
iwe.u.data.length = 0;
|
|
current_ev = iwe_stream_add_point(info, current_ev, end_buf,
|
|
&iwe, "");
|
|
|
|
while (rem >= 2) {
|
|
/* invalid data */
|
|
if (ie[1] > rem - 2)
|
|
break;
|
|
|
|
switch (ie[0]) {
|
|
case WLAN_EID_SSID:
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = SIOCGIWESSID;
|
|
iwe.u.data.length = ie[1];
|
|
iwe.u.data.flags = 1;
|
|
current_ev = iwe_stream_add_point(info, current_ev, end_buf,
|
|
&iwe, ie + 2);
|
|
break;
|
|
case WLAN_EID_MESH_ID:
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = SIOCGIWESSID;
|
|
iwe.u.data.length = ie[1];
|
|
iwe.u.data.flags = 1;
|
|
current_ev = iwe_stream_add_point(info, current_ev, end_buf,
|
|
&iwe, ie + 2);
|
|
break;
|
|
case WLAN_EID_MESH_CONFIG:
|
|
ismesh = true;
|
|
if (ie[1] != IEEE80211_MESH_CONFIG_LEN)
|
|
break;
|
|
buf = kmalloc(50, GFP_ATOMIC);
|
|
if (!buf)
|
|
break;
|
|
cfg = ie + 2;
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = IWEVCUSTOM;
|
|
sprintf(buf, "Mesh network (version %d)", cfg[0]);
|
|
iwe.u.data.length = strlen(buf);
|
|
current_ev = iwe_stream_add_point(info, current_ev,
|
|
end_buf,
|
|
&iwe, buf);
|
|
sprintf(buf, "Path Selection Protocol ID: "
|
|
"0x%02X%02X%02X%02X", cfg[1], cfg[2], cfg[3],
|
|
cfg[4]);
|
|
iwe.u.data.length = strlen(buf);
|
|
current_ev = iwe_stream_add_point(info, current_ev,
|
|
end_buf,
|
|
&iwe, buf);
|
|
sprintf(buf, "Path Selection Metric ID: "
|
|
"0x%02X%02X%02X%02X", cfg[5], cfg[6], cfg[7],
|
|
cfg[8]);
|
|
iwe.u.data.length = strlen(buf);
|
|
current_ev = iwe_stream_add_point(info, current_ev,
|
|
end_buf,
|
|
&iwe, buf);
|
|
sprintf(buf, "Congestion Control Mode ID: "
|
|
"0x%02X%02X%02X%02X", cfg[9], cfg[10],
|
|
cfg[11], cfg[12]);
|
|
iwe.u.data.length = strlen(buf);
|
|
current_ev = iwe_stream_add_point(info, current_ev,
|
|
end_buf,
|
|
&iwe, buf);
|
|
sprintf(buf, "Channel Precedence: "
|
|
"0x%02X%02X%02X%02X", cfg[13], cfg[14],
|
|
cfg[15], cfg[16]);
|
|
iwe.u.data.length = strlen(buf);
|
|
current_ev = iwe_stream_add_point(info, current_ev,
|
|
end_buf,
|
|
&iwe, buf);
|
|
kfree(buf);
|
|
break;
|
|
case WLAN_EID_SUPP_RATES:
|
|
case WLAN_EID_EXT_SUPP_RATES:
|
|
/* display all supported rates in readable format */
|
|
p = current_ev + iwe_stream_lcp_len(info);
|
|
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = SIOCGIWRATE;
|
|
/* Those two flags are ignored... */
|
|
iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0;
|
|
|
|
for (i = 0; i < ie[1]; i++) {
|
|
iwe.u.bitrate.value =
|
|
((ie[i + 2] & 0x7f) * 500000);
|
|
p = iwe_stream_add_value(info, current_ev, p,
|
|
end_buf, &iwe, IW_EV_PARAM_LEN);
|
|
}
|
|
current_ev = p;
|
|
break;
|
|
}
|
|
rem -= ie[1] + 2;
|
|
ie += ie[1] + 2;
|
|
}
|
|
|
|
if (bss->pub.capability & (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS)
|
|
|| ismesh) {
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = SIOCGIWMODE;
|
|
if (ismesh)
|
|
iwe.u.mode = IW_MODE_MESH;
|
|
else if (bss->pub.capability & WLAN_CAPABILITY_ESS)
|
|
iwe.u.mode = IW_MODE_MASTER;
|
|
else
|
|
iwe.u.mode = IW_MODE_ADHOC;
|
|
current_ev = iwe_stream_add_event(info, current_ev, end_buf,
|
|
&iwe, IW_EV_UINT_LEN);
|
|
}
|
|
|
|
buf = kmalloc(30, GFP_ATOMIC);
|
|
if (buf) {
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = IWEVCUSTOM;
|
|
sprintf(buf, "tsf=%016llx", (unsigned long long)(bss->pub.tsf));
|
|
iwe.u.data.length = strlen(buf);
|
|
current_ev = iwe_stream_add_point(info, current_ev, end_buf,
|
|
&iwe, buf);
|
|
memset(&iwe, 0, sizeof(iwe));
|
|
iwe.cmd = IWEVCUSTOM;
|
|
sprintf(buf, " Last beacon: %ums ago",
|
|
elapsed_jiffies_msecs(bss->ts));
|
|
iwe.u.data.length = strlen(buf);
|
|
current_ev = iwe_stream_add_point(info, current_ev,
|
|
end_buf, &iwe, buf);
|
|
kfree(buf);
|
|
}
|
|
|
|
ieee80211_scan_add_ies(info, &bss->pub, ¤t_ev, end_buf);
|
|
|
|
return current_ev;
|
|
}
|
|
|
|
|
|
static int ieee80211_scan_results(struct cfg80211_registered_device *dev,
|
|
struct iw_request_info *info,
|
|
char *buf, size_t len)
|
|
{
|
|
char *current_ev = buf;
|
|
char *end_buf = buf + len;
|
|
struct cfg80211_internal_bss *bss;
|
|
|
|
spin_lock_bh(&dev->bss_lock);
|
|
cfg80211_bss_expire(dev);
|
|
|
|
list_for_each_entry(bss, &dev->bss_list, list) {
|
|
if (buf + len - current_ev <= IW_EV_ADDR_LEN) {
|
|
spin_unlock_bh(&dev->bss_lock);
|
|
return -E2BIG;
|
|
}
|
|
current_ev = ieee80211_bss(&dev->wiphy, info, bss,
|
|
current_ev, end_buf);
|
|
}
|
|
spin_unlock_bh(&dev->bss_lock);
|
|
return current_ev - buf;
|
|
}
|
|
|
|
|
|
int cfg80211_wext_giwscan(struct net_device *dev,
|
|
struct iw_request_info *info,
|
|
struct iw_point *data, char *extra)
|
|
{
|
|
struct cfg80211_registered_device *rdev;
|
|
int res;
|
|
|
|
if (!netif_running(dev))
|
|
return -ENETDOWN;
|
|
|
|
rdev = cfg80211_get_dev_from_ifindex(dev_net(dev), dev->ifindex);
|
|
|
|
if (IS_ERR(rdev))
|
|
return PTR_ERR(rdev);
|
|
|
|
if (rdev->scan_req) {
|
|
res = -EAGAIN;
|
|
goto out;
|
|
}
|
|
|
|
res = ieee80211_scan_results(rdev, info, extra, data->length);
|
|
data->length = 0;
|
|
if (res >= 0) {
|
|
data->length = res;
|
|
res = 0;
|
|
}
|
|
|
|
out:
|
|
cfg80211_unlock_rdev(rdev);
|
|
return res;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cfg80211_wext_giwscan);
|
|
#endif
|