remarkable-linux/net/mac80211/wext.c

1144 lines
30 KiB
C
Raw Normal View History

/*
* Copyright 2002-2005, Instant802 Networks, Inc.
* Copyright 2005-2006, Devicescape Software, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/skbuff.h>
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <linux/wireless.h>
#include <net/iw_handler.h>
#include <asm/uaccess.h>
#include <net/mac80211.h>
#include "ieee80211_i.h"
#include "led.h"
#include "rate.h"
#include "wpa.h"
#include "aes_ccm.h"
static int ieee80211_set_encryption(struct ieee80211_sub_if_data *sdata, u8 *sta_addr,
int idx, int alg, int remove,
int set_tx_key, const u8 *_key,
size_t key_len)
{
struct ieee80211_local *local = sdata->local;
struct sta_info *sta;
[MAC80211]: rework key handling This moves all the key handling code out from ieee80211_ioctl.c into key.c and also does the following changes including documentation updates in mac80211.h: 1) Turn off hardware acceleration for keys when the interface is down. This is necessary because otherwise monitor interfaces could be decrypting frames for other interfaces that are down at the moment. Also, it should go some way towards better suspend/resume support, in any case the routines used here could be used for that as well. Additionally, this makes the driver interface nicer, keys for a specific local MAC address are only ever present while an interface with that MAC address is enabled. 2) Change driver set_key() callback interface to allow only return values of -ENOSPC, -EOPNOTSUPP and 0, warn on all other return values. This allows debugging the stack when a driver notices it's handed a key while it is down. 3) Invert the flag meaning to KEY_FLAG_UPLOADED_TO_HARDWARE. 4) Remove REMOVE_ALL_KEYS command as it isn't used nor do we want to use it, we'll use DISABLE_KEY for each key. It is hard to use REMOVE_ALL_KEYS because we can handle multiple virtual interfaces with different key configuration, so we'd have to keep track of a lot of state for this and that isn't worth it. 5) Warn when disabling a key fails, it musn't. 6) Remove IEEE80211_HW_NO_TKIP_WMM_HWACCEL in favour of per-key IEEE80211_KEY_FLAG_WMM_STA to let driver sort it out itself. 7) Tell driver that a (non-WEP) key is used only for transmission by using an all-zeroes station MAC address when configuring. 8) Change the set_key() callback to have access to the local MAC address the key is being added for. Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Acked-by: Michael Wu <flamingice@sourmilk.net> Signed-off-by: John W. Linville <linville@tuxdriver.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2007-08-28 15:01:55 -06:00
struct ieee80211_key *key;
int err;
if (alg == ALG_AES_CMAC) {
if (idx < NUM_DEFAULT_KEYS ||
idx >= NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS) {
printk(KERN_DEBUG "%s: set_encrypt - invalid idx=%d "
"(BIP)\n", sdata->dev->name, idx);
return -EINVAL;
}
} else if (idx < 0 || idx >= NUM_DEFAULT_KEYS) {
printk(KERN_DEBUG "%s: set_encrypt - invalid idx=%d\n",
sdata->dev->name, idx);
return -EINVAL;
}
if (remove) {
rcu_read_lock();
err = 0;
if (is_broadcast_ether_addr(sta_addr)) {
key = sdata->keys[idx];
} else {
sta = sta_info_get(local, sta_addr);
if (!sta) {
err = -ENOENT;
goto out_unlock;
}
key = sta->key;
}
ieee80211_key_free(key);
} else {
key = ieee80211_key_alloc(alg, idx, key_len, _key);
if (!key)
return -ENOMEM;
sta = NULL;
err = 0;
rcu_read_lock();
if (!is_broadcast_ether_addr(sta_addr)) {
set_tx_key = 0;
/*
* According to the standard, the key index of a
* pairwise key must be zero. However, some AP are
* broken when it comes to WEP key indices, so we
* work around this.
*/
if (idx != 0 && alg != ALG_WEP) {
ieee80211_key_free(key);
err = -EINVAL;
goto out_unlock;
}
sta = sta_info_get(local, sta_addr);
if (!sta) {
ieee80211_key_free(key);
err = -ENOENT;
goto out_unlock;
}
}
if (alg == ALG_WEP &&
key_len != LEN_WEP40 && key_len != LEN_WEP104) {
ieee80211_key_free(key);
err = -EINVAL;
goto out_unlock;
}
ieee80211_key_link(key, sdata, sta);
if (set_tx_key || (!sta && !sdata->default_key && key))
ieee80211_set_default_key(sdata, idx);
if (alg == ALG_AES_CMAC &&
(set_tx_key || (!sta && !sdata->default_mgmt_key && key)))
ieee80211_set_default_mgmt_key(sdata, idx);
}
out_unlock:
rcu_read_unlock();
return err;
}
static int ieee80211_ioctl_siwgenie(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211_sub_if_data *sdata;
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
if (sdata->vif.type == NL80211_IFTYPE_STATION) {
int ret = ieee80211_sta_set_extra_ie(sdata, extra, data->length);
if (ret)
return ret;
sdata->u.mgd.flags &= ~IEEE80211_STA_AUTO_BSSID_SEL;
nl80211: Add MLME primitives to support external SME This patch adds new nl80211 commands to allow user space to request authentication and association (and also deauthentication and disassociation). The commands are structured to allow separate authentication and association steps, i.e., the interface between kernel and user space is similar to the MLME SAP interface in IEEE 802.11 standard and an user space application takes the role of the SME. The patch introduces MLME-AUTHENTICATE.request, MLME-{,RE}ASSOCIATE.request, MLME-DEAUTHENTICATE.request, and MLME-DISASSOCIATE.request primitives. The authentication and association commands request the actual operations in two steps (assuming the driver supports this; if not, separate authentication step is skipped; this could end up being a separate "connect" command). The initial implementation for mac80211 uses the current net/mac80211/mlme.c for actual sending and processing of management frames and the new nl80211 commands will just stop the current state machine from moving automatically from authentication to association. Future cleanup may move more of the MLME operations into cfg80211. The goal of this design is to provide more control of authentication and association process to user space without having to move the full MLME implementation. This should be enough to allow IEEE 802.11r FT protocol and 802.11s SAE authentication to be implemented. Obviously, this will also bring the extra benefit of not having to use WEXT for association requests with mac80211. An example implementation of a user space SME using the new nl80211 commands is available for wpa_supplicant. This patch is enough to get IEEE 802.11r FT protocol working with over-the-air mechanism (over-the-DS will need additional MLME primitives for handling the FT Action frames). Signed-off-by: Jouni Malinen <j@w1.fi> Signed-off-by: John W. Linville <linville@tuxdriver.com>
2009-03-19 05:39:22 -06:00
sdata->u.mgd.flags &= ~IEEE80211_STA_EXT_SME;
ieee80211_sta_req_auth(sdata);
return 0;
}
return -EOPNOTSUPP;
}
static int ieee80211_ioctl_siwfreq(struct net_device *dev,
struct iw_request_info *info,
struct iw_freq *freq, char *extra)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
if (sdata->vif.type == NL80211_IFTYPE_ADHOC)
sdata->u.ibss.flags &= ~IEEE80211_IBSS_AUTO_CHANNEL_SEL;
else if (sdata->vif.type == NL80211_IFTYPE_STATION)
sdata->u.mgd.flags &= ~IEEE80211_STA_AUTO_CHANNEL_SEL;
/* freq->e == 0: freq->m = channel; otherwise freq = m * 10^e */
if (freq->e == 0) {
if (freq->m < 0) {
if (sdata->vif.type == NL80211_IFTYPE_ADHOC)
sdata->u.ibss.flags |=
IEEE80211_IBSS_AUTO_CHANNEL_SEL;
else if (sdata->vif.type == NL80211_IFTYPE_STATION)
sdata->u.mgd.flags |=
IEEE80211_STA_AUTO_CHANNEL_SEL;
return 0;
} else
return ieee80211_set_freq(sdata,
ieee80211_channel_to_frequency(freq->m));
} else {
int i, div = 1000000;
for (i = 0; i < freq->e; i++)
div /= 10;
if (div > 0)
return ieee80211_set_freq(sdata, freq->m / div);
else
return -EINVAL;
}
}
static int ieee80211_ioctl_giwfreq(struct net_device *dev,
struct iw_request_info *info,
struct iw_freq *freq, char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
freq->m = local->hw.conf.channel->center_freq;
freq->e = 6;
return 0;
}
static int ieee80211_ioctl_siwessid(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *data, char *ssid)
{
struct ieee80211_sub_if_data *sdata;
size_t len = data->length;
int ret;
/* iwconfig uses nul termination in SSID.. */
if (len > 0 && ssid[len - 1] == '\0')
len--;
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
if (sdata->vif.type == NL80211_IFTYPE_STATION) {
if (data->flags)
sdata->u.mgd.flags &= ~IEEE80211_STA_AUTO_SSID_SEL;
else
sdata->u.mgd.flags |= IEEE80211_STA_AUTO_SSID_SEL;
ret = ieee80211_sta_set_ssid(sdata, ssid, len);
if (ret)
return ret;
nl80211: Add MLME primitives to support external SME This patch adds new nl80211 commands to allow user space to request authentication and association (and also deauthentication and disassociation). The commands are structured to allow separate authentication and association steps, i.e., the interface between kernel and user space is similar to the MLME SAP interface in IEEE 802.11 standard and an user space application takes the role of the SME. The patch introduces MLME-AUTHENTICATE.request, MLME-{,RE}ASSOCIATE.request, MLME-DEAUTHENTICATE.request, and MLME-DISASSOCIATE.request primitives. The authentication and association commands request the actual operations in two steps (assuming the driver supports this; if not, separate authentication step is skipped; this could end up being a separate "connect" command). The initial implementation for mac80211 uses the current net/mac80211/mlme.c for actual sending and processing of management frames and the new nl80211 commands will just stop the current state machine from moving automatically from authentication to association. Future cleanup may move more of the MLME operations into cfg80211. The goal of this design is to provide more control of authentication and association process to user space without having to move the full MLME implementation. This should be enough to allow IEEE 802.11r FT protocol and 802.11s SAE authentication to be implemented. Obviously, this will also bring the extra benefit of not having to use WEXT for association requests with mac80211. An example implementation of a user space SME using the new nl80211 commands is available for wpa_supplicant. This patch is enough to get IEEE 802.11r FT protocol working with over-the-air mechanism (over-the-DS will need additional MLME primitives for handling the FT Action frames). Signed-off-by: Jouni Malinen <j@w1.fi> Signed-off-by: John W. Linville <linville@tuxdriver.com>
2009-03-19 05:39:22 -06:00
sdata->u.mgd.flags &= ~IEEE80211_STA_EXT_SME;
ieee80211_sta_req_auth(sdata);
return 0;
} else if (sdata->vif.type == NL80211_IFTYPE_ADHOC)
return ieee80211_ibss_set_ssid(sdata, ssid, len);
return -EOPNOTSUPP;
}
static int ieee80211_ioctl_giwessid(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *data, char *ssid)
{
size_t len;
struct ieee80211_sub_if_data *sdata;
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
if (sdata->vif.type == NL80211_IFTYPE_STATION) {
int res = ieee80211_sta_get_ssid(sdata, ssid, &len);
if (res == 0) {
data->length = len;
data->flags = 1;
} else
data->flags = 0;
return res;
} else if (sdata->vif.type == NL80211_IFTYPE_ADHOC) {
int res = ieee80211_ibss_get_ssid(sdata, ssid, &len);
if (res == 0) {
data->length = len;
data->flags = 1;
} else
data->flags = 0;
return res;
}
return -EOPNOTSUPP;
}
static int ieee80211_ioctl_siwap(struct net_device *dev,
struct iw_request_info *info,
struct sockaddr *ap_addr, char *extra)
{
struct ieee80211_sub_if_data *sdata;
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
if (sdata->vif.type == NL80211_IFTYPE_STATION) {
int ret;
if (is_zero_ether_addr((u8 *) &ap_addr->sa_data))
sdata->u.mgd.flags |= IEEE80211_STA_AUTO_BSSID_SEL |
IEEE80211_STA_AUTO_CHANNEL_SEL;
else if (is_broadcast_ether_addr((u8 *) &ap_addr->sa_data))
sdata->u.mgd.flags |= IEEE80211_STA_AUTO_BSSID_SEL;
else
sdata->u.mgd.flags &= ~IEEE80211_STA_AUTO_BSSID_SEL;
ret = ieee80211_sta_set_bssid(sdata, (u8 *) &ap_addr->sa_data);
if (ret)
return ret;
nl80211: Add MLME primitives to support external SME This patch adds new nl80211 commands to allow user space to request authentication and association (and also deauthentication and disassociation). The commands are structured to allow separate authentication and association steps, i.e., the interface between kernel and user space is similar to the MLME SAP interface in IEEE 802.11 standard and an user space application takes the role of the SME. The patch introduces MLME-AUTHENTICATE.request, MLME-{,RE}ASSOCIATE.request, MLME-DEAUTHENTICATE.request, and MLME-DISASSOCIATE.request primitives. The authentication and association commands request the actual operations in two steps (assuming the driver supports this; if not, separate authentication step is skipped; this could end up being a separate "connect" command). The initial implementation for mac80211 uses the current net/mac80211/mlme.c for actual sending and processing of management frames and the new nl80211 commands will just stop the current state machine from moving automatically from authentication to association. Future cleanup may move more of the MLME operations into cfg80211. The goal of this design is to provide more control of authentication and association process to user space without having to move the full MLME implementation. This should be enough to allow IEEE 802.11r FT protocol and 802.11s SAE authentication to be implemented. Obviously, this will also bring the extra benefit of not having to use WEXT for association requests with mac80211. An example implementation of a user space SME using the new nl80211 commands is available for wpa_supplicant. This patch is enough to get IEEE 802.11r FT protocol working with over-the-air mechanism (over-the-DS will need additional MLME primitives for handling the FT Action frames). Signed-off-by: Jouni Malinen <j@w1.fi> Signed-off-by: John W. Linville <linville@tuxdriver.com>
2009-03-19 05:39:22 -06:00
sdata->u.mgd.flags &= ~IEEE80211_STA_EXT_SME;
ieee80211_sta_req_auth(sdata);
return 0;
} else if (sdata->vif.type == NL80211_IFTYPE_ADHOC) {
if (is_zero_ether_addr((u8 *) &ap_addr->sa_data))
sdata->u.ibss.flags |= IEEE80211_IBSS_AUTO_BSSID_SEL |
IEEE80211_IBSS_AUTO_CHANNEL_SEL;
else if (is_broadcast_ether_addr((u8 *) &ap_addr->sa_data))
sdata->u.ibss.flags |= IEEE80211_IBSS_AUTO_BSSID_SEL;
else
sdata->u.ibss.flags &= ~IEEE80211_IBSS_AUTO_BSSID_SEL;
return ieee80211_ibss_set_bssid(sdata, (u8 *) &ap_addr->sa_data);
} else if (sdata->vif.type == NL80211_IFTYPE_WDS) {
/*
* If it is necessary to update the WDS peer address
* while the interface is running, then we need to do
* more work here, namely if it is running we need to
* add a new and remove the old STA entry, this is
* normally handled by _open() and _stop().
*/
if (netif_running(dev))
return -EBUSY;
memcpy(&sdata->u.wds.remote_addr, (u8 *) &ap_addr->sa_data,
ETH_ALEN);
return 0;
}
return -EOPNOTSUPP;
}
static int ieee80211_ioctl_giwap(struct net_device *dev,
struct iw_request_info *info,
struct sockaddr *ap_addr, char *extra)
{
struct ieee80211_sub_if_data *sdata;
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
if (sdata->vif.type == NL80211_IFTYPE_STATION) {
if (sdata->u.mgd.state == IEEE80211_STA_MLME_ASSOCIATED) {
ap_addr->sa_family = ARPHRD_ETHER;
memcpy(&ap_addr->sa_data, sdata->u.mgd.bssid, ETH_ALEN);
} else
memset(&ap_addr->sa_data, 0, ETH_ALEN);
return 0;
} else if (sdata->vif.type == NL80211_IFTYPE_ADHOC) {
if (sdata->u.ibss.state == IEEE80211_IBSS_MLME_JOINED) {
ap_addr->sa_family = ARPHRD_ETHER;
memcpy(&ap_addr->sa_data, sdata->u.ibss.bssid, ETH_ALEN);
} else
memset(&ap_addr->sa_data, 0, ETH_ALEN);
return 0;
} else if (sdata->vif.type == NL80211_IFTYPE_WDS) {
ap_addr->sa_family = ARPHRD_ETHER;
memcpy(&ap_addr->sa_data, sdata->u.wds.remote_addr, ETH_ALEN);
return 0;
}
return -EOPNOTSUPP;
}
static int ieee80211_ioctl_siwrate(struct net_device *dev,
struct iw_request_info *info,
struct iw_param *rate, char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
int i, err = -EINVAL;
u32 target_rate = rate->value / 100000;
struct ieee80211_sub_if_data *sdata;
struct ieee80211_supported_band *sband;
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
/* target_rate = -1, rate->fixed = 0 means auto only, so use all rates
* target_rate = X, rate->fixed = 1 means only rate X
* target_rate = X, rate->fixed = 0 means all rates <= X */
sdata->max_ratectrl_rateidx = -1;
sdata->force_unicast_rateidx = -1;
if (rate->value < 0)
return 0;
for (i=0; i< sband->n_bitrates; i++) {
struct ieee80211_rate *brate = &sband->bitrates[i];
int this_rate = brate->bitrate;
if (target_rate == this_rate) {
sdata->max_ratectrl_rateidx = i;
if (rate->fixed)
sdata->force_unicast_rateidx = i;
err = 0;
break;
}
}
return err;
}
static int ieee80211_ioctl_giwrate(struct net_device *dev,
struct iw_request_info *info,
struct iw_param *rate, char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
struct sta_info *sta;
struct ieee80211_sub_if_data *sdata;
struct ieee80211_supported_band *sband;
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
if (sdata->vif.type != NL80211_IFTYPE_STATION)
return -EOPNOTSUPP;
sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
rcu_read_lock();
sta = sta_info_get(local, sdata->u.mgd.bssid);
if (sta && !(sta->last_tx_rate.flags & IEEE80211_TX_RC_MCS))
rate->value = sband->bitrates[sta->last_tx_rate.idx].bitrate;
else
rate->value = 0;
rcu_read_unlock();
if (!sta)
return -ENODEV;
rate->value *= 100000;
return 0;
}
static int ieee80211_ioctl_siwtxpower(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *data, char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
struct ieee80211_channel* chan = local->hw.conf.channel;
bool reconf = false;
u32 reconf_flags = 0;
int new_power_level;
if ((data->txpower.flags & IW_TXPOW_TYPE) != IW_TXPOW_DBM)
return -EINVAL;
if (data->txpower.flags & IW_TXPOW_RANGE)
return -EINVAL;
if (!chan)
return -EINVAL;
/* only change when not disabling */
if (!data->txpower.disabled) {
if (data->txpower.fixed) {
if (data->txpower.value < 0)
return -EINVAL;
new_power_level = data->txpower.value;
/*
* Debatable, but we cannot do a fixed power
* level above the regulatory constraint.
* Use "iwconfig wlan0 txpower 15dBm" instead.
*/
if (new_power_level > chan->max_power)
return -EINVAL;
} else {
/*
* Automatic power level setting, max being the value
* passed in from userland.
*/
if (data->txpower.value < 0)
new_power_level = -1;
else
new_power_level = data->txpower.value;
}
reconf = true;
/*
* ieee80211_hw_config() will limit to the channel's
* max power and possibly power constraint from AP.
*/
local->user_power_level = new_power_level;
}
if (local->hw.conf.radio_enabled != !(data->txpower.disabled)) {
local->hw.conf.radio_enabled = !(data->txpower.disabled);
reconf_flags |= IEEE80211_CONF_CHANGE_RADIO_ENABLED;
ieee80211_led_radio(local, local->hw.conf.radio_enabled);
}
if (reconf || reconf_flags)
ieee80211_hw_config(local, reconf_flags);
return 0;
}
static int ieee80211_ioctl_giwtxpower(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *data, char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
data->txpower.fixed = 1;
data->txpower.disabled = !(local->hw.conf.radio_enabled);
data->txpower.value = local->hw.conf.power_level;
data->txpower.flags = IW_TXPOW_DBM;
return 0;
}
static int ieee80211_ioctl_siwrts(struct net_device *dev,
struct iw_request_info *info,
struct iw_param *rts, char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
if (rts->disabled)
local->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD;
else if (!rts->fixed)
/* if the rts value is not fixed, then take default */
local->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD;
else if (rts->value < 0 || rts->value > IEEE80211_MAX_RTS_THRESHOLD)
return -EINVAL;
else
local->rts_threshold = rts->value;
/* If the wlan card performs RTS/CTS in hardware/firmware,
* configure it here */
if (local->ops->set_rts_threshold)
local->ops->set_rts_threshold(local_to_hw(local),
local->rts_threshold);
return 0;
}
static int ieee80211_ioctl_giwrts(struct net_device *dev,
struct iw_request_info *info,
struct iw_param *rts, char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
rts->value = local->rts_threshold;
rts->disabled = (rts->value >= IEEE80211_MAX_RTS_THRESHOLD);
rts->fixed = 1;
return 0;
}
static int ieee80211_ioctl_siwfrag(struct net_device *dev,
struct iw_request_info *info,
struct iw_param *frag, char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
if (frag->disabled)
local->fragmentation_threshold = IEEE80211_MAX_FRAG_THRESHOLD;
else if (!frag->fixed)
local->fragmentation_threshold = IEEE80211_MAX_FRAG_THRESHOLD;
else if (frag->value < 256 ||
frag->value > IEEE80211_MAX_FRAG_THRESHOLD)
return -EINVAL;
else {
/* Fragment length must be even, so strip LSB. */
local->fragmentation_threshold = frag->value & ~0x1;
}
return 0;
}
static int ieee80211_ioctl_giwfrag(struct net_device *dev,
struct iw_request_info *info,
struct iw_param *frag, char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
frag->value = local->fragmentation_threshold;
frag->disabled = (frag->value >= IEEE80211_MAX_FRAG_THRESHOLD);
frag->fixed = 1;
return 0;
}
static int ieee80211_ioctl_siwretry(struct net_device *dev,
struct iw_request_info *info,
struct iw_param *retry, char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
if (retry->disabled ||
(retry->flags & IW_RETRY_TYPE) != IW_RETRY_LIMIT)
return -EINVAL;
if (retry->flags & IW_RETRY_MAX) {
local->hw.conf.long_frame_max_tx_count = retry->value;
} else if (retry->flags & IW_RETRY_MIN) {
local->hw.conf.short_frame_max_tx_count = retry->value;
} else {
local->hw.conf.long_frame_max_tx_count = retry->value;
local->hw.conf.short_frame_max_tx_count = retry->value;
}
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_RETRY_LIMITS);
return 0;
}
static int ieee80211_ioctl_giwretry(struct net_device *dev,
struct iw_request_info *info,
struct iw_param *retry, char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
retry->disabled = 0;
if (retry->flags == 0 || retry->flags & IW_RETRY_MIN) {
/* first return min value, iwconfig will ask max value
* later if needed */
retry->flags |= IW_RETRY_LIMIT;
retry->value = local->hw.conf.short_frame_max_tx_count;
if (local->hw.conf.long_frame_max_tx_count !=
local->hw.conf.short_frame_max_tx_count)
retry->flags |= IW_RETRY_MIN;
return 0;
}
if (retry->flags & IW_RETRY_MAX) {
retry->flags = IW_RETRY_LIMIT | IW_RETRY_MAX;
retry->value = local->hw.conf.long_frame_max_tx_count;
}
return 0;
}
static int ieee80211_ioctl_siwmlme(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211_sub_if_data *sdata;
struct iw_mlme *mlme = (struct iw_mlme *) extra;
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
if (!(sdata->vif.type == NL80211_IFTYPE_STATION))
return -EINVAL;
switch (mlme->cmd) {
case IW_MLME_DEAUTH:
/* TODO: mlme->addr.sa_data */
return ieee80211_sta_deauthenticate(sdata, mlme->reason_code);
case IW_MLME_DISASSOC:
/* TODO: mlme->addr.sa_data */
return ieee80211_sta_disassociate(sdata, mlme->reason_code);
default:
return -EOPNOTSUPP;
}
}
static int ieee80211_ioctl_siwencode(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *erq, char *keybuf)
{
struct ieee80211_sub_if_data *sdata;
int idx, i, alg = ALG_WEP;
u8 bcaddr[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
int remove = 0, ret;
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
idx = erq->flags & IW_ENCODE_INDEX;
if (idx == 0) {
if (sdata->default_key)
for (i = 0; i < NUM_DEFAULT_KEYS; i++) {
if (sdata->default_key == sdata->keys[i]) {
idx = i;
break;
}
}
} else if (idx < 1 || idx > 4)
return -EINVAL;
else
idx--;
if (erq->flags & IW_ENCODE_DISABLED)
remove = 1;
else if (erq->length == 0) {
/* No key data - just set the default TX key index */
[MAC80211]: rework key handling This moves all the key handling code out from ieee80211_ioctl.c into key.c and also does the following changes including documentation updates in mac80211.h: 1) Turn off hardware acceleration for keys when the interface is down. This is necessary because otherwise monitor interfaces could be decrypting frames for other interfaces that are down at the moment. Also, it should go some way towards better suspend/resume support, in any case the routines used here could be used for that as well. Additionally, this makes the driver interface nicer, keys for a specific local MAC address are only ever present while an interface with that MAC address is enabled. 2) Change driver set_key() callback interface to allow only return values of -ENOSPC, -EOPNOTSUPP and 0, warn on all other return values. This allows debugging the stack when a driver notices it's handed a key while it is down. 3) Invert the flag meaning to KEY_FLAG_UPLOADED_TO_HARDWARE. 4) Remove REMOVE_ALL_KEYS command as it isn't used nor do we want to use it, we'll use DISABLE_KEY for each key. It is hard to use REMOVE_ALL_KEYS because we can handle multiple virtual interfaces with different key configuration, so we'd have to keep track of a lot of state for this and that isn't worth it. 5) Warn when disabling a key fails, it musn't. 6) Remove IEEE80211_HW_NO_TKIP_WMM_HWACCEL in favour of per-key IEEE80211_KEY_FLAG_WMM_STA to let driver sort it out itself. 7) Tell driver that a (non-WEP) key is used only for transmission by using an all-zeroes station MAC address when configuring. 8) Change the set_key() callback to have access to the local MAC address the key is being added for. Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Acked-by: Michael Wu <flamingice@sourmilk.net> Signed-off-by: John W. Linville <linville@tuxdriver.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2007-08-28 15:01:55 -06:00
ieee80211_set_default_key(sdata, idx);
return 0;
}
ret = ieee80211_set_encryption(
sdata, bcaddr,
idx, alg, remove,
!sdata->default_key,
keybuf, erq->length);
if (!ret) {
if (remove)
sdata->u.mgd.flags &= ~IEEE80211_STA_TKIP_WEP_USED;
else
sdata->u.mgd.flags |= IEEE80211_STA_TKIP_WEP_USED;
}
return ret;
}
static int ieee80211_ioctl_giwencode(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *erq, char *key)
{
struct ieee80211_sub_if_data *sdata;
int idx, i;
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
idx = erq->flags & IW_ENCODE_INDEX;
if (idx < 1 || idx > 4) {
idx = -1;
if (!sdata->default_key)
idx = 0;
else for (i = 0; i < NUM_DEFAULT_KEYS; i++) {
if (sdata->default_key == sdata->keys[i]) {
idx = i;
break;
}
}
if (idx < 0)
return -EINVAL;
} else
idx--;
erq->flags = idx + 1;
if (!sdata->keys[idx]) {
erq->length = 0;
erq->flags |= IW_ENCODE_DISABLED;
return 0;
}
memcpy(key, sdata->keys[idx]->conf.key,
[MAC80211]: rework key handling This moves all the key handling code out from ieee80211_ioctl.c into key.c and also does the following changes including documentation updates in mac80211.h: 1) Turn off hardware acceleration for keys when the interface is down. This is necessary because otherwise monitor interfaces could be decrypting frames for other interfaces that are down at the moment. Also, it should go some way towards better suspend/resume support, in any case the routines used here could be used for that as well. Additionally, this makes the driver interface nicer, keys for a specific local MAC address are only ever present while an interface with that MAC address is enabled. 2) Change driver set_key() callback interface to allow only return values of -ENOSPC, -EOPNOTSUPP and 0, warn on all other return values. This allows debugging the stack when a driver notices it's handed a key while it is down. 3) Invert the flag meaning to KEY_FLAG_UPLOADED_TO_HARDWARE. 4) Remove REMOVE_ALL_KEYS command as it isn't used nor do we want to use it, we'll use DISABLE_KEY for each key. It is hard to use REMOVE_ALL_KEYS because we can handle multiple virtual interfaces with different key configuration, so we'd have to keep track of a lot of state for this and that isn't worth it. 5) Warn when disabling a key fails, it musn't. 6) Remove IEEE80211_HW_NO_TKIP_WMM_HWACCEL in favour of per-key IEEE80211_KEY_FLAG_WMM_STA to let driver sort it out itself. 7) Tell driver that a (non-WEP) key is used only for transmission by using an all-zeroes station MAC address when configuring. 8) Change the set_key() callback to have access to the local MAC address the key is being added for. Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Acked-by: Michael Wu <flamingice@sourmilk.net> Signed-off-by: John W. Linville <linville@tuxdriver.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2007-08-28 15:01:55 -06:00
min_t(int, erq->length, sdata->keys[idx]->conf.keylen));
erq->length = sdata->keys[idx]->conf.keylen;
erq->flags |= IW_ENCODE_ENABLED;
if (sdata->vif.type == NL80211_IFTYPE_STATION) {
switch (sdata->u.mgd.auth_alg) {
case WLAN_AUTH_OPEN:
case WLAN_AUTH_LEAP:
erq->flags |= IW_ENCODE_OPEN;
break;
case WLAN_AUTH_SHARED_KEY:
erq->flags |= IW_ENCODE_RESTRICTED;
break;
}
}
return 0;
}
static int ieee80211_ioctl_siwpower(struct net_device *dev,
struct iw_request_info *info,
struct iw_param *wrq,
char *extra)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
struct ieee80211_conf *conf = &local->hw.conf;
int ret = 0, timeout = 0;
bool ps;
if (!(local->hw.flags & IEEE80211_HW_SUPPORTS_PS))
return -EOPNOTSUPP;
if (sdata->vif.type != NL80211_IFTYPE_STATION)
return -EINVAL;
if (wrq->disabled) {
ps = false;
timeout = 0;
goto set;
}
switch (wrq->flags & IW_POWER_MODE) {
case IW_POWER_ON: /* If not specified */
case IW_POWER_MODE: /* If set all mask */
case IW_POWER_ALL_R: /* If explicitely state all */
ps = true;
break;
default: /* Otherwise we ignore */
return -EINVAL;
}
if (wrq->flags & ~(IW_POWER_MODE | IW_POWER_TIMEOUT))
return -EINVAL;
if (wrq->flags & IW_POWER_TIMEOUT)
timeout = wrq->value / 1000;
set:
if (ps == local->powersave && timeout == conf->dynamic_ps_timeout)
return ret;
local->powersave = ps;
conf->dynamic_ps_timeout = timeout;
if (local->hw.flags & IEEE80211_HW_SUPPORTS_DYNAMIC_PS)
ret = ieee80211_hw_config(local,
IEEE80211_CONF_CHANGE_DYNPS_TIMEOUT);
if (!(sdata->u.mgd.flags & IEEE80211_STA_ASSOCIATED))
return ret;
if (conf->dynamic_ps_timeout > 0 &&
!(local->hw.flags & IEEE80211_HW_SUPPORTS_DYNAMIC_PS)) {
mod_timer(&local->dynamic_ps_timer, jiffies +
msecs_to_jiffies(conf->dynamic_ps_timeout));
} else {
if (local->powersave) {
if (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK)
ieee80211_send_nullfunc(local, sdata, 1);
conf->flags |= IEEE80211_CONF_PS;
ret = ieee80211_hw_config(local,
IEEE80211_CONF_CHANGE_PS);
} else {
conf->flags &= ~IEEE80211_CONF_PS;
ret = ieee80211_hw_config(local,
IEEE80211_CONF_CHANGE_PS);
if (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK)
ieee80211_send_nullfunc(local, sdata, 0);
del_timer_sync(&local->dynamic_ps_timer);
cancel_work_sync(&local->dynamic_ps_enable_work);
}
}
return ret;
}
static int ieee80211_ioctl_giwpower(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *wrqu,
char *extra)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
wrqu->power.disabled = !local->powersave;
return 0;
}
static int ieee80211_ioctl_siwauth(struct net_device *dev,
struct iw_request_info *info,
struct iw_param *data, char *extra)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
int ret = 0;
switch (data->flags & IW_AUTH_INDEX) {
case IW_AUTH_WPA_VERSION:
case IW_AUTH_CIPHER_GROUP:
case IW_AUTH_WPA_ENABLED:
case IW_AUTH_RX_UNENCRYPTED_EAPOL:
case IW_AUTH_KEY_MGMT:
case IW_AUTH_CIPHER_GROUP_MGMT:
break;
case IW_AUTH_CIPHER_PAIRWISE:
if (sdata->vif.type == NL80211_IFTYPE_STATION) {
if (data->value & (IW_AUTH_CIPHER_WEP40 |
IW_AUTH_CIPHER_WEP104 | IW_AUTH_CIPHER_TKIP))
sdata->u.mgd.flags |=
IEEE80211_STA_TKIP_WEP_USED;
else
sdata->u.mgd.flags &=
~IEEE80211_STA_TKIP_WEP_USED;
}
break;
case IW_AUTH_DROP_UNENCRYPTED:
sdata->drop_unencrypted = !!data->value;
break;
case IW_AUTH_PRIVACY_INVOKED:
if (sdata->vif.type != NL80211_IFTYPE_STATION)
ret = -EINVAL;
else {
sdata->u.mgd.flags &= ~IEEE80211_STA_PRIVACY_INVOKED;
/*
* Privacy invoked by wpa_supplicant, store the
* value and allow associating to a protected
* network without having a key up front.
*/
if (data->value)
sdata->u.mgd.flags |=
IEEE80211_STA_PRIVACY_INVOKED;
}
break;
case IW_AUTH_80211_AUTH_ALG:
if (sdata->vif.type == NL80211_IFTYPE_STATION)
sdata->u.mgd.auth_algs = data->value;
else
ret = -EOPNOTSUPP;
break;
case IW_AUTH_MFP:
if (!(sdata->local->hw.flags & IEEE80211_HW_MFP_CAPABLE)) {
ret = -EOPNOTSUPP;
break;
}
if (sdata->vif.type == NL80211_IFTYPE_STATION) {
switch (data->value) {
case IW_AUTH_MFP_DISABLED:
sdata->u.mgd.mfp = IEEE80211_MFP_DISABLED;
break;
case IW_AUTH_MFP_OPTIONAL:
sdata->u.mgd.mfp = IEEE80211_MFP_OPTIONAL;
break;
case IW_AUTH_MFP_REQUIRED:
sdata->u.mgd.mfp = IEEE80211_MFP_REQUIRED;
break;
default:
ret = -EINVAL;
}
} else
ret = -EOPNOTSUPP;
break;
default:
ret = -EOPNOTSUPP;
break;
}
return ret;
}
/* Get wireless statistics. Called by /proc/net/wireless and by SIOCGIWSTATS */
static struct iw_statistics *ieee80211_get_wireless_stats(struct net_device *dev)
{
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
struct iw_statistics *wstats = &local->wstats;
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct sta_info *sta = NULL;
rcu_read_lock();
if (sdata->vif.type == NL80211_IFTYPE_STATION)
sta = sta_info_get(local, sdata->u.mgd.bssid);
if (!sta) {
wstats->discard.fragment = 0;
wstats->discard.misc = 0;
wstats->qual.qual = 0;
wstats->qual.level = 0;
wstats->qual.noise = 0;
wstats->qual.updated = IW_QUAL_ALL_INVALID;
} else {
wstats->qual.updated = 0;
/*
* mirror what cfg80211 does for iwrange/scan results,
* otherwise userspace gets confused.
*/
if (local->hw.flags & (IEEE80211_HW_SIGNAL_UNSPEC |
IEEE80211_HW_SIGNAL_DBM)) {
wstats->qual.updated |= IW_QUAL_LEVEL_UPDATED;
wstats->qual.updated |= IW_QUAL_QUAL_UPDATED;
} else {
wstats->qual.updated |= IW_QUAL_LEVEL_INVALID;
wstats->qual.updated |= IW_QUAL_QUAL_INVALID;
}
if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC) {
wstats->qual.level = sta->last_signal;
wstats->qual.qual = sta->last_signal;
} else if (local->hw.flags & IEEE80211_HW_SIGNAL_DBM) {
int sig = sta->last_signal;
wstats->qual.updated |= IW_QUAL_DBM;
wstats->qual.level = sig;
if (sig < -110)
sig = -110;
else if (sig > -40)
sig = -40;
wstats->qual.qual = sig + 110;
}
if (local->hw.flags & IEEE80211_HW_NOISE_DBM) {
/*
* This assumes that if driver reports noise, it also
* reports signal in dBm.
*/
wstats->qual.noise = sta->last_noise;
wstats->qual.updated |= IW_QUAL_NOISE_UPDATED;
} else {
wstats->qual.updated |= IW_QUAL_NOISE_INVALID;
}
}
rcu_read_unlock();
return wstats;
}
static int ieee80211_ioctl_giwauth(struct net_device *dev,
struct iw_request_info *info,
struct iw_param *data, char *extra)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
int ret = 0;
switch (data->flags & IW_AUTH_INDEX) {
case IW_AUTH_80211_AUTH_ALG:
if (sdata->vif.type == NL80211_IFTYPE_STATION)
data->value = sdata->u.mgd.auth_algs;
else
ret = -EOPNOTSUPP;
break;
default:
ret = -EOPNOTSUPP;
break;
}
return ret;
}
static int ieee80211_ioctl_siwencodeext(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *erq, char *extra)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct iw_encode_ext *ext = (struct iw_encode_ext *) extra;
int uninitialized_var(alg), idx, i, remove = 0;
switch (ext->alg) {
case IW_ENCODE_ALG_NONE:
remove = 1;
break;
case IW_ENCODE_ALG_WEP:
alg = ALG_WEP;
break;
case IW_ENCODE_ALG_TKIP:
alg = ALG_TKIP;
break;
case IW_ENCODE_ALG_CCMP:
alg = ALG_CCMP;
break;
case IW_ENCODE_ALG_AES_CMAC:
alg = ALG_AES_CMAC;
break;
default:
return -EOPNOTSUPP;
}
if (erq->flags & IW_ENCODE_DISABLED)
remove = 1;
idx = erq->flags & IW_ENCODE_INDEX;
if (alg == ALG_AES_CMAC) {
if (idx < NUM_DEFAULT_KEYS + 1 ||
idx > NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS) {
idx = -1;
if (!sdata->default_mgmt_key)
idx = 0;
else for (i = NUM_DEFAULT_KEYS;
i < NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS;
i++) {
if (sdata->default_mgmt_key == sdata->keys[i])
{
idx = i;
break;
}
}
if (idx < 0)
return -EINVAL;
} else
idx--;
} else {
if (idx < 1 || idx > 4) {
idx = -1;
if (!sdata->default_key)
idx = 0;
else for (i = 0; i < NUM_DEFAULT_KEYS; i++) {
if (sdata->default_key == sdata->keys[i]) {
idx = i;
break;
}
}
if (idx < 0)
return -EINVAL;
} else
idx--;
}
return ieee80211_set_encryption(sdata, ext->addr.sa_data, idx, alg,
remove,
ext->ext_flags &
IW_ENCODE_EXT_SET_TX_KEY,
ext->key, ext->key_len);
}
/* Structures to export the Wireless Handlers */
static const iw_handler ieee80211_handler[] =
{
(iw_handler) NULL, /* SIOCSIWCOMMIT */
(iw_handler) cfg80211_wext_giwname, /* SIOCGIWNAME */
(iw_handler) NULL, /* SIOCSIWNWID */
(iw_handler) NULL, /* SIOCGIWNWID */
(iw_handler) ieee80211_ioctl_siwfreq, /* SIOCSIWFREQ */
(iw_handler) ieee80211_ioctl_giwfreq, /* SIOCGIWFREQ */
(iw_handler) cfg80211_wext_siwmode, /* SIOCSIWMODE */
(iw_handler) cfg80211_wext_giwmode, /* SIOCGIWMODE */
(iw_handler) NULL, /* SIOCSIWSENS */
(iw_handler) NULL, /* SIOCGIWSENS */
(iw_handler) NULL /* not used */, /* SIOCSIWRANGE */
(iw_handler) cfg80211_wext_giwrange, /* SIOCGIWRANGE */
(iw_handler) NULL /* not used */, /* SIOCSIWPRIV */
(iw_handler) NULL /* kernel code */, /* SIOCGIWPRIV */
(iw_handler) NULL /* not used */, /* SIOCSIWSTATS */
(iw_handler) NULL /* kernel code */, /* SIOCGIWSTATS */
(iw_handler) NULL, /* SIOCSIWSPY */
(iw_handler) NULL, /* SIOCGIWSPY */
(iw_handler) NULL, /* SIOCSIWTHRSPY */
(iw_handler) NULL, /* SIOCGIWTHRSPY */
(iw_handler) ieee80211_ioctl_siwap, /* SIOCSIWAP */
(iw_handler) ieee80211_ioctl_giwap, /* SIOCGIWAP */
(iw_handler) ieee80211_ioctl_siwmlme, /* SIOCSIWMLME */
(iw_handler) NULL, /* SIOCGIWAPLIST */
(iw_handler) cfg80211_wext_siwscan, /* SIOCSIWSCAN */
(iw_handler) cfg80211_wext_giwscan, /* SIOCGIWSCAN */
(iw_handler) ieee80211_ioctl_siwessid, /* SIOCSIWESSID */
(iw_handler) ieee80211_ioctl_giwessid, /* SIOCGIWESSID */
(iw_handler) NULL, /* SIOCSIWNICKN */
(iw_handler) NULL, /* SIOCGIWNICKN */
(iw_handler) NULL, /* -- hole -- */
(iw_handler) NULL, /* -- hole -- */
(iw_handler) ieee80211_ioctl_siwrate, /* SIOCSIWRATE */
(iw_handler) ieee80211_ioctl_giwrate, /* SIOCGIWRATE */
(iw_handler) ieee80211_ioctl_siwrts, /* SIOCSIWRTS */
(iw_handler) ieee80211_ioctl_giwrts, /* SIOCGIWRTS */
(iw_handler) ieee80211_ioctl_siwfrag, /* SIOCSIWFRAG */
(iw_handler) ieee80211_ioctl_giwfrag, /* SIOCGIWFRAG */
(iw_handler) ieee80211_ioctl_siwtxpower, /* SIOCSIWTXPOW */
(iw_handler) ieee80211_ioctl_giwtxpower, /* SIOCGIWTXPOW */
(iw_handler) ieee80211_ioctl_siwretry, /* SIOCSIWRETRY */
(iw_handler) ieee80211_ioctl_giwretry, /* SIOCGIWRETRY */
(iw_handler) ieee80211_ioctl_siwencode, /* SIOCSIWENCODE */
(iw_handler) ieee80211_ioctl_giwencode, /* SIOCGIWENCODE */
(iw_handler) ieee80211_ioctl_siwpower, /* SIOCSIWPOWER */
(iw_handler) ieee80211_ioctl_giwpower, /* SIOCGIWPOWER */
(iw_handler) NULL, /* -- hole -- */
(iw_handler) NULL, /* -- hole -- */
(iw_handler) ieee80211_ioctl_siwgenie, /* SIOCSIWGENIE */
(iw_handler) NULL, /* SIOCGIWGENIE */
(iw_handler) ieee80211_ioctl_siwauth, /* SIOCSIWAUTH */
(iw_handler) ieee80211_ioctl_giwauth, /* SIOCGIWAUTH */
(iw_handler) ieee80211_ioctl_siwencodeext, /* SIOCSIWENCODEEXT */
(iw_handler) NULL, /* SIOCGIWENCODEEXT */
(iw_handler) NULL, /* SIOCSIWPMKSA */
(iw_handler) NULL, /* -- hole -- */
};
const struct iw_handler_def ieee80211_iw_handler_def =
{
.num_standard = ARRAY_SIZE(ieee80211_handler),
.standard = (iw_handler *) ieee80211_handler,
.get_wireless_stats = ieee80211_get_wireless_stats,
};