mirror of
https://github.com/torvalds/linux.git
synced 2024-12-21 10:31:54 +00:00
fd5b74dcb8
After a station is added to the kernel's structures, userspace has to be able to retrieve statistics about that station, especially whether the station was idle and how much bytes were transferred to and from it. This adds the necessary code to nl80211. Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: John W. Linville <linville@tuxdriver.com> Signed-off-by: David S. Miller <davem@davemloft.net>
1169 lines
26 KiB
C
1169 lines
26 KiB
C
/*
|
|
* This is the new netlink-based wireless configuration interface.
|
|
*
|
|
* Copyright 2006, 2007 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/if_ether.h>
|
|
#include <linux/ieee80211.h>
|
|
#include <linux/nl80211.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/netlink.h>
|
|
#include <net/genetlink.h>
|
|
#include <net/cfg80211.h>
|
|
#include "core.h"
|
|
#include "nl80211.h"
|
|
|
|
/* the netlink family */
|
|
static struct genl_family nl80211_fam = {
|
|
.id = GENL_ID_GENERATE, /* don't bother with a hardcoded ID */
|
|
.name = "nl80211", /* have users key off the name instead */
|
|
.hdrsize = 0, /* no private header */
|
|
.version = 1, /* no particular meaning now */
|
|
.maxattr = NL80211_ATTR_MAX,
|
|
};
|
|
|
|
/* internal helper: get drv and dev */
|
|
static int get_drv_dev_by_info_ifindex(struct genl_info *info,
|
|
struct cfg80211_registered_device **drv,
|
|
struct net_device **dev)
|
|
{
|
|
int ifindex;
|
|
|
|
if (!info->attrs[NL80211_ATTR_IFINDEX])
|
|
return -EINVAL;
|
|
|
|
ifindex = nla_get_u32(info->attrs[NL80211_ATTR_IFINDEX]);
|
|
*dev = dev_get_by_index(&init_net, ifindex);
|
|
if (!*dev)
|
|
return -ENODEV;
|
|
|
|
*drv = cfg80211_get_dev_from_ifindex(ifindex);
|
|
if (IS_ERR(*drv)) {
|
|
dev_put(*dev);
|
|
return PTR_ERR(*drv);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* policy for the attributes */
|
|
static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
|
|
[NL80211_ATTR_WIPHY] = { .type = NLA_U32 },
|
|
[NL80211_ATTR_WIPHY_NAME] = { .type = NLA_NUL_STRING,
|
|
.len = BUS_ID_SIZE-1 },
|
|
|
|
[NL80211_ATTR_IFTYPE] = { .type = NLA_U32 },
|
|
[NL80211_ATTR_IFINDEX] = { .type = NLA_U32 },
|
|
[NL80211_ATTR_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ-1 },
|
|
|
|
[NL80211_ATTR_MAC] = { .type = NLA_BINARY, .len = ETH_ALEN },
|
|
|
|
[NL80211_ATTR_KEY_DATA] = { .type = NLA_BINARY,
|
|
.len = WLAN_MAX_KEY_LEN },
|
|
[NL80211_ATTR_KEY_IDX] = { .type = NLA_U8 },
|
|
[NL80211_ATTR_KEY_CIPHER] = { .type = NLA_U32 },
|
|
[NL80211_ATTR_KEY_DEFAULT] = { .type = NLA_FLAG },
|
|
|
|
[NL80211_ATTR_BEACON_INTERVAL] = { .type = NLA_U32 },
|
|
[NL80211_ATTR_DTIM_PERIOD] = { .type = NLA_U32 },
|
|
[NL80211_ATTR_BEACON_HEAD] = { .type = NLA_BINARY,
|
|
.len = IEEE80211_MAX_DATA_LEN },
|
|
[NL80211_ATTR_BEACON_TAIL] = { .type = NLA_BINARY,
|
|
.len = IEEE80211_MAX_DATA_LEN },
|
|
[NL80211_ATTR_STA_AID] = { .type = NLA_U16 },
|
|
[NL80211_ATTR_STA_FLAGS] = { .type = NLA_NESTED },
|
|
[NL80211_ATTR_STA_LISTEN_INTERVAL] = { .type = NLA_U16 },
|
|
[NL80211_ATTR_STA_SUPPORTED_RATES] = { .type = NLA_BINARY,
|
|
.len = NL80211_MAX_SUPP_RATES },
|
|
[NL80211_ATTR_STA_VLAN] = { .type = NLA_U32 },
|
|
};
|
|
|
|
/* message building helper */
|
|
static inline void *nl80211hdr_put(struct sk_buff *skb, u32 pid, u32 seq,
|
|
int flags, u8 cmd)
|
|
{
|
|
/* since there is no private header just add the generic one */
|
|
return genlmsg_put(skb, pid, seq, &nl80211_fam, flags, cmd);
|
|
}
|
|
|
|
/* netlink command implementations */
|
|
|
|
static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
|
|
struct cfg80211_registered_device *dev)
|
|
{
|
|
void *hdr;
|
|
|
|
hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_WIPHY);
|
|
if (!hdr)
|
|
return -1;
|
|
|
|
NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, dev->idx);
|
|
NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&dev->wiphy));
|
|
return genlmsg_end(msg, hdr);
|
|
|
|
nla_put_failure:
|
|
return genlmsg_cancel(msg, hdr);
|
|
}
|
|
|
|
static int nl80211_dump_wiphy(struct sk_buff *skb, struct netlink_callback *cb)
|
|
{
|
|
int idx = 0;
|
|
int start = cb->args[0];
|
|
struct cfg80211_registered_device *dev;
|
|
|
|
mutex_lock(&cfg80211_drv_mutex);
|
|
list_for_each_entry(dev, &cfg80211_drv_list, list) {
|
|
if (++idx < start)
|
|
continue;
|
|
if (nl80211_send_wiphy(skb, NETLINK_CB(cb->skb).pid,
|
|
cb->nlh->nlmsg_seq, NLM_F_MULTI,
|
|
dev) < 0)
|
|
break;
|
|
}
|
|
mutex_unlock(&cfg80211_drv_mutex);
|
|
|
|
cb->args[0] = idx;
|
|
|
|
return skb->len;
|
|
}
|
|
|
|
static int nl80211_get_wiphy(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct sk_buff *msg;
|
|
struct cfg80211_registered_device *dev;
|
|
|
|
dev = cfg80211_get_dev_from_info(info);
|
|
if (IS_ERR(dev))
|
|
return PTR_ERR(dev);
|
|
|
|
msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
|
|
if (!msg)
|
|
goto out_err;
|
|
|
|
if (nl80211_send_wiphy(msg, info->snd_pid, info->snd_seq, 0, dev) < 0)
|
|
goto out_free;
|
|
|
|
cfg80211_put_dev(dev);
|
|
|
|
return genlmsg_unicast(msg, info->snd_pid);
|
|
|
|
out_free:
|
|
nlmsg_free(msg);
|
|
out_err:
|
|
cfg80211_put_dev(dev);
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *rdev;
|
|
int result;
|
|
|
|
if (!info->attrs[NL80211_ATTR_WIPHY_NAME])
|
|
return -EINVAL;
|
|
|
|
rdev = cfg80211_get_dev_from_info(info);
|
|
if (IS_ERR(rdev))
|
|
return PTR_ERR(rdev);
|
|
|
|
result = cfg80211_dev_rename(rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME]));
|
|
|
|
cfg80211_put_dev(rdev);
|
|
return result;
|
|
}
|
|
|
|
|
|
static int nl80211_send_iface(struct sk_buff *msg, u32 pid, u32 seq, int flags,
|
|
struct net_device *dev)
|
|
{
|
|
void *hdr;
|
|
|
|
hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_INTERFACE);
|
|
if (!hdr)
|
|
return -1;
|
|
|
|
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
|
|
NLA_PUT_STRING(msg, NL80211_ATTR_IFNAME, dev->name);
|
|
/* TODO: interface type */
|
|
return genlmsg_end(msg, hdr);
|
|
|
|
nla_put_failure:
|
|
return genlmsg_cancel(msg, hdr);
|
|
}
|
|
|
|
static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *cb)
|
|
{
|
|
int wp_idx = 0;
|
|
int if_idx = 0;
|
|
int wp_start = cb->args[0];
|
|
int if_start = cb->args[1];
|
|
struct cfg80211_registered_device *dev;
|
|
struct wireless_dev *wdev;
|
|
|
|
mutex_lock(&cfg80211_drv_mutex);
|
|
list_for_each_entry(dev, &cfg80211_drv_list, list) {
|
|
if (++wp_idx < wp_start)
|
|
continue;
|
|
if_idx = 0;
|
|
|
|
mutex_lock(&dev->devlist_mtx);
|
|
list_for_each_entry(wdev, &dev->netdev_list, list) {
|
|
if (++if_idx < if_start)
|
|
continue;
|
|
if (nl80211_send_iface(skb, NETLINK_CB(cb->skb).pid,
|
|
cb->nlh->nlmsg_seq, NLM_F_MULTI,
|
|
wdev->netdev) < 0)
|
|
break;
|
|
}
|
|
mutex_unlock(&dev->devlist_mtx);
|
|
}
|
|
mutex_unlock(&cfg80211_drv_mutex);
|
|
|
|
cb->args[0] = wp_idx;
|
|
cb->args[1] = if_idx;
|
|
|
|
return skb->len;
|
|
}
|
|
|
|
static int nl80211_get_interface(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct sk_buff *msg;
|
|
struct cfg80211_registered_device *dev;
|
|
struct net_device *netdev;
|
|
int err;
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &dev, &netdev);
|
|
if (err)
|
|
return err;
|
|
|
|
msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
|
|
if (!msg)
|
|
goto out_err;
|
|
|
|
if (nl80211_send_iface(msg, info->snd_pid, info->snd_seq, 0, netdev) < 0)
|
|
goto out_free;
|
|
|
|
dev_put(netdev);
|
|
cfg80211_put_dev(dev);
|
|
|
|
return genlmsg_unicast(msg, info->snd_pid);
|
|
|
|
out_free:
|
|
nlmsg_free(msg);
|
|
out_err:
|
|
dev_put(netdev);
|
|
cfg80211_put_dev(dev);
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int err, ifindex;
|
|
enum nl80211_iftype type;
|
|
struct net_device *dev;
|
|
|
|
if (info->attrs[NL80211_ATTR_IFTYPE]) {
|
|
type = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]);
|
|
if (type > NL80211_IFTYPE_MAX)
|
|
return -EINVAL;
|
|
} else
|
|
return -EINVAL;
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
ifindex = dev->ifindex;
|
|
dev_put(dev);
|
|
|
|
if (!drv->ops->change_virtual_intf) {
|
|
err = -EOPNOTSUPP;
|
|
goto unlock;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->change_virtual_intf(&drv->wiphy, ifindex, type);
|
|
rtnl_unlock();
|
|
|
|
unlock:
|
|
cfg80211_put_dev(drv);
|
|
return err;
|
|
}
|
|
|
|
static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int err;
|
|
enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED;
|
|
|
|
if (!info->attrs[NL80211_ATTR_IFNAME])
|
|
return -EINVAL;
|
|
|
|
if (info->attrs[NL80211_ATTR_IFTYPE]) {
|
|
type = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]);
|
|
if (type > NL80211_IFTYPE_MAX)
|
|
return -EINVAL;
|
|
}
|
|
|
|
drv = cfg80211_get_dev_from_info(info);
|
|
if (IS_ERR(drv))
|
|
return PTR_ERR(drv);
|
|
|
|
if (!drv->ops->add_virtual_intf) {
|
|
err = -EOPNOTSUPP;
|
|
goto unlock;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->add_virtual_intf(&drv->wiphy,
|
|
nla_data(info->attrs[NL80211_ATTR_IFNAME]), type);
|
|
rtnl_unlock();
|
|
|
|
unlock:
|
|
cfg80211_put_dev(drv);
|
|
return err;
|
|
}
|
|
|
|
static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int ifindex, err;
|
|
struct net_device *dev;
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
ifindex = dev->ifindex;
|
|
dev_put(dev);
|
|
|
|
if (!drv->ops->del_virtual_intf) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->del_virtual_intf(&drv->wiphy, ifindex);
|
|
rtnl_unlock();
|
|
|
|
out:
|
|
cfg80211_put_dev(drv);
|
|
return err;
|
|
}
|
|
|
|
struct get_key_cookie {
|
|
struct sk_buff *msg;
|
|
int error;
|
|
};
|
|
|
|
static void get_key_callback(void *c, struct key_params *params)
|
|
{
|
|
struct get_key_cookie *cookie = c;
|
|
|
|
if (params->key)
|
|
NLA_PUT(cookie->msg, NL80211_ATTR_KEY_DATA,
|
|
params->key_len, params->key);
|
|
|
|
if (params->seq)
|
|
NLA_PUT(cookie->msg, NL80211_ATTR_KEY_SEQ,
|
|
params->seq_len, params->seq);
|
|
|
|
if (params->cipher)
|
|
NLA_PUT_U32(cookie->msg, NL80211_ATTR_KEY_CIPHER,
|
|
params->cipher);
|
|
|
|
return;
|
|
nla_put_failure:
|
|
cookie->error = 1;
|
|
}
|
|
|
|
static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int err;
|
|
struct net_device *dev;
|
|
u8 key_idx = 0;
|
|
u8 *mac_addr = NULL;
|
|
struct get_key_cookie cookie = {
|
|
.error = 0,
|
|
};
|
|
void *hdr;
|
|
struct sk_buff *msg;
|
|
|
|
if (info->attrs[NL80211_ATTR_KEY_IDX])
|
|
key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);
|
|
|
|
if (key_idx > 3)
|
|
return -EINVAL;
|
|
|
|
if (info->attrs[NL80211_ATTR_MAC])
|
|
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!drv->ops->get_key) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
|
|
if (!msg) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
hdr = nl80211hdr_put(msg, info->snd_pid, info->snd_seq, 0,
|
|
NL80211_CMD_NEW_KEY);
|
|
|
|
if (IS_ERR(hdr)) {
|
|
err = PTR_ERR(hdr);
|
|
goto out;
|
|
}
|
|
|
|
cookie.msg = msg;
|
|
|
|
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
|
|
NLA_PUT_U8(msg, NL80211_ATTR_KEY_IDX, key_idx);
|
|
if (mac_addr)
|
|
NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr);
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->get_key(&drv->wiphy, dev, key_idx, mac_addr,
|
|
&cookie, get_key_callback);
|
|
rtnl_unlock();
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
if (cookie.error)
|
|
goto nla_put_failure;
|
|
|
|
genlmsg_end(msg, hdr);
|
|
err = genlmsg_unicast(msg, info->snd_pid);
|
|
goto out;
|
|
|
|
nla_put_failure:
|
|
err = -ENOBUFS;
|
|
nlmsg_free(msg);
|
|
out:
|
|
cfg80211_put_dev(drv);
|
|
dev_put(dev);
|
|
return err;
|
|
}
|
|
|
|
static int nl80211_set_key(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int err;
|
|
struct net_device *dev;
|
|
u8 key_idx;
|
|
|
|
if (!info->attrs[NL80211_ATTR_KEY_IDX])
|
|
return -EINVAL;
|
|
|
|
key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);
|
|
|
|
if (key_idx > 3)
|
|
return -EINVAL;
|
|
|
|
/* currently only support setting default key */
|
|
if (!info->attrs[NL80211_ATTR_KEY_DEFAULT])
|
|
return -EINVAL;
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!drv->ops->set_default_key) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->set_default_key(&drv->wiphy, dev, key_idx);
|
|
rtnl_unlock();
|
|
|
|
out:
|
|
cfg80211_put_dev(drv);
|
|
dev_put(dev);
|
|
return err;
|
|
}
|
|
|
|
static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int err;
|
|
struct net_device *dev;
|
|
struct key_params params;
|
|
u8 key_idx = 0;
|
|
u8 *mac_addr = NULL;
|
|
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
if (!info->attrs[NL80211_ATTR_KEY_CIPHER])
|
|
return -EINVAL;
|
|
|
|
if (info->attrs[NL80211_ATTR_KEY_DATA]) {
|
|
params.key = nla_data(info->attrs[NL80211_ATTR_KEY_DATA]);
|
|
params.key_len = nla_len(info->attrs[NL80211_ATTR_KEY_DATA]);
|
|
}
|
|
|
|
if (info->attrs[NL80211_ATTR_KEY_IDX])
|
|
key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);
|
|
|
|
params.cipher = nla_get_u32(info->attrs[NL80211_ATTR_KEY_CIPHER]);
|
|
|
|
if (info->attrs[NL80211_ATTR_MAC])
|
|
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
|
|
|
|
if (key_idx > 3)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Disallow pairwise keys with non-zero index unless it's WEP
|
|
* (because current deployments use pairwise WEP keys with
|
|
* non-zero indizes but 802.11i clearly specifies to use zero)
|
|
*/
|
|
if (mac_addr && key_idx &&
|
|
params.cipher != WLAN_CIPHER_SUITE_WEP40 &&
|
|
params.cipher != WLAN_CIPHER_SUITE_WEP104)
|
|
return -EINVAL;
|
|
|
|
/* TODO: add definitions for the lengths to linux/ieee80211.h */
|
|
switch (params.cipher) {
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
if (params.key_len != 5)
|
|
return -EINVAL;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
if (params.key_len != 32)
|
|
return -EINVAL;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
if (params.key_len != 16)
|
|
return -EINVAL;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
if (params.key_len != 13)
|
|
return -EINVAL;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!drv->ops->add_key) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->add_key(&drv->wiphy, dev, key_idx, mac_addr, ¶ms);
|
|
rtnl_unlock();
|
|
|
|
out:
|
|
cfg80211_put_dev(drv);
|
|
dev_put(dev);
|
|
return err;
|
|
}
|
|
|
|
static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int err;
|
|
struct net_device *dev;
|
|
u8 key_idx = 0;
|
|
u8 *mac_addr = NULL;
|
|
|
|
if (info->attrs[NL80211_ATTR_KEY_IDX])
|
|
key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);
|
|
|
|
if (key_idx > 3)
|
|
return -EINVAL;
|
|
|
|
if (info->attrs[NL80211_ATTR_MAC])
|
|
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!drv->ops->del_key) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->del_key(&drv->wiphy, dev, key_idx, mac_addr);
|
|
rtnl_unlock();
|
|
|
|
out:
|
|
cfg80211_put_dev(drv);
|
|
dev_put(dev);
|
|
return err;
|
|
}
|
|
|
|
static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
int (*call)(struct wiphy *wiphy, struct net_device *dev,
|
|
struct beacon_parameters *info);
|
|
struct cfg80211_registered_device *drv;
|
|
int err;
|
|
struct net_device *dev;
|
|
struct beacon_parameters params;
|
|
int haveinfo = 0;
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
|
|
switch (info->genlhdr->cmd) {
|
|
case NL80211_CMD_NEW_BEACON:
|
|
/* these are required for NEW_BEACON */
|
|
if (!info->attrs[NL80211_ATTR_BEACON_INTERVAL] ||
|
|
!info->attrs[NL80211_ATTR_DTIM_PERIOD] ||
|
|
!info->attrs[NL80211_ATTR_BEACON_HEAD]) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
call = drv->ops->add_beacon;
|
|
break;
|
|
case NL80211_CMD_SET_BEACON:
|
|
call = drv->ops->set_beacon;
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
if (!call) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
if (info->attrs[NL80211_ATTR_BEACON_INTERVAL]) {
|
|
params.interval =
|
|
nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]);
|
|
haveinfo = 1;
|
|
}
|
|
|
|
if (info->attrs[NL80211_ATTR_DTIM_PERIOD]) {
|
|
params.dtim_period =
|
|
nla_get_u32(info->attrs[NL80211_ATTR_DTIM_PERIOD]);
|
|
haveinfo = 1;
|
|
}
|
|
|
|
if (info->attrs[NL80211_ATTR_BEACON_HEAD]) {
|
|
params.head = nla_data(info->attrs[NL80211_ATTR_BEACON_HEAD]);
|
|
params.head_len =
|
|
nla_len(info->attrs[NL80211_ATTR_BEACON_HEAD]);
|
|
haveinfo = 1;
|
|
}
|
|
|
|
if (info->attrs[NL80211_ATTR_BEACON_TAIL]) {
|
|
params.tail = nla_data(info->attrs[NL80211_ATTR_BEACON_TAIL]);
|
|
params.tail_len =
|
|
nla_len(info->attrs[NL80211_ATTR_BEACON_TAIL]);
|
|
haveinfo = 1;
|
|
}
|
|
|
|
if (!haveinfo) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = call(&drv->wiphy, dev, ¶ms);
|
|
rtnl_unlock();
|
|
|
|
out:
|
|
cfg80211_put_dev(drv);
|
|
dev_put(dev);
|
|
return err;
|
|
}
|
|
|
|
static int nl80211_del_beacon(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int err;
|
|
struct net_device *dev;
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!drv->ops->del_beacon) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->del_beacon(&drv->wiphy, dev);
|
|
rtnl_unlock();
|
|
|
|
out:
|
|
cfg80211_put_dev(drv);
|
|
dev_put(dev);
|
|
return err;
|
|
}
|
|
|
|
static const struct nla_policy sta_flags_policy[NL80211_STA_FLAG_MAX + 1] = {
|
|
[NL80211_STA_FLAG_AUTHORIZED] = { .type = NLA_FLAG },
|
|
[NL80211_STA_FLAG_SHORT_PREAMBLE] = { .type = NLA_FLAG },
|
|
[NL80211_STA_FLAG_WME] = { .type = NLA_FLAG },
|
|
};
|
|
|
|
static int parse_station_flags(struct nlattr *nla, u32 *staflags)
|
|
{
|
|
struct nlattr *flags[NL80211_STA_FLAG_MAX + 1];
|
|
int flag;
|
|
|
|
*staflags = 0;
|
|
|
|
if (!nla)
|
|
return 0;
|
|
|
|
if (nla_parse_nested(flags, NL80211_STA_FLAG_MAX,
|
|
nla, sta_flags_policy))
|
|
return -EINVAL;
|
|
|
|
*staflags = STATION_FLAG_CHANGED;
|
|
|
|
for (flag = 1; flag <= NL80211_STA_FLAG_MAX; flag++)
|
|
if (flags[flag])
|
|
*staflags |= (1<<flag);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nl80211_send_station(struct sk_buff *msg, u32 pid, u32 seq,
|
|
int flags, struct net_device *dev,
|
|
u8 *mac_addr, struct station_stats *stats)
|
|
{
|
|
void *hdr;
|
|
struct nlattr *statsattr;
|
|
|
|
hdr = nl80211hdr_put(msg, pid, seq, flags, NL80211_CMD_NEW_STATION);
|
|
if (!hdr)
|
|
return -1;
|
|
|
|
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
|
|
NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr);
|
|
|
|
statsattr = nla_nest_start(msg, NL80211_ATTR_STA_STATS);
|
|
if (!statsattr)
|
|
goto nla_put_failure;
|
|
if (stats->filled & STATION_STAT_INACTIVE_TIME)
|
|
NLA_PUT_U32(msg, NL80211_STA_STAT_INACTIVE_TIME,
|
|
stats->inactive_time);
|
|
if (stats->filled & STATION_STAT_RX_BYTES)
|
|
NLA_PUT_U32(msg, NL80211_STA_STAT_RX_BYTES,
|
|
stats->rx_bytes);
|
|
if (stats->filled & STATION_STAT_TX_BYTES)
|
|
NLA_PUT_U32(msg, NL80211_STA_STAT_TX_BYTES,
|
|
stats->tx_bytes);
|
|
|
|
nla_nest_end(msg, statsattr);
|
|
|
|
return genlmsg_end(msg, hdr);
|
|
|
|
nla_put_failure:
|
|
return genlmsg_cancel(msg, hdr);
|
|
}
|
|
|
|
|
|
static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int err;
|
|
struct net_device *dev;
|
|
struct station_stats stats;
|
|
struct sk_buff *msg;
|
|
u8 *mac_addr = NULL;
|
|
|
|
memset(&stats, 0, sizeof(stats));
|
|
|
|
if (!info->attrs[NL80211_ATTR_MAC])
|
|
return -EINVAL;
|
|
|
|
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!drv->ops->get_station) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->get_station(&drv->wiphy, dev, mac_addr, &stats);
|
|
rtnl_unlock();
|
|
|
|
msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
|
|
if (!msg)
|
|
goto out;
|
|
|
|
if (nl80211_send_station(msg, info->snd_pid, info->snd_seq, 0,
|
|
dev, mac_addr, &stats) < 0)
|
|
goto out_free;
|
|
|
|
err = genlmsg_unicast(msg, info->snd_pid);
|
|
goto out;
|
|
|
|
out_free:
|
|
nlmsg_free(msg);
|
|
|
|
out:
|
|
cfg80211_put_dev(drv);
|
|
dev_put(dev);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Get vlan interface making sure it is on the right wiphy.
|
|
*/
|
|
static int get_vlan(struct nlattr *vlanattr,
|
|
struct cfg80211_registered_device *rdev,
|
|
struct net_device **vlan)
|
|
{
|
|
*vlan = NULL;
|
|
|
|
if (vlanattr) {
|
|
*vlan = dev_get_by_index(&init_net, nla_get_u32(vlanattr));
|
|
if (!*vlan)
|
|
return -ENODEV;
|
|
if (!(*vlan)->ieee80211_ptr)
|
|
return -EINVAL;
|
|
if ((*vlan)->ieee80211_ptr->wiphy != &rdev->wiphy)
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int err;
|
|
struct net_device *dev;
|
|
struct station_parameters params;
|
|
u8 *mac_addr = NULL;
|
|
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
params.listen_interval = -1;
|
|
|
|
if (info->attrs[NL80211_ATTR_STA_AID])
|
|
return -EINVAL;
|
|
|
|
if (!info->attrs[NL80211_ATTR_MAC])
|
|
return -EINVAL;
|
|
|
|
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
|
|
|
|
if (info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]) {
|
|
params.supported_rates =
|
|
nla_data(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
|
|
params.supported_rates_len =
|
|
nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
|
|
}
|
|
|
|
if (info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
|
|
params.listen_interval =
|
|
nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
|
|
|
|
if (parse_station_flags(info->attrs[NL80211_ATTR_STA_FLAGS],
|
|
¶ms.station_flags))
|
|
return -EINVAL;
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
|
|
err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, ¶ms.vlan);
|
|
if (err)
|
|
goto out;
|
|
|
|
if (!drv->ops->change_station) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->change_station(&drv->wiphy, dev, mac_addr, ¶ms);
|
|
rtnl_unlock();
|
|
|
|
out:
|
|
if (params.vlan)
|
|
dev_put(params.vlan);
|
|
cfg80211_put_dev(drv);
|
|
dev_put(dev);
|
|
return err;
|
|
}
|
|
|
|
static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int err;
|
|
struct net_device *dev;
|
|
struct station_parameters params;
|
|
u8 *mac_addr = NULL;
|
|
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
if (!info->attrs[NL80211_ATTR_MAC])
|
|
return -EINVAL;
|
|
|
|
if (!info->attrs[NL80211_ATTR_STA_AID])
|
|
return -EINVAL;
|
|
|
|
if (!info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
|
|
return -EINVAL;
|
|
|
|
if (!info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES])
|
|
return -EINVAL;
|
|
|
|
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
|
|
params.supported_rates =
|
|
nla_data(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
|
|
params.supported_rates_len =
|
|
nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
|
|
params.listen_interval =
|
|
nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
|
|
params.listen_interval = nla_get_u16(info->attrs[NL80211_ATTR_STA_AID]);
|
|
|
|
if (parse_station_flags(info->attrs[NL80211_ATTR_STA_FLAGS],
|
|
¶ms.station_flags))
|
|
return -EINVAL;
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
|
|
err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, ¶ms.vlan);
|
|
if (err)
|
|
goto out;
|
|
|
|
if (!drv->ops->add_station) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->add_station(&drv->wiphy, dev, mac_addr, ¶ms);
|
|
rtnl_unlock();
|
|
|
|
out:
|
|
if (params.vlan)
|
|
dev_put(params.vlan);
|
|
cfg80211_put_dev(drv);
|
|
dev_put(dev);
|
|
return err;
|
|
}
|
|
|
|
static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct cfg80211_registered_device *drv;
|
|
int err;
|
|
struct net_device *dev;
|
|
u8 *mac_addr = NULL;
|
|
|
|
if (info->attrs[NL80211_ATTR_MAC])
|
|
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
|
|
|
|
err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!drv->ops->del_station) {
|
|
err = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
rtnl_lock();
|
|
err = drv->ops->del_station(&drv->wiphy, dev, mac_addr);
|
|
rtnl_unlock();
|
|
|
|
out:
|
|
cfg80211_put_dev(drv);
|
|
dev_put(dev);
|
|
return err;
|
|
}
|
|
|
|
static struct genl_ops nl80211_ops[] = {
|
|
{
|
|
.cmd = NL80211_CMD_GET_WIPHY,
|
|
.doit = nl80211_get_wiphy,
|
|
.dumpit = nl80211_dump_wiphy,
|
|
.policy = nl80211_policy,
|
|
/* can be retrieved by unprivileged users */
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_SET_WIPHY,
|
|
.doit = nl80211_set_wiphy,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_GET_INTERFACE,
|
|
.doit = nl80211_get_interface,
|
|
.dumpit = nl80211_dump_interface,
|
|
.policy = nl80211_policy,
|
|
/* can be retrieved by unprivileged users */
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_SET_INTERFACE,
|
|
.doit = nl80211_set_interface,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_NEW_INTERFACE,
|
|
.doit = nl80211_new_interface,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_DEL_INTERFACE,
|
|
.doit = nl80211_del_interface,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_GET_KEY,
|
|
.doit = nl80211_get_key,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_SET_KEY,
|
|
.doit = nl80211_set_key,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_NEW_KEY,
|
|
.doit = nl80211_new_key,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_DEL_KEY,
|
|
.doit = nl80211_del_key,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_SET_BEACON,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
.doit = nl80211_addset_beacon,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_NEW_BEACON,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
.doit = nl80211_addset_beacon,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_DEL_BEACON,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
.doit = nl80211_del_beacon,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_GET_STATION,
|
|
.doit = nl80211_get_station,
|
|
/* TODO: implement dumpit */
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_SET_STATION,
|
|
.doit = nl80211_set_station,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_NEW_STATION,
|
|
.doit = nl80211_new_station,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NL80211_CMD_DEL_STATION,
|
|
.doit = nl80211_del_station,
|
|
.policy = nl80211_policy,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
};
|
|
|
|
/* multicast groups */
|
|
static struct genl_multicast_group nl80211_config_mcgrp = {
|
|
.name = "config",
|
|
};
|
|
|
|
/* notification functions */
|
|
|
|
void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev)
|
|
{
|
|
struct sk_buff *msg;
|
|
|
|
msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
|
|
if (!msg)
|
|
return;
|
|
|
|
if (nl80211_send_wiphy(msg, 0, 0, 0, rdev) < 0) {
|
|
nlmsg_free(msg);
|
|
return;
|
|
}
|
|
|
|
genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL);
|
|
}
|
|
|
|
/* initialisation/exit functions */
|
|
|
|
int nl80211_init(void)
|
|
{
|
|
int err, i;
|
|
|
|
err = genl_register_family(&nl80211_fam);
|
|
if (err)
|
|
return err;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(nl80211_ops); i++) {
|
|
err = genl_register_ops(&nl80211_fam, &nl80211_ops[i]);
|
|
if (err)
|
|
goto err_out;
|
|
}
|
|
|
|
err = genl_register_mc_group(&nl80211_fam, &nl80211_config_mcgrp);
|
|
if (err)
|
|
goto err_out;
|
|
|
|
return 0;
|
|
err_out:
|
|
genl_unregister_family(&nl80211_fam);
|
|
return err;
|
|
}
|
|
|
|
void nl80211_exit(void)
|
|
{
|
|
genl_unregister_family(&nl80211_fam);
|
|
}
|