blob: 5cefcc8aa8d6729c6dc5c27e5e58c7c7d23a0fdd [file] [log] [blame]
/*
* 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 <net/inet_connection_sock.h>
#include "core.h"
#include "nl80211.h"
#include "reg.h"
#include "rdev-ops.h"
static int nl80211_crypto_settings(struct cfg80211_registered_device *rdev,
struct genl_info *info,
struct cfg80211_crypto_settings *settings,
int cipher_limit);
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,
};
/* returns ERR_PTR values */
static struct wireless_dev *
__cfg80211_wdev_from_attrs(struct net *netns, struct nlattr **attrs)
{
struct cfg80211_registered_device *rdev;
struct wireless_dev *result = NULL;
bool have_ifidx = attrs[NL80211_ATTR_IFINDEX];
bool have_wdev_id = attrs[NL80211_ATTR_WDEV];
u64 wdev_id;
int wiphy_idx = -1;
int ifidx = -1;
assert_cfg80211_lock();
if (!have_ifidx && !have_wdev_id)
return ERR_PTR(-EINVAL);
if (have_ifidx)
ifidx = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]);
if (have_wdev_id) {
wdev_id = nla_get_u64(attrs[NL80211_ATTR_WDEV]);
wiphy_idx = wdev_id >> 32;
}
list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
struct wireless_dev *wdev;
if (wiphy_net(&rdev->wiphy) != netns)
continue;
if (have_wdev_id && rdev->wiphy_idx != wiphy_idx)
continue;
mutex_lock(&rdev->devlist_mtx);
list_for_each_entry(wdev, &rdev->wdev_list, list) {
if (have_ifidx && wdev->netdev &&
wdev->netdev->ifindex == ifidx) {
result = wdev;
break;
}
if (have_wdev_id && wdev->identifier == (u32)wdev_id) {
result = wdev;
break;
}
}
mutex_unlock(&rdev->devlist_mtx);
if (result)
break;
}
if (result)
return result;
return ERR_PTR(-ENODEV);
}
static struct cfg80211_registered_device *
__cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)
{
struct cfg80211_registered_device *rdev = NULL, *tmp;
struct net_device *netdev;
assert_cfg80211_lock();
if (!attrs[NL80211_ATTR_WIPHY] &&
!attrs[NL80211_ATTR_IFINDEX] &&
!attrs[NL80211_ATTR_WDEV])
return ERR_PTR(-EINVAL);
if (attrs[NL80211_ATTR_WIPHY])
rdev = cfg80211_rdev_by_wiphy_idx(
nla_get_u32(attrs[NL80211_ATTR_WIPHY]));
if (attrs[NL80211_ATTR_WDEV]) {
u64 wdev_id = nla_get_u64(attrs[NL80211_ATTR_WDEV]);
struct wireless_dev *wdev;
bool found = false;
tmp = cfg80211_rdev_by_wiphy_idx(wdev_id >> 32);
if (tmp) {
/* make sure wdev exists */
mutex_lock(&tmp->devlist_mtx);
list_for_each_entry(wdev, &tmp->wdev_list, list) {
if (wdev->identifier != (u32)wdev_id)
continue;
found = true;
break;
}
mutex_unlock(&tmp->devlist_mtx);
if (!found)
tmp = NULL;
if (rdev && tmp != rdev)
return ERR_PTR(-EINVAL);
rdev = tmp;
}
}
if (attrs[NL80211_ATTR_IFINDEX]) {
int ifindex = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]);
netdev = dev_get_by_index(netns, ifindex);
if (netdev) {
if (netdev->ieee80211_ptr)
tmp = wiphy_to_dev(
netdev->ieee80211_ptr->wiphy);
else
tmp = NULL;
dev_put(netdev);
/* not wireless device -- return error */
if (!tmp)
return ERR_PTR(-EINVAL);
/* mismatch -- return error */
if (rdev && tmp != rdev)
return ERR_PTR(-EINVAL);
rdev = tmp;
}
}
if (!rdev)
return ERR_PTR(-ENODEV);
if (netns != wiphy_net(&rdev->wiphy))
return ERR_PTR(-ENODEV);
return rdev;
}
/*
* This function returns a pointer to the driver
* that the genl_info item that is passed refers to.
* If successful, it returns non-NULL and also locks
* the driver's mutex!
*
* This means that you need to call cfg80211_unlock_rdev()
* before being allowed to acquire &cfg80211_mutex!
*
* This is necessary because we need to lock the global
* mutex to get an item off the list safely, and then
* we lock the rdev mutex so it doesn't go away under us.
*
* We don't want to keep cfg80211_mutex locked
* for all the time in order to allow requests on
* other interfaces to go through at the same time.
*
* The result of this can be a PTR_ERR and hence must
* be checked with IS_ERR() for errors.
*/
static struct cfg80211_registered_device *
cfg80211_get_dev_from_info(struct net *netns, struct genl_info *info)
{
struct cfg80211_registered_device *rdev;
mutex_lock(&cfg80211_mutex);
rdev = __cfg80211_rdev_from_attrs(netns, info->attrs);
/* if it is not an error we grab the lock on
* it to assure it won't be going away while
* we operate on it */
if (!IS_ERR(rdev))
mutex_lock(&rdev->mtx);
mutex_unlock(&cfg80211_mutex);
return rdev;
}
/* 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_CHANNEL_WIDTH] = { .type = NLA_U32 },
[NL80211_ATTR_CENTER_FREQ1] = { .type = NLA_U32 },
[NL80211_ATTR_CENTER_FREQ2] = { .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] = { .len = ETH_ALEN },
[NL80211_ATTR_PREV_BSSID] = { .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 = 16 },
[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] = { .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 },
[NL80211_ATTR_REKEY_DATA] = { .type = NLA_NESTED },
[NL80211_ATTR_SCAN_SUPP_RATES] = { .type = NLA_NESTED },
[NL80211_ATTR_HIDDEN_SSID] = { .type = NLA_U32 },
[NL80211_ATTR_IE_PROBE_RESP] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_DATA_LEN },
[NL80211_ATTR_IE_ASSOC_RESP] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_DATA_LEN },
[NL80211_ATTR_ROAM_SUPPORT] = { .type = NLA_FLAG },
[NL80211_ATTR_SCHED_SCAN_MATCH] = { .type = NLA_NESTED },
[NL80211_ATTR_TX_NO_CCK_RATE] = { .type = NLA_FLAG },
[NL80211_ATTR_TDLS_ACTION] = { .type = NLA_U8 },
[NL80211_ATTR_TDLS_DIALOG_TOKEN] = { .type = NLA_U8 },
[NL80211_ATTR_TDLS_OPERATION] = { .type = NLA_U8 },
[NL80211_ATTR_TDLS_SUPPORT] = { .type = NLA_FLAG },
[NL80211_ATTR_TDLS_EXTERNAL_SETUP] = { .type = NLA_FLAG },
[NL80211_ATTR_DONT_WAIT_FOR_ACK] = { .type = NLA_FLAG },
[NL80211_ATTR_PROBE_RESP] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_DATA_LEN },
[NL80211_ATTR_DFS_REGION] = { .type = NLA_U8 },
[NL80211_ATTR_DISABLE_HT] = { .type = NLA_FLAG },
[NL80211_ATTR_HT_CAPABILITY_MASK] = {
.len = NL80211_HT_CAPABILITY_LEN
},
[NL80211_ATTR_NOACK_MAP] = { .type = NLA_U16 },
[NL80211_ATTR_INACTIVITY_TIMEOUT] = { .type = NLA_U16 },
[NL80211_ATTR_BG_SCAN_PERIOD] = { .type = NLA_U16 },
[NL80211_ATTR_WDEV] = { .type = NLA_U64 },
[NL80211_ATTR_USER_REG_HINT_TYPE] = { .type = NLA_U32 },
[NL80211_ATTR_SAE_DATA] = { .type = NLA_BINARY, },
[NL80211_ATTR_VHT_CAPABILITY] = { .len = NL80211_VHT_CAPABILITY_LEN },
[NL80211_ATTR_SCAN_FLAGS] = { .type = NLA_U32 },
[NL80211_ATTR_P2P_CTWINDOW] = { .type = NLA_U8 },
[NL80211_ATTR_P2P_OPPPS] = { .type = NLA_U8 },
[NL80211_ATTR_ACL_POLICY] = {. type = NLA_U32 },
[NL80211_ATTR_MAC_ADDRS] = { .type = NLA_NESTED },
[NL80211_ATTR_STA_CAPABILITY] = { .type = NLA_U16 },
[NL80211_ATTR_STA_EXT_CAPABILITY] = { .type = NLA_BINARY, },
[NL80211_ATTR_SPLIT_WIPHY_DUMP] = { .type = NLA_FLAG, },
[NL80211_ATTR_DISABLE_VHT] = { .type = NLA_FLAG },
[NL80211_ATTR_VHT_CAPABILITY_MASK] = {
.len = NL80211_VHT_CAPABILITY_LEN,
},
[NL80211_ATTR_MDID] = { .type = NLA_U16 },
[NL80211_ATTR_IE_RIC] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_DATA_LEN },
[NL80211_ATTR_PEER_AID] = { .type = NLA_U16 },
[NL80211_ATTR_STA_SUPPORTED_CHANNELS] = { .type = NLA_BINARY },
[NL80211_ATTR_STA_SUPPORTED_OPER_CLASSES] = { .type = NLA_BINARY },
[NL80211_ATTR_VENDOR_ID] = { .type = NLA_U32 },
[NL80211_ATTR_VENDOR_SUBCMD] = { .type = NLA_U32 },
[NL80211_ATTR_VENDOR_DATA] = { .type = NLA_BINARY },
};
/* 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 = 16 },
[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 },
[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE] = { .type = NLA_FLAG },
[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST] = { .type = NLA_FLAG },
[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE] = { .type = NLA_FLAG },
[NL80211_WOWLAN_TRIG_RFKILL_RELEASE] = { .type = NLA_FLAG },
[NL80211_WOWLAN_TRIG_TCP_CONNECTION] = { .type = NLA_NESTED },
};
static const struct nla_policy
nl80211_wowlan_tcp_policy[NUM_NL80211_WOWLAN_TCP] = {
[NL80211_WOWLAN_TCP_SRC_IPV4] = { .type = NLA_U32 },
[NL80211_WOWLAN_TCP_DST_IPV4] = { .type = NLA_U32 },
[NL80211_WOWLAN_TCP_DST_MAC] = { .len = ETH_ALEN },
[NL80211_WOWLAN_TCP_SRC_PORT] = { .type = NLA_U16 },
[NL80211_WOWLAN_TCP_DST_PORT] = { .type = NLA_U16 },
[NL80211_WOWLAN_TCP_DATA_PAYLOAD] = { .len = 1 },
[NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ] = {
.len = sizeof(struct nl80211_wowlan_tcp_data_seq)
},
[NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN] = {
.len = sizeof(struct nl80211_wowlan_tcp_data_token)
},
[NL80211_WOWLAN_TCP_DATA_INTERVAL] = { .type = NLA_U32 },
[NL80211_WOWLAN_TCP_WAKE_PAYLOAD] = { .len = 1 },
[NL80211_WOWLAN_TCP_WAKE_MASK] = { .len = 1 },
};
/* policy for GTK rekey offload attributes */
static const struct nla_policy
nl80211_rekey_policy[NUM_NL80211_REKEY_DATA] = {
[NL80211_REKEY_DATA_KEK] = { .len = NL80211_KEK_LEN },
[NL80211_REKEY_DATA_KCK] = { .len = NL80211_KCK_LEN },
[NL80211_REKEY_DATA_REPLAY_CTR] = { .len = NL80211_REPLAY_CTR_LEN },
};
static const struct nla_policy
nl80211_match_policy[NL80211_SCHED_SCAN_MATCH_ATTR_MAX + 1] = {
[NL80211_SCHED_SCAN_MATCH_ATTR_SSID] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_SSID_LEN },
[NL80211_SCHED_SCAN_MATCH_ATTR_RSSI] = { .type = NLA_U32 },
};
static int nl80211_prepare_wdev_dump(struct sk_buff *skb,
struct netlink_callback *cb,
struct cfg80211_registered_device **rdev,
struct wireless_dev **wdev)
{
int err;
rtnl_lock();
mutex_lock(&cfg80211_mutex);
if (!cb->args[0]) {
err = nlmsg_parse(cb->nlh, GENL_HDRLEN + nl80211_fam.hdrsize,
nl80211_fam.attrbuf, nl80211_fam.maxattr,
nl80211_policy);
if (err)
goto out_unlock;
*wdev = __cfg80211_wdev_from_attrs(sock_net(skb->sk),
nl80211_fam.attrbuf);
if (IS_ERR(*wdev)) {
err = PTR_ERR(*wdev);
goto out_unlock;
}
*rdev = wiphy_to_dev((*wdev)->wiphy);
/* 0 is the first index - add 1 to parse only once */
cb->args[0] = (*rdev)->wiphy_idx + 1;
cb->args[1] = (*wdev)->identifier;
} else {
/* subtract the 1 again here */
struct wiphy *wiphy = wiphy_idx_to_wiphy(cb->args[0] - 1);
struct wireless_dev *tmp;
if (!wiphy) {
err = -ENODEV;
goto out_unlock;
}
*rdev = wiphy_to_dev(wiphy);
*wdev = NULL;
mutex_lock(&(*rdev)->devlist_mtx);
list_for_each_entry(tmp, &(*rdev)->wdev_list, list) {
if (tmp->identifier == cb->args[1]) {
*wdev = tmp;
break;
}
}
mutex_unlock(&(*rdev)->devlist_mtx);
if (!*wdev) {
err = -ENODEV;
goto out_unlock;
}
}
cfg80211_lock_rdev(*rdev);
mutex_unlock(&cfg80211_mutex);
return 0;
out_unlock:
mutex_unlock(&cfg80211_mutex);
rtnl_unlock();
return err;
}
static void nl80211_finish_wdev_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 portid, u32 seq,
int flags, u8 cmd)
{
/* since there is no private header just add the generic one */
return genlmsg_put(skb, portid, seq, &nl80211_fam, flags, cmd);
}
static int nl80211_msg_put_channel(struct sk_buff *msg,
struct ieee80211_channel *chan,
bool large)
{
if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_FREQ,
chan->center_freq))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_DISABLED) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_DISABLED))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_PASSIVE_SCAN) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_PASSIVE_SCAN))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_NO_IBSS) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_IBSS))
goto nla_put_failure;
if (chan->flags & IEEE80211_CHAN_RADAR) {
if (nla_put_flag(msg, NL80211_FREQUENCY_ATTR_RADAR))
goto nla_put_failure;
if (large) {
u32 time;
time = elapsed_jiffies_msecs(chan->dfs_state_entered);
if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_DFS_STATE,
chan->dfs_state))
goto nla_put_failure;
if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_DFS_TIME,
time))
goto nla_put_failure;
}
}
if (large) {
if ((chan->flags & IEEE80211_CHAN_NO_HT40MINUS) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_HT40_MINUS))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_NO_HT40PLUS) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_HT40_PLUS))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_NO_80MHZ) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_80MHZ))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_NO_160MHZ) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_160MHZ))
goto nla_put_failure;
}
if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER,
DBM_TO_MBM(chan->max_power)))
goto nla_put_failure;
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];
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, bool *no_ht)
{
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);
if (parse.p.cipher == WLAN_CIPHER_SUITE_WEP40 ||
parse.p.cipher == WLAN_CIPHER_SUITE_WEP104) {
if (no_ht)
*no_ht = true;
}
}
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))
goto nla_put_failure;
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,
bool large)
{
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;
if (nla_put_u32(msg, NL80211_IFACE_LIMIT_MAX,
c->limits[j].max))
goto nla_put_failure;
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))
goto nla_put_failure;
if (nla_put_u32(msg, NL80211_IFACE_COMB_NUM_CHANNELS,
c->num_different_channels) ||
nla_put_u32(msg, NL80211_IFACE_COMB_MAXNUM,
c->max_interfaces))
goto nla_put_failure;
if (large &&
nla_put_u32(msg, NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS,
c->radar_detect_widths))
goto nla_put_failure;
nla_nest_end(msg, nl_combi);
}
nla_nest_end(msg, nl_combis);
return 0;
nla_put_failure:
return -ENOBUFS;
}
#ifdef CONFIG_PM
static int nl80211_send_wowlan_tcp_caps(struct cfg80211_registered_device *rdev,
struct sk_buff *msg)
{
const struct wiphy_wowlan_tcp_support *tcp = rdev->wiphy.wowlan.tcp;
struct nlattr *nl_tcp;
if (!tcp)
return 0;
nl_tcp = nla_nest_start(msg, NL80211_WOWLAN_TRIG_TCP_CONNECTION);
if (!nl_tcp)
return -ENOBUFS;
if (nla_put_u32(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD,
tcp->data_payload_max))
return -ENOBUFS;
if (nla_put_u32(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD,
tcp->data_payload_max))
return -ENOBUFS;
if (tcp->seq && nla_put_flag(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ))
return -ENOBUFS;
if (tcp->tok && nla_put(msg, NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN,
sizeof(*tcp->tok), tcp->tok))
return -ENOBUFS;
if (nla_put_u32(msg, NL80211_WOWLAN_TCP_DATA_INTERVAL,
tcp->data_interval_max))
return -ENOBUFS;
if (nla_put_u32(msg, NL80211_WOWLAN_TCP_WAKE_PAYLOAD,
tcp->wake_payload_max))
return -ENOBUFS;
nla_nest_end(msg, nl_tcp);
return 0;
}
static int nl80211_send_wowlan(struct sk_buff *msg,
struct cfg80211_registered_device *dev,
bool large)
{
struct nlattr *nl_wowlan;
if (!dev->wiphy.wowlan.flags && !dev->wiphy.wowlan.n_patterns)
return 0;
nl_wowlan = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED);
if (!nl_wowlan)
return -ENOBUFS;
if (((dev->wiphy.wowlan.flags & WIPHY_WOWLAN_ANY) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_ANY)) ||
((dev->wiphy.wowlan.flags & WIPHY_WOWLAN_DISCONNECT) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_DISCONNECT)) ||
((dev->wiphy.wowlan.flags & WIPHY_WOWLAN_MAGIC_PKT) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT)) ||
((dev->wiphy.wowlan.flags & WIPHY_WOWLAN_SUPPORTS_GTK_REKEY) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_SUPPORTED)) ||
((dev->wiphy.wowlan.flags & WIPHY_WOWLAN_GTK_REKEY_FAILURE) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE)) ||
((dev->wiphy.wowlan.flags & WIPHY_WOWLAN_EAP_IDENTITY_REQ) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST)) ||
((dev->wiphy.wowlan.flags & WIPHY_WOWLAN_4WAY_HANDSHAKE) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE)) ||
((dev->wiphy.wowlan.flags & WIPHY_WOWLAN_RFKILL_RELEASE) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE)))
return -ENOBUFS;
if (dev->wiphy.wowlan.n_patterns) {
struct nl80211_wowlan_pattern_support pat = {
.max_patterns = dev->wiphy.wowlan.n_patterns,
.min_pattern_len = dev->wiphy.wowlan.pattern_min_len,
.max_pattern_len = dev->wiphy.wowlan.pattern_max_len,
.max_pkt_offset = dev->wiphy.wowlan.max_pkt_offset,
};
if (nla_put(msg, NL80211_WOWLAN_TRIG_PKT_PATTERN,
sizeof(pat), &pat))
return -ENOBUFS;
}
if (large && nl80211_send_wowlan_tcp_caps(dev, msg))
return -ENOBUFS;
nla_nest_end(msg, nl_wowlan);
return 0;
}
#endif
static int nl80211_send_band_rateinfo(struct sk_buff *msg,
struct ieee80211_supported_band *sband)
{
struct nlattr *nl_rates, *nl_rate;
struct ieee80211_rate *rate;
int i;
/* add HT info */
if (sband->ht_cap.ht_supported &&
(nla_put(msg, NL80211_BAND_ATTR_HT_MCS_SET,
sizeof(sband->ht_cap.mcs),
&sband->ht_cap.mcs) ||
nla_put_u16(msg, NL80211_BAND_ATTR_HT_CAPA,
sband->ht_cap.cap) ||
nla_put_u8(msg, NL80211_BAND_ATTR_HT_AMPDU_FACTOR,
sband->ht_cap.ampdu_factor) ||
nla_put_u8(msg, NL80211_BAND_ATTR_HT_AMPDU_DENSITY,
sband->ht_cap.ampdu_density)))
return -ENOBUFS;
/* add VHT info */
if (sband->vht_cap.vht_supported &&
(nla_put(msg, NL80211_BAND_ATTR_VHT_MCS_SET,
sizeof(sband->vht_cap.vht_mcs),
&sband->vht_cap.vht_mcs) ||
nla_put_u32(msg, NL80211_BAND_ATTR_VHT_CAPA,
sband->vht_cap.cap)))
return -ENOBUFS;
/* add bitrates */
nl_rates = nla_nest_start(msg, NL80211_BAND_ATTR_RATES);
if (!nl_rates)
return -ENOBUFS;
for (i = 0; i < sband->n_bitrates; i++) {
nl_rate = nla_nest_start(msg, i);
if (!nl_rate)
return -ENOBUFS;
rate = &sband->bitrates[i];
if (nla_put_u32(msg, NL80211_BITRATE_ATTR_RATE,
rate->bitrate))
return -ENOBUFS;
if ((rate->flags & IEEE80211_RATE_SHORT_PREAMBLE) &&
nla_put_flag(msg,
NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE))
return -ENOBUFS;
nla_nest_end(msg, nl_rate);
}
nla_nest_end(msg, nl_rates);
return 0;
}
static int
nl80211_send_mgmt_stypes(struct sk_buff *msg,
const struct ieee80211_txrx_stypes *mgmt_stypes)
{
u16 stypes;
struct nlattr *nl_ftypes, *nl_ifs;
enum nl80211_iftype ift;
int i;
if (!mgmt_stypes)
return 0;
nl_ifs = nla_nest_start(msg, NL80211_ATTR_TX_FRAME_TYPES);
if (!nl_ifs)
return -ENOBUFS;
for (ift = 0; ift < NUM_NL80211_IFTYPES; ift++) {
nl_ftypes = nla_nest_start(msg, ift);
if (!nl_ftypes)
return -ENOBUFS;
i = 0;
stypes = mgmt_stypes[ift].tx;
while (stypes) {
if ((stypes & 1) &&
nla_put_u16(msg, NL80211_ATTR_FRAME_TYPE,
(i << 4) | IEEE80211_FTYPE_MGMT))
return -ENOBUFS;
stypes >>= 1;
i++;
}
nla_nest_end(msg, nl_ftypes);
}
nla_nest_end(msg, nl_ifs);
nl_ifs = nla_nest_start(msg, NL80211_ATTR_RX_FRAME_TYPES);
if (!nl_ifs)
return -ENOBUFS;
for (ift = 0; ift < NUM_NL80211_IFTYPES; ift++) {
nl_ftypes = nla_nest_start(msg, ift);
if (!nl_ftypes)
return -ENOBUFS;
i = 0;
stypes = mgmt_stypes[ift].rx;
while (stypes) {
if ((stypes & 1) &&
nla_put_u16(msg, NL80211_ATTR_FRAME_TYPE,
(i << 4) | IEEE80211_FTYPE_MGMT))
return -ENOBUFS;
stypes >>= 1;
i++;
}
nla_nest_end(msg, nl_ftypes);
}
nla_nest_end(msg, nl_ifs);
return 0;
}
static int nl80211_send_wiphy(struct cfg80211_registered_device *dev,
struct sk_buff *msg, u32 portid, u32 seq,
int flags, bool split, long *split_start,
long *band_start, long *chan_start)
{
void *hdr;
struct nlattr *nl_bands, *nl_band;
struct nlattr *nl_freqs, *nl_freq;
struct nlattr *nl_cmds;
enum ieee80211_band band;
struct ieee80211_channel *chan;
int i;
const struct ieee80211_txrx_stypes *mgmt_stypes =
dev->wiphy.mgmt_stypes;
long start = 0, start_chan = 0, start_band = 0;
u32 features;
hdr = nl80211hdr_put(msg, portid, seq, flags, NL80211_CMD_NEW_WIPHY);
if (!hdr)
return -ENOBUFS;
/* allow always using the variables */
if (!split) {
split_start = &start;
band_start = &start_band;
chan_start = &start_chan;
}
if (nla_put_u32(msg, NL80211_ATTR_WIPHY, dev->wiphy_idx) ||
nla_put_string(msg, NL80211_ATTR_WIPHY_NAME,
wiphy_name(&dev->wiphy)) ||
nla_put_u32(msg, NL80211_ATTR_GENERATION,
cfg80211_rdev_list_generation))
goto nla_put_failure;
switch (*split_start) {
case 0:
if (nla_put_u8(msg, NL80211_ATTR_WIPHY_RETRY_SHORT,
dev->wiphy.retry_short) ||
nla_put_u8(msg, NL80211_ATTR_WIPHY_RETRY_LONG,
dev->wiphy.retry_long) ||
nla_put_u32(msg, NL80211_ATTR_WIPHY_FRAG_THRESHOLD,
dev->wiphy.frag_threshold) ||
nla_put_u32(msg, NL80211_ATTR_WIPHY_RTS_THRESHOLD,
dev->wiphy.rts_threshold) ||
nla_put_u8(msg, NL80211_ATTR_WIPHY_COVERAGE_CLASS,
dev->wiphy.coverage_class) ||
nla_put_u8(msg, NL80211_ATTR_MAX_NUM_SCAN_SSIDS,
dev->wiphy.max_scan_ssids) ||
nla_put_u8(msg, NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS,
dev->wiphy.max_sched_scan_ssids) ||
nla_put_u16(msg, NL80211_ATTR_MAX_SCAN_IE_LEN,
dev->wiphy.max_scan_ie_len) ||
nla_put_u16(msg, NL80211_ATTR_MAX_SCHED_SCAN_IE_LEN,
dev->wiphy.max_sched_scan_ie_len) ||
nla_put_u8(msg, NL80211_ATTR_MAX_MATCH_SETS,
dev->wiphy.max_match_sets))
goto nla_put_failure;
if ((dev->wiphy.flags & WIPHY_FLAG_IBSS_RSN) &&
nla_put_flag(msg, NL80211_ATTR_SUPPORT_IBSS_RSN))
goto nla_put_failure;
if ((dev->wiphy.flags & WIPHY_FLAG_MESH_AUTH) &&
nla_put_flag(msg, NL80211_ATTR_SUPPORT_MESH_AUTH))
goto nla_put_failure;
if ((dev->wiphy.flags & WIPHY_FLAG_AP_UAPSD) &&
nla_put_flag(msg, NL80211_ATTR_SUPPORT_AP_UAPSD))
goto nla_put_failure;
if ((dev->wiphy.flags & WIPHY_FLAG_SUPPORTS_FW_ROAM) &&
nla_put_flag(msg, NL80211_ATTR_ROAM_SUPPORT))
goto nla_put_failure;
if ((dev->wiphy.flags & WIPHY_FLAG_SUPPORTS_TDLS) &&
nla_put_flag(msg, NL80211_ATTR_TDLS_SUPPORT))
goto nla_put_failure;
if ((dev->wiphy.flags & WIPHY_FLAG_TDLS_EXTERNAL_SETUP) &&
nla_put_flag(msg, NL80211_ATTR_TDLS_EXTERNAL_SETUP))
goto nla_put_failure;
(*split_start)++;
if (split)
break;
case 1:
if (nla_put(msg, NL80211_ATTR_CIPHER_SUITES,
sizeof(u32) * dev->wiphy.n_cipher_suites,
dev->wiphy.cipher_suites))
goto nla_put_failure;
if (nla_put_u8(msg, NL80211_ATTR_MAX_NUM_PMKIDS,
dev->wiphy.max_num_pmkids))
goto nla_put_failure;
if ((dev->wiphy.flags & WIPHY_FLAG_CONTROL_PORT_PROTOCOL) &&
nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_ETHERTYPE))
goto nla_put_failure;
if (nla_put_u32(msg, NL80211_ATTR_WIPHY_ANTENNA_AVAIL_TX,
dev->wiphy.available_antennas_tx) ||
nla_put_u32(msg, NL80211_ATTR_WIPHY_ANTENNA_AVAIL_RX,
dev->wiphy.available_antennas_rx))
goto nla_put_failure;
if ((dev->wiphy.flags & WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD) &&
nla_put_u32(msg, NL80211_ATTR_PROBE_RESP_OFFLOAD,
dev->wiphy.probe_resp_offload))
goto nla_put_failure;
if ((dev->wiphy.available_antennas_tx ||
dev->wiphy.available_antennas_rx) &&
dev->ops->get_antenna) {
u32 tx_ant = 0, rx_ant = 0;
int res;
res = rdev_get_antenna(dev, &tx_ant, &rx_ant);
if (!res) {
if (nla_put_u32(msg,
NL80211_ATTR_WIPHY_ANTENNA_TX,
tx_ant) ||
nla_put_u32(msg,
NL80211_ATTR_WIPHY_ANTENNA_RX,
rx_ant))
goto nla_put_failure;
}
}
(*split_start)++;
if (split)
break;
case 2:
if (nl80211_put_iftypes(msg, NL80211_ATTR_SUPPORTED_IFTYPES,
dev->wiphy.interface_modes))
goto nla_put_failure;
(*split_start)++;
if (split)
break;
case 3:
nl_bands = nla_nest_start(msg, NL80211_ATTR_WIPHY_BANDS);
if (!nl_bands)
goto nla_put_failure;
for (band = *band_start; band < IEEE80211_NUM_BANDS; band++) {
struct ieee80211_supported_band *sband;
sband = dev->wiphy.bands[band];
if (!sband)
continue;
nl_band = nla_nest_start(msg, band);
if (!nl_band)
goto nla_put_failure;
switch (*chan_start) {
case 0:
if (nl80211_send_band_rateinfo(msg, sband))
goto nla_put_failure;
(*chan_start)++;
if (split)
break;
default:
/* add frequencies */
nl_freqs = nla_nest_start(
msg, NL80211_BAND_ATTR_FREQS);
if (!nl_freqs)
goto nla_put_failure;
for (i = *chan_start - 1;
i < sband->n_channels;
i++) {
nl_freq = nla_nest_start(msg, i);
if (!nl_freq)
goto nla_put_failure;
chan = &sband->channels[i];
if (nl80211_msg_put_channel(msg, chan,
split))
goto nla_put_failure;
nla_nest_end(msg, nl_freq);
if (split)
break;
}
if (i < sband->n_channels)
*chan_start = i + 2;
else
*chan_start = 0;
nla_nest_end(msg, nl_freqs);
}
nla_nest_end(msg, nl_band);
if (split) {
/* start again here */
if (*chan_start)
band--;
break;
}
}
nla_nest_end(msg, nl_bands);
if (band < IEEE80211_NUM_BANDS)
*band_start = band + 1;
else
*band_start = 0;
/* if bands & channels are done, continue outside */
if (*band_start == 0 && *chan_start == 0)
(*split_start)++;
if (split)
break;
case 4:
nl_cmds = nla_nest_start(msg, NL80211_ATTR_SUPPORTED_COMMANDS);
if (!nl_cmds)
goto nla_put_failure;
i = 0;
#define CMD(op, n) \
do { \
if (dev->ops->op) { \
i++; \
if (nla_put_u32(msg, i, NL80211_CMD_ ## n)) \
goto nla_put_failure; \
} \
} while (0)
CMD(add_virtual_intf, NEW_INTERFACE);
CMD(change_virtual_intf, SET_INTERFACE);
CMD(add_key, NEW_KEY);
CMD(start_ap, START_AP);
CMD(add_station, NEW_STATION);
CMD(add_mpath, NEW_MPATH);
CMD(update_mesh_config, SET_MESH_CONFIG);
CMD(change_bss, SET_BSS);
CMD(auth, AUTHENTICATE);
CMD(assoc, ASSOCIATE);
CMD(deauth, DEAUTHENTICATE);
CMD(disassoc, DISASSOCIATE);
CMD(join_ibss, JOIN_IBSS);
CMD(join_mesh, JOIN_MESH);
CMD(set_pmksa, SET_PMKSA);
CMD(del_pmksa, DEL_PMKSA);
CMD(flush_pmksa, FLUSH_PMKSA);
if (dev->wiphy.flags & WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL)
CMD(remain_on_channel, REMAIN_ON_CHANNEL);
CMD(set_bitrate_mask, SET_TX_BITRATE_MASK);
CMD(mgmt_tx, FRAME);
CMD(mgmt_tx_cancel_wait, FRAME_WAIT_CANCEL);
if (dev->wiphy.flags & WIPHY_FLAG_NETNS_OK) {
i++;
if (nla_put_u32(msg, i, NL80211_CMD_SET_WIPHY_NETNS))
goto nla_put_failure;
}
if (dev->ops->set_monitor_channel || dev->ops->start_ap ||
dev->ops->join_mesh) {
i++;
if (nla_put_u32(msg, i, NL80211_CMD_SET_CHANNEL))
goto nla_put_failure;
}
CMD(set_wds_peer, SET_WDS_PEER);
if (dev->wiphy.flags & WIPHY_FLAG_SUPPORTS_TDLS) {
CMD(tdls_mgmt, TDLS_MGMT);
CMD(tdls_oper, TDLS_OPER);
}
if (dev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN)
CMD(sched_scan_start, START_SCHED_SCAN);
CMD(probe_client, PROBE_CLIENT);
CMD(set_noack_map, SET_NOACK_MAP);
if (dev->wiphy.flags & WIPHY_FLAG_REPORTS_OBSS) {
i++;
if (nla_put_u32(msg, i, NL80211_CMD_REGISTER_BEACONS))
goto nla_put_failure;
}
CMD(start_p2p_device, START_P2P_DEVICE);
CMD(set_mcast_rate, SET_MCAST_RATE);
if (split) {
CMD(crit_proto_start, CRIT_PROTOCOL_START);
CMD(crit_proto_stop, CRIT_PROTOCOL_STOP);
}
#ifdef CONFIG_NL80211_TESTMODE
CMD(testmode_cmd, TESTMODE);
#endif
#undef CMD
if (dev->ops->connect || dev->ops->auth) {
i++;
if (nla_put_u32(msg, i, NL80211_CMD_CONNECT))
goto nla_put_failure;
}
if (dev->ops->disconnect || dev->ops->deauth) {
i++;
if (nla_put_u32(msg, i, NL80211_CMD_DISCONNECT))
goto nla_put_failure;
}
nla_nest_end(msg, nl_cmds);
(*split_start)++;
if (split)
break;
case 5:
if (dev->ops->remain_on_channel &&
(dev->wiphy.flags & WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL) &&
nla_put_u32(msg,
NL80211_ATTR_MAX_REMAIN_ON_CHANNEL_DURATION,
dev->wiphy.max_remain_on_channel_duration))
goto nla_put_failure;
if ((dev->wiphy.flags & WIPHY_FLAG_OFFCHAN_TX) &&
nla_put_flag(msg, NL80211_ATTR_OFFCHANNEL_TX_OK))
goto nla_put_failure;
if (nl80211_send_mgmt_stypes(msg, mgmt_stypes))
goto nla_put_failure;
(*split_start)++;
if (split)
break;
case 6:
#ifdef CONFIG_PM
if (nl80211_send_wowlan(msg, dev, split))
goto nla_put_failure;
(*split_start)++;
if (split)
break;
#else
(*split_start)++;
#endif
case 7:
if (nl80211_put_iftypes(msg, NL80211_ATTR_SOFTWARE_IFTYPES,
dev->wiphy.software_iftypes))
goto nla_put_failure;
if (nl80211_put_iface_combinations(&dev->wiphy, msg, split))
goto nla_put_failure;
(*split_start)++;
if (split)
break;
case 8:
if ((dev->wiphy.flags & WIPHY_FLAG_HAVE_AP_SME) &&
nla_put_u32(msg, NL80211_ATTR_DEVICE_AP_SME,
dev->wiphy.ap_sme_capa))
goto nla_put_failure;
features = dev->wiphy.features;
/*
* We can only add the per-channel limit information if the
* dump is split, otherwise it makes it too big. Therefore
* only advertise it in that case.
*/
if (split)
features |= NL80211_FEATURE_ADVERTISE_CHAN_LIMITS;
if (nla_put_u32(msg, NL80211_ATTR_FEATURE_FLAGS, features))
goto nla_put_failure;
if (dev->wiphy.ht_capa_mod_mask &&
nla_put(msg, NL80211_ATTR_HT_CAPABILITY_MASK,
sizeof(*dev->wiphy.ht_capa_mod_mask),
dev->wiphy.ht_capa_mod_mask))
goto nla_put_failure;
if (dev->wiphy.flags & WIPHY_FLAG_HAVE_AP_SME &&
dev->wiphy.max_acl_mac_addrs &&
nla_put_u32(msg, NL80211_ATTR_MAC_ACL_MAX,
dev->wiphy.max_acl_mac_addrs))
goto nla_put_failure;
if (dev->wiphy.n_vendor_commands) {
const struct nl80211_vendor_cmd_info *info;
struct nlattr *nested;
nested = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
if (!nested)
goto nla_put_failure;
for (i = 0; i < dev->wiphy.n_vendor_commands; i++) {
info = &dev->wiphy.vendor_commands[i].info;
if (nla_put(msg, i + 1, sizeof(*info), info))
goto nla_put_failure;
}
nla_nest_end(msg, nested);
}
if (dev->wiphy.n_vendor_events) {
const struct nl80211_vendor_cmd_info *info;
struct nlattr *nested;
nested = nla_nest_start(msg,
NL80211_ATTR_VENDOR_EVENTS);
if (!nested)
goto nla_put_failure;
for (i = 0; i < dev->wiphy.n_vendor_events; i++) {
info = &dev->wiphy.vendor_events[i];
if (nla_put(msg, i + 1, sizeof(*info), info))
goto nla_put_failure;
}
nla_nest_end(msg, nested);
}
/*
* Any information below this point is only available to
* applications that can deal with it being split. This
* helps ensure that newly added capabilities don't break
* older tools by overrunning their buffers.
*
* We still increment split_start so that in the split
* case we'll continue with more data in the next round,
* but break unconditionally so unsplit data stops here.
*/
(*split_start)++;
break;
case 9:
if (dev->wiphy.extended_capabilities &&
(nla_put(msg, NL80211_ATTR_EXT_CAPA,
dev->wiphy.extended_capabilities_len,
dev->wiphy.extended_capabilities) ||
nla_put(msg, NL80211_ATTR_EXT_CAPA_MASK,
dev->wiphy.extended_capabilities_len,
dev->wiphy.extended_capabilities_mask)))
goto nla_put_failure;
if (dev->wiphy.vht_capa_mod_mask &&
nla_put(msg, NL80211_ATTR_VHT_CAPABILITY_MASK,
sizeof(*dev->wiphy.vht_capa_mod_mask),
dev->wiphy.vht_capa_mod_mask))
goto nla_put_failure;
/* done */
*split_start = 0;
break;
}
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, ret;
int start = cb->args[0];
struct cfg80211_registered_device *dev;
s64 filter_wiphy = -1;
bool split = false;
struct nlattr **tb;
int res;
/* will be zeroed in nlmsg_parse() */
tb = kmalloc(sizeof(*tb) * (NL80211_ATTR_MAX + 1), GFP_KERNEL);
if (!tb)
return -ENOMEM;
mutex_lock(&cfg80211_mutex);
res = nlmsg_parse(cb->nlh, GENL_HDRLEN + nl80211_fam.hdrsize,
tb, NL80211_ATTR_MAX, nl80211_policy);
if (res == 0) {
split = tb[NL80211_ATTR_SPLIT_WIPHY_DUMP];
if (tb[NL80211_ATTR_WIPHY])
filter_wiphy = nla_get_u32(tb[NL80211_ATTR_WIPHY]);
if (tb[NL80211_ATTR_WDEV])
filter_wiphy = nla_get_u64(tb[NL80211_ATTR_WDEV]) >> 32;
if (tb[NL80211_ATTR_IFINDEX]) {
struct net_device *netdev;
int ifidx = nla_get_u32(tb[NL80211_ATTR_IFINDEX]);
netdev = dev_get_by_index(sock_net(skb->sk), ifidx);
if (!netdev) {
mutex_unlock(&cfg80211_mutex);
kfree(tb);
return -ENODEV;
}
if (netdev->ieee80211_ptr) {
dev = wiphy_to_dev(
netdev->ieee80211_ptr->wiphy);
filter_wiphy = dev->wiphy_idx;
}
dev_put(netdev);
}
}
kfree(tb);
list_for_each_entry(dev, &cfg80211_rdev_list, list) {
if (!net_eq(wiphy_net(&dev->wiphy), sock_net(skb->sk)))
continue;
if (++idx <= start)
continue;
if (filter_wiphy != -1 && dev->wiphy_idx != filter_wiphy)
continue;
/* attempt to fit multiple wiphy data chunks into the skb */
do {
ret = nl80211_send_wiphy(dev, skb,
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq,
NLM_F_MULTI,
split, &cb->args[1],
&cb->args[2],
&cb->args[3]);
if (ret < 0) {
/*
* If sending the wiphy data didn't fit (ENOBUFS
* or EMSGSIZE returned), this SKB is still
* empty (so it's not too big because another
* wiphy dataset is already in the skb) and
* we've not tried to adjust the dump allocation
* yet ... then adjust the alloc size to be
* bigger, and return 1 but with the empty skb.
* This results in an empty message being RX'ed
* in userspace, but that is ignored.
*
* We can then retry with the larger buffer.
*/
if ((ret == -ENOBUFS || ret == -EMSGSIZE) &&
!skb->len &&
cb->min_dump_alloc < 4096) {
cb->min_dump_alloc = 4096;
mutex_unlock(&cfg80211_mutex);
return 1;
}
idx--;
break;
}
} while (cb->args[1] > 0);
break;
}
mutex_unlock(&cfg80211_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 = info->user_ptr[0];
msg = nlmsg_new(4096, GFP_KERNEL);
if (!msg)
return -ENOMEM;
if (nl80211_send_wiphy(dev, msg, info->snd_portid, info->snd_seq, 0,
false, NULL, NULL, NULL) < 0) {
nlmsg_free(msg);
return -ENOBUFS;
}
return genlmsg_reply(msg, info);
}
static const struct nla_policy txq_params_policy[NL80211_TXQ_ATTR_MAX + 1] = {
[NL80211_TXQ_ATTR_QUEUE] = { .type = NLA_U8 },
[NL80211_TXQ_ATTR_TXOP] = { .type = NLA_U16 },
[NL80211_TXQ_ATTR_CWMIN] = { .type = NLA_U16 },
[NL80211_TXQ_ATTR_CWMAX] = { .type = NLA_U16 },
[NL80211_TXQ_ATTR_AIFS] = { .type = NLA_U8 },
};
static int parse_txq_params(struct nlattr *tb[],
struct ieee80211_txq_params *txq_params)
{
if (!tb[NL80211_TXQ_ATTR_AC] || !tb[NL80211_TXQ_ATTR_TXOP] ||
!tb[NL80211_TXQ_ATTR_CWMIN] || !tb[NL80211_TXQ_ATTR_CWMAX] ||
!tb[NL80211_TXQ_ATTR_AIFS])
return -EINVAL;
txq_params->ac = nla_get_u8(tb[NL80211_TXQ_ATTR_AC]);
txq_params->txop = nla_get_u16(tb[NL80211_TXQ_ATTR_TXOP]);
txq_params->cwmin = nla_get_u16(tb[NL80211_TXQ_ATTR_CWMIN]);
txq_params->cwmax = nla_get_u16(tb[NL80211_TXQ_ATTR_CWMAX]);
txq_params->aifs = nla_get_u8(tb[NL80211_TXQ_ATTR_AIFS]);
if (txq_params->ac >= NL80211_NUM_ACS)
return -EINVAL;
return 0;
}
static bool nl80211_can_set_dev_channel(struct wireless_dev *wdev)
{
/*
* You can only set the channel explicitly for WDS interfaces,
* all others have their channel managed via their respective
* "establish a connection" command (connect, join, ...)
*
* For AP/GO and mesh mode, the channel can be set with the
* channel userspace API, but is only stored and passed to the
* low-level driver when the AP starts or the mesh is joined.
* This is for backward compatibility, userspace can also give
* the channel in the start-ap or join-mesh commands instead.
*
* Monitors are special as they are normally slaved to
* whatever else is going on, so they have their own special
* operation to set the monitor channel if possible.
*/
return !wdev ||
wdev->iftype == NL80211_IFTYPE_AP ||
wdev->iftype == NL80211_IFTYPE_MESH_POINT ||
wdev->iftype == NL80211_IFTYPE_MONITOR ||
wdev->iftype == NL80211_IFTYPE_P2P_GO;
}
static int nl80211_parse_chandef(struct cfg80211_registered_device *rdev,
struct genl_info *info,
struct cfg80211_chan_def *chandef)
{
u32 control_freq;
if (!info->attrs[NL80211_ATTR_WIPHY_FREQ])
return -EINVAL;
control_freq = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]);
chandef->chan = ieee80211_get_channel(&rdev->wiphy, control_freq);
chandef->width = NL80211_CHAN_WIDTH_20_NOHT;
chandef->center_freq1 = control_freq;
chandef->center_freq2 = 0;
/* Primary channel not allowed */
if (!chandef->chan || chandef->chan->flags & IEEE80211_CHAN_DISABLED)
return -EINVAL;
if (info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) {
enum nl80211_channel_type chantype;
chantype = nla_get_u32(
info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]);
switch (chantype) {
case NL80211_CHAN_NO_HT:
case NL80211_CHAN_HT20:
case NL80211_CHAN_HT40PLUS:
case NL80211_CHAN_HT40MINUS:
cfg80211_chandef_create(chandef, chandef->chan,
chantype);
break;
default:
return -EINVAL;
}
} else if (info->attrs[NL80211_ATTR_CHANNEL_WIDTH]) {
chandef->width =
nla_get_u32(info->attrs[NL80211_ATTR_CHANNEL_WIDTH]);
if (info->attrs[NL80211_ATTR_CENTER_FREQ1])
chandef->center_freq1 =
nla_get_u32(
info->attrs[NL80211_ATTR_CENTER_FREQ1]);
if (info->attrs[NL80211_ATTR_CENTER_FREQ2])
chandef->center_freq2 =
nla_get_u32(
info->attrs[NL80211_ATTR_CENTER_FREQ2]);
}
if (!cfg80211_chandef_valid(chandef))
return -EINVAL;
if (!cfg80211_chandef_usable(&rdev->wiphy, chandef,
IEEE80211_CHAN_DISABLED))
return -EINVAL;
return 0;
}
static int __nl80211_set_channel(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
struct genl_info *info)
{
struct cfg80211_chan_def chandef;
int result;
enum nl80211_iftype iftype = NL80211_IFTYPE_MONITOR;
if (wdev)
iftype = wdev->iftype;
if (!nl80211_can_set_dev_channel(wdev))
return -EOPNOTSUPP;
result = nl80211_parse_chandef(rdev, info, &chandef);
if (result)
return result;
mutex_lock(&rdev->devlist_mtx);
switch (iftype) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_P2P_GO:
if (wdev->beacon_interval) {
result = -EBUSY;
break;
}
if (!cfg80211_reg_can_beacon(&rdev->wiphy, &chandef)) {
result = -EINVAL;
break;
}
wdev->preset_chandef = chandef;
result = 0;
break;
case NL80211_IFTYPE_MESH_POINT:
result = cfg80211_set_mesh_channel(rdev, wdev, &chandef);
break;
case NL80211_IFTYPE_MONITOR:
result = cfg80211_set_monitor_channel(rdev, &chandef);
break;
default:
result = -EINVAL;
}
mutex_unlock(&rdev->devlist_mtx);
return result;
}
static int nl80211_set_channel(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct net_device *netdev = info->user_ptr[1];
return __nl80211_set_channel(rdev, netdev->ieee80211_ptr, info);
}
static int nl80211_set_wds_peer(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct net_device *dev = info->user_ptr[1];
struct wireless_dev *wdev = dev->ieee80211_ptr;
const u8 *bssid;
if (!info->attrs[NL80211_ATTR_MAC])
return -EINVAL;
if (netif_running(dev))
return -EBUSY;
if (!rdev->ops->set_wds_peer)
return -EOPNOTSUPP;
if (wdev->iftype != NL80211_IFTYPE_WDS)
return -EOPNOTSUPP;
bssid = nla_data(info->attrs[NL80211_ATTR_MAC]);
return rdev_set_wds_peer(rdev, dev, bssid);
}
static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev;
struct net_device *netdev = NULL;
struct wireless_dev *wdev;
int result = 0, rem_txq_params = 0;
struct nlattr *nl_txq_params;
u32 changed;
u8 retry_short = 0, retry_long = 0;
u32 frag_threshold = 0, rts_threshold = 0;
u8 coverage_class = 0;
/*
* Try to find the wiphy and netdev. Normally this
* function shouldn't need the netdev, but this is
* done for backward compatibility -- previously
* setting the channel was done per wiphy, but now
* it is per netdev. Previous userland like hostapd
* also passed a netdev to set_wiphy, so that it is
* possible to let that go to the right netdev!
*/
mutex_lock(&cfg80211_mutex);
if (info->attrs[NL80211_ATTR_IFINDEX]) {
int ifindex = nla_get_u32(info->attrs[NL80211_ATTR_IFINDEX]);
netdev = dev_get_by_index(genl_info_net(info), ifindex);
if (netdev && netdev->ieee80211_ptr) {
rdev = wiphy_to_dev(netdev->ieee80211_ptr->wiphy);
mutex_lock(&rdev->mtx);
} else
netdev = NULL;
}
if (!netdev) {
rdev = __cfg80211_rdev_from_attrs(genl_info_net(info),
info->attrs);
if (IS_ERR(rdev)) {
mutex_unlock(&cfg80211_mutex);
return PTR_ERR(rdev);
}
wdev = NULL;
netdev = NULL;
result = 0;
mutex_lock(&rdev->mtx);
} else
wdev = netdev->ieee80211_ptr;
/*
* end workaround code, by now the rdev is available
* and locked, and wdev may or may not be NULL.
*/
if (info->attrs[NL80211_ATTR_WIPHY_NAME])
result = cfg80211_dev_rename(
rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME]));
mutex_unlock(&cfg80211_mutex);
if (result)
goto bad_res;
if (info->attrs[NL80211_ATTR_WIPHY_TXQ_PARAMS]) {
struct ieee80211_txq_params txq_params;
struct nlattr *tb[NL80211_TXQ_ATTR_MAX + 1];
if (!rdev->ops->set_txq_params) {
result = -EOPNOTSUPP;
goto bad_res;
}
if (!netdev) {
result = -EINVAL;
goto bad_res;
}
if (netdev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
netdev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) {
result = -EINVAL;
goto bad_res;
}
if (!netif_running(netdev)) {
result = -ENETDOWN;
goto bad_res;
}
nla_for_each_nested(nl_txq_params,
info->attrs[NL80211_ATTR_WIPHY_TXQ_PARAMS],
rem_txq_params) {
nla_parse(tb, NL80211_TXQ_ATTR_MAX,
nla_data(nl_txq_params),
nla_len(nl_txq_params),
txq_params_policy);
result = parse_txq_params(tb, &txq_params);
if (result)
goto bad_res;
result = rdev_set_txq_params(rdev, netdev,
&txq_params);
if (result)
goto bad_res;
}
}
if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
result = __nl80211_set_channel(rdev,
nl80211_can_set_dev_channel(wdev) ? wdev : NULL,
info);
if (result)
goto bad_res;
}
if (info->attrs[NL80211_ATTR_WIPHY_TX_POWER_SETTING]) {
struct wireless_dev *txp_wdev = wdev;
enum nl80211_tx_power_setting type;
int idx, mbm = 0;
if (!(rdev->wiphy.features & NL80211_FEATURE_VIF_TXPOWER))
txp_wdev = NULL;
if (!rdev->ops->set_tx_power) {
result = -EOPNOTSUPP;
goto bad_res;
}
idx = NL80211_ATTR_WIPHY_TX_POWER_SETTING;
type = nla_get_u32(info->attrs[idx]);
if (!info->attrs[NL80211_ATTR_WIPHY_TX_POWER_LEVEL] &&
(type != NL80211_TX_POWER_AUTOMATIC)) {
result = -EINVAL;
goto bad_res;
}
if (type != NL80211_TX_POWER_AUTOMATIC) {
idx = NL80211_ATTR_WIPHY_TX_POWER_LEVEL;
mbm = nla_get_u32(info->attrs[idx]);
}
result = rdev_set_tx_power(rdev, txp_wdev, type, mbm);
if (result)
goto bad_res;
}
if (info->attrs[NL80211_ATTR_WIPHY_ANTENNA_TX] &&
info->attrs[NL80211_ATTR_WIPHY_ANTENNA_RX]) {
u32 tx_ant, rx_ant;
if ((!rdev->wiphy.available_antennas_tx &&
!rdev->wiphy.available_antennas_rx) ||
!rdev->ops->set_antenna) {
result = -EOPNOTSUPP;
goto bad_res;
}
tx_ant = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_ANTENNA_TX]);
rx_ant = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_ANTENNA_RX]);
/* reject antenna configurations which don't match the
* available antenna masks, except for the "all" mask */
if ((~tx_ant && (tx_ant & ~rdev->wiphy.available_antennas_tx)) ||
(~rx_ant && (rx_ant & ~rdev->wiphy.available_antennas_rx))) {
result = -EINVAL;
goto bad_res;
}
tx_ant = tx_ant & rdev->wiphy.available_antennas_tx;
rx_ant = rx_ant & rdev->wiphy.available_antennas_rx;
result = rdev_set_antenna(rdev, tx_ant, rx_ant);
if (result)
goto bad_res;
}
changed = 0;
if (info->attrs[NL80211_ATTR_WIPHY_RETRY_SHORT]) {
retry_short = nla_get_u8(
info->attrs[NL80211_ATTR_WIPHY_RETRY_SHORT]);
if (retry_short == 0) {
result = -EINVAL;
goto bad_res;
}
changed |= WIPHY_PARAM_RETRY_SHORT;
}
if (info->attrs[NL80211_ATTR_WIPHY_RETRY_LONG]) {
retry_long = nla_get_u8(
info->attrs[NL80211_ATTR_WIPHY_RETRY_LONG]);
if (retry_long == 0) {
result = -EINVAL;
goto bad_res;
}
changed |= WIPHY_PARAM_RETRY_LONG;
}
if (info->attrs[NL80211_ATTR_WIPHY_FRAG_THRESHOLD]) {
frag_threshold = nla_get_u32(
info->attrs[NL80211_ATTR_WIPHY_FRAG_THRESHOLD]);
if (frag_threshold < 256) {
result = -EINVAL;
goto bad_res;
}
if (frag_threshold != (u32) -1) {
/*
* Fragments (apart from the last one) are required to
* have even length. Make the fragmentation code
* simpler by stripping LSB should someone try to use
* odd threshold value.
*/
frag_threshold &= ~0x1;
}
changed |= WIPHY_PARAM_FRAG_THRESHOLD;
}
if (info->attrs[NL80211_ATTR_WIPHY_RTS_THRESHOLD]) {
rts_threshold = nla_get_u32(
info->attrs[NL80211_ATTR_WIPHY_RTS_THRESHOLD]);
changed |= WIPHY_PARAM_RTS_THRESHOLD;
}
if (info->attrs[NL80211_ATTR_WIPHY_COVERAGE_CLASS]) {
coverage_class = nla_get_u8(
info->attrs[NL80211_ATTR_WIPHY_COVERAGE_CLASS]);
changed |= WIPHY_PARAM_COVERAGE_CLASS;
}
if (changed) {
u8 old_retry_short, old_retry_long;
u32 old_frag_threshold, old_rts_threshold;
u8 old_coverage_class;
if (!rdev->ops->set_wiphy_params) {
result = -EOPNOTSUPP;
goto bad_res;
}
old_retry_short = rdev->wiphy.retry_short;
old_retry_long = rdev->wiphy.retry_long;
old_frag_threshold = rdev->wiphy.frag_threshold;
old_rts_threshold = rdev->wiphy.rts_threshold;
old_coverage_class = rdev->wiphy.coverage_class;
if (changed & WIPHY_PARAM_RETRY_SHORT)
rdev->wiphy.retry_short = retry_short;
if (changed & WIPHY_PARAM_RETRY_LONG)
rdev->wiphy.retry_long = retry_long;
if (changed & WIPHY_PARAM_FRAG_THRESHOLD)
rdev->wiphy.frag_threshold = frag_threshold;
if (changed & WIPHY_PARAM_RTS_THRESHOLD)
rdev->wiphy.rts_threshold = rts_threshold;
if (changed & WIPHY_PARAM_COVERAGE_CLASS)
rdev->wiphy.coverage_class = coverage_class;
result = rdev_set_wiphy_params(rdev, changed);
if (result) {
rdev->wiphy.retry_short = old_retry_short;
rdev->wiphy.retry_long = old_retry_long;
rdev->wiphy.frag_threshold = old_frag_threshold;
rdev->wiphy.rts_threshold = old_rts_threshold;
rdev->wiphy.coverage_class = old_coverage_class;
}
}
bad_res:
mutex_unlock(&rdev->mtx);
if (netdev)
dev_put(netdev);
return result;
}
static inline u64 wdev_id(struct wireless_dev *wdev)
{
return (u64)wdev->identifier |
((u64)wiphy_to_dev(wdev->wiphy)->wiphy_idx << 32);
}
static int nl80211_send_chandef(struct sk_buff *msg,
struct cfg80211_chan_def *chandef)
{
WARN_ON(!cfg80211_chandef_valid(chandef));
if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ,
chandef->chan->center_freq))
return -ENOBUFS;
switch (chandef->width) {
case NL80211_CHAN_WIDTH_20_NOHT:
case NL80211_CHAN_WIDTH_20:
case NL80211_CHAN_WIDTH_40:
if (nla_put_u32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE,
cfg80211_get_chandef_type(chandef)))
return -ENOBUFS;
break;
default:
break;
}
if (nla_put_u32(msg, NL80211_ATTR_CHANNEL_WIDTH, chandef->width))
return -ENOBUFS;
if (nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1, chandef->center_freq1))
return -ENOBUFS;
if (chandef->center_freq2 &&
nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ2, chandef->center_freq2))
return -ENOBUFS;
return 0;
}
static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flags,
struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev)
{
struct net_device *dev = wdev->netdev;
void *hdr;
hdr = nl80211hdr_put(msg, portid, seq, flags, NL80211_CMD_NEW_INTERFACE);
if (!hdr)
return -1;
if (dev &&
(nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex) ||
nla_put_string(msg, NL80211_ATTR_IFNAME, dev->name)))
goto nla_put_failure;
if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
nla_put_u32(msg, NL80211_ATTR_IFTYPE, wdev->iftype) ||
nla_put_u64(msg, NL80211_ATTR_WDEV, wdev_id(wdev)) ||
nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, wdev_address(wdev)) ||
nla_put_u32(msg, NL80211_ATTR_GENERATION,
rdev->devlist_generation ^
(cfg80211_rdev_list_generation << 2)))
goto nla_put_failure;
if (rdev->ops->get_channel) {
int ret;
struct cfg80211_chan_def chandef;
ret = rdev_get_channel(rdev, wdev, &chandef);
if (ret == 0) {
if (nl80211_send_chandef(msg, &chandef))
goto nla_put_failure;
}
}
if (wdev->ssid_len) {
if (nla_put(msg, NL80211_ATTR_SSID, wdev->ssid_len, wdev->ssid))
goto nla_put_failure;
}
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 *rdev;
struct wireless_dev *wdev;
mutex_lock(&cfg80211_mutex);
list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
if (!net_eq(wiphy_net(&rdev->wiphy), sock_net(skb->sk)))
continue;
if (wp_idx < wp_start) {
wp_idx++;
continue;
}
if_idx = 0;
mutex_lock(&rdev->devlist_mtx);
list_for_each_entry(wdev, &rdev->wdev_list, list) {
if (if_idx < if_start) {
if_idx++;
continue;
}
if (nl80211_send_iface(skb, NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, NLM_F_MULTI,
rdev, wdev) < 0) {
mutex_unlock(&rdev->devlist_mtx);
goto out;
}
if_idx++;
}
mutex_unlock(&rdev->devlist_mtx);
wp_idx++;
}
out:
mutex_unlock(&cfg80211_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 = info->user_ptr[0];
struct wireless_dev *wdev = info->user_ptr[1];
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg)
return -ENOMEM;
if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0,
dev, wdev) < 0) {
nlmsg_free(msg);
return -ENOBUFS;
}
return genlmsg_reply(msg, info);
}
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_valid_4addr(struct cfg80211_registered_device *rdev,
struct net_device *netdev, u8 use_4addr,
enum nl80211_iftype iftype)
{
if (!use_4addr) {
if (netdev && (netdev->priv_flags & IFF_BRIDGE_PORT))
return -EBUSY;
return 0;
}
switch (iftype) {
case NL80211_IFTYPE_AP_VLAN:
if (rdev->wiphy.flags & WIPHY_FLAG_4ADDR_AP)
return 0;
break;
case NL80211_IFTYPE_STATION:
if (rdev->wiphy.flags & WIPHY_FLAG_4ADDR_STATION)
return 0;
break;
default:
break;
}
return -EOPNOTSUPP;
}
static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct vif_params params;
int err;
enum nl80211_iftype otype, ntype;
struct net_device *dev = info->user_ptr[1];
u32 _flags, *flags = NULL;
bool change = false;
memset(&params, 0, sizeof(params));
otype = ntype = dev->ieee80211_ptr->iftype;
if (info->attrs[NL80211_ATTR_IFTYPE]) {
ntype = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]);
if (otype != ntype)
change = true;
if (ntype > NL80211_IFTYPE_MAX)
return -EINVAL;
}
if (info->attrs[NL80211_ATTR_MESH_ID]) {
struct wireless_dev *wdev = dev->ieee80211_ptr;
if (ntype != NL80211_IFTYPE_MESH_POINT)
return -EINVAL;
if (netif_running(dev))
return -EBUSY;
wdev_lock(wdev);
BUILD_BUG_ON(IEEE80211_MAX_SSID_LEN !=
IEEE80211_MAX_MESH_ID_LEN);
wdev->mesh_id_up_len =
nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
memcpy(wdev->ssid, nla_data(info->attrs[NL80211_ATTR_MESH_ID]),
wdev->mesh_id_up_len);
wdev_unlock(wdev);
}
if (info->attrs[NL80211_ATTR_4ADDR]) {
params.use_4addr = !!nla_get_u8(info->attrs[NL80211_ATTR_4ADDR]);
change = true;
err = nl80211_valid_4addr(rdev, dev, params.use_4addr, ntype);
if (err)
return err;
} else {
params.use_4addr = -1;
}
if (info->attrs[NL80211_ATTR_MNTR_FLAGS]) {
if (ntype != NL80211_IFTYPE_MONITOR)
return -EINVAL;
err = parse_monitor_flags(info->attrs[NL80211_ATTR_MNTR_FLAGS],
&_flags);
if (err)
return err;
flags = &_flags;
change = true;
}
if (change)
err = cfg80211_change_iface(rdev, dev, ntype, flags, &params);
else
err = 0;
if (!err && params.use_4addr != -1)
dev->ieee80211_ptr->use_4addr = params.use_4addr;
return err;
}
static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct vif_params params;
struct wireless_dev *wdev;
struct sk_buff *msg;
int err;
enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED;
u32 flags;
memset(&params, 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;
}
if (!rdev->ops->add_virtual_intf ||
!(rdev->wiphy.interface_modes & (1 << type)))
return -EOPNOTSUPP;
if (type == NL80211_IFTYPE_P2P_DEVICE && info->attrs[NL80211_ATTR_MAC]) {
nla_memcpy(params.macaddr, info->attrs[NL80211_ATTR_MAC],
ETH_ALEN);
if (!is_valid_ether_addr(params.macaddr))
return -EADDRNOTAVAIL;
}
if (info->attrs[NL80211_ATTR_4ADDR]) {
params.use_4addr = !!nla_get_u8(info->attrs[NL80211_ATTR_4ADDR]);
err = nl80211_valid_4addr(rdev, NULL, params.use_4addr, type);
if (err)
return err;
}
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg)
return -ENOMEM;
err = parse_monitor_flags(type == NL80211_IFTYPE_MONITOR ?
info->attrs[NL80211_ATTR_MNTR_FLAGS] : NULL,
&flags);
wdev = rdev_add_virtual_intf(rdev,
nla_data(info->attrs[NL80211_ATTR_IFNAME]),
type, err ? NULL : &flags, &params);
if (IS_ERR(wdev)) {
nlmsg_free(msg);
return PTR_ERR(wdev);
}
switch (type) {
case NL80211_IFTYPE_MESH_POINT:
if (!info->attrs[NL80211_ATTR_MESH_ID])
break;
wdev_lock(wdev);
BUILD_BUG_ON(IEEE80211_MAX_SSID_LEN !=
IEEE80211_MAX_MESH_ID_LEN);
wdev->mesh_id_up_len =
nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
memcpy(wdev->ssid, nla_data(info->attrs[NL80211_ATTR_MESH_ID]),
wdev->mesh_id_up_len);
wdev_unlock(wdev);
break;
case NL80211_IFTYPE_P2P_DEVICE:
/*
* P2P Device doesn't have a netdev, so doesn't go
* through the netdev notifier and must be added here
*/
mutex_init(&wdev->mtx);
INIT_LIST_HEAD(&wdev->event_list);
spin_lock_init(&wdev->event_lock);
INIT_LIST_HEAD(&wdev->mgmt_registrations);
spin_lock_init(&wdev->mgmt_registrations_lock);
mutex_lock(&rdev->devlist_mtx);
wdev->identifier = ++rdev->wdev_id;
list_add_rcu(&wdev->list, &rdev->wdev_list);
rdev->devlist_generation++;
mutex_unlock(&rdev->devlist_mtx);
break;
default:
break;
}
if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0,
rdev, wdev) < 0) {
nlmsg_free(msg);
return -ENOBUFS;
}
return genlmsg_reply(msg, info);
}
static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct wireless_dev *wdev = info->user_ptr[1];
if (!rdev->ops->del_virtual_intf)
return -EOPNOTSUPP;
/*
* If we remove a wireless device without a netdev then clear
* user_ptr[1] so that nl80211_post_doit won't dereference it
* to check if it needs to do dev_put(). Otherwise it crashes
* since the wdev has been freed, unlike with a netdev where
* we need the dev_put() for the netdev to really be freed.
*/
if (!wdev->netdev)
info->user_ptr[1] = NULL;
return rdev_del_virtual_intf(rdev, wdev);
}
static int nl80211_set_noack_map(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct net_device *dev = info->user_ptr[1];
u16 noack_map;
if (!info->attrs[NL80211_ATTR_NOACK_MAP])
return -EINVAL;
if (!rdev->ops->set_noack_map)
return -EOPNOTSUPP;
noack_map = nla_get_u16(info->attrs[NL80211_ATTR_NOACK_MAP]);
return rdev_set_noack_map(rdev, dev, noack_map);
}
struct get_key_cookie {
struct sk_buff *msg;
int error;
int idx;
};
static void get_key_callback(void *c, struct key_params *params)
{
struct nlattr *key;
struct get_key_cookie *cookie = c;
if ((params->key &&
nla_put(cookie->msg, NL80211_ATTR_KEY_DATA,
params->key_len, params->key)) ||
(params->seq &&
nla_put(cookie->msg, NL80211_ATTR_KEY_SEQ,
params->seq_len, params->seq)) ||
(params->cipher &&
nla_put_u32(cookie->msg, NL80211_ATTR_KEY_CIPHER,
params->cipher)))
goto nla_put_failure;
key = nla_nest_start(cookie->msg, NL80211_ATTR_KEY);
if (!key)
goto nla_put_failure;
if ((params->key &&
nla_put(cookie->msg, NL80211_KEY_DATA,
params->key_len, params->key)) ||
(params->seq &&
nla_put(cookie->msg, NL80211_KEY_SEQ,
params->seq_len, params->seq)) ||
(params->cipher &&
nla_put_u32(cookie->msg, NL80211_KEY_CIPHER,
params->cipher)))
goto nla_put_failure;
if (nla_put_u8(cookie->msg, NL80211_ATTR_KEY_IDX, cookie->idx))
goto nla_put_failure;
nla_nest_end(cookie->msg, key);
return;
nla_put_failure:
cookie->error = 1;
}
static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
int err;
struct net_device *dev = info->user_ptr[1];
u8 key_idx = 0;
const u8 *mac_addr = NULL;
bool pairwise;
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 > 5)
return -EINVAL;
if (info->attrs[NL80211_ATTR_MAC])
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
pairwise = !!mac_addr;
if (info->attrs[NL80211_ATTR_KEY_TYPE]) {
u32 kt = nla_get_u32(info->attrs[NL80211_ATTR_KEY_TYPE]);
if (kt >= NUM_NL80211_KEYTYPES)
return -EINVAL;
if (kt != NL80211_KEYTYPE_GROUP &&
kt != NL80211_KEYTYPE_PAIRWISE)
return -EINVAL;
pairwise = kt == NL80211_KEYTYPE_PAIRWISE;
}
if (!rdev->ops->get_key)
return -EOPNOTSUPP;
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg)
return -ENOMEM;
hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
NL80211_CMD_NEW_KEY);
if (!hdr)
goto nla_put_failure;
cookie.msg = msg;
cookie.idx = key_idx;
if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex) ||
nla_put_u8(msg, NL80211_ATTR_KEY_IDX, key_idx))
goto nla_put_failure;
if (mac_addr &&
nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr))
goto nla_put_failure;
if (pairwise && mac_addr &&
!(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN))
return -ENOENT;
err = rdev_get_key(rdev, dev, key_idx, pairwise, mac_addr, &cookie,
get_key_callback);
if (err)
goto free_msg;
if (cookie.error)
goto nla_put_failure;
genlmsg_end(msg, hdr);
return genlmsg_reply(msg, info);
nla_put_failure:
err = -ENOBUFS;
free_msg:
nlmsg_free(msg);
return err;
}
static int nl80211_set_key(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct key_parse key;
int err;
struct net_device *dev = info->user_ptr[1];
err = nl80211_parse_key(info, &key);
if (err)
return err;
if (key.idx < 0)
return -EINVAL;
/* only support setting default key */
if (!key.def && !key.defmgmt)
return -EINVAL;
wdev_lock(dev->ieee80211_ptr);
if (key.def) {
if (!rdev->ops->set_default_key) {
err = -EOPNOTSUPP;
goto out;
}
err = nl80211_key_allowed(dev->ieee80211_ptr);
if (err)
goto out;
err = rdev_set_default_key(rdev, dev, key.idx,
key.def_uni, key.def_multi);
if (err)
goto out;
#ifdef CONFIG_CFG80211_WEXT
dev->ieee80211_ptr->wext.default_key = key.idx;
#endif
} else {
if (key.def_uni || !key.def_multi) {
err = -EINVAL;
goto out;
}
if (!rdev->ops->set_default_mgmt_key) {
err = -EOPNOTSUPP;
goto out;
}
err = nl80211_key_allowed(dev->ieee80211_ptr);
if (err)
goto out;
err = rdev_set_default_mgmt_key(rdev, dev, key.idx);
if (err)
goto out;
#ifdef CONFIG_CFG80211_WEXT
dev->ieee80211_ptr->wext.default_mgmt_key = key.idx;
#endif
}
out:
wdev_unlock(dev->ieee80211_ptr);
return err;
}
static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
int err;
struct net_device *dev = info->user_ptr[1];
struct key_parse key;
const u8 *mac_addr = NULL;
err = nl80211_parse_key(info, &key);
if (err)
return err;
if (!key.p.key)
return -EINVAL;
if (info->attrs[NL80211_ATTR_MAC])
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
if (key.type == -1) {
if (mac_addr)
key.type = NL80211_KEYTYPE_PAIRWISE;
else
key.type = NL80211_KEYTYPE_GROUP;
}
/* for now */
if (key.type != NL80211_KEYTYPE_PAIRWISE &&
key.type != NL80211_KEYTYPE_GROUP)
return -EINVAL;
if (!rdev->ops->add_key)
return -EOPNOTSUPP;
if (cfg80211_validate_key_settings(rdev, &key.p, key.idx,
key.type == NL80211_KEYTYPE_PAIRWISE,
mac_addr))
return -EINVAL;
wdev_lock(dev->ieee80211_ptr);
err = nl80211_key_allowed(dev->ieee80211_ptr);
if (!err)
err = rdev_add_key(rdev, dev, key.idx,
key.type == NL80211_KEYTYPE_PAIRWISE,
mac_addr, &key.p);
wdev_unlock(dev->ieee80211_ptr);
return err;
}
static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
int err;
struct net_device *dev = info->user_ptr[1];
u8 *mac_addr = NULL;
struct key_parse key;
err = nl80211_parse_key(info, &key);
if (err)
return err;
if (info->attrs[NL80211_ATTR_MAC])
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
if (key.type == -1) {
if (mac_addr)
key.type = NL80211_KEYTYPE_PAIRWISE;
else
key.type = NL80211_KEYTYPE_GROUP;
}
/* for now */
if (key.type != NL80211_KEYTYPE_PAIRWISE &&
key.type != NL80211_KEYTYPE_GROUP)
return -EINVAL;
if (!rdev->ops->del_key)
return -EOPNOTSUPP;
wdev_lock(dev->ieee80211_ptr);
err = nl80211_key_allowed(dev->ieee80211_ptr);
if (key.type == NL80211_KEYTYPE_PAIRWISE && mac_addr &&
!(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN))
err = -ENOENT;
if (!err)
err = rdev_del_key(rdev, dev, key.idx,
key.type == NL80211_KEYTYPE_PAIRWISE,
mac_addr);
#ifdef CONFIG_CFG80211_WEXT
if (!err) {
if (key.idx == dev->ieee80211_ptr->wext.default_key)
dev->ieee80211_ptr->wext.default_key = -1;
else if (key.idx == dev->ieee80211_ptr->wext.default_mgmt_key)
dev->ieee80211_ptr->wext.default_mgmt_key = -1;
}
#endif
wdev_unlock(dev->ieee80211_ptr);