mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-04 09:59:32 +00:00
![Luis R. Rodriguez](/assets/img/avatar_default.png)
This adds country IE parsing to mac80211 and enables its usage within the new regulatory infrastructure in cfg80211. We parse the country IEs only on management beacons for the BSSID you are associated to and disregard the IEs when the country and environment (indoor, outdoor, any) matches the already processed country IE. To avoid following misinformed or outdated APs we build and use a regulatory domain out of the intersection between what the AP provides us on the country IE and what CRDA is aware is allowed on the same country. A secondary device is allowed to follow only the same country IE as it make no sense for two devices on a system to be in two different countries. In the case the AP is using country IEs for an incorrect country the user may help compliance further by setting the regulatory domain before or after the IE is parsed and in that case another intersection will be performed. CONFIG_WIRELESS_OLD_REGULATORY is supported but requires CRDA present. Signed-off-by: Luis R. Rodriguez <lrodriguez@atheros.com> Acked-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: John W. Linville <linville@tuxdriver.com>
462 lines
10 KiB
C
462 lines
10 KiB
C
/*
|
|
* This is the linux wireless configuration interface.
|
|
*
|
|
* Copyright 2006-2008 Johannes Berg <johannes@sipsolutions.net>
|
|
*/
|
|
|
|
#include <linux/if.h>
|
|
#include <linux/module.h>
|
|
#include <linux/err.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/list.h>
|
|
#include <linux/nl80211.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/device.h>
|
|
#include <net/genetlink.h>
|
|
#include <net/cfg80211.h>
|
|
#include <net/wireless.h>
|
|
#include "nl80211.h"
|
|
#include "core.h"
|
|
#include "sysfs.h"
|
|
|
|
/* name for sysfs, %d is appended */
|
|
#define PHY_NAME "phy"
|
|
|
|
MODULE_AUTHOR("Johannes Berg");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("wireless configuration support");
|
|
|
|
/* RCU might be appropriate here since we usually
|
|
* only read the list, and that can happen quite
|
|
* often because we need to do it for each command */
|
|
LIST_HEAD(cfg80211_drv_list);
|
|
DEFINE_MUTEX(cfg80211_drv_mutex);
|
|
|
|
/* for debugfs */
|
|
static struct dentry *ieee80211_debugfs_dir;
|
|
|
|
/* requires cfg80211_drv_mutex to be held! */
|
|
static struct cfg80211_registered_device *cfg80211_drv_by_wiphy(int wiphy)
|
|
{
|
|
struct cfg80211_registered_device *result = NULL, *drv;
|
|
|
|
list_for_each_entry(drv, &cfg80211_drv_list, list) {
|
|
if (drv->idx == wiphy) {
|
|
result = drv;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* requires cfg80211_drv_mutex to be held! */
|
|
static struct cfg80211_registered_device *
|
|
__cfg80211_drv_from_info(struct genl_info *info)
|
|
{
|
|
int ifindex;
|
|
struct cfg80211_registered_device *bywiphy = NULL, *byifidx = NULL;
|
|
struct net_device *dev;
|
|
int err = -EINVAL;
|
|
|
|
if (info->attrs[NL80211_ATTR_WIPHY]) {
|
|
bywiphy = cfg80211_drv_by_wiphy(
|
|
nla_get_u32(info->attrs[NL80211_ATTR_WIPHY]));
|
|
err = -ENODEV;
|
|
}
|
|
|
|
if (info->attrs[NL80211_ATTR_IFINDEX]) {
|
|
ifindex = nla_get_u32(info->attrs[NL80211_ATTR_IFINDEX]);
|
|
dev = dev_get_by_index(&init_net, ifindex);
|
|
if (dev) {
|
|
if (dev->ieee80211_ptr)
|
|
byifidx =
|
|
wiphy_to_dev(dev->ieee80211_ptr->wiphy);
|
|
dev_put(dev);
|
|
}
|
|
err = -ENODEV;
|
|
}
|
|
|
|
if (bywiphy && byifidx) {
|
|
if (bywiphy != byifidx)
|
|
return ERR_PTR(-EINVAL);
|
|
else
|
|
return bywiphy; /* == byifidx */
|
|
}
|
|
if (bywiphy)
|
|
return bywiphy;
|
|
|
|
if (byifidx)
|
|
return byifidx;
|
|
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
struct cfg80211_registered_device *
|
|
cfg80211_get_dev_from_info(struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
|
|
mutex_lock(&cfg80211_drv_mutex);
|
|
drv = __cfg80211_drv_from_info(info);
|
|
|
|
/* if it is not an error we grab the lock on
|
|
* it to assure it won't be going away while
|
|
* we operate on it */
|
|
if (!IS_ERR(drv))
|
|
mutex_lock(&drv->mtx);
|
|
|
|
mutex_unlock(&cfg80211_drv_mutex);
|
|
|
|
return drv;
|
|
}
|
|
|
|
struct cfg80211_registered_device *
|
|
cfg80211_get_dev_from_ifindex(int ifindex)
|
|
{
|
|
struct cfg80211_registered_device *drv = ERR_PTR(-ENODEV);
|
|
struct net_device *dev;
|
|
|
|
mutex_lock(&cfg80211_drv_mutex);
|
|
dev = dev_get_by_index(&init_net, ifindex);
|
|
if (!dev)
|
|
goto out;
|
|
if (dev->ieee80211_ptr) {
|
|
drv = wiphy_to_dev(dev->ieee80211_ptr->wiphy);
|
|
mutex_lock(&drv->mtx);
|
|
} else
|
|
drv = ERR_PTR(-ENODEV);
|
|
dev_put(dev);
|
|
out:
|
|
mutex_unlock(&cfg80211_drv_mutex);
|
|
return drv;
|
|
}
|
|
|
|
void cfg80211_put_dev(struct cfg80211_registered_device *drv)
|
|
{
|
|
BUG_ON(IS_ERR(drv));
|
|
mutex_unlock(&drv->mtx);
|
|
}
|
|
|
|
int cfg80211_dev_rename(struct cfg80211_registered_device *rdev,
|
|
char *newname)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int idx, taken = -1, result, digits;
|
|
|
|
mutex_lock(&cfg80211_drv_mutex);
|
|
|
|
/* prohibit calling the thing phy%d when %d is not its number */
|
|
sscanf(newname, PHY_NAME "%d%n", &idx, &taken);
|
|
if (taken == strlen(newname) && idx != rdev->idx) {
|
|
/* count number of places needed to print idx */
|
|
digits = 1;
|
|
while (idx /= 10)
|
|
digits++;
|
|
/*
|
|
* deny the name if it is phy<idx> where <idx> is printed
|
|
* without leading zeroes. taken == strlen(newname) here
|
|
*/
|
|
result = -EINVAL;
|
|
if (taken == strlen(PHY_NAME) + digits)
|
|
goto out_unlock;
|
|
}
|
|
|
|
|
|
/* Ignore nop renames */
|
|
result = 0;
|
|
if (strcmp(newname, dev_name(&rdev->wiphy.dev)) == 0)
|
|
goto out_unlock;
|
|
|
|
/* Ensure another device does not already have this name. */
|
|
list_for_each_entry(drv, &cfg80211_drv_list, list) {
|
|
result = -EINVAL;
|
|
if (strcmp(newname, dev_name(&drv->wiphy.dev)) == 0)
|
|
goto out_unlock;
|
|
}
|
|
|
|
/* this will only check for collisions in sysfs
|
|
* which is not even always compiled in.
|
|
*/
|
|
result = device_rename(&rdev->wiphy.dev, newname);
|
|
if (result)
|
|
goto out_unlock;
|
|
|
|
if (rdev->wiphy.debugfsdir &&
|
|
!debugfs_rename(rdev->wiphy.debugfsdir->d_parent,
|
|
rdev->wiphy.debugfsdir,
|
|
rdev->wiphy.debugfsdir->d_parent,
|
|
newname))
|
|
printk(KERN_ERR "cfg80211: failed to rename debugfs dir to %s!\n",
|
|
newname);
|
|
|
|
result = 0;
|
|
out_unlock:
|
|
mutex_unlock(&cfg80211_drv_mutex);
|
|
if (result == 0)
|
|
nl80211_notify_dev_rename(rdev);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* exported functions */
|
|
|
|
struct wiphy *wiphy_new(struct cfg80211_ops *ops, int sizeof_priv)
|
|
{
|
|
static int wiphy_counter;
|
|
|
|
struct cfg80211_registered_device *drv;
|
|
int alloc_size;
|
|
|
|
WARN_ON(!ops->add_key && ops->del_key);
|
|
WARN_ON(ops->add_key && !ops->del_key);
|
|
|
|
alloc_size = sizeof(*drv) + sizeof_priv;
|
|
|
|
drv = kzalloc(alloc_size, GFP_KERNEL);
|
|
if (!drv)
|
|
return NULL;
|
|
|
|
drv->ops = ops;
|
|
|
|
mutex_lock(&cfg80211_drv_mutex);
|
|
|
|
drv->idx = wiphy_counter++;
|
|
|
|
if (unlikely(drv->idx < 0)) {
|
|
wiphy_counter--;
|
|
mutex_unlock(&cfg80211_drv_mutex);
|
|
/* ugh, wrapped! */
|
|
kfree(drv);
|
|
return NULL;
|
|
}
|
|
|
|
mutex_unlock(&cfg80211_drv_mutex);
|
|
|
|
/* give it a proper name */
|
|
dev_set_name(&drv->wiphy.dev, PHY_NAME "%d", drv->idx);
|
|
|
|
mutex_init(&drv->mtx);
|
|
mutex_init(&drv->devlist_mtx);
|
|
INIT_LIST_HEAD(&drv->netdev_list);
|
|
|
|
device_initialize(&drv->wiphy.dev);
|
|
drv->wiphy.dev.class = &ieee80211_class;
|
|
drv->wiphy.dev.platform_data = drv;
|
|
|
|
return &drv->wiphy;
|
|
}
|
|
EXPORT_SYMBOL(wiphy_new);
|
|
|
|
int wiphy_register(struct wiphy *wiphy)
|
|
{
|
|
struct cfg80211_registered_device *drv = wiphy_to_dev(wiphy);
|
|
int res;
|
|
enum ieee80211_band band;
|
|
struct ieee80211_supported_band *sband;
|
|
bool have_band = false;
|
|
int i;
|
|
u16 ifmodes = wiphy->interface_modes;
|
|
|
|
/* sanity check ifmodes */
|
|
WARN_ON(!ifmodes);
|
|
ifmodes &= ((1 << __NL80211_IFTYPE_AFTER_LAST) - 1) & ~1;
|
|
if (WARN_ON(ifmodes != wiphy->interface_modes))
|
|
wiphy->interface_modes = ifmodes;
|
|
|
|
/* sanity check supported bands/channels */
|
|
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
|
|
sband = wiphy->bands[band];
|
|
if (!sband)
|
|
continue;
|
|
|
|
sband->band = band;
|
|
|
|
if (!sband->n_channels || !sband->n_bitrates) {
|
|
WARN_ON(1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < sband->n_channels; i++) {
|
|
sband->channels[i].orig_flags =
|
|
sband->channels[i].flags;
|
|
sband->channels[i].orig_mag =
|
|
sband->channels[i].max_antenna_gain;
|
|
sband->channels[i].orig_mpwr =
|
|
sband->channels[i].max_power;
|
|
sband->channels[i].band = band;
|
|
}
|
|
|
|
have_band = true;
|
|
}
|
|
|
|
if (!have_band) {
|
|
WARN_ON(1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check and set up bitrates */
|
|
ieee80211_set_bitrate_flags(wiphy);
|
|
|
|
mutex_lock(&cfg80211_drv_mutex);
|
|
|
|
/* set up regulatory info */
|
|
wiphy_update_regulatory(wiphy, REGDOM_SET_BY_CORE);
|
|
|
|
res = device_add(&drv->wiphy.dev);
|
|
if (res)
|
|
goto out_unlock;
|
|
|
|
list_add(&drv->list, &cfg80211_drv_list);
|
|
|
|
/* add to debugfs */
|
|
drv->wiphy.debugfsdir =
|
|
debugfs_create_dir(wiphy_name(&drv->wiphy),
|
|
ieee80211_debugfs_dir);
|
|
if (IS_ERR(drv->wiphy.debugfsdir))
|
|
drv->wiphy.debugfsdir = NULL;
|
|
|
|
res = 0;
|
|
out_unlock:
|
|
mutex_unlock(&cfg80211_drv_mutex);
|
|
return res;
|
|
}
|
|
EXPORT_SYMBOL(wiphy_register);
|
|
|
|
void wiphy_unregister(struct wiphy *wiphy)
|
|
{
|
|
struct cfg80211_registered_device *drv = wiphy_to_dev(wiphy);
|
|
|
|
/* protect the device list */
|
|
mutex_lock(&cfg80211_drv_mutex);
|
|
|
|
BUG_ON(!list_empty(&drv->netdev_list));
|
|
|
|
/*
|
|
* Try to grab drv->mtx. If a command is still in progress,
|
|
* hopefully the driver will refuse it since it's tearing
|
|
* down the device already. We wait for this command to complete
|
|
* before unlinking the item from the list.
|
|
* Note: as codified by the BUG_ON above we cannot get here if
|
|
* a virtual interface is still associated. Hence, we can only
|
|
* get to lock contention here if userspace issues a command
|
|
* that identified the hardware by wiphy index.
|
|
*/
|
|
mutex_lock(&drv->mtx);
|
|
/* unlock again before freeing */
|
|
mutex_unlock(&drv->mtx);
|
|
|
|
/* If this device got a regulatory hint tell core its
|
|
* free to listen now to a new shiny device regulatory hint */
|
|
reg_device_remove(wiphy);
|
|
|
|
list_del(&drv->list);
|
|
device_del(&drv->wiphy.dev);
|
|
debugfs_remove(drv->wiphy.debugfsdir);
|
|
|
|
mutex_unlock(&cfg80211_drv_mutex);
|
|
}
|
|
EXPORT_SYMBOL(wiphy_unregister);
|
|
|
|
void cfg80211_dev_free(struct cfg80211_registered_device *drv)
|
|
{
|
|
mutex_destroy(&drv->mtx);
|
|
mutex_destroy(&drv->devlist_mtx);
|
|
kfree(drv);
|
|
}
|
|
|
|
void wiphy_free(struct wiphy *wiphy)
|
|
{
|
|
put_device(&wiphy->dev);
|
|
}
|
|
EXPORT_SYMBOL(wiphy_free);
|
|
|
|
static int cfg80211_netdev_notifier_call(struct notifier_block * nb,
|
|
unsigned long state,
|
|
void *ndev)
|
|
{
|
|
struct net_device *dev = ndev;
|
|
struct cfg80211_registered_device *rdev;
|
|
|
|
if (!dev->ieee80211_ptr)
|
|
return 0;
|
|
|
|
rdev = wiphy_to_dev(dev->ieee80211_ptr->wiphy);
|
|
|
|
WARN_ON(dev->ieee80211_ptr->iftype == NL80211_IFTYPE_UNSPECIFIED);
|
|
|
|
switch (state) {
|
|
case NETDEV_REGISTER:
|
|
mutex_lock(&rdev->devlist_mtx);
|
|
list_add(&dev->ieee80211_ptr->list, &rdev->netdev_list);
|
|
if (sysfs_create_link(&dev->dev.kobj, &rdev->wiphy.dev.kobj,
|
|
"phy80211")) {
|
|
printk(KERN_ERR "wireless: failed to add phy80211 "
|
|
"symlink to netdev!\n");
|
|
}
|
|
dev->ieee80211_ptr->netdev = dev;
|
|
mutex_unlock(&rdev->devlist_mtx);
|
|
break;
|
|
case NETDEV_UNREGISTER:
|
|
mutex_lock(&rdev->devlist_mtx);
|
|
if (!list_empty(&dev->ieee80211_ptr->list)) {
|
|
sysfs_remove_link(&dev->dev.kobj, "phy80211");
|
|
list_del_init(&dev->ieee80211_ptr->list);
|
|
}
|
|
mutex_unlock(&rdev->devlist_mtx);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct notifier_block cfg80211_netdev_notifier = {
|
|
.notifier_call = cfg80211_netdev_notifier_call,
|
|
};
|
|
|
|
static int cfg80211_init(void)
|
|
{
|
|
int err;
|
|
|
|
err = wiphy_sysfs_init();
|
|
if (err)
|
|
goto out_fail_sysfs;
|
|
|
|
err = register_netdevice_notifier(&cfg80211_netdev_notifier);
|
|
if (err)
|
|
goto out_fail_notifier;
|
|
|
|
err = nl80211_init();
|
|
if (err)
|
|
goto out_fail_nl80211;
|
|
|
|
ieee80211_debugfs_dir = debugfs_create_dir("ieee80211", NULL);
|
|
|
|
err = regulatory_init();
|
|
if (err)
|
|
goto out_fail_reg;
|
|
|
|
return 0;
|
|
|
|
out_fail_reg:
|
|
debugfs_remove(ieee80211_debugfs_dir);
|
|
out_fail_nl80211:
|
|
unregister_netdevice_notifier(&cfg80211_netdev_notifier);
|
|
out_fail_notifier:
|
|
wiphy_sysfs_exit();
|
|
out_fail_sysfs:
|
|
return err;
|
|
}
|
|
|
|
subsys_initcall(cfg80211_init);
|
|
|
|
static void cfg80211_exit(void)
|
|
{
|
|
debugfs_remove(ieee80211_debugfs_dir);
|
|
nl80211_exit();
|
|
unregister_netdevice_notifier(&cfg80211_netdev_notifier);
|
|
wiphy_sysfs_exit();
|
|
regulatory_exit();
|
|
}
|
|
module_exit(cfg80211_exit);
|