summaryrefslogtreecommitdiffstats
path: root/package/mac80211/src/wireless/nl80211.c
diff options
context:
space:
mode:
Diffstat (limited to 'package/mac80211/src/wireless/nl80211.c')
-rw-r--r--package/mac80211/src/wireless/nl80211.c994
1 files changed, 994 insertions, 0 deletions
diff --git a/package/mac80211/src/wireless/nl80211.c b/package/mac80211/src/wireless/nl80211.c
new file mode 100644
index 0000000000..d6a44a386c
--- /dev/null
+++ b/package/mac80211/src/wireless/nl80211.c
@@ -0,0 +1,994 @@
+/*
+ * This is the new netlink-based wireless configuration interface.
+ *
+ * Copyright 2006 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 <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: validate an information element attribute */
+static int check_information_element(struct nlattr *nla)
+{
+ int len = nla_len(nla);
+ u8 *data = nla_data(nla);
+ int elementlen;
+
+ while (len >= 2) {
+ /* 1 byte ID, 1 byte len, `len' bytes data */
+ elementlen = *(data+1) + 2;
+ data += elementlen;
+ len -= elementlen;
+ }
+ return len ? -EINVAL : 0;
+}
+
+/* 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(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_IFINDEX] = { .type = NLA_U32 },
+ [NL80211_ATTR_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ-1 },
+ [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_BSSID] = { .len = ETH_ALEN },
+ [NL80211_ATTR_SSID] = { .type = NLA_BINARY,
+ .len = IEEE80211_MAX_SSID_LEN },
+ [NL80211_ATTR_CHANNEL] = { .type = NLA_U32 },
+ [NL80211_ATTR_PHYMODE] = { .type = NLA_U32 },
+ [NL80211_ATTR_CHANNEL_LIST] = { .type = NLA_NESTED },
+ [NL80211_ATTR_BSS_LIST] = { .type = NLA_NESTED },
+ [NL80211_ATTR_BSSTYPE] = { .type = NLA_U32 },
+ [NL80211_ATTR_BEACON_PERIOD] = { .type = NLA_U32 },
+ [NL80211_ATTR_DTIM_PERIOD] = { .type = NLA_U32 },
+ [NL80211_ATTR_TIMESTAMP] = { .type = NLA_U64 },
+ [NL80211_ATTR_IE] = { .type = NLA_BINARY, .len = NL80211_MAX_IE_LEN },
+ [NL80211_ATTR_AUTH_ALGORITHM] = { .type = NLA_U32 },
+ [NL80211_ATTR_TIMEOUT_TU] = { .type = NLA_U32 },
+ [NL80211_ATTR_REASON_CODE] = { .type = NLA_U32 },
+ [NL80211_ATTR_ASSOCIATION_ID] = { .type = NLA_U16 },
+ [NL80211_ATTR_DEAUTHENTICATED] = { .type = NLA_FLAG },
+ [NL80211_ATTR_RX_SENSITIVITY] = { .type = NLA_U32 },
+ [NL80211_ATTR_TRANSMIT_POWER] = { .type = NLA_U32 },
+ [NL80211_ATTR_FRAG_THRESHOLD] = { .type = NLA_U32 },
+ [NL80211_ATTR_FLAG_SCAN_ACTIVE] = { .type = NLA_FLAG },
+ [NL80211_ATTR_BEACON_HEAD] = { .type = NLA_BINARY },
+ [NL80211_ATTR_BEACON_TAIL] = { .type = NLA_BINARY },
+ [NL80211_ATTR_KEY_DATA] = { .type = NLA_BINARY,
+ .len = WLAN_MAX_KEY_LEN },
+ [NL80211_ATTR_KEY_ID] = { .type = NLA_U32 },
+ [NL80211_ATTR_KEY_TYPE] = { .type = NLA_U32 },
+ [NL80211_ATTR_MAC] = { .len = ETH_ALEN },
+ [NL80211_ATTR_KEY_CIPHER] = { .type = NLA_U32 },
+};
+
+/* netlink command implementations */
+
+#define CHECK_CMD(ptr, cmd) \
+ if (drv->ops->ptr) \
+ NLA_PUT_FLAG(msg, NL80211_CMD_##cmd);
+
+static int nl80211_get_cmdlist(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *drv;
+ struct sk_buff *msg;
+ void *hdr;
+ int err;
+ struct nlattr *start;
+
+ drv = cfg80211_get_dev_from_info(info);
+ if (IS_ERR(drv))
+ return PTR_ERR(drv);
+
+ hdr = nl80211msg_new(&msg, info->snd_pid, info->snd_seq, 0,
+ NL80211_CMD_NEW_CMDLIST);
+ if (IS_ERR(hdr)) {
+ err = PTR_ERR(hdr);
+ goto put_drv;
+ }
+
+ NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, drv->idx);
+
+ start = nla_nest_start(msg, NL80211_ATTR_CMDS);
+ if (!start)
+ goto nla_put_failure;
+
+ /* unconditionally allow some common commands we handle centrally
+ * or where we require the implementation */
+ NLA_PUT_FLAG(msg, NL80211_CMD_GET_CMDLIST);
+ NLA_PUT_FLAG(msg, NL80211_CMD_GET_WIPHYS);
+ NLA_PUT_FLAG(msg, NL80211_CMD_GET_INTERFACES);
+ NLA_PUT_FLAG(msg, NL80211_CMD_RENAME_WIPHY);
+
+ CHECK_CMD(add_virtual_intf, ADD_VIRTUAL_INTERFACE);
+ CHECK_CMD(del_virtual_intf, DEL_VIRTUAL_INTERFACE);
+ CHECK_CMD(associate, ASSOCIATE);
+ CHECK_CMD(disassociate, DISASSOCIATE);
+ CHECK_CMD(deauth, DEAUTH);
+ CHECK_CMD(initiate_scan, INITIATE_SCAN);
+ CHECK_CMD(get_association, GET_ASSOCIATION);
+ CHECK_CMD(get_auth_list, GET_AUTH_LIST);
+ CHECK_CMD(add_key, ADD_KEY);
+ CHECK_CMD(del_key, DEL_KEY);
+
+ nla_nest_end(msg, start);
+
+ genlmsg_end(msg, hdr);
+
+ err = genlmsg_unicast(msg, info->snd_pid);
+ goto put_drv;
+
+ nla_put_failure:
+ err = -ENOBUFS;
+ nlmsg_free(msg);
+ put_drv:
+ cfg80211_put_dev(drv);
+ return err;
+}
+#undef CHECK_CMD
+
+static int nl80211_get_wiphys(struct sk_buff *skb, struct genl_info *info)
+{
+ struct sk_buff *msg;
+ void *hdr;
+ struct nlattr *start, *indexstart;
+ struct cfg80211_registered_device *drv;
+ int idx = 1;
+
+ hdr = nl80211msg_new(&msg, info->snd_pid, info->snd_seq, 0,
+ NL80211_CMD_NEW_WIPHYS);
+ if (IS_ERR(hdr))
+ return PTR_ERR(hdr);
+
+ start = nla_nest_start(msg, NL80211_ATTR_WIPHY_LIST);
+ if (!start)
+ goto nla_outer_nest_failure;
+
+ mutex_lock(&cfg80211_drv_mutex);
+ list_for_each_entry(drv, &cfg80211_drv_list, list) {
+ indexstart = nla_nest_start(msg, idx++);
+ if (!indexstart)
+ goto nla_put_failure;
+ NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, drv->idx);
+ nla_nest_end(msg, indexstart);
+ }
+ mutex_unlock(&cfg80211_drv_mutex);
+
+ nla_nest_end(msg, start);
+
+ genlmsg_end(msg, hdr);
+
+ return genlmsg_unicast(msg, info->snd_pid);
+
+ nla_put_failure:
+ mutex_unlock(&cfg80211_drv_mutex);
+ nla_outer_nest_failure:
+ nlmsg_free(msg);
+ return -ENOBUFS;
+}
+
+static int addifidx(struct net_device *dev, struct sk_buff *skb, int *idx)
+{
+ int err = -ENOBUFS;
+ struct nlattr *start;
+
+ dev_hold(dev);
+
+ start = nla_nest_start(skb, *idx++);
+ if (!start)
+ goto nla_put_failure;
+
+ NLA_PUT_U32(skb, NL80211_ATTR_IFINDEX, dev->ifindex);
+ NLA_PUT_STRING(skb, NL80211_ATTR_IFNAME, dev->name);
+
+ nla_nest_end(skb, start);
+ err = 0;
+
+ nla_put_failure:
+ dev_put(dev);
+ return err;
+}
+
+static int nl80211_get_intfs(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *drv;
+ struct sk_buff *msg;
+ void *hdr;
+ int err, array_idx;
+ struct nlattr *start;
+ struct wireless_dev *wdev;
+
+ drv = cfg80211_get_dev_from_info(info);
+ if (IS_ERR(drv))
+ return PTR_ERR(drv);
+
+ hdr = nl80211msg_new(&msg, info->snd_pid, info->snd_seq, 0,
+ NL80211_CMD_NEW_INTERFACES);
+ if (IS_ERR(hdr)) {
+ err = PTR_ERR(hdr);
+ goto put_drv;
+ }
+
+ NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, drv->idx);
+
+ start = nla_nest_start(msg, NL80211_ATTR_INTERFACE_LIST);
+ if (!start) {
+ err = -ENOBUFS;
+ goto msg_free;
+ }
+
+ array_idx = 1;
+ err = 0;
+ mutex_lock(&drv->devlist_mtx);
+ list_for_each_entry(wdev, &drv->netdev_list, list) {
+ err = addifidx(wdev->netdev, msg, &array_idx);
+ if (err)
+ break;
+ }
+ mutex_unlock(&drv->devlist_mtx);
+ if (err)
+ goto msg_free;
+
+ nla_nest_end(msg, start);
+
+ genlmsg_end(msg, hdr);
+
+ err = genlmsg_unicast(msg, info->snd_pid);
+ goto put_drv;
+
+ nla_put_failure:
+ err = -ENOBUFS;
+ msg_free:
+ nlmsg_free(msg);
+ put_drv:
+ cfg80211_put_dev(drv);
+ return err;
+}
+
+static int nl80211_add_virt_intf(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *drv;
+ int err;
+ unsigned int 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_virt_intf(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;
+}
+
+static int nl80211_change_virt_intf(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *drv;
+ int err, ifindex;
+ unsigned int 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_get_association(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *drv;
+ int err;
+ struct net_device *dev;
+ struct sk_buff *msg;
+ void *hdr;
+ u8 bssid[ETH_ALEN];
+
+ err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+ if (err)
+ return err;
+
+ if (!drv->ops->get_association) {
+ err = -EOPNOTSUPP;
+ goto out_put_drv;
+ }
+
+ rtnl_lock();
+ err = drv->ops->get_association(&drv->wiphy, dev, bssid);
+ rtnl_unlock();
+ if (err < 0)
+ goto out_put_drv;
+
+ hdr = nl80211msg_new(&msg, info->snd_pid, info->snd_seq, 0,
+ NL80211_CMD_ASSOCIATION_CHANGED);
+
+ if (IS_ERR(hdr)) {
+ err = PTR_ERR(hdr);
+ goto out_put_drv;
+ }
+
+ NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
+ if (err == 1)
+ NLA_PUT(msg, NL80211_ATTR_BSSID, ETH_ALEN, bssid);
+
+ genlmsg_end(msg, hdr);
+ err = genlmsg_unicast(msg, info->snd_pid);
+ goto out_put_drv;
+
+ nla_put_failure:
+ err = -ENOBUFS;
+ nlmsg_free(msg);
+ out_put_drv:
+ cfg80211_put_dev(drv);
+ dev_put(dev);
+ return err;
+}
+
+static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *drv;
+ int err;
+ struct net_device *dev;
+ struct association_params assoc_params;
+
+ memset(&assoc_params, 0, sizeof(assoc_params));
+
+ err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+ if (err)
+ return err;
+
+ if (!drv->ops->associate) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ if (!info->attrs[NL80211_ATTR_SSID])
+ return -EINVAL;
+
+ assoc_params.ssid = nla_data(info->attrs[NL80211_ATTR_SSID]);
+ assoc_params.ssid_len = nla_len(info->attrs[NL80211_ATTR_SSID]);
+
+ if (info->attrs[NL80211_ATTR_BSSID])
+ assoc_params.bssid = nla_data(info->attrs[NL80211_ATTR_BSSID]);
+
+ if (info->attrs[NL80211_ATTR_IE]) {
+ err = check_information_element(info->attrs[NL80211_ATTR_IE]);
+ if (err)
+ goto out;
+ assoc_params.ie = nla_data(info->attrs[NL80211_ATTR_IE]);
+ assoc_params.ie_len = nla_len(info->attrs[NL80211_ATTR_IE]);
+ }
+
+ if (info->attrs[NL80211_ATTR_TIMEOUT_TU]) {
+ assoc_params.timeout =
+ nla_get_u32(info->attrs[NL80211_ATTR_TIMEOUT_TU]);
+ assoc_params.valid |= ASSOC_PARAMS_TIMEOUT;
+ }
+
+ rtnl_lock();
+ err = drv->ops->associate(&drv->wiphy, dev, &assoc_params);
+ rtnl_unlock();
+
+ out:
+ cfg80211_put_dev(drv);
+ dev_put(dev);
+ return err;
+}
+
+static int nl80211_disassoc_deauth(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *drv;
+ int err;
+ struct net_device *dev;
+ int (*act)(struct wiphy *wiphy, struct net_device *dev);
+
+ err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+ if (err)
+ return err;
+
+ switch (info->genlhdr->cmd) {
+ case NL80211_CMD_DISASSOCIATE:
+ act = drv->ops->disassociate;
+ break;
+ case NL80211_CMD_DEAUTH:
+ act = drv->ops->deauth;
+ break;
+ default:
+ act = NULL;
+ }
+
+ if (!act) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ rtnl_lock();
+ err = act(&drv->wiphy, dev);
+ rtnl_unlock();
+ out:
+ cfg80211_put_dev(drv);
+ dev_put(dev);
+ return err;
+}
+
+struct add_cb_data {
+ int idx;
+ struct sk_buff *skb;
+};
+
+static int add_bssid(void *data, u8 *bssid)
+{
+ struct add_cb_data *cb = data;
+ int err = -ENOBUFS;
+ struct nlattr *start;
+
+ start = nla_nest_start(cb->skb, cb->idx++);
+ if (!start)
+ goto nla_put_failure;
+
+ NLA_PUT(cb->skb, NL80211_ATTR_BSSID, ETH_ALEN, bssid);
+
+ nla_nest_end(cb->skb, start);
+ err = 0;
+
+ nla_put_failure:
+ return err;
+}
+
+static int nl80211_get_auth_list(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *drv;
+ struct net_device *dev;
+ struct sk_buff *msg;
+ void *hdr;
+ int err;
+ struct nlattr *start;
+ struct add_cb_data cb;
+
+ err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+ if (err)
+ return err;
+
+ if (!drv->ops->get_auth_list) {
+ err = -EOPNOTSUPP;
+ goto put_drv;
+ }
+
+ hdr = nl80211msg_new(&msg, info->snd_pid, info->snd_seq, 0,
+ NL80211_CMD_NEW_AUTH_LIST);
+ if (IS_ERR(hdr)) {
+ err = PTR_ERR(hdr);
+ goto put_drv;
+ }
+
+ NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
+
+ start = nla_nest_start(msg, NL80211_ATTR_BSS_LIST);
+ if (!start) {
+ err = -ENOBUFS;
+ goto msg_free;
+ }
+
+ cb.skb = msg;
+ cb.idx = 1;
+ rtnl_lock();
+ err = drv->ops->get_auth_list(&drv->wiphy, dev, &cb, add_bssid);
+ rtnl_unlock();
+ if (err)
+ goto msg_free;
+
+ nla_nest_end(msg, start);
+
+ genlmsg_end(msg, hdr);
+
+ err = genlmsg_unicast(msg, info->snd_pid);
+ goto put_drv;
+
+ nla_put_failure:
+ err = -ENOBUFS;
+ msg_free:
+ nlmsg_free(msg);
+ put_drv:
+ cfg80211_put_dev(drv);
+ dev_put(dev);
+ return err;
+}
+
+static int nl80211_initiate_scan(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *drv;
+ int err;
+ struct net_device *dev;
+ struct scan_params params;
+ struct scan_channel *channels = NULL;
+ int count = -1;
+
+ if (info->attrs[NL80211_ATTR_PHYMODE])
+ params.phymode = nla_get_u32(info->attrs[NL80211_ATTR_PHYMODE]);
+
+ if (params.phymode > NL80211_PHYMODE_MAX)
+ return -EINVAL;
+
+ err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+ if (err)
+ return err;
+
+ if (!drv->ops->initiate_scan) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ params.active = nla_get_flag(info->attrs[NL80211_ATTR_FLAG_SCAN_ACTIVE]);
+
+ if (info->attrs[NL80211_ATTR_CHANNEL_LIST]) {
+ struct nlattr *attr = info->attrs[NL80211_ATTR_CHANNEL_LIST];
+ struct nlattr *nla;
+ int rem;
+ struct nlattr **tb;
+
+ /* let's count first */
+ count = 0;
+ nla_for_each_attr(nla, nla_data(attr), nla_len(attr), rem)
+ count++;
+
+ if (count == 0) {
+ /* assume we should actually scan all channels,
+ * scanning no channels make no sense */
+ count = -1;
+ goto done_channels;
+ }
+
+ if (count > NL80211_MAX_CHANNEL_LIST_ITEM) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ channels = kmalloc(count * sizeof(struct scan_channel),
+ GFP_KERNEL);
+ tb = kmalloc((NL80211_ATTR_MAX+1) * sizeof(struct nlattr),
+ GFP_KERNEL);
+
+ count = 0;
+ nla_for_each_attr(nla, nla_data(attr), nla_len(attr), rem) {
+ err = nla_parse(tb, NL80211_ATTR_MAX, nla_data(nla),
+ nla_len(nla), nl80211_policy);
+
+ if (err || !tb[NL80211_ATTR_CHANNEL]) {
+ err = -EINVAL;
+ kfree(tb);
+ kfree(channels);
+ goto out;
+ }
+
+ channels[count].phymode = params.phymode;
+
+ if (tb[NL80211_ATTR_PHYMODE])
+ channels[count].phymode =
+ nla_get_u32(tb[NL80211_ATTR_PHYMODE]);
+
+ if (channels[count].phymode > NL80211_PHYMODE_MAX) {
+ err = -EINVAL;
+ kfree(tb);
+ kfree(channels);
+ goto out;
+ }
+
+ channels[count].channel =
+ nla_get_u32(tb[NL80211_ATTR_CHANNEL]);
+
+ channels[count].active =
+ nla_get_flag(tb[NL80211_ATTR_FLAG_SCAN_ACTIVE]);
+ count++;
+ }
+ kfree(tb);
+ }
+
+ done_channels:
+ params.channels = channels;
+ params.n_channels = count;
+
+ rtnl_lock();
+ err = drv->ops->initiate_scan(&drv->wiphy, dev, &params);
+ rtnl_unlock();
+
+ kfree(channels);
+ out:
+ cfg80211_put_dev(drv);
+ dev_put(dev);
+ return err;
+}
+
+static int nl80211_rename_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_key_cmd(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *drv;
+ int err, del;
+ struct net_device *dev;
+ struct key_params params;
+ int (*act)(struct wiphy *wiphy, struct net_device *dev,
+ struct key_params *params);
+
+ memset(&params, 0, sizeof(params));
+
+ if (!info->attrs[NL80211_ATTR_KEY_TYPE])
+ return -EINVAL;
+
+ if (!info->attrs[NL80211_ATTR_KEY_CIPHER])
+ return -EINVAL;
+
+ params.key_type = nla_get_u32(info->attrs[NL80211_ATTR_KEY_TYPE]);
+ if (params.key_type > NL80211_KEYTYPE_MAX)
+ return -EINVAL;
+
+ err = get_drv_dev_by_info_ifindex(info, &drv, &dev);
+ if (err)
+ return err;
+
+ switch (info->genlhdr->cmd) {
+ case NL80211_CMD_ADD_KEY:
+ act = drv->ops->add_key;
+ del = 0;
+ break;
+ case NL80211_CMD_DEL_KEY:
+ act = drv->ops->del_key;
+ del = 1;
+ break;
+ default:
+ act = NULL;
+ }
+
+ if (!act) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ 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_ID]) {
+ params.key_id = nla_get_u32(info->attrs[NL80211_ATTR_KEY_ID]);
+ } else {
+ params.key_id = -1;
+ }
+
+ params.cipher = nla_get_u32(info->attrs[NL80211_ATTR_KEY_CIPHER]);
+
+ if (info->attrs[NL80211_ATTR_MAC]) {
+ params.macaddress = nla_data(info->attrs[NL80211_ATTR_MAC]);
+ } else {
+ params.macaddress = NULL;
+ }
+
+ rtnl_lock();
+ err = act(&drv->wiphy, dev, &params);
+ rtnl_unlock();
+
+ out:
+ cfg80211_put_dev(drv);
+ dev_put(dev);
+ return err;
+}
+
+static struct genl_ops nl80211_ops[] = {
+ {
+ .cmd = NL80211_CMD_RENAME_WIPHY,
+ .doit = nl80211_rename_wiphy,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_GET_CMDLIST,
+ .doit = nl80211_get_cmdlist,
+ .policy = nl80211_policy,
+ /* can be retrieved by unprivileged users */
+ },
+ {
+ .cmd = NL80211_CMD_ADD_VIRTUAL_INTERFACE,
+ .doit = nl80211_add_virt_intf,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_DEL_VIRTUAL_INTERFACE,
+ .doit = nl80211_del_virt_intf,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_CHANGE_VIRTUAL_INTERFACE,
+ .doit = nl80211_change_virt_intf,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_GET_WIPHYS,
+ .doit = nl80211_get_wiphys,
+ .policy = nl80211_policy,
+ /* can be retrieved by unprivileged users */
+ },
+ {
+ .cmd = NL80211_CMD_GET_INTERFACES,
+ .doit = nl80211_get_intfs,
+ .policy = nl80211_policy,
+ /* can be retrieved by unprivileged users */
+ },
+ {
+ .cmd = NL80211_CMD_INITIATE_SCAN,
+ .doit = nl80211_initiate_scan,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_GET_ASSOCIATION,
+ .doit = nl80211_get_association,
+ .policy = nl80211_policy,
+ /* can be retrieved by unprivileged users */
+ },
+ {
+ .cmd = NL80211_CMD_ASSOCIATE,
+ .doit = nl80211_associate,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_DISASSOCIATE,
+ .doit = nl80211_disassoc_deauth,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_DEAUTH,
+ .doit = nl80211_disassoc_deauth,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_GET_AUTH_LIST,
+ .doit = nl80211_get_auth_list,
+ .policy = nl80211_policy,
+ /* can be retrieved by unprivileged users */
+ },
+/*
+ {
+ .cmd = NL80211_CMD_AP_SET_BEACON,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_AP_ADD_STA,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_AP_UPDATE_STA,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_AP_GET_STA_INFO,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_AP_SET_RATESETS,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+*/
+ {
+ .cmd = NL80211_CMD_ADD_KEY,
+ .doit = nl80211_key_cmd,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_DEL_KEY,
+ .doit = nl80211_key_cmd,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+};
+
+
+/* exported functions */
+
+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);
+}
+EXPORT_SYMBOL_GPL(nl80211hdr_put);
+
+void *nl80211msg_new(struct sk_buff **skb, u32 pid, u32 seq, int flags, u8 cmd)
+{
+ void *hdr;
+
+ *skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!*skb)
+ return ERR_PTR(-ENOBUFS);
+
+ hdr = nl80211hdr_put(*skb, pid, seq, flags, cmd);
+ if (!hdr) {
+ nlmsg_free(*skb);
+ return ERR_PTR(-ENOBUFS);
+ }
+
+ return hdr;
+}
+EXPORT_SYMBOL_GPL(nl80211msg_new);
+
+/* notification functions */
+
+void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev)
+{
+ struct sk_buff *msg;
+ void *hdr;
+
+ hdr = nl80211msg_new(&msg, 0, 0, 0, NL80211_CMD_WIPHY_NEWNAME);
+ if (IS_ERR(hdr))
+ return;
+
+ NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->idx);
+ NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&rdev->wiphy));
+
+ genlmsg_end(msg, hdr);
+ genlmsg_multicast(msg, 0, NL80211_GROUP_CONFIG, GFP_KERNEL);
+
+ return;
+
+ nla_put_failure:
+ nlmsg_free(msg);
+}
+
+/* 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;
+ }
+ return 0;
+ err_out:
+ genl_unregister_family(&nl80211_fam);
+ return err;
+}
+
+void nl80211_exit(void)
+{
+ genl_unregister_family(&nl80211_fam);
+}