| /* | |
| * 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_PLINK_ACTION] = { .type = NLA_U8 }, | |
| [NL80211_ATTR_STA_VLAN] = { .type = NLA_U32 }, | |
| [NL80211_ATTR_MNTR_FLAGS] = { .type = NLA_NESTED }, | |
| [NL80211_ATTR_MESH_ID] = { .type = NLA_BINARY, | |
| .len = IEEE80211_MAX_MESH_ID_LEN }, | |
| [NL80211_ATTR_MPATH_NEXT_HOP] = { .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; | |
| struct nlattr *nl_bands, *nl_band; | |
| struct nlattr *nl_freqs, *nl_freq; | |
| struct nlattr *nl_rates, *nl_rate; | |
| enum ieee80211_band band; | |
| struct ieee80211_channel *chan; | |
| struct ieee80211_rate *rate; | |
| int i; | |
| 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)); | |
| nl_bands = nla_nest_start(msg, NL80211_ATTR_WIPHY_BANDS); | |
| if (!nl_bands) | |
| goto nla_put_failure; | |
| for (band = 0; band < IEEE80211_NUM_BANDS; band++) { | |
| if (!dev->wiphy.bands[band]) | |
| continue; | |
| nl_band = nla_nest_start(msg, band); | |
| if (!nl_band) | |
| goto nla_put_failure; | |
| /* add frequencies */ | |
| nl_freqs = nla_nest_start(msg, NL80211_BAND_ATTR_FREQS); | |
| if (!nl_freqs) | |
| goto nla_put_failure; | |
| for (i = 0; i < dev->wiphy.bands[band]->n_channels; i++) { | |
| nl_freq = nla_nest_start(msg, i); | |
| if (!nl_freq) | |
| goto nla_put_failure; | |
| chan = &dev->wiphy.bands[band]->channels[i]; | |
| NLA_PUT_U32(msg, NL80211_FREQUENCY_ATTR_FREQ, | |
| chan->center_freq); | |
| if (chan->flags & IEEE80211_CHAN_DISABLED) | |
| NLA_PUT_FLAG(msg, NL80211_FREQUENCY_ATTR_DISABLED); | |
| if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN) | |
| NLA_PUT_FLAG(msg, NL80211_FREQUENCY_ATTR_PASSIVE_SCAN); | |
| if (chan->flags & IEEE80211_CHAN_NO_IBSS) | |
| NLA_PUT_FLAG(msg, NL80211_FREQUENCY_ATTR_NO_IBSS); | |
| if (chan->flags & IEEE80211_CHAN_RADAR) | |
| NLA_PUT_FLAG(msg, NL80211_FREQUENCY_ATTR_RADAR); | |
| nla_nest_end(msg, nl_freq); | |
| } | |
| nla_nest_end(msg, nl_freqs); | |
| /* add bitrates */ | |
| nl_rates = nla_nest_start(msg, NL80211_BAND_ATTR_RATES); | |
| if (!nl_rates) | |
| goto nla_put_failure; | |
| for (i = 0; i < dev->wiphy.bands[band]->n_bitrates; i++) { | |
| nl_rate = nla_nest_start(msg, i); | |
| if (!nl_rate) | |
| goto nla_put_failure; | |
| rate = &dev->wiphy.bands[band]->bitrates[i]; | |
| NLA_PUT_U32(msg, NL80211_BITRATE_ATTR_RATE, | |
| rate->bitrate); | |
| if (rate->flags & IEEE80211_RATE_SHORT_PREAMBLE) | |
| NLA_PUT_FLAG(msg, | |
| NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE); | |
| nla_nest_end(msg, nl_rate); | |
| } | |
| nla_nest_end(msg, nl_rates); | |
| nla_nest_end(msg, nl_band); | |
| } | |
| nla_nest_end(msg, nl_bands); | |
| return genlmsg_end(msg, hdr); | |
| nla_put_failure: | |
| genlmsg_cancel(msg, hdr); | |
| return -EMSGSIZE; | |
| } | |
| 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: | |
| genlmsg_cancel(msg, hdr); | |
| return -EMSGSIZE; | |
| } | |
| 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 const struct nla_policy mntr_flags_policy[NL80211_MNTR_FLAG_MAX + 1] = { | |
| [NL80211_MNTR_FLAG_FCSFAIL] = { .type = NLA_FLAG }, | |
| [NL80211_MNTR_FLAG_PLCPFAIL] = { .type = NLA_FLAG }, | |
| [NL80211_MNTR_FLAG_CONTROL] = { .type = NLA_FLAG }, | |
| [NL80211_MNTR_FLAG_OTHER_BSS] = { .type = NLA_FLAG }, | |
| [NL80211_MNTR_FLAG_COOK_FRAMES] = { .type = NLA_FLAG }, | |
| }; | |
| static int parse_monitor_flags(struct nlattr *nla, u32 *mntrflags) | |
| { | |
| struct nlattr *flags[NL80211_MNTR_FLAG_MAX + 1]; | |
| int flag; | |
| *mntrflags = 0; | |
| if (!nla) | |
| return -EINVAL; | |
| if (nla_parse_nested(flags, NL80211_MNTR_FLAG_MAX, | |
| nla, mntr_flags_policy)) | |
| return -EINVAL; | |
| for (flag = 1; flag <= NL80211_MNTR_FLAG_MAX; flag++) | |
| if (flags[flag]) | |
| *mntrflags |= (1<<flag); | |
| return 0; | |
| } | |
| static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info) | |
| { | |
| struct cfg80211_registered_device *drv; | |
| struct vif_params params; | |
| int err, ifindex; | |
| enum nl80211_iftype type; | |
| struct net_device *dev; | |
| u32 flags; | |
| memset(¶ms, 0, sizeof(params)); | |
| 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; | |
| } | |
| if (type == NL80211_IFTYPE_MESH_POINT && | |
| info->attrs[NL80211_ATTR_MESH_ID]) { | |
| params.mesh_id = nla_data(info->attrs[NL80211_ATTR_MESH_ID]); | |
| params.mesh_id_len = nla_len(info->attrs[NL80211_ATTR_MESH_ID]); | |
| } | |
| rtnl_lock(); | |
| err = parse_monitor_flags(type == NL80211_IFTYPE_MONITOR ? | |
| info->attrs[NL80211_ATTR_MNTR_FLAGS] : NULL, | |
| &flags); | |
| err = drv->ops->change_virtual_intf(&drv->wiphy, ifindex, | |
| type, err ? NULL : &flags, ¶ms); | |
| 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; | |
| struct vif_params params; | |
| int err; | |
| enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED; | |
| u32 flags; | |
| memset(¶ms, 0, sizeof(params)); | |
| 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; | |
| } | |
| if (type == NL80211_IFTYPE_MESH_POINT && | |
| info->attrs[NL80211_ATTR_MESH_ID]) { | |
| params.mesh_id = nla_data(info->attrs[NL80211_ATTR_MESH_ID]); | |
| params.mesh_id_len = nla_len(info->attrs[NL80211_ATTR_MESH_ID]); | |
| } | |
| rtnl_lock(); | |
| err = parse_monitor_flags(type == NL80211_IFTYPE_MONITOR ? | |
| info->attrs[NL80211_ATTR_MNTR_FLAGS] : NULL, | |
| &flags); | |
| err = drv->ops->add_virtual_intf(&drv->wiphy, | |
| nla_data(info->attrs[NL80211_ATTR_IFNAME]), | |
| type, err ? NULL : &flags, ¶ms); | |
| 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_info *sinfo) | |
| { | |
| void *hdr; | |
| struct nlattr *sinfoattr; | |
| 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); | |
| sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO); | |
| if (!sinfoattr) | |
| goto nla_put_failure; | |
| if (sinfo->filled & STATION_INFO_INACTIVE_TIME) | |
| NLA_PUT_U32(msg, NL80211_STA_INFO_INACTIVE_TIME, | |
| sinfo->inactive_time); | |
| if (sinfo->filled & STATION_INFO_RX_BYTES) | |
| NLA_PUT_U32(msg, NL80211_STA_INFO_RX_BYTES, | |
| sinfo->rx_bytes); | |
| if (sinfo->filled & STATION_INFO_TX_BYTES) | |
| NLA_PUT_U32(msg, NL80211_STA_INFO_TX_BYTES, | |
| sinfo->tx_bytes); | |
| if (sinfo->filled & STATION_INFO_LLID) | |
| NLA_PUT_U16(msg, NL80211_STA_INFO_LLID, | |
| sinfo->llid); | |
| if (sinfo->filled & STATION_INFO_PLID) | |
| NLA_PUT_U16(msg, NL80211_STA_INFO_PLID, | |
| sinfo->plid); | |
| if (sinfo->filled & STATION_INFO_PLINK_STATE) | |
| NLA_PUT_U8(msg, NL80211_STA_INFO_PLINK_STATE, | |
| sinfo->plink_state); | |
| nla_nest_end(msg, sinfoattr); | |
| return genlmsg_end(msg, hdr); | |
| nla_put_failure: | |
| genlmsg_cancel(msg, hdr); | |
| return -EMSGSIZE; | |
| } | |
| static int nl80211_dump_station(struct sk_buff *skb, | |
| struct netlink_callback *cb) | |
| { | |
| int wp_idx = 0; | |
| int if_idx = 0; | |
| int sta_idx = cb->args[2]; | |
| int wp_start = cb->args[0]; | |
| int if_start = cb->args[1]; | |
| struct station_info sinfo; | |
| struct cfg80211_registered_device *dev; | |
| struct wireless_dev *wdev; | |
| u8 mac_addr[ETH_ALEN]; | |
| int err; | |
| int exit = 0; | |
| /* TODO: filter by device */ | |
| mutex_lock(&cfg80211_drv_mutex); | |
| list_for_each_entry(dev, &cfg80211_drv_list, list) { | |
| if (exit) | |
| break; | |
| if (++wp_idx < wp_start) | |
| continue; | |
| if_idx = 0; | |
| mutex_lock(&dev->devlist_mtx); | |
| list_for_each_entry(wdev, &dev->netdev_list, list) { | |
| if (exit) | |
| break; | |
| if (++if_idx < if_start) | |
| continue; | |
| if (!dev->ops->dump_station) | |
| continue; | |
| for (;; ++sta_idx) { | |
| rtnl_lock(); | |
| err = dev->ops->dump_station(&dev->wiphy, | |
| wdev->netdev, sta_idx, mac_addr, | |
| &sinfo); | |
| rtnl_unlock(); | |
| if (err) { | |
| sta_idx = 0; | |
| break; | |
| } | |
| if (nl80211_send_station(skb, | |
| NETLINK_CB(cb->skb).pid, | |
| cb->nlh->nlmsg_seq, NLM_F_MULTI, | |
| wdev->netdev, mac_addr, | |
| &sinfo) < 0) { | |
| exit = 1; | |
| break; | |
| } | |
| } | |
| } | |
| mutex_unlock(&dev->devlist_mtx); | |
| } | |
| mutex_unlock(&cfg80211_drv_mutex); | |
| cb->args[0] = wp_idx; | |
| cb->args[1] = if_idx; | |
| cb->args[2] = sta_idx; | |
| return skb->len; | |
| } | |
| 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_info sinfo; | |
| struct sk_buff *msg; | |
| u8 *mac_addr = NULL; | |
| memset(&sinfo, 0, sizeof(sinfo)); | |
| 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, &sinfo); | |
| rtnl_unlock(); | |
| if (err) | |
| goto out; | |
| 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, &sinfo) < 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; | |
| if (info->attrs[NL80211_ATTR_STA_PLINK_ACTION]) | |
| params.plink_action = | |
| nla_get_u8(info->attrs[NL80211_ATTR_STA_PLINK_ACTION]); | |
| 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 int nl80211_send_mpath(struct sk_buff *msg, u32 pid, u32 seq, | |
| int flags, struct net_device *dev, | |
| u8 *dst, u8 *next_hop, | |
| struct mpath_info *pinfo) | |
| { | |
| void *hdr; | |
| struct nlattr *pinfoattr; | |
| 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, dst); | |
| NLA_PUT(msg, NL80211_ATTR_MPATH_NEXT_HOP, ETH_ALEN, next_hop); | |
| pinfoattr = nla_nest_start(msg, NL80211_ATTR_MPATH_INFO); | |
| if (!pinfoattr) | |
| goto nla_put_failure; | |
| if (pinfo->filled & MPATH_INFO_FRAME_QLEN) | |
| NLA_PUT_U32(msg, NL80211_MPATH_INFO_FRAME_QLEN, | |
| pinfo->frame_qlen); | |
| if (pinfo->filled & MPATH_INFO_DSN) | |
| NLA_PUT_U32(msg, NL80211_MPATH_INFO_DSN, | |
| pinfo->dsn); | |
| if (pinfo->filled & MPATH_INFO_METRIC) | |
| NLA_PUT_U32(msg, NL80211_MPATH_INFO_METRIC, | |
| pinfo->metric); | |
| if (pinfo->filled & MPATH_INFO_EXPTIME) | |
| NLA_PUT_U32(msg, NL80211_MPATH_INFO_EXPTIME, | |
| pinfo->exptime); | |
| if (pinfo->filled & MPATH_INFO_FLAGS) | |
| NLA_PUT_U8(msg, NL80211_MPATH_INFO_FLAGS, | |
| pinfo->flags); | |
| if (pinfo->filled & MPATH_INFO_DISCOVERY_TIMEOUT) | |
| NLA_PUT_U32(msg, NL80211_MPATH_INFO_DISCOVERY_TIMEOUT, | |
| pinfo->discovery_timeout); | |
| if (pinfo->filled & MPATH_INFO_DISCOVERY_RETRIES) | |
| NLA_PUT_U8(msg, NL80211_MPATH_INFO_DISCOVERY_RETRIES, | |
| pinfo->discovery_retries); | |
| nla_nest_end(msg, pinfoattr); | |
| return genlmsg_end(msg, hdr); | |
| nla_put_failure: | |
| genlmsg_cancel(msg, hdr); | |
| return -EMSGSIZE; | |
| } | |
| static int nl80211_dump_mpath(struct sk_buff *skb, | |
| struct netlink_callback *cb) | |
| { | |
| int wp_idx = 0; | |
| int if_idx = 0; | |
| int sta_idx = cb->args[2]; | |
| int wp_start = cb->args[0]; | |
| int if_start = cb->args[1]; | |
| struct mpath_info pinfo; | |
| struct cfg80211_registered_device *dev; | |
| struct wireless_dev *wdev; | |
| u8 dst[ETH_ALEN]; | |
| u8 next_hop[ETH_ALEN]; | |
| int err; | |
| int exit = 0; | |
| /* TODO: filter by device */ | |
| mutex_lock(&cfg80211_drv_mutex); | |
| list_for_each_entry(dev, &cfg80211_drv_list, list) { | |
| if (exit) | |
| break; | |
| if (++wp_idx < wp_start) | |
| continue; | |
| if_idx = 0; | |
| mutex_lock(&dev->devlist_mtx); | |
| list_for_each_entry(wdev, &dev->netdev_list, list) { | |
| if (exit) | |
| break; | |
| if (++if_idx < if_start) | |
| continue; | |
| if (!dev->ops->dump_mpath) | |
| continue; | |
| for (;; ++sta_idx) { | |
| rtnl_lock(); | |
| err = dev->ops->dump_mpath(&dev->wiphy, | |
| wdev->netdev, sta_idx, dst, | |
| next_hop, &pinfo); | |
| rtnl_unlock(); | |
| if (err) { | |
| sta_idx = 0; | |
| break; | |
| } | |
| if (nl80211_send_mpath(skb, | |
| NETLINK_CB(cb->skb).pid, | |
| cb->nlh->nlmsg_seq, NLM_F_MULTI, | |
| wdev->netdev, dst, next_hop, | |
| &pinfo) < 0) { | |
| exit = 1; | |
| break; | |
| } | |
| } | |
| } | |
| mutex_unlock(&dev->devlist_mtx); | |
| } | |
| mutex_unlock(&cfg80211_drv_mutex); | |
| cb->args[0] = wp_idx; | |
| cb->args[1] = if_idx; | |
| cb->args[2] = sta_idx; | |
| return skb->len; | |
| } | |
| static int nl80211_get_mpath(struct sk_buff *skb, struct genl_info *info) | |
| { | |
| struct cfg80211_registered_device *drv; | |
| int err; | |
| struct net_device *dev; | |
| struct mpath_info pinfo; | |
| struct sk_buff *msg; | |
| u8 *dst = NULL; | |
| u8 next_hop[ETH_ALEN]; | |
| memset(&pinfo, 0, sizeof(pinfo)); | |
| if (!info->attrs[NL80211_ATTR_MAC]) | |
| return -EINVAL; | |
| dst = 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_mpath) { | |
| err = -EOPNOTSUPP; | |
| goto out; | |
| } | |
| rtnl_lock(); | |
| err = drv->ops->get_mpath(&drv->wiphy, dev, dst, next_hop, &pinfo); | |
| rtnl_unlock(); | |
| if (err) | |
| goto out; | |
| msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | |
| if (!msg) | |
| goto out; | |
| if (nl80211_send_mpath(msg, info->snd_pid, info->snd_seq, 0, | |
| dev, dst, next_hop, &pinfo) < 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; | |
| } | |
| static int nl80211_set_mpath(struct sk_buff *skb, struct genl_info *info) | |
| { | |
| struct cfg80211_registered_device *drv; | |
| int err; | |
| struct net_device *dev; | |
| u8 *dst = NULL; | |
| u8 *next_hop = NULL; | |
| if (!info->attrs[NL80211_ATTR_MAC]) | |
| return -EINVAL; | |
| if (!info->attrs[NL80211_ATTR_MPATH_NEXT_HOP]) | |
| return -EINVAL; | |
| dst = nla_data(info->attrs[NL80211_ATTR_MAC]); | |
| next_hop = nla_data(info->attrs[NL80211_ATTR_MPATH_NEXT_HOP]); | |
| err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | |
| if (err) | |
| return err; | |
| if (!drv->ops->change_mpath) { | |
| err = -EOPNOTSUPP; | |
| goto out; | |
| } | |
| rtnl_lock(); | |
| err = drv->ops->change_mpath(&drv->wiphy, dev, dst, next_hop); | |
| rtnl_unlock(); | |
| out: | |
| cfg80211_put_dev(drv); | |
| dev_put(dev); | |
| return err; | |
| } | |
| static int nl80211_new_mpath(struct sk_buff *skb, struct genl_info *info) | |
| { | |
| struct cfg80211_registered_device *drv; | |
| int err; | |
| struct net_device *dev; | |
| u8 *dst = NULL; | |
| u8 *next_hop = NULL; | |
| if (!info->attrs[NL80211_ATTR_MAC]) | |
| return -EINVAL; | |
| if (!info->attrs[NL80211_ATTR_MPATH_NEXT_HOP]) | |
| return -EINVAL; | |
| dst = nla_data(info->attrs[NL80211_ATTR_MAC]); | |
| next_hop = nla_data(info->attrs[NL80211_ATTR_MPATH_NEXT_HOP]); | |
| err = get_drv_dev_by_info_ifindex(info, &drv, &dev); | |
| if (err) | |
| return err; | |
| if (!drv->ops->add_mpath) { | |
| err = -EOPNOTSUPP; | |
| goto out; | |
| } | |
| rtnl_lock(); | |
| err = drv->ops->add_mpath(&drv->wiphy, dev, dst, next_hop); | |
| rtnl_unlock(); | |
| out: | |
| cfg80211_put_dev(drv); | |
| dev_put(dev); | |
| return err; | |
| } | |
| static int nl80211_del_mpath(struct sk_buff *skb, struct genl_info *info) | |
| { | |
| struct cfg80211_registered_device *drv; | |
| int err; | |
| struct net_device *dev; | |
| u8 *dst = NULL; | |
| if (info->attrs[NL80211_ATTR_MAC]) | |
| dst = 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_mpath) { | |
| err = -EOPNOTSUPP; | |
| goto out; | |
| } | |
| rtnl_lock(); | |
| err = drv->ops->del_mpath(&drv->wiphy, dev, dst); | |
| 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, | |
| .dumpit = nl80211_dump_station, | |
| .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, | |
| }, | |
| { | |
| .cmd = NL80211_CMD_GET_MPATH, | |
| .doit = nl80211_get_mpath, | |
| .dumpit = nl80211_dump_mpath, | |
| .policy = nl80211_policy, | |
| .flags = GENL_ADMIN_PERM, | |
| }, | |
| { | |
| .cmd = NL80211_CMD_SET_MPATH, | |
| .doit = nl80211_set_mpath, | |
| .policy = nl80211_policy, | |
| .flags = GENL_ADMIN_PERM, | |
| }, | |
| { | |
| .cmd = NL80211_CMD_NEW_MPATH, | |
| .doit = nl80211_new_mpath, | |
| .policy = nl80211_policy, | |
| .flags = GENL_ADMIN_PERM, | |
| }, | |
| { | |
| .cmd = NL80211_CMD_DEL_MPATH, | |
| .doit = nl80211_del_mpath, | |
| .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); | |
| } |