/*
* This is the new netlink-based wireless configuration interface.
*
* Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
*/
#include <linux/if.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/slab.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 <linux/etherdevice.h>
#include <net/net_namespace.h>
#include <net/genetlink.h>
#include <net/cfg80211.h>
#include <net/sock.h>
#include "core.h"
#include "nl80211.h"
#include "reg.h"
static int nl80211_pre_doit(struct genl_ops *ops, struct sk_buff *skb,
struct genl_info *info);
static void nl80211_post_doit(struct genl_ops *ops, struct sk_buff *skb,
struct genl_info *info);
/* 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,
.netnsok = true,
.pre_doit = nl80211_pre_doit,
.post_doit = nl80211_post_doit,
};
/* internal helper: get rdev and dev */
static int get_rdev_dev_by_info_ifindex(struct genl_info *info,
struct cfg80211_registered_device **rdev,
struct net_device **dev)
{
struct nlattr **attrs = info->attrs;
int ifindex;
if (!attrs[NL80211_ATTR_IFINDEX])
return -EINVAL;
ifindex = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]);
*dev = dev_get_by_index(genl_info_net(info), ifindex);
if (!*dev)
return -ENODEV;
*rdev = cfg80211_get_dev_from_ifindex(genl_info_net(info), ifindex);
if (IS_ERR(*rdev)) {
dev_put(*dev);
return PTR_ERR(*rdev);
}
return 0;
}
/* policy for the attributes */
static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
[NL80211_ATTR_WIPHY] = { .type = NLA_U32 },
[NL80211_ATTR_WIPHY_NAME] = { .type = NLA_NUL_STRING,
.len = 20-1 },
[NL80211_ATTR_WIPHY_TXQ_PARAMS] = { .type = NLA_NESTED },
[NL80211_ATTR_WIPHY_FREQ] = { .type = NLA_U32 },
[NL80211_ATTR_WIPHY_CHANNEL_TYPE] = { .type = NLA_U32 },
[NL80211_ATTR_WIPHY_RETRY_SHORT] = { .type = NLA_U8 },
[NL80211_ATTR_WIPHY_RETRY_LONG] = { .type = NLA_U8 },
[NL80211_ATTR_WIPHY_FRAG_THRESHOLD] = { .type = NLA_U32 },
[NL80211_ATTR_WIPHY_RTS_THRESHOLD] = { .type = NLA_U32 },
[NL80211_ATTR_WIPHY_COVERAGE_CLASS] = { .type = NLA_U8 },
[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_PREV_BSSID] = { .type = NLA_BINARY, .len = ETH_ALEN },
[NL80211_ATTR_KEY] = { .type = NLA_NESTED, },
[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_KEY_SEQ] = { .type = NLA_BINARY, .len = 8 },
[NL80211_ATTR_KEY_TYPE] = { .type = NLA_U32 },
[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] = { /* NLA_NESTED can't be empty */ },
[NL80211_ATTR_MESH_ID] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_MESH_ID_LEN },
[NL80211_ATTR_MPATH_NEXT_HOP] = { .type = NLA_U32 },
[NL80211_ATTR_REG_ALPHA2] = { .type = NLA_STRING, .len = 2 },
[NL80211_ATTR_REG_RULES] = { .type = NLA_NESTED },
[NL80211_ATTR_BSS_CTS_PROT] = { .type = NLA_U8 },
[NL80211_ATTR_BSS_SHORT_PREAMBLE] = { .type = NLA_U8 },
[NL80211_ATTR_BSS_SHORT_SLOT_TIME] = { .type = NLA_U8 },
[NL80211_ATTR_BSS_BASIC_RATES] = { .type = NLA_BINARY,
.len = NL80211_MAX_SUPP_RATES },
[NL80211_ATTR_BSS_HT_OPMODE] = { .type = NLA_U16 },
[NL80211_ATTR_MESH_CONFIG] = { .type = NLA_NESTED },
[NL80211_ATTR_SUPPORT_MESH_AUTH] = { .type = NLA_FLAG },
[NL80211_ATTR_HT_CAPABILITY] = { .type = NLA_BINARY,
.len = NL80211_HT_CAPABILITY_LEN },
[NL80211_ATTR_MGMT_SUBTYPE] = { .type = NLA_U8 },
[NL80211_ATTR_IE] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_DATA_LEN },
[NL80211_ATTR_SCAN_FREQUENCIES] = { .type = NLA_NESTED },
[NL80211_ATTR_SCAN_SSIDS] = { .type = NLA_NESTED },
[NL80211_ATTR_SSID] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_SSID_LEN },
[NL80211_ATTR_AUTH_TYPE] = { .type = NLA_U32 },
[NL80211_ATTR_REASON_CODE] = { .type = NLA_U16 },
[NL80211_ATTR_FREQ_FIXED] = { .type = NLA_FLAG },
[NL80211_ATTR_TIMED_OUT] = { .type = NLA_FLAG },
[NL80211_ATTR_USE_MFP] = { .type = NLA_U32 },
[NL80211_ATTR_STA_FLAGS2] = {
.len = sizeof(struct nl80211_sta_flag_update),
},
[NL80211_ATTR_CONTROL_PORT] = { .type = NLA_FLAG },
[NL80211_ATTR_CONTROL_PORT_ETHERTYPE] = { .type = NLA_U16 },
[NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT] = { .type = NLA_FLAG },
[NL80211_ATTR_PRIVACY] = { .type = NLA_FLAG },
[NL80211_ATTR_CIPHER_SUITE_GROUP] = { .type = NLA_U32 },
[NL80211_ATTR_WPA_VERSIONS] = { .type = NLA_U32 },
[NL80211_ATTR_PID] = { .type = NLA_U32 },
[NL80211_ATTR_4ADDR] = { .type = NLA_U8 },
[NL80211_ATTR_PMKID] = { .type = NLA_BINARY,
.len = WLAN_PMKID_LEN },
[NL80211_ATTR_DURATION] = { .type = NLA_U32 },
[NL80211_ATTR_COOKIE] = { .type = NLA_U64 },
[NL80211_ATTR_TX_RATES] = { .type = NLA_NESTED },
[NL80211_ATTR_FRAME] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_DATA_LEN },
[NL80211_ATTR_FRAME_MATCH] = { .type = NLA_BINARY, },
[NL80211_ATTR_PS_STATE] = { .type = NLA_U32 },
[NL80211_ATTR_CQM] = { .type = NLA_NESTED, },
[NL80211_ATTR_LOCAL_STATE_CHANGE] = { .type = NLA_FLAG },
[NL80211_ATTR_AP_ISOLATE] = { .type = NLA_U8 },
[NL80211_ATTR_WIPHY_TX_POWER_SETTING] = { .type = NLA_U32 },
[NL80211_ATTR_WIPHY_TX_POWER_LEVEL] = { .type = NLA_U32 },
[NL80211_ATTR_FRAME_TYPE] = { .type = NLA_U16 },
[NL80211_ATTR_WIPHY_ANTENNA_TX] = { .type = NLA_U32 },
[NL80211_ATTR_WIPHY_ANTENNA_RX] = { .type = NLA_U32 },
[NL80211_ATTR_MCAST_RATE] = { .type = NLA_U32 },
[NL80211_ATTR_OFFCHANNEL_TX_OK] = { .type = NLA_FLAG },
[NL80211_ATTR_KEY_DEFAULT_TYPES] = { .type = NLA_NESTED },
[NL80211_ATTR_WOWLAN_TRIGGERS] = { .type = NLA_NESTED },
[NL80211_ATTR_STA_PLINK_STATE] = { .type = NLA_U8 },
[NL80211_ATTR_SCHED_SCAN_INTERVAL] = { .type = NLA_U32 },
};
/* policy for the key attributes */
static const struct nla_policy nl80211_key_policy[NL80211_KEY_MAX + 1] = {
[NL80211_KEY_DATA] = { .type = NLA_BINARY, .len = WLAN_MAX_KEY_LEN },
[NL80211_KEY_IDX] = { .type = NLA_U8 },
[NL80211_KEY_CIPHER] = { .type = NLA_U32 },
[NL80211_KEY_SEQ] = { .type = NLA_BINARY, .len = 8 },
[NL80211_KEY_DEFAULT] = { .type = NLA_FLAG },
[NL80211_KEY_DEFAULT_MGMT] = { .type = NLA_FLAG },
[NL80211_KEY_TYPE] = { .type = NLA_U32 },
[NL80211_KEY_DEFAULT_TYPES] = { .type = NLA_NESTED },
};
/* policy for the key default flags */
static const struct nla_policy
nl80211_key_default_policy[NUM_NL80211_KEY_DEFAULT_TYPES] = {
[NL80211_KEY_DEFAULT_TYPE_UNICAST] = { .type = NLA_FLAG },
[NL80211_KEY_DEFAULT_TYPE_MULTICAST] = { .type = NLA_FLAG },
};
/* policy for WoWLAN attributes */
static const struct nla_policy
nl80211_wowlan_policy[NUM_NL80211_WOWLAN_TRIG] = {
[NL80211_WOWLAN_TRIG_ANY] = { .type = NLA_FLAG },
[NL80211_WOWLAN_TRIG_DISCONNECT] = { .type = NLA_FLAG },
[NL80211_WOWLAN_TRIG_MAGIC_PKT] = { .type = NLA_FLAG },
[NL80211_WOWLAN_TRIG_PKT_PATTERN] = { .type = NLA_NESTED },
};
/* ifidx get helper */
static int nl80211_get_ifidx(struct netlink_callback *cb)
{
int res;
res = nlmsg_parse(cb->nlh, GENL_HDRLEN + nl80211_fam.hdrsize,
nl80211_fam.attrbuf, nl80211_fam.maxattr,
nl80211_policy);
if (res)
return res;
if (!nl80211_fam.attrbuf[NL80211_ATTR_IFINDEX])
return -EINVAL;
res = nla_get_u32(nl80211_fam.attrbuf[NL80211_ATTR_IFINDEX]);
if (!res)
return -EINVAL;
return res;
}
static int nl80211_prepare_netdev_dump(struct sk_buff *skb,
struct netlink_callback *cb,
struct cfg80211_registered_device **rdev,
struct net_device **dev)
{
int ifidx = cb->args[0];
int err;
if (!ifidx)
ifidx = nl80211_get_ifidx(cb);
if (ifidx < 0)
return ifidx;
cb->args[0] = ifidx;
rtnl_lock();
*dev = __dev_get_by_index(sock_net(skb->sk), ifidx);
if (!*dev) {
err = -ENODEV;
goto out_rtnl;
}
*rdev = cfg80211_get_dev_from_ifindex(sock_net(skb->sk), ifidx);
if (IS_ERR(*rdev)) {
err = PTR_ERR(*rdev);
goto out_rtnl;
}
return 0;
out_rtnl:
rtnl_unlock();
return err;
}
static void nl80211_finish_netdev_dump(struct cfg80211_registered_device *rdev)
{
cfg80211_unlock_rdev(rdev);
rtnl_unlock();
}
/* IE validation */
static bool is_valid_ie_attr(const struct nlattr *attr)
{
const u8 *pos;
int len;
if (!attr)
return true;
pos = nla_data(attr);
len = nla_len(attr);
while (len) {
u8 elemlen;
if (len < 2)
return false;
len -= 2;
elemlen = pos[1];
if (elemlen > len)
return false;
len -= elemlen;
pos += 2 + elemlen;
}
return true;
}
/* 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);
}
static int nl80211_msg_put_channel(struct sk_buff *msg,
struct ieee80211_channel *chan)
{
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_PUT_U32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER,
DBM_TO_MBM(chan->max_power));
return 0;
nla_put_failure:
return -ENOBUFS;
}
/* netlink command implementations */
struct key_parse {
struct key_params p;
int idx;
int type;
bool def, defmgmt;
bool def_uni, def_multi;
};
static int nl80211_parse_key_new(struct nlattr *key, struct key_parse *k)
{
struct nlattr *tb[NL80211_KEY_MAX + 1];
int err = nla_parse_nested(tb, NL80211_KEY_MAX, key,
nl80211_key_policy);
if (err)
return err;
k->def = !!tb[NL80211_KEY_DEFAULT];
k->defmgmt = !!tb[NL80211_KEY_DEFAULT_MGMT];
if (k->def) {
k->def_uni = true;
k->def_multi = true;
}
if (k->defmgmt)
k->def_multi = true;
if (tb[NL80211_KEY_IDX])
k->idx = nla_get_u8(tb[NL80211_KEY_IDX]);
if (tb[NL80211_KEY_DATA]) {
k->p.key = nla_data(tb[NL80211_KEY_DATA]);
k->p.key_len = nla_len(tb[NL80211_KEY_DATA]);
}
if (tb[NL80211_KEY_SEQ]) {
k->p.seq = nla_data(tb[NL80211_KEY_SEQ]);
k->p.seq_len = nla_len(tb[NL80211_KEY_SEQ]);
}
if (tb[NL80211_KEY_CIPHER])
k->p.cipher = nla_get_u32(tb[NL80211_KEY_CIPHER]);
if (tb[NL80211_KEY_TYPE]) {
k->type = nla_get_u32(tb[NL80211_KEY_TYPE]);
if (k->type < 0 || k->type >= NUM_NL80211_KEYTYPES)
return -EINVAL;
}
if (tb[NL80211_KEY_DEFAULT_TYPES]) {
struct nlattr *kdt[NUM_NL80211_KEY_DEFAULT_TYPES];
int err = nla_parse_nested(kdt,
NUM_NL80211_KEY_DEFAULT_TYPES - 1,
tb[NL80211_KEY_DEFAULT_TYPES],
nl80211_key_default_policy);
if (err)
return err;
k->def_uni = kdt[NL80211_KEY_DEFAULT_TYPE_UNICAST];
k->def_multi = kdt[NL80211_KEY_DEFAULT_TYPE_MULTICAST];
}
return 0;
}
static int nl80211_parse_key_old(struct genl_info *info, struct key_parse *k)
{
if (info->attrs[NL80211_ATTR_KEY_DATA]) {
k->p.key = nla_data(info->attrs[NL80211_ATTR_KEY_DATA]);
k->p.key_len = nla_len(info->attrs[NL80211_ATTR_KEY_DATA]);
}
if (info->attrs[NL80211_ATTR_KEY_SEQ]) {
k->p.seq = nla_data(info->attrs[NL80211_ATTR_KEY_SEQ]);
k->p.seq_len = nla_len(info->attrs[NL80211_ATTR_KEY_SEQ]);
}
if (info->attrs[NL80211_ATTR_KEY_IDX])
k->idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);
if (info->attrs[NL80211_ATTR_KEY_CIPHER])
k->p.cipher = nla_get_u32(info->attrs[NL80211_ATTR_KEY_CIPHER]);
k->def = !!info->attrs[NL80211_ATTR_KEY_DEFAULT];
k->defmgmt = !!info->attrs[NL80211_ATTR_KEY_DEFAULT_MGMT];
if (k->def) {
k->def_uni = true;
k->def_multi = true;
}
if (k->defmgmt)
k->def_multi = true;
if (info->attrs[NL80211_ATTR_KEY_TYPE]) {
k->type = nla_get_u32(info->attrs[NL80211_ATTR_KEY_TYPE]);
if (k->type < 0 || k->type >= NUM_NL80211_KEYTYPES)
return -EINVAL;
}
if (info->attrs[NL80211_ATTR_KEY_DEFAULT_TYPES]) {
struct nlattr *kdt[NUM_NL80211_KEY_DEFAULT_TYPES];
int err = nla_parse_nested(
kdt, NUM_NL80211_KEY_DEFAULT_TYPES - 1,
info->attrs[NL80211_ATTR_KEY_DEFAULT_TYPES],
nl80211_key_default_policy);
if (err)
return err;
k->def_uni = kdt[NL80211_KEY_DEFAULT_TYPE_UNICAST];
k->def_multi = kdt[NL80211_KEY_DEFAULT_TYPE_MULTICAST];
}
return 0;
}
static int nl80211_parse_key(struct genl_info *info, struct key_parse *k)
{
int err;
memset(k, 0, sizeof(*k));
k->idx = -1;
k->type = -1;
if (info->attrs[NL80211_ATTR_KEY])
err = nl80211_parse_key_new(info->attrs[NL80211_ATTR_KEY], k);
else
err = nl80211_parse_key_old(info, k);
if (err)
return err;
if (k->def && k->defmgmt)
return -EINVAL;
if (k->defmgmt) {
if (k->def_uni || !k->def_multi)
return -EINVAL;
}
if (k->idx != -1) {
if (k->defmgmt) {
if (k->idx < 4 || k->idx > 5)
return -EINVAL;
} else if (k->def) {
if (k->idx < 0 || k->idx > 3)
return -EINVAL;
} else {
if (k->idx < 0 || k->idx > 5)
return -EINVAL;
}
}
return 0;
}
static struct cfg80211_cached_keys *
nl80211_parse_connkeys(struct cfg80211_registered_device *rdev,
struct nlattr *keys)
{
struct key_parse parse;
struct nlattr *key;
struct cfg80211_cached_keys *result;
int rem, err, def = 0;
result = kzalloc(sizeof(*result), GFP_KERNEL);
if (!result)
return ERR_PTR(-ENOMEM);
result->def = -1;
result->defmgmt = -1;
nla_for_each_nested(key, keys, rem) {
memset(&parse, 0, sizeof(parse));
parse.idx = -1;
err = nl80211_parse_key_new(key, &parse);
if (err)
goto error;
err = -EINVAL;
if (!parse.p.key)
goto error;
if (parse.idx < 0 || parse.idx > 4)
goto error;
if (parse.def) {
if (def)
goto error;
def = 1;
result->def = parse.idx;
if (!parse.def_uni || !parse.def_multi)
goto error;
} else if (parse.defmgmt)
goto error;
err = cfg80211_validate_key_settings(rdev, &parse.p,
parse.idx, false, NULL);
if (err)
goto error;
result->params[parse.idx].cipher = parse.p.cipher;
result->params[parse.idx].key_len = parse.p.key_len;
result->params[parse.idx].key = result->data[parse.idx];
memcpy(result->data[parse.idx], parse.p.key, parse.p.key_len);
}
return result;
error:
kfree(result);
return ERR_PTR(err);
}
static int nl80211_key_allowed(struct wireless_dev *wdev)
{
ASSERT_WDEV_LOCK(wdev);
switch (wdev->iftype) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_MESH_POINT:
break;
case NL80211_IFTYPE_ADHOC:
if (!wdev->current_bss)
return -ENOLINK;
break;
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_P2P_CLIENT:
if (wdev->sme_state != CFG80211_SME_CONNECTED)
return -ENOLINK;
break;
default:
return -EINVAL;
}
return 0;
}
static int nl80211_put_iftypes(struct sk_buff *msg, u32 attr, u16 ifmodes)
{
struct nlattr *nl_modes = nla_nest_start(msg, attr);
int i;
if (!nl_modes)
goto nla_put_failure;
i = 0;
while (ifmodes) {
if (ifmodes & 1)
NLA_PUT_FLAG(msg, i);
ifmodes >>= 1;
i++;
}
nla_nest_end(msg, nl_modes);
return 0;
nla_put_failure:
return -ENOBUFS;
}
static int nl80211_put_iface_combinations(struct wiphy *wiphy,
struct sk_buff *msg)
{
struct nlattr *nl_combis;
int i, j;
nl_combis = nla_nest_start(msg,
NL80211_ATTR_INTERFACE_COMBINATIONS);
if (!nl_combis)
goto nla_put_failure;
for (i = 0; i < wiphy->n_iface_combinations; i++) {
const struct ieee80211_iface_combination *c;
struct nlattr *nl_combi, *nl_limits;
c = &wiphy->iface_combinations[i];
nl_combi = nla_nest_start(msg, i + 1);
if (!nl_combi)
goto nla_put_failure;
nl_limits = nla_nest_start(msg, NL80211_IFACE_COMB_LIMITS);
if (!nl_limits)
goto nla_put_failure;
for (j = 0; j < c->n_limits; j++) {
struct nlattr *nl_limit;
nl_limit = nla_nest_start(msg, j + 1);
if (!nl_limit)
goto nla_put_failure;
NLA_PUT_U32(msg, NL80211_IFACE_LIMIT_MAX,
c->limits[j].max);
if (nl80211_put_iftypes(msg, NL80211_IFACE_LIMIT_TYPES,
c->limits[j].types))
goto nla_put_failure;
nla_nest_end(msg, nl_limit);
}
nla_nest_end(msg, nl_limits);
if (c->beacon_int_infra_match)
NLA_PUT_FLAG(msg,
NL80211_IFACE_COMB_STA_AP_BI_MATCH);
NLA_PUT_U32(msg, NL80211_IFACE_COMB_NUM_CHANNELS,
c->num_different_channels);
NLA_PUT_U32(msg, NL80211_IFACE_COMB_MAXNUM,
c->max_interfaces);
nla_nest_end(msg, nl_combi);
}
nla_nest_end(msg, nl_combis);
|