| /* |
| * 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 }, |
| }; |
| |
| /* 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; |
| } |
| |
| 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, |
| }, |
| }; |
| |
| /* 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); |
| } |