/* * This is the new netlink-based wireless configuration interface. * * Copyright 2006 Johannes Berg */ #include #include #include #include #include #include #include #include #include #include #include #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; 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_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; 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_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, ¶ms); 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(¶ms, 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, ¶ms); 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); }