| /* |
| * 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.aid = 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); |
| } |