#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/nospec.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.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 struct genl_family nl80211_fam;
enum nl80211_multicast_groups {
NL80211_MCGRP_CONFIG,
NL80211_MCGRP_SCAN,
NL80211_MCGRP_REGULATORY,
NL80211_MCGRP_MLME,
NL80211_MCGRP_VENDOR,
NL80211_MCGRP_NAN,
NL80211_MCGRP_TESTMODE
};
static const struct genl_multicast_group nl80211_mcgrps[] = {
[NL80211_MCGRP_CONFIG] = { .name = NL80211_MULTICAST_GROUP_CONFIG },
[NL80211_MCGRP_SCAN] = { .name = NL80211_MULTICAST_GROUP_SCAN },
[NL80211_MCGRP_REGULATORY] = { .name = NL80211_MULTICAST_GROUP_REG },
[NL80211_MCGRP_MLME] = { .name = NL80211_MULTICAST_GROUP_MLME },
[NL80211_MCGRP_VENDOR] = { .name = NL80211_MULTICAST_GROUP_VENDOR },
[NL80211_MCGRP_NAN] = { .name = NL80211_MULTICAST_GROUP_NAN },
#ifdef CONFIG_NL80211_TESTMODE
[NL80211_MCGRP_TESTMODE] = { .name = NL80211_MULTICAST_GROUP_TESTMODE }
#endif
};
static struct wireless_dev *
__cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,
struct net *netns, struct nlattr **attrs)
{
struct wireless_dev *result = NULL;
bool have_ifidx = attrs[NL80211_ATTR_IFINDEX];
bool have_wdev_id = attrs[NL80211_ATTR_WDEV];
u64 wdev_id = 0;
int wiphy_idx = -1;
int ifidx = -1;
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;
}
if (rdev) {
struct wireless_dev *wdev;
lockdep_assert_held(&rdev->wiphy.mtx);
list_for_each_entry(wdev, &rdev->wiphy.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;
}
}
return result ?: ERR_PTR(-ENODEV);
}
ASSERT_RTNL();
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;
list_for_each_entry(wdev, &rdev->wiphy.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;
}
}
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_RTNL();
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) {
list_for_each_entry(wdev, &tmp->wiphy.wdev_list, list) {
if (wdev->identifier != (u32)wdev_id)
continue;
found = true;
break;
}
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_rdev(
netdev->ieee80211_ptr->wiphy);
else
tmp = NULL;
if (!tmp)
return ERR_PTR(-EINVAL);
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;
}
static struct cfg80211_registered_device *
cfg80211_get_dev_from_info(struct net *netns, struct genl_info *info)
{
return __cfg80211_rdev_from_attrs(netns, info->attrs);
}
static int validate_beacon_head(const struct nlattr *attr,
struct netlink_ext_ack *extack)
{
const u8 *data = nla_data(attr);
unsigned int len = nla_len(attr);
const struct element *elem;
const struct ieee80211_mgmt *mgmt = (void *)data;
unsigned int fixedlen, hdrlen;
bool s1g_bcn;
if (len < offsetofend(typeof(*mgmt), frame_control))
goto err;
s1g_bcn = ieee80211_is_s1g_beacon(mgmt->frame_control);
if (s1g_bcn) {
fixedlen = offsetof(struct ieee80211_ext,
u.s1g_beacon.variable);
hdrlen = offsetof(struct ieee80211_ext, u.s1g_beacon);
} else {
fixedlen = offsetof(struct ieee80211_mgmt,
u.beacon.variable);
hdrlen = offsetof(struct ieee80211_mgmt, u.beacon);
}
if (len < fixedlen)
goto err;
if (ieee80211_hdrlen(mgmt->frame_control) != hdrlen)
goto err;
data += fixedlen;
len -= fixedlen;
for_each_element(elem, data, len) {
}
if (for_each_element_completed(elem, data, len))
return 0;
err:
NL_SET_ERR_MSG_ATTR(extack, attr, "malformed beacon head");
return -EINVAL;
}
static int validate_ie_attr(const struct nlattr *attr,
struct netlink_ext_ack *extack)
{
const u8 *data = nla_data(attr);
unsigned int len = nla_len(attr);
const struct element *elem;
for_each_element(elem, data, len) {
}
if (for_each_element_completed(elem, data, len))
return 0;
NL_SET_ERR_MSG_ATTR(extack, attr, "malformed information elements");
return -EINVAL;
}
static int validate_he_capa(const struct nlattr *attr,
struct netlink_ext_ack *extack)
{
if (!ieee80211_he_capa_size_ok(nla_data(attr), nla_len(attr)))
return -EINVAL;
return 0;
}
static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR];
static const struct nla_policy
nl80211_ftm_responder_policy[NL80211_FTM_RESP_ATTR_MAX + 1] = {
[NL80211_FTM_RESP_ATTR_ENABLED] = { .type = NLA_FLAG, },
[NL80211_FTM_RESP_ATTR_LCI] = { .type = NLA_BINARY,
.len = U8_MAX },
[NL80211_FTM_RESP_ATTR_CIVICLOC] = { .type = NLA_BINARY,
.len = U8_MAX },
};
static const struct nla_policy
nl80211_pmsr_ftm_req_attr_policy[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1] = {
[NL80211_PMSR_FTM_REQ_ATTR_ASAP] = { .type = NLA_FLAG },
[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE] = { .type = NLA_U32 },
[NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP] =
NLA_POLICY_MAX(NLA_U8, 15),
[NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD] = { .type = NLA_U16 },
[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION] =
NLA_POLICY_MAX(NLA_U8, 15),
[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST] =
NLA_POLICY_MAX(NLA_U8, 31),
[NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES] = { .type = NLA_U8 },
[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI] = { .type = NLA_FLAG },
[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC] = { .type = NLA_FLAG },
[NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED] = { .type = NLA_FLAG },
[NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED] = { .type = NLA_FLAG },
[NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK] = { .type = NLA_FLAG },
};
static const struct nla_policy
nl80211_pmsr_req_data_policy[NL80211_PMSR_TYPE_MAX + 1] = {
[NL80211_PMSR_TYPE_FTM] =
NLA_POLICY_NESTED(nl80211_pmsr_ftm_req_attr_policy),
};
static const struct nla_policy
nl80211_pmsr_req_attr_policy[NL80211_PMSR_REQ_ATTR_MAX + 1] = {
[NL80211_PMSR_REQ_ATTR_DATA] =
NLA_POLICY_NESTED(nl80211_pmsr_req_data_policy),
[NL80211_PMSR_REQ_ATTR_GET_AP_TSF] = { .type = NLA_FLAG },
};
static const struct nla_policy
nl80211_pmsr_peer_attr_policy[NL80211_PMSR_PEER_ATTR_MAX + 1] = {
[NL80211_PMSR_PEER_ATTR_ADDR] = NLA_POLICY_ETH_ADDR,
[NL80211_PMSR_PEER_ATTR_CHAN] = NLA_POLICY_NESTED(nl80211_policy),
[NL80211_PMSR_PEER_ATTR_REQ] =
NLA_POLICY_NESTED(nl80211_pmsr_req_attr_policy),
[NL80211_PMSR_PEER_ATTR_RESP] = { .type = NLA_REJECT },
};
static const struct nla_policy
nl80211_pmsr_attr_policy[NL80211_PMSR_ATTR_MAX + 1] = {
[NL80211_PMSR_ATTR_MAX_PEERS] = { .type = NLA_REJECT },
[NL80211_PMSR_ATTR_REPORT_AP_TSF] = { .type = NLA_REJECT },
[NL80211_PMSR_ATTR_RANDOMIZE_MAC_ADDR] = { .type = NLA_REJECT },
[NL80211_PMSR_ATTR_TYPE_CAPA] = { .type = NLA_REJECT },
[NL80211_PMSR_ATTR_PEERS] =
NLA_POLICY_NESTED_ARRAY(nl80211_pmsr_peer_attr_policy),
};
static const struct nla_policy
he_obss_pd_policy[NL80211_HE_OBSS_PD_ATTR_MAX + 1] = {
[NL80211_HE_OBSS_PD_ATTR_MIN_OFFSET] =
NLA_POLICY_RANGE(NLA_U8, 1, 20),
[NL80211_HE_OBSS_PD_ATTR_MAX_OFFSET] =
NLA_POLICY_RANGE(NLA_U8, 1, 20),
[NL80211_HE_OBSS_PD_ATTR_NON_SRG_MAX_OFFSET] =
NLA_POLICY_RANGE(NLA_U8, 1, 20),
[NL80211_HE_OBSS_PD_ATTR_BSS_COLOR_BITMAP] =
NLA_POLICY_EXACT_LEN(8),
[NL80211_HE_OBSS_PD_ATTR_PARTIAL_BSSID_BITMAP] =
NLA_POLICY_EXACT_LEN(8),
[NL80211_HE_OBSS_PD_ATTR_SR_CTRL] = { .type = NLA_U8 },
};
static const struct nla_policy
he_bss_color_policy[NL80211_HE_BSS_COLOR_ATTR_MAX + 1] = {
[NL80211_HE_BSS_COLOR_ATTR_COLOR] = NLA_POLICY_RANGE(NLA_U8, 1, 63),
[NL80211_HE_BSS_COLOR_ATTR_DISABLED] = { .type = NLA_FLAG },
[NL80211_HE_BSS_COLOR_ATTR_PARTIAL] = { .type = NLA_FLAG },
};
static const struct nla_policy nl80211_txattr_policy[NL80211_TXRATE_MAX + 1] = {
[NL80211_TXRATE_LEGACY] = { .type = NLA_BINARY,
.len = NL80211_MAX_SUPP_RATES },
[NL80211_TXRATE_HT] = { .type = NLA_BINARY,
.len = NL80211_MAX_SUPP_HT_RATES },
[NL80211_TXRATE_VHT] = NLA_POLICY_EXACT_LEN_WARN(sizeof(struct nl80211_txrate_vht)),
[NL80211_TXRATE_GI] = { .type = NLA_U8 },
[NL80211_TXRATE_HE] = NLA_POLICY_EXACT_LEN(sizeof(struct nl80211_txrate_he)),
[NL80211_TXRATE_HE_GI] = NLA_POLICY_RANGE(NLA_U8,
NL80211_RATE_INFO_HE_GI_0_8,
NL80211_RATE_INFO_HE_GI_3_2),
[NL80211_TXRATE_HE_LTF] = NLA_POLICY_RANGE(NLA_U8,
NL80211_RATE_INFO_HE_1XLTF,
NL80211_RATE_INFO_HE_4XLTF),
};
static const struct nla_policy
nl80211_tid_config_attr_policy[NL80211_TID_CONFIG_ATTR_MAX + 1] = {
[NL80211_TID_CONFIG_ATTR_VIF_SUPP] = { .type = NLA_U64 },
[NL80211_TID_CONFIG_ATTR_PEER_SUPP] = { .type = NLA_U64 },
[NL80211_TID_CONFIG_ATTR_OVERRIDE] = { .type = NLA_FLAG },
[NL80211_TID_CONFIG_ATTR_TIDS] = NLA_POLICY_RANGE(NLA_U16, 1, 0xff),
[NL80211_TID_CONFIG_ATTR_NOACK] =
NLA_POLICY_MAX(NLA_U8, NL80211_TID_CONFIG_DISABLE),
[NL80211_TID_CONFIG_ATTR_RETRY_SHORT] = NLA_POLICY_MIN(NLA_U8, 1),
[NL80211_TID_CONFIG_ATTR_RETRY_LONG] = NLA_POLICY_MIN(NLA_U8, 1),
[NL80211_TID_CONFIG_ATTR_AMPDU_CTRL] =
NLA_POLICY_MAX(NLA_U8, NL80211_TID_CONFIG_DISABLE),
[NL80211_TID_CONFIG_ATTR_RTSCTS_CTRL] =
NLA_POLICY_MAX(NLA_U8, NL80211_TID_CONFIG_DISABLE),
[NL80211_TID_CONFIG_ATTR_AMSDU_CTRL] =
NLA_POLICY_MAX(NLA_U8, NL80211_TID_CONFIG_DISABLE),
[NL80211_TID_CONFIG_ATTR_TX_RATE_TYPE] =
NLA_POLICY_MAX(NLA_U8, NL80211_TX_RATE_FIXED),
[NL80211_TID_CONFIG_ATTR_TX_RATE] =
NLA_POLICY_NESTED(nl80211_txattr_policy),
};
static const struct nla_policy
nl80211_fils_discovery_policy[NL80211_FILS_DISCOVERY_ATTR_MAX + 1] = {
[NL80211_FILS_DISCOVERY_ATTR_INT_MIN] = NLA_POLICY_MAX(NLA_U32, 10000),
[NL80211_FILS_DISCOVERY_ATTR_INT_MAX] = NLA_POLICY_MAX(NLA_U32, 10000),
[NL80211_FILS_DISCOVERY_ATTR_TMPL] =
NLA_POLICY_RANGE(NLA_BINARY,
NL80211_FILS_DISCOVERY_TMPL_MIN_LEN,
IEEE80211_MAX_DATA_LEN),
};
static const struct nla_policy
nl80211_unsol_bcast_probe_resp_policy[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_MAX + 1] = {
[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_INT] = NLA_POLICY_MAX(NLA_U32, 20),
[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_TMPL] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_DATA_LEN }
};
static const struct nla_policy
sar_specs_policy[NL80211_SAR_ATTR_SPECS_MAX + 1] = {
[NL80211_SAR_ATTR_SPECS_POWER] = { .type = NLA_S32 },
[NL80211_SAR_ATTR_SPECS_RANGE_INDEX] = {.type = NLA_U32 },
};
static const struct nla_policy
sar_policy[NL80211_SAR_ATTR_MAX + 1] = {
[NL80211_SAR_ATTR_TYPE] = NLA_POLICY_MAX(NLA_U32, NUM_NL80211_SAR_TYPE),
[NL80211_SAR_ATTR_SPECS] = NLA_POLICY_NESTED_ARRAY(sar_specs_policy),
};
static const struct nla_policy
nl80211_mbssid_config_policy[NL80211_MBSSID_CONFIG_ATTR_MAX + 1] = {
[NL80211_MBSSID_CONFIG_ATTR_MAX_INTERFACES] = NLA_POLICY_MIN(NLA_U8, 2),
[NL80211_MBSSID_CONFIG_ATTR_MAX_EMA_PROFILE_PERIODICITY] =
NLA_POLICY_MIN(NLA_U8, 1),
[NL80211_MBSSID_CONFIG_ATTR_INDEX] = { .type = NLA_U8 },
[NL80211_MBSSID_CONFIG_ATTR_TX_IFINDEX] = { .type = NLA_U32 },
[NL80211_MBSSID_CONFIG_ATTR_EMA] = { .type = NLA_FLAG },
};
static const struct nla_policy
nl80211_sta_wme_policy[NL80211_STA_WME_MAX + 1] = {
[NL80211_STA_WME_UAPSD_QUEUES] = { .type = NLA_U8 },
[NL80211_STA_WME_MAX_SP] = { .type = NLA_U8 },
};
static struct netlink_range_validation nl80211_punct_bitmap_range = {
.min = 0,
.max = 0xffff,
};
static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[0] = { .strict_start_type = NL80211_ATTR_HE_OBSS_PD },
[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_EDMG_CHANNELS] = NLA_POLICY_RANGE(NLA_U8,
NL80211_EDMG_CHANNELS_MIN,
NL80211_EDMG_CHANNELS_MAX),
[NL80211_ATTR_WIPHY_EDMG_BW_CONFIG] = NLA_POLICY_RANGE(NLA_U8,
NL80211_EDMG_BW_CONFIG_MIN,
NL80211_EDMG_BW_CONFIG_MAX),
[NL80211_ATTR_CHANNEL_WIDTH] = { .type = NLA_U32 },
[NL80211_ATTR_CENTER_FREQ1] = { .type = NLA_U32 },
[NL80211_ATTR_CENTER_FREQ1_OFFSET] = NLA_POLICY_RANGE(NLA_U32, 0, 999),
[NL80211_ATTR_CENTER_FREQ2] = { .type = NLA_U32 },
[NL80211_ATTR_WIPHY_RETRY_SHORT] = NLA_POLICY_MIN(NLA_U8, 1),
[NL80211_ATTR_WIPHY_RETRY_LONG] = NLA_POLICY_MIN(NLA_U8, 1),
[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_WIPHY_DYN_ACK] = { .type = NLA_FLAG },
[NL80211_ATTR_IFTYPE] = NLA_POLICY_MAX(NLA_U32, NL80211_IFTYPE_MAX),
[NL80211_ATTR_IFINDEX] = { .type = NLA_U32 },
[NL80211_ATTR_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ-1 },
[NL80211_ATTR_MAC] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
[NL80211_ATTR_PREV_BSSID] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
[NL80211_ATTR_KEY] = { .type = NLA_NESTED, },
[NL80211_ATTR_KEY_DATA] = { .type = NLA_BINARY,
.len = WLAN_MAX_KEY_LEN },
[NL80211_ATTR_KEY_IDX] = NLA_POLICY_MAX(NLA_U8, 7),
[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] =
NLA_POLICY_MAX(NLA_U32, NUM_NL80211_KEYTYPES),
[NL80211_ATTR_BEACON_INTERVAL] = { .type = NLA_U32 },
[NL80211_ATTR_DTIM_PERIOD] = { .type = NLA_U32 },
[NL80211_ATTR_BEACON_HEAD] =
NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_beacon_head,
IEEE80211_MAX_DATA_LEN),
[NL80211_ATTR_BEACON_TAIL] =
NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_ie_attr,
IEEE80211_MAX_DATA_LEN),
[NL80211_ATTR_STA_AID] =
NLA_POLICY_RANGE(NLA_U16, 1, IEEE80211_MAX_AID),
[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] =
NLA_POLICY_MAX(NLA_U8, NUM_NL80211_PLINK_ACTIONS - 1),
[NL80211_ATTR_STA_TX_POWER_SETTING] =
NLA_POLICY_RANGE(NLA_U8,
NL80211_TX_POWER_AUTOMATIC,
NL80211_TX_POWER_FIXED),
[NL80211_ATTR_STA_TX_POWER] = { .type = NLA_S16 },
[NL80211_ATTR_STA_VLAN] = { .type = NLA_U32 },
[NL80211_ATTR_MNTR_FLAGS] = { },
[NL80211_ATTR_MESH_ID] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_MESH_ID_LEN },
[NL80211_ATTR_MPATH_NEXT_HOP] = NLA_POLICY_ETH_ADDR_COMPAT,
[NL80211_ATTR_REG_ALPHA2] = NLA_POLICY_RANGE(NLA_BINARY, 2, 3),
[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] = NLA_POLICY_EXACT_LEN_WARN(NL80211_HT_CAPABILITY_LEN),
[NL80211_ATTR_MGMT_SUBTYPE] = { .type = NLA_U8 },
[NL80211_ATTR_IE] = NLA_POLICY_VALIDATE_FN(NLA_BINARY,
validate_ie_attr,
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] = NLA_POLICY_RANGE(NLA_U32,
NL80211_MFP_NO,
NL80211_MFP_OPTIONAL),
[NL80211_ATTR_STA_FLAGS2] =
NLA_POLICY_EXACT_LEN_WARN(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_CONTROL_PORT_OVER_NL80211] = { .type = NLA_FLAG },
[NL80211_ATTR_PRIVACY] = { .type = NLA_FLAG },
[NL80211_ATTR_STATUS_CODE] = { .type = NLA_U16 },
[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] = NLA_POLICY_EXACT_LEN_WARN(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] = NLA_POLICY_RANGE(NLA_U32,
NL80211_PS_DISABLED,
NL80211_PS_ENABLED),
[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] =
NLA_POLICY_MAX(NLA_U8, NUM_NL80211_PLINK_STATES - 1),
[NL80211_ATTR_MEASUREMENT_DURATION] = { .type = NLA_U16 },
[NL80211_ATTR_MEASUREMENT_DURATION_MANDATORY] = { .type = NLA_FLAG },
[NL80211_ATTR_MESH_PEER_AID] =
NLA_POLICY_RANGE(NLA_U16, 1, IEEE80211_MAX_AID),
[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] =
NLA_POLICY_RANGE(NLA_U32,
NL80211_HIDDEN_SSID_NOT_IN_USE,
NL80211_HIDDEN_SSID_ZERO_CONTENTS),
[NL80211_ATTR_IE_PROBE_RESP] =
NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_ie_attr,
IEEE80211_MAX_DATA_LEN),
[NL80211_ATTR_IE_ASSOC_RESP] =
NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_ie_attr,
IEEE80211_MAX_DATA_LEN),
[NL80211_ATTR_ROAM_SUPPORT] = { .type = NLA_FLAG },
[NL80211_ATTR_STA_WME] = NLA_POLICY_NESTED(nl80211_sta_wme_policy),
[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_TDLS_INITIATOR] = { .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_AUTH_DATA] = NLA_POLICY_MIN_LEN(4),
[NL80211_ATTR_VHT_CAPABILITY] = NLA_POLICY_EXACT_LEN_WARN(NL80211_VHT_CAPABILITY_LEN),
[NL80211_ATTR_SCAN_FLAGS] = { .type = NLA_U32 },
[NL80211_ATTR_P2P_CTWINDOW] = NLA_POLICY_MAX(NLA_U8, 127),
[NL80211_ATTR_P2P_OPPPS] = NLA_POLICY_MAX(NLA_U8, 1),
[NL80211_ATTR_LOCAL_MESH_POWER_MODE] =
NLA_POLICY_RANGE(NLA_U32,
NL80211_MESH_POWER_UNKNOWN + 1,
NL80211_MESH_POWER_MAX),
[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_CRIT_PROT_ID] = { .type = NLA_U16 },
[NL80211_ATTR_MAX_CRIT_PROT_DURATION] =
NLA_POLICY_MAX(NLA_U16, NL80211_CRIT_PROTO_MAX_DURATION),
[NL80211_ATTR_PEER_AID] =
NLA_POLICY_RANGE(NLA_U16, 1, IEEE80211_MAX_AID),
[NL80211_ATTR_CH_SWITCH_COUNT] = { .type = NLA_U32 },
[NL80211_ATTR_CH_SWITCH_BLOCK_TX] = { .type = NLA_FLAG },
[NL80211_ATTR_CSA_IES] = { .type = NLA_NESTED },
[NL80211_ATTR_CNTDWN_OFFS_BEACON] = { .type = NLA_BINARY },
[NL80211_ATTR_CNTDWN_OFFS_PRESP] = { .type = NLA_BINARY },
[NL80211_ATTR_STA_SUPPORTED_CHANNELS] = NLA_POLICY_MIN_LEN(2),
[NL80211_ATTR_STA_SUPPORTED_OPER_CLASSES] =
NLA_POLICY_RANGE(NLA_BINARY, 2, 253),
[NL80211_ATTR_HANDLE_DFS] = { .type = NLA_FLAG },
[NL80211_ATTR_OPMODE_NOTIF] = { .type = NLA_U8 },
[NL80211_ATTR_VENDOR_ID] = { .type = NLA_U32 },
[NL80211_ATTR_VENDOR_SUBCMD] = { .type = NLA_U32 },
[NL80211_ATTR_VENDOR_DATA] = { .type = NLA_BINARY },
[NL80211_ATTR_QOS_MAP] = NLA_POLICY_RANGE(NLA_BINARY,
IEEE80211_QOS_MAP_LEN_MIN,
IEEE80211_QOS_MAP_LEN_MAX),
[NL80211_ATTR_MAC_HINT] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
[NL80211_ATTR_WIPHY_FREQ_HINT] = { .type = NLA_U32 },
[NL80211_ATTR_TDLS_PEER_CAPABILITY] = { .type = NLA_U32 },
[NL80211_ATTR_SOCKET_OWNER] = { .type = NLA_FLAG },
[NL80211_ATTR_CSA_C_OFFSETS_TX] = { .type = NLA_BINARY },
[NL80211_ATTR_USE_RRM] = { .type = NLA_FLAG },
[NL80211_ATTR_TSID] = NLA_POLICY_MAX(NLA_U8, IEEE80211_NUM_TIDS - 1),
[NL80211_ATTR_USER_PRIO] =
NLA_POLICY_MAX(NLA_U8, IEEE80211_NUM_UPS - 1),
[NL80211_ATTR_ADMITTED_TIME] = { .type = NLA_U16 },
[NL80211_ATTR_SMPS_MODE] = { .type = NLA_U8 },
[NL80211_ATTR_OPER_CLASS] = { .type = NLA_U8 },
[NL80211_ATTR_MAC_MASK] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
[NL80211_ATTR_WIPHY_SELF_MANAGED_REG] = { .type = NLA_FLAG },
[NL80211_ATTR_NETNS_FD] = { .type = NLA_U32 },
[NL80211_ATTR_SCHED_SCAN_DELAY] = { .type = NLA_U32 },
[NL80211_ATTR_REG_INDOOR] = { .type = NLA_FLAG },
[NL80211_ATTR_PBSS] = { .type = NLA_FLAG },
[NL80211_ATTR_BSS_SELECT] = { .type = NLA_NESTED },
[NL80211_ATTR_STA_SUPPORT_P2P_PS] =
NLA_POLICY_MAX(NLA_U8, NUM_NL80211_P2P_PS_STATUS - 1),
[NL80211_ATTR_MU_MIMO_GROUP_DATA] = {
.len = VHT_MUMIMO_GROUPS_DATA_LEN
},
[NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
[NL80211_ATTR_NAN_MASTER_PREF] = NLA_POLICY_MIN(NLA_U8, 1),
[NL80211_ATTR_BANDS] = { .type = NLA_U32 },
[NL80211_ATTR_NAN_FUNC] = { .type = NLA_NESTED },
[NL80211_ATTR_FILS_KEK] = { .type = NLA_BINARY,
.len = FILS_MAX_KEK_LEN },
[NL80211_ATTR_FILS_NONCES] = NLA_POLICY_EXACT_LEN_WARN(2 * FILS_NONCE_LEN),
[NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED] = { .type = NLA_FLAG, },
[NL80211_ATTR_BSSID] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
[NL80211_ATTR_SCHED_SCAN_RELATIVE_RSSI] = { .type = NLA_S8 },
[NL80211_ATTR_SCHED_SCAN_RSSI_ADJUST] = {
.len = sizeof(struct nl80211_bss_select_rssi_adjust)
},
[NL80211_ATTR_TIMEOUT_REASON] = { .type = NLA_U32 },
[NL80211_ATTR_FILS_ERP_USERNAME] = { .type = NLA_BINARY,
.len = FILS_ERP_MAX_USERNAME_LEN },
[NL80211_ATTR_FILS_ERP_REALM] = { .type = NLA_BINARY,
.len = FILS_ERP_MAX_REALM_LEN },
[NL80211_ATTR_FILS_ERP_NEXT_SEQ_NUM] = { .type = NLA_U16 },
[NL80211_ATTR_FILS_ERP_RRK] = { .type = NLA_BINARY,
.len = FILS_ERP_MAX_RRK_LEN },
[NL80211_ATTR_FILS_CACHE_ID] = NLA_POLICY_EXACT_LEN_WARN(2),
[NL80211_ATTR_PMK] = { .type = NLA_BINARY, .len = PMK_MAX_LEN },
[NL80211_ATTR_PMKR0_NAME] = NLA_POLICY_EXACT_LEN(WLAN_PMK_NAME_LEN),
[NL80211_ATTR_SCHED_SCAN_MULTI] = { .type = NLA_FLAG },
[NL80211_ATTR_EXTERNAL_AUTH_SUPPORT] = { .type = NLA_FLAG },
[NL80211_ATTR_TXQ_LIMIT] = { .type = NLA_U32 },
[NL80211_ATTR_TXQ_MEMORY_LIMIT] = { .type = NLA_U32 },
[NL80211_ATTR_TXQ_QUANTUM] = { .type = NLA_U32 },
[NL80211_ATTR_HE_CAPABILITY] =
NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_he_capa,
NL80211_HE_MAX_CAPABILITY_LEN),
[NL80211_ATTR_FTM_RESPONDER] =
NLA_POLICY_NESTED(nl80211_ftm_responder_policy),
[NL80211_ATTR_TIMEOUT] = NLA_POLICY_MIN(NLA_U32, 1),
[NL80211_ATTR_PEER_MEASUREMENTS] =
NLA_POLICY_NESTED(nl80211_pmsr_attr_policy),
[NL80211_ATTR_AIRTIME_WEIGHT] = NLA_POLICY_MIN(NLA_U16, 1),
[NL80211_ATTR_SAE_PASSWORD] = { .type = NLA_BINARY,
.len = SAE_PASSWORD_MAX_LEN },
[NL80211_ATTR_TWT_RESPONDER] = { .type = NLA_FLAG },
[NL80211_ATTR_HE_OBSS_PD] = NLA_POLICY_NESTED(he_obss_pd_policy),
[NL80211_ATTR_VLAN_ID] = NLA_POLICY_RANGE(NLA_U16, 1, VLAN_N_VID - 2),
[NL80211_ATTR_HE_BSS_COLOR] = NLA_POLICY_NESTED(he_bss_color_policy),
[NL80211_ATTR_TID_CONFIG] =
NLA_POLICY_NESTED_ARRAY(nl80211_tid_config_attr_policy),
[NL80211_ATTR_CONTROL_PORT_NO_PREAUTH] = { .type = NLA_FLAG },
[NL80211_ATTR_PMK_LIFETIME] = NLA_POLICY_MIN(NLA_U32, 1),
[NL80211_ATTR_PMK_REAUTH_THRESHOLD] = NLA_POLICY_RANGE(NLA_U8, 1, 100),
[NL80211_ATTR_RECEIVE_MULTICAST] = { .type = NLA_FLAG },
[NL80211_ATTR_WIPHY_FREQ_OFFSET] = NLA_POLICY_RANGE(NLA_U32, 0, 999),
[NL80211_ATTR_SCAN_FREQ_KHZ] = { .type = NLA_NESTED },
[NL80211_ATTR_HE_6GHZ_CAPABILITY] =
NLA_POLICY_EXACT_LEN(sizeof(struct ieee80211_he_6ghz_capa)),
[NL80211_ATTR_FILS_DISCOVERY] =
NLA_POLICY_NESTED(nl80211_fils_discovery_policy),
[NL80211_ATTR_UNSOL_BCAST_PROBE_RESP] =
NLA_POLICY_NESTED(nl80211_unsol_bcast_probe_resp_policy),
[NL80211_ATTR_S1G_CAPABILITY] =
NLA_POLICY_EXACT_LEN(IEEE80211_S1G_CAPABILITY_LEN),
[NL80211_ATTR_S1G_CAPABILITY_MASK] =
NLA_POLICY_EXACT_LEN(IEEE80211_S1G_CAPABILITY_LEN),
[NL80211_ATTR_SAE_PWE] =
NLA_POLICY_RANGE(NLA_U8, NL80211_SAE_PWE_HUNT_AND_PECK,
NL80211_SAE_PWE_BOTH),
[NL80211_ATTR_RECONNECT_REQUESTED] = { .type = NLA_REJECT },
[NL80211_ATTR_SAR_SPEC] = NLA_POLICY_NESTED(sar_policy),
[NL80211_ATTR_DISABLE_HE] = { .type = NLA_FLAG },
[NL80211_ATTR_OBSS_COLOR_BITMAP] = { .type = NLA_U64 },
[NL80211_ATTR_COLOR_CHANGE_COUNT] = { .type = NLA_U8 },
[NL80211_ATTR_COLOR_CHANGE_COLOR] = { .type = NLA_U8 },
[NL80211_ATTR_COLOR_CHANGE_ELEMS] = NLA_POLICY_NESTED(nl80211_policy),
[NL80211_ATTR_MBSSID_CONFIG] =
NLA_POLICY_NESTED(nl80211_mbssid_config_policy),
[NL80211_ATTR_MBSSID_ELEMS] = { .type = NLA_NESTED },
[NL80211_ATTR_RADAR_BACKGROUND] = { .type = NLA_FLAG },
[NL80211_ATTR_AP_SETTINGS_FLAGS] = { .type = NLA_U32 },
[NL80211_ATTR_EHT_CAPABILITY] =
NLA_POLICY_RANGE(NLA_BINARY,
NL80211_EHT_MIN_CAPABILITY_LEN,
NL80211_EHT_MAX_CAPABILITY_LEN),
[NL80211_ATTR_DISABLE_EHT] = { .type = NLA_FLAG },
[NL80211_ATTR_MLO_LINKS] =
NLA_POLICY_NESTED_ARRAY(nl80211_policy),
[NL80211_ATTR_MLO_LINK_ID] =
NLA_POLICY_RANGE(NLA_U8, 0, IEEE80211_MLD_MAX_NUM_LINKS),
[NL80211_ATTR_MLD_ADDR] = NLA_POLICY_EXACT_LEN(ETH_ALEN),
[NL80211_ATTR_MLO_SUPPORT] = { .type = NLA_FLAG },
[NL80211_ATTR_MAX_NUM_AKM_SUITES] = { .type = NLA_REJECT },
[NL80211_ATTR_PUNCT_BITMAP] =
NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range),
};
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] = NLA_POLICY_MAX(NLA_U32, NUM_NL80211_KEYTYPES - 1),
[NL80211_KEY_DEFAULT_TYPES] = { .type = NLA_NESTED },
[NL80211_KEY_MODE] = NLA_POLICY_RANGE(NLA_U8, 0, NL80211_KEY_SET_TX),
};
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 },
};
#ifdef CONFIG_PM
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 },
[NL80211_WOWLAN_TRIG_NET_DETECT] = { .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] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
[NL80211_WOWLAN_TCP_SRC_PORT] = { .type = NLA_U16 },
[NL80211_WOWLAN_TCP_DST_PORT] = { .type = NLA_U16 },
[NL80211_WOWLAN_TCP_DATA_PAYLOAD] = NLA_POLICY_MIN_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] = NLA_POLICY_MIN_LEN(1),
[NL80211_WOWLAN_TCP_WAKE_MASK] = NLA_POLICY_MIN_LEN(1),
};
#endif /* CONFIG_PM */
static const struct nla_policy
nl80211_coalesce_policy[NUM_NL80211_ATTR_COALESCE_RULE] = {
[NL80211_ATTR_COALESCE_RULE_DELAY] = { .type = NLA_U32 },
[NL80211_ATTR_COALESCE_RULE_CONDITION] =
NLA_POLICY_RANGE(NLA_U32,
NL80211_COALESCE_CONDITION_MATCH,
NL80211_COALESCE_CONDITION_NO_MATCH),
[NL80211_ATTR_COALESCE_RULE_PKT_PATTERN] = { .type = NLA_NESTED },
};
static const struct nla_policy
nl80211_rekey_policy[NUM_NL80211_REKEY_DATA] = {
[NL80211_REKEY_DATA_KEK] = {
.type = NLA_BINARY,
.len = NL80211_KEK_EXT_LEN
},
[NL80211_REKEY_DATA_KCK] = {
.type = NLA_BINARY,
.len = NL80211_KCK_EXT_LEN_32
},
[NL80211_REKEY_DATA_REPLAY_CTR] = NLA_POLICY_EXACT_LEN(NL80211_REPLAY_CTR_LEN),
[NL80211_REKEY_DATA_AKM] = { .type = NLA_U32 },
};
static const struct nla_policy
nl80211_match_band_rssi_policy[NUM_NL80211_BANDS] = {
[NL80211_BAND_2GHZ] = { .type = NLA_S32 },
[NL80211_BAND_5GHZ] = { .type = NLA_S32 },
[NL80211_BAND_6GHZ] = { .type = NLA_S32 },
[NL80211_BAND_60GHZ] = { .type = NLA_S32 },
[NL80211_BAND_LC] = { .type = NLA_S32 },
};
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_BSSID] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
[NL80211_SCHED_SCAN_MATCH_ATTR_RSSI] = { .type = NLA_U32 },
[NL80211_SCHED_SCAN_MATCH_PER_BAND_RSSI] =
NLA_POLICY_NESTED(nl80211_match_band_rssi_policy),
};
static const struct nla_policy
nl80211_plan_policy[NL80211_SCHED_SCAN_PLAN_MAX + 1] = {
[NL80211_SCHED_SCAN_PLAN_INTERVAL] = { .type = NLA_U32 },
[NL80211_SCHED_SCAN_PLAN_ITERATIONS] = { .type = NLA_U32 },
};
static const struct nla_policy
nl80211_bss_select_policy[NL80211_BSS_SELECT_ATTR_MAX + 1] = {
[NL80211_BSS_SELECT_ATTR_RSSI] = { .type = NLA_FLAG },
[NL80211_BSS_SELECT_ATTR_BAND_PREF] = { .type = NLA_U32 },
[NL80211_BSS_SELECT_ATTR_RSSI_ADJUST] = {
.len = sizeof(struct nl80211_bss_select_rssi_adjust)
},
};
static const struct nla_policy
nl80211_nan_func_policy[NL80211_NAN_FUNC_ATTR_MAX + 1] = {
[NL80211_NAN_FUNC_TYPE] =
NLA_POLICY_MAX(NLA_U8, NL80211_NAN_FUNC_MAX_TYPE),
[NL80211_NAN_FUNC_SERVICE_ID] = {
.len = NL80211_NAN_FUNC_SERVICE_ID_LEN },
[NL80211_NAN_FUNC_PUBLISH_TYPE] = { .type = NLA_U8 },
[NL80211_NAN_FUNC_PUBLISH_BCAST] = { .type = NLA_FLAG },
[NL80211_NAN_FUNC_SUBSCRIBE_ACTIVE] = { .type = NLA_FLAG },
[NL80211_NAN_FUNC_FOLLOW_UP_ID] = { .type = NLA_U8 },
[NL80211_NAN_FUNC_FOLLOW_UP_REQ_ID] = { .type = NLA_U8 },
[NL80211_NAN_FUNC_FOLLOW_UP_DEST] = NLA_POLICY_EXACT_LEN_WARN(ETH_ALEN),
[NL80211_NAN_FUNC_CLOSE_RANGE] = { .type = NLA_FLAG },
[NL80211_NAN_FUNC_TTL] = { .type = NLA_U32 },
[NL80211_NAN_FUNC_SERVICE_INFO] = { .type = NLA_BINARY,
.len = NL80211_NAN_FUNC_SERVICE_SPEC_INFO_MAX_LEN },
[NL80211_NAN_FUNC_SRF] = { .type = NLA_NESTED },
[NL80211_NAN_FUNC_RX_MATCH_FILTER] = { .type = NLA_NESTED },
[NL80211_NAN_FUNC_TX_MATCH_FILTER] = { .type = NLA_NESTED },
[NL80211_NAN_FUNC_INSTANCE_ID] = { .type = NLA_U8 },
[NL80211_NAN_FUNC_TERM_REASON] = { .type = NLA_U8 },
};
static const struct nla_policy
nl80211_nan_srf_policy[NL80211_NAN_SRF_ATTR_MAX + 1] = {
[NL80211_NAN_SRF_INCLUDE] = { .type = NLA_FLAG },
[NL80211_NAN_SRF_BF] = { .type = NLA_BINARY,
.len = NL80211_NAN_FUNC_SRF_MAX_LEN },
[NL80211_NAN_SRF_BF_IDX] = { .type = NLA_U8 },
[NL80211_NAN_SRF_MAC_ADDRS] = { .type = NLA_NESTED },
};
static const struct nla_policy
nl80211_packet_pattern_policy[MAX_NL80211_PKTPAT + 1] = {
[NL80211_PKTPAT_MASK] = { .type = NLA_BINARY, },
[NL80211_PKTPAT_PATTERN] = { .type = NLA_BINARY, },
[NL80211_PKTPAT_OFFSET] = { .type = NLA_U32 },
};
static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,
struct cfg80211_registered_device **rdev,
struct wireless_dev **wdev,
struct nlattr **attrbuf)
{
int err;
if (!cb->args[0]) {
struct nlattr **attrbuf_free = NULL;
if (!attrbuf) {
attrbuf = kcalloc(NUM_NL80211_ATTR, sizeof(*attrbuf),
GFP_KERNEL);
if (!attrbuf)
return -ENOMEM;
attrbuf_free = attrbuf;
}
err = nlmsg_parse_deprecated(cb->nlh,
GENL_HDRLEN + nl80211_fam.hdrsize,
attrbuf, nl80211_fam.maxattr,
nl80211_policy, NULL);
if (err) {
kfree(attrbuf_free);
return err;
}
rtnl_lock();
*wdev = __cfg80211_wdev_from_attrs(NULL, sock_net(cb->skb->sk),
attrbuf);
kfree(attrbuf_free);
if (IS_ERR(*wdev)) {
rtnl_unlock();
return PTR_ERR(*wdev);
}
*rdev = wiphy_to_rdev((*wdev)->wiphy);
mutex_lock(&(*rdev)->wiphy.mtx);
rtnl_unlock();
cb->args[0] = (*rdev)->wiphy_idx + 1;
cb->args[1] = (*wdev)->identifier;
} else {
struct wiphy *wiphy;
struct wireless_dev *tmp;
rtnl_lock();
wiphy = wiphy_idx_to_wiphy(cb->args[0] - 1);
if (!wiphy) {
rtnl_unlock();
return -ENODEV;
}
*rdev = wiphy_to_rdev(wiphy);
*wdev = NULL;
list_for_each_entry(tmp, &(*rdev)->wiphy.wdev_list, list) {
if (tmp->identifier == cb->args[1]) {
*wdev = tmp;
break;
}
}
if (!*wdev) {
rtnl_unlock();
return -ENODEV;
}
mutex_lock(&(*rdev)->wiphy.mtx);
rtnl_unlock();
}
return 0;
}
void *nl80211hdr_put(struct sk_buff *skb, u32 portid, u32 seq,
int flags, u8 cmd)
{
return genlmsg_put(skb, portid, seq, &nl80211_fam, flags, cmd);
}
static int nl80211_msg_put_wmm_rules(struct sk_buff *msg,
const struct ieee80211_reg_rule *rule)
{
int j;
struct nlattr *nl_wmm_rules =
nla_nest_start_noflag(msg, NL80211_FREQUENCY_ATTR_WMM);
if (!nl_wmm_rules)
goto nla_put_failure;
for (j = 0; j < IEEE80211_NUM_ACS; j++) {
struct nlattr *nl_wmm_rule = nla_nest_start_noflag(msg, j);
if (!nl_wmm_rule)
goto nla_put_failure;
if (nla_put_u16(msg, NL80211_WMMR_CW_MIN,
rule->wmm_rule.client[j].cw_min) ||
nla_put_u16(msg, NL80211_WMMR_CW_MAX,
rule->wmm_rule.client[j].cw_max) ||
nla_put_u8(msg, NL80211_WMMR_AIFSN,
rule->wmm_rule.client[j].aifsn) ||
nla_put_u16(msg, NL80211_WMMR_TXOP,
rule->wmm_rule.client[j].cot))
goto nla_put_failure;
nla_nest_end(msg, nl_wmm_rule);
}
nla_nest_end(msg, nl_wmm_rules);
return 0;
nla_put_failure:
return -ENOBUFS;
}
static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy,
struct ieee80211_channel *chan,
bool large)
{
if (!large && chan->flags &
(IEEE80211_CHAN_NO_10MHZ | IEEE80211_CHAN_NO_20MHZ))
return 0;
if (!large && chan->freq_offset)
return 0;
if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_FREQ,
chan->center_freq))
goto nla_put_failure;
if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_OFFSET, chan->freq_offset))
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_NO_IR) {
if (nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_IR))
goto nla_put_failure;
if (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 (nla_put_u32(msg,
NL80211_FREQUENCY_ATTR_DFS_CAC_TIME,
chan->dfs_cac_ms))
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 ((chan->flags & IEEE80211_CHAN_INDOOR_ONLY) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_INDOOR_ONLY))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_IR_CONCURRENT) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_IR_CONCURRENT))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_NO_20MHZ) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_20MHZ))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_NO_10MHZ) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_10MHZ))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_NO_HE) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_HE))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_1MHZ) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_1MHZ))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_2MHZ) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_2MHZ))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_4MHZ) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_4MHZ))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_8MHZ) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_8MHZ))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_16MHZ) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_16MHZ))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_NO_320MHZ) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_320MHZ))
goto nla_put_failure;
if ((chan->flags & IEEE80211_CHAN_NO_EHT) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_EHT))
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;
if (large) {
const struct ieee80211_reg_rule *rule =
freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq));
if (!IS_ERR_OR_NULL(rule) && rule->has_wmm) {
if (nl80211_msg_put_wmm_rules(msg, rule))
goto nla_put_failure;
}
}
return 0;
nla_put_failure:
return -ENOBUFS;
}
static bool nl80211_put_txq_stats(struct sk_buff *msg,
struct cfg80211_txq_stats *txqstats,
int attrtype)
{
struct nlattr *txqattr;
#define PUT_TXQVAL_U32(attr, memb) do { \
if (txqstats->filled & BIT(NL80211_TXQ_STATS_ ## attr) && \
nla_put_u32(msg, NL80211_TXQ_STATS_ ## attr, txqstats->memb)) \
return false; \
} while (0)
txqattr = nla_nest_start_noflag(msg, attrtype);
if (!txqattr)
return false;
PUT_TXQVAL_U32(BACKLOG_BYTES, backlog_bytes);
PUT_TXQVAL_U32(BACKLOG_PACKETS, backlog_packets);
PUT_TXQVAL_U32(FLOWS, flows);
PUT_TXQVAL_U32(DROPS, drops);
PUT_TXQVAL_U32(ECN_MARKS, ecn_marks);
PUT_TXQVAL_U32(OVERLIMIT, overlimit);
PUT_TXQVAL_U32(OVERMEMORY, overmemory);
PUT_TXQVAL_U32(COLLISIONS, collisions);
PUT_TXQVAL_U32(TX_BYTES, tx_bytes);
PUT_TXQVAL_U32(TX_PACKETS, tx_packets);
PUT_TXQVAL_U32(MAX_FLOWS, max_flows);
nla_nest_end(msg, txqattr);
#undef PUT_TXQVAL_U32
return true;
}
static unsigned int nl80211_link_id(struct nlattr **attrs)
{
struct nlattr *linkid = attrs[NL80211_ATTR_MLO_LINK_ID];
if (!linkid)
return 0;
return nla_get_u8(linkid);
}
static int nl80211_link_id_or_invalid(struct nlattr **attrs)
{
struct nlattr *linkid = attrs[NL80211_ATTR_MLO_LINK_ID];
if (!linkid)
return -1;
return nla_get_u8(linkid);
}
struct key_parse {
struct key_params p;
int idx;
int type;
bool def, defmgmt, defbeacon;
bool def_uni, def_multi;
};
static int nl80211_parse_key_new(struct genl_info *info, struct nlattr *key,
struct key_parse *k)
{
struct nlattr *tb[NL80211_KEY_MAX + 1];
int err = nla_parse_nested_deprecated(tb, NL80211_KEY_MAX, key,
nl80211_key_policy,
info->extack);
if (err)
return err;
k->def = !!tb[NL80211_KEY_DEFAULT];
k->defmgmt = !!tb[NL80211_KEY_DEFAULT_MGMT];
k->defbeacon = !!tb[NL80211_KEY_DEFAULT_BEACON];
if (k->def) {
k->def_uni = true;
k->def_multi = true;
}
if (k->defmgmt || k->defbeacon)
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 (tb[NL80211_KEY_DEFAULT_TYPES]) {
struct nlattr *kdt[NUM_NL80211_KEY_DEFAULT_TYPES];
err = nla_parse_nested_deprecated(kdt,
NUM_NL80211_KEY_DEFAULT_TYPES - 1,
tb[NL80211_KEY_DEFAULT_TYPES],
nl80211_key_default_policy,
info->extack);
if (err)
return err;
k->def_uni = kdt[NL80211_KEY_DEFAULT_TYPE_UNICAST];
k->def_multi = kdt[NL80211_KEY_DEFAULT_TYPE_MULTICAST];
}
if (tb[NL80211_KEY_MODE])
k->p.mode = nla_get_u8(tb[NL80211_KEY_MODE]);
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 (info->attrs[NL80211_ATTR_KEY_DEFAULT_TYPES]) {
struct nlattr *kdt[NUM_NL80211_KEY_DEFAULT_TYPES];
int err = nla_parse_nested_deprecated(kdt,
NUM_NL80211_KEY_DEFAULT_TYPES - 1,
info->attrs[NL80211_ATTR_KEY_DEFAULT_TYPES],
nl80211_key_default_policy,
info->extack);
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, info->attrs[NL80211_ATTR_KEY], k);
else
err = nl80211_parse_key_old(info, k);
if (err)
return err;
if ((k->def ? 1 : 0) + (k->defmgmt ? 1 : 0) +
(k->defbeacon ? 1 : 0) > 1) {
GENL_SET_ERR_MSG(info,
"key with multiple default flags is invalid");
return -EINVAL;
}
if (k->defmgmt || k->defbeacon) {
if (k->def_uni || !k->def_multi) {
GENL_SET_ERR_MSG(info,
"defmgmt/defbeacon key must be mcast");
return -EINVAL;
}
}
if (k->idx != -1) {
if (k->defmgmt) {
if (k->idx < 4 || k->idx > 5) {
GENL_SET_ERR_MSG(info,
"defmgmt key idx not 4 or 5");
return -EINVAL;
}
} else if (k->defbeacon) {
if (k->idx < 6 || k->idx > 7) {
GENL_SET_ERR_MSG(info,
"defbeacon key idx not 6 or 7");
return -EINVAL;
}
} else if (k->def) {
if (k->idx < 0 || k->idx > 3) {
GENL_SET_ERR_MSG(info, "def key idx not 0-3");
return -EINVAL;
}
} else {
if (k->idx < 0 || k->idx > 7) {
GENL_SET_ERR_MSG(info, "key idx not 0-7");
return -EINVAL;
}
}
}
return 0;
}
static struct cfg80211_cached_keys *
nl80211_parse_connkeys(struct cfg80211_registered_device *rdev,
struct genl_info *info, bool *no_ht)
{
struct nlattr *keys = info->attrs[NL80211_ATTR_KEYS];
struct key_parse parse;
struct nlattr *key;
struct cfg80211_cached_keys *result;
int rem, err, def = 0;
bool have_key = false;
nla_for_each_nested(key, keys, rem) {
have_key = true;
break;
}
if (!have_key)
return NULL;
result = kzalloc(sizeof(*result), GFP_KERNEL);
if (!result)
return ERR_PTR(-ENOMEM);
result->def = -1;
nla_for_each_nested(key, keys, rem) {
memset(&parse, 0, sizeof(parse));
parse.idx = -1;
err = nl80211_parse_key_new(info, key, &parse);
if (err)
goto error;
err = -EINVAL;
if (!parse.p.key)
goto error;
if (parse.idx < 0 || parse.idx > 3) {
GENL_SET_ERR_MSG(info, "key index out of range [0-3]");
goto error;
}
if (parse.def) {
if (def) {
GENL_SET_ERR_MSG(info,
"only one key can be default");
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;
if (parse.p.cipher != WLAN_CIPHER_SUITE_WEP40 &&
parse.p.cipher != WLAN_CIPHER_SUITE_WEP104) {
GENL_SET_ERR_MSG(info, "connect key must be WEP");
err = -EINVAL;
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 (no_ht)
*no_ht = true;
}
if (result->def < 0) {
err = -EINVAL;
GENL_SET_ERR_MSG(info, "need a default/TX key");
goto error;
}
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->u.ibss.current_bss)
return 0;
return -ENOLINK;
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_P2P_CLIENT:
if (wdev->connected)
return 0;
return -ENOLINK;
case NL80211_IFTYPE_NAN:
if (wiphy_ext_feature_isset(wdev->wiphy,
NL80211_EXT_FEATURE_SECURE_NAN))
return 0;
return -EINVAL;
case NL80211_IFTYPE_UNSPECIFIED:
case NL80211_IFTYPE_OCB:
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_P2P_DEVICE:
case NL80211_IFTYPE_WDS:
case NUM_NL80211_IFTYPES:
return -EINVAL;
}
return 0;
}
static struct ieee80211_channel *nl80211_get_valid_chan(struct wiphy *wiphy,
u32 freq)
{
struct ieee80211_channel *chan;
chan = ieee80211_get_channel_khz(wiphy, freq);
if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
return NULL;
return chan;
}
static int nl80211_put_iftypes(struct sk_buff *msg, u32 attr, u16 ifmodes)
{
struct nlattr *nl_modes = nla_nest_start_noflag(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_noflag(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_noflag(msg, i + 1);
if (!nl_combi)
goto nla_put_failure;
nl_limits = nla_nest_start_noflag(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_noflag(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) ||
nla_put_u32(msg, NL80211_IFACE_COMB_RADAR_DETECT_REGIONS,
c->radar_detect_regions)))
goto nla_put_failure;
if (c->beacon_int_min_gcd &&
nla_put_u32(msg, NL80211_IFACE_COMB_BI_MIN_GCD,
c->beacon_int_min_gcd))
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_noflag(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 *rdev,
bool large)
{
struct nlattr *nl_wowlan;
if (!rdev->wiphy.wowlan)
return 0;
nl_wowlan = nla_nest_start_noflag(msg,
NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED);
if (!nl_wowlan)
return -ENOBUFS;
if (((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_ANY) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_ANY)) ||
((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_DISCONNECT) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_DISCONNECT)) ||
((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_MAGIC_PKT) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT)) ||
((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_SUPPORTS_GTK_REKEY) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_SUPPORTED)) ||
((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_GTK_REKEY_FAILURE) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE)) ||
((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_EAP_IDENTITY_REQ) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST)) ||
((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_4WAY_HANDSHAKE) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE)) ||
((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_RFKILL_RELEASE) &&
nla_put_flag(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE)))
return -ENOBUFS;
if (rdev->wiphy.wowlan->n_patterns) {
struct nl80211_pattern_support pat = {
.max_patterns = rdev->wiphy.wowlan->n_patterns,
.min_pattern_len = rdev->wiphy.wowlan->pattern_min_len,
.max_pattern_len = rdev->wiphy.wowlan->pattern_max_len,
.max_pkt_offset = rdev->wiphy.wowlan->max_pkt_offset,
};
if (nla_put(msg, NL80211_WOWLAN_TRIG_PKT_PATTERN,
sizeof(pat), &pat))
return -ENOBUFS;
}
if ((rdev->wiphy.wowlan->flags & WIPHY_WOWLAN_NET_DETECT) &&
nla_put_u32(msg, NL80211_WOWLAN_TRIG_NET_DETECT,
rdev->wiphy.wowlan->max_nd_match_sets))
return -ENOBUFS;
if (large && nl80211_send_wowlan_tcp_caps(rdev, msg))
return -ENOBUFS;
nla_nest_end(msg, nl_wowlan);
return 0;
}
#endif
static int nl80211_send_coalesce(struct sk_buff *msg,
struct cfg80211_registered_device *rdev)
{
struct nl80211_coalesce_rule_support rule;
if (!rdev->wiphy.coalesce)
return 0;
rule.max_rules = rdev->wiphy.coalesce->n_rules;
rule.max_delay = rdev->wiphy.coalesce->max_delay;
rule.pat.max_patterns = rdev->wiphy.coalesce->n_patterns;
rule.pat.min_pattern_len = rdev->wiphy.coalesce->pattern_min_len;
rule.pat.max_pattern_len = rdev->wiphy.coalesce->pattern_max_len;
rule.pat.max_pkt_offset = rdev->wiphy.coalesce->max_pkt_offset;
if (nla_put(msg, NL80211_ATTR_COALESCE_RULE, sizeof(rule), &rule))
return -ENOBUFS;
return 0;
}
static int
nl80211_send_iftype_data(struct sk_buff *msg,
const struct ieee80211_supported_band *sband,
const struct ieee80211_sband_iftype_data *iftdata)
{
const struct ieee80211_sta_he_cap *he_cap = &iftdata->he_cap;
const struct ieee80211_sta_eht_cap *eht_cap = &iftdata->eht_cap;
if (nl80211_put_iftypes(msg, NL80211_BAND_IFTYPE_ATTR_IFTYPES,
iftdata->types_mask))
return -ENOBUFS;
if (he_cap->has_he) {
if (nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC,
sizeof(he_cap->he_cap_elem.mac_cap_info),
he_cap->he_cap_elem.mac_cap_info) ||
nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_CAP_PHY,
sizeof(he_cap->he_cap_elem.phy_cap_info),
he_cap->he_cap_elem.phy_cap_info) ||
nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_CAP_MCS_SET,
sizeof(he_cap->he_mcs_nss_supp),
&he_cap->he_mcs_nss_supp) ||
nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_CAP_PPE,
sizeof(he_cap->ppe_thres), he_cap->ppe_thres))
return -ENOBUFS;
}
if (eht_cap->has_eht && he_cap->has_he) {
u8 mcs_nss_size, ppe_thresh_size;
u16 ppe_thres_hdr;
bool is_ap;
is_ap = iftdata->types_mask & BIT(NL80211_IFTYPE_AP) ||
iftdata->types_mask & BIT(NL80211_IFTYPE_P2P_GO);
mcs_nss_size =
ieee80211_eht_mcs_nss_size(&he_cap->he_cap_elem,
&eht_cap->eht_cap_elem,
is_ap);
ppe_thres_hdr = get_unaligned_le16(&eht_cap->eht_ppe_thres[0]);
ppe_thresh_size =
ieee80211_eht_ppe_size(ppe_thres_hdr,
eht_cap->eht_cap_elem.phy_cap_info);
if (nla_put(msg, NL80211_BAND_IFTYPE_ATTR_EHT_CAP_MAC,
sizeof(eht_cap->eht_cap_elem.mac_cap_info),
eht_cap->eht_cap_elem.mac_cap_info) ||
nla_put(msg, NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PHY,
sizeof(eht_cap->eht_cap_elem.phy_cap_info),
eht_cap->eht_cap_elem.phy_cap_info) ||
nla_put(msg, NL80211_BAND_IFTYPE_ATTR_EHT_CAP_MCS_SET,
mcs_nss_size, &eht_cap->eht_mcs_nss_supp) ||
nla_put(msg, NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PPE,
ppe_thresh_size, eht_cap->eht_ppe_thres))
return -ENOBUFS;
}
if (sband->band == NL80211_BAND_6GHZ &&
nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA,
sizeof(iftdata->he_6ghz_capa),
&iftdata->he_6ghz_capa))
return -ENOBUFS;
if (iftdata->vendor_elems.data && iftdata->vendor_elems.len &&
nla_put(msg, NL80211_BAND_IFTYPE_ATTR_VENDOR_ELEMS,
iftdata->vendor_elems.len, iftdata->vendor_elems.data))
return -ENOBUFS;
return 0;
}
static int nl80211_send_band_rateinfo(struct sk_buff *msg,
struct ieee80211_supported_band *sband,
bool large)
{
struct nlattr *nl_rates, *nl_rate;
struct ieee80211_rate *rate;
int i;
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;
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;
if (large && sband->n_iftype_data) {
struct nlattr *nl_iftype_data =
nla_nest_start_noflag(msg,
NL80211_BAND_ATTR_IFTYPE_DATA);
int err;
if (!nl_iftype_data)
return -ENOBUFS;
for (i = 0; i < sband->n_iftype_data; i++) {
struct nlattr *iftdata;
iftdata = nla_nest_start_noflag(msg, i + 1);
if (!iftdata)
return -ENOBUFS;
err = nl80211_send_iftype_data(msg, sband,
&sband->iftype_data[i]);
if (err)
return err;
nla_nest_end(msg, iftdata);
}
nla_nest_end(msg, nl_iftype_data);
}
if (large && sband->edmg_cap.channels &&
(nla_put_u8(msg, NL80211_BAND_ATTR_EDMG_CHANNELS,
sband->edmg_cap.channels) ||
nla_put_u8(msg, NL80211_BAND_ATTR_EDMG_BW_CONFIG,
sband->edmg_cap.bw_config)))
return -ENOBUFS;
nl_rates = nla_nest_start_noflag(msg, NL80211_BAND_ATTR_RATES);
if (!nl_rates)
return -ENOBUFS;
for (i = 0; i < sband->n_bitrates; i++) {
nl_rate = nla_nest_start_noflag(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_noflag(msg, NL80211_ATTR_TX_FRAME_TYPES);
if (!nl_ifs)
return -ENOBUFS;
for (ift = 0; ift < NUM_NL80211_IFTYPES; ift++) {
nl_ftypes = nla_nest_start_noflag(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_noflag(msg, NL80211_ATTR_RX_FRAME_TYPES);
if (!nl_ifs)
return -ENOBUFS;
for (ift = 0; ift < NUM_NL80211_IFTYPES; ift++) {
nl_ftypes = nla_nest_start_noflag(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;
}
#define CMD(op, n) \
do { \
if (rdev->ops->op) { \
i++; \
if (nla_put_u32(msg, i, NL80211_CMD_ ## n)) \
goto nla_put_failure; \
} \
} while (0)
static int nl80211_add_commands_unsplit(struct cfg80211_registered_device *rdev,
struct sk_buff *msg)
{
int i = 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 (rdev->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 (rdev->wiphy.flags & WIPHY_FLAG_NETNS_OK) {
i++;
if (nla_put_u32(msg, i, NL80211_CMD_SET_WIPHY_NETNS))
goto nla_put_failure;
}
if (rdev->ops->set_monitor_channel || rdev->ops->start_ap ||
rdev->ops->join_mesh) {
i++;
if (nla_put_u32(msg, i, NL80211_CMD_SET_CHANNEL))
goto nla_put_failure;
}
if (rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_TDLS) {
CMD(tdls_mgmt, TDLS_MGMT);
CMD(tdls_oper, TDLS_OPER);
}
if (rdev->wiphy.max_sched_scan_reqs)
CMD(sched_scan_start, START_SCHED_SCAN);
CMD(probe_client, PROBE_CLIENT);
CMD(set_noack_map, SET_NOACK_MAP);
if (rdev->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);
#ifdef CONFIG_NL80211_TESTMODE
CMD(testmode_cmd, TESTMODE);
#endif
if (rdev->ops->connect || rdev->ops->auth) {
i++;
if (nla_put_u32(msg, i, NL80211_CMD_CONNECT))
goto nla_put_failure;
}
if (rdev->ops->disconnect || rdev->ops->deauth) {
i++;
if (nla_put_u32(msg, i, NL80211_CMD_DISCONNECT))
goto nla_put_failure;
}
return i;
nla_put_failure:
return -ENOBUFS;
}
static int
nl80211_send_pmsr_ftm_capa(const struct cfg80211_pmsr_capabilities *cap,
struct sk_buff *msg)
{
struct nlattr *ftm;
if (!cap->ftm.supported)
return 0;
ftm = nla_nest_start_noflag(msg, NL80211_PMSR_TYPE_FTM);
if (!ftm)
return -ENOBUFS;
if (cap->ftm.asap && nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_ASAP))
return -ENOBUFS;
if (cap->ftm.non_asap &&
nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_NON_ASAP))
return -ENOBUFS;
if (cap->ftm.request_lci &&
nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_REQ_LCI))
return -ENOBUFS;
if (cap->ftm.request_civicloc &&
nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_REQ_CIVICLOC))
return -ENOBUFS;
if (nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_PREAMBLES,
cap->ftm.preambles))
return -ENOBUFS;
if (nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_BANDWIDTHS,
cap->ftm.bandwidths))
return -ENOBUFS;
if (cap->ftm.max_bursts_exponent >= 0 &&
nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_BURSTS_EXPONENT,
cap->ftm.max_bursts_exponent))
return -ENOBUFS;
if (cap->ftm.max_ftms_per_burst &&
nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_FTMS_PER_BURST,
cap->ftm.max_ftms_per_burst))
return -ENOBUFS;
if (cap->ftm.trigger_based &&
nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_TRIGGER_BASED))
return -ENOBUFS;
if (cap->ftm.non_trigger_based &&
nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_NON_TRIGGER_BASED))
return -ENOBUFS;
nla_nest_end(msg, ftm);
return 0;
}
static int nl80211_send_pmsr_capa(struct cfg80211_registered_device *rdev,
struct sk_buff *msg)
{
const struct cfg80211_pmsr_capabilities *cap = rdev->wiphy.pmsr_capa;
struct nlattr *pmsr, *caps;
if (!cap)
return 0;
pmsr = nla_nest_start_noflag(msg, NL80211_ATTR_PEER_MEASUREMENTS);
if (!pmsr)
return -ENOBUFS;
if (nla_put_u32(msg, NL80211_PMSR_ATTR_MAX_PEERS, cap->max_peers))
return -ENOBUFS;
if (cap->report_ap_tsf &&
nla_put_flag(msg, NL80211_PMSR_ATTR_REPORT_AP_TSF))
return -ENOBUFS;
if (cap->randomize_mac_addr &&
nla_put_flag(msg, NL80211_PMSR_ATTR_RANDOMIZE_MAC_ADDR))
return -ENOBUFS;
caps = nla_nest_start_noflag(msg, NL80211_PMSR_ATTR_TYPE_CAPA);
if (!caps)
return -ENOBUFS;
if (nl80211_send_pmsr_ftm_capa(cap, msg))
return -ENOBUFS;
nla_nest_end(msg, caps);
nla_nest_end(msg, pmsr);
return 0;
}
static int
nl80211_put_iftype_akm_suites(struct cfg80211_registered_device *rdev,
struct sk_buff *msg)
{
int i;
struct nlattr *nested, *nested_akms;
const struct wiphy_iftype_akm_suites *iftype_akms;
if (!rdev->wiphy.num_iftype_akm_suites ||
!rdev->wiphy.iftype_akm_suites)
return 0;
nested = nla_nest_start(msg, NL80211_ATTR_IFTYPE_AKM_SUITES);
if (!nested)
return -ENOBUFS;
for (i = 0; i < rdev->wiphy.num_iftype_akm_suites; i++) {
nested_akms = nla_nest_start(msg, i + 1);
if (!nested_akms)
return -ENOBUFS;
iftype_akms = &rdev->wiphy.iftype_akm_suites[i];
if (nl80211_put_iftypes(msg, NL80211_IFTYPE_AKM_ATTR_IFTYPES,
iftype_akms->iftypes_mask))
return -ENOBUFS;
if (nla_put(msg, NL80211_IFTYPE_AKM_ATTR_SUITES,
sizeof(u32) * iftype_akms->n_akm_suites,
iftype_akms->akm_suites)) {
return -ENOBUFS;
}
nla_nest_end(msg, nested_akms);
}
nla_nest_end(msg, nested);
return 0;
}
static int
nl80211_put_tid_config_support(struct cfg80211_registered_device *rdev,
struct sk_buff *msg)
{
struct nlattr *supp;
if (!rdev->wiphy.tid_config_support.vif &&
!rdev->wiphy.tid_config_support.peer)
return 0;
supp = nla_nest_start(msg, NL80211_ATTR_TID_CONFIG);
if (!supp)
return -ENOSPC;
if (rdev->wiphy.tid_config_support.vif &&
nla_put_u64_64bit(msg, NL80211_TID_CONFIG_ATTR_VIF_SUPP,
rdev->wiphy.tid_config_support.vif,
NL80211_TID_CONFIG_ATTR_PAD))
goto fail;
if (rdev->wiphy.tid_config_support.peer &&
nla_put_u64_64bit(msg, NL80211_TID_CONFIG_ATTR_PEER_SUPP,
rdev->wiphy.tid_config_support.peer,
NL80211_TID_CONFIG_ATTR_PAD))
goto fail;
if (nla_put_u8(msg, NL80211_TID_CONFIG_ATTR_RETRY_SHORT,
rdev->wiphy.tid_config_support.max_retry))
goto fail;
if (nla_put_u8(msg, NL80211_TID_CONFIG_ATTR_RETRY_LONG,
rdev->wiphy.tid_config_support.max_retry))
goto fail;
nla_nest_end(msg, supp);
return 0;
fail:
nla_nest_cancel(msg, supp);
return -ENOBUFS;
}
static int
nl80211_put_sar_specs(struct cfg80211_registered_device *rdev,
struct sk_buff *msg)
{
struct nlattr *sar_capa, *specs, *sub_freq_range;
u8 num_freq_ranges;
int i;
if (!rdev->wiphy.sar_capa)
return 0;
num_freq_ranges = rdev->wiphy.sar_capa->num_freq_ranges;
sar_capa = nla_nest_start(msg, NL80211_ATTR_SAR_SPEC);
if (!sar_capa)
return -ENOSPC;
if (nla_put_u32(msg, NL80211_SAR_ATTR_TYPE, rdev->wiphy.sar_capa->type))
goto fail;
specs = nla_nest_start(msg, NL80211_SAR_ATTR_SPECS);
if (!specs)
goto fail;
for (i = 0; i < num_freq_ranges; i++) {
sub_freq_range = nla_nest_start(msg, i + 1);
if (!sub_freq_range)
goto fail;
if (nla_put_u32(msg, NL80211_SAR_ATTR_SPECS_START_FREQ,
rdev->wiphy.sar_capa->freq_ranges[i].start_freq))
goto fail;
if (nla_put_u32(msg, NL80211_SAR_ATTR_SPECS_END_FREQ,
rdev->wiphy.sar_capa->freq_ranges[i].end_freq))
goto fail;
nla_nest_end(msg, sub_freq_range);
}
nla_nest_end(msg, specs);
nla_nest_end(msg, sar_capa);
return 0;
fail:
nla_nest_cancel(msg, sar_capa);
return -ENOBUFS;
}
static int nl80211_put_mbssid_support(struct wiphy *wiphy, struct sk_buff *msg)
{
struct nlattr *config;
if (!wiphy->mbssid_max_interfaces)
return 0;
config = nla_nest_start(msg, NL80211_ATTR_MBSSID_CONFIG);
if (!config)
return -ENOBUFS;
if (nla_put_u8(msg, NL80211_MBSSID_CONFIG_ATTR_MAX_INTERFACES,
wiphy->mbssid_max_interfaces))
goto fail;
if (wiphy->ema_max_profile_periodicity &&
nla_put_u8(msg,
NL80211_MBSSID_CONFIG_ATTR_MAX_EMA_PROFILE_PERIODICITY,
wiphy->ema_max_profile_periodicity))
goto fail;
nla_nest_end(msg, config);
return 0;
fail:
nla_nest_cancel(msg, config);
return -ENOBUFS;
}
struct nl80211_dump_wiphy_state {
s64 filter_wiphy;
long start;
long split_start, band_start, chan_start, capa_start;
bool split;
};
static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev,
enum nl80211_commands cmd,
struct sk_buff *msg, u32 portid, u32 seq,
int flags, struct nl80211_dump_wiphy_state *state)
{
void *hdr;
struct nlattr *nl_bands, *nl_band;
struct nlattr *nl_freqs, *nl_freq;
struct nlattr *nl_cmds;
enum nl80211_band band;
struct ieee80211_channel *chan;
int i;
const struct ieee80211_txrx_stypes *mgmt_stypes =
rdev->wiphy.mgmt_stypes;
u32 features;
hdr = nl80211hdr_put(msg, portid, seq, flags, cmd);
if (!hdr)
return -ENOBUFS;
if (WARN_ON(!state))
return -EINVAL;
if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
nla_put_string(msg, NL80211_ATTR_WIPHY_NAME,
wiphy_name(&rdev->wiphy)) ||
nla_put_u32(msg, NL80211_ATTR_GENERATION,
cfg80211_rdev_list_generation))
goto nla_put_failure;
if (cmd != NL80211_CMD_NEW_WIPHY)
goto finish;
switch (state->split_start) {
case 0:
if (nla_put_u8(msg, NL80211_ATTR_WIPHY_RETRY_SHORT,
rdev->wiphy.retry_short) ||
nla_put_u8(msg, NL80211_ATTR_WIPHY_RETRY_LONG,
rdev->wiphy.retry_long) ||
nla_put_u32(msg, NL80211_ATTR_WIPHY_FRAG_THRESHOLD,
rdev->wiphy.frag_threshold) ||
nla_put_u32(msg, NL80211_ATTR_WIPHY_RTS_THRESHOLD,
rdev->wiphy.rts_threshold) ||
nla_put_u8(msg, NL80211_ATTR_WIPHY_COVERAGE_CLASS,
rdev->wiphy.coverage_class) ||
nla_put_u8(msg, NL80211_ATTR_MAX_NUM_SCAN_SSIDS,
rdev->wiphy.max_scan_ssids) ||
nla_put_u8(msg, NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS,
rdev->wiphy.max_sched_scan_ssids) ||
nla_put_u16(msg, NL80211_ATTR_MAX_SCAN_IE_LEN,
rdev->wiphy.max_scan_ie_len) ||
nla_put_u16(msg, NL80211_ATTR_MAX_SCHED_SCAN_IE_LEN,
rdev->wiphy.max_sched_scan_ie_len) ||
nla_put_u8(msg, NL80211_ATTR_MAX_MATCH_SETS,
rdev->wiphy.max_match_sets))
goto nla_put_failure;
if ((rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN) &&
nla_put_flag(msg, NL80211_ATTR_SUPPORT_IBSS_RSN))
goto nla_put_failure;
if ((rdev->wiphy.flags & WIPHY_FLAG_MESH_AUTH) &&
nla_put_flag(msg, NL80211_ATTR_SUPPORT_MESH_AUTH))
goto nla_put_failure;
if ((rdev->wiphy.flags & WIPHY_FLAG_AP_UAPSD) &&
nla_put_flag(msg, NL80211_ATTR_SUPPORT_AP_UAPSD))
goto nla_put_failure;
if ((rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_FW_ROAM) &&
nla_put_flag(msg, NL80211_ATTR_ROAM_SUPPORT))
goto nla_put_failure;
if ((rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_TDLS) &&
nla_put_flag(msg, NL80211_ATTR_TDLS_SUPPORT))
goto nla_put_failure;
if ((rdev->wiphy.flags & WIPHY_FLAG_TDLS_EXTERNAL_SETUP) &&
nla_put_flag(msg, NL80211_ATTR_TDLS_EXTERNAL_SETUP))
goto nla_put_failure;
state->split_start++;
if (state->split)
break;
fallthrough;
case 1:
if (nla_put(msg, NL80211_ATTR_CIPHER_SUITES,
sizeof(u32) * rdev->wiphy.n_cipher_suites,
rdev->wiphy.cipher_suites))
goto nla_put_failure;
if (nla_put_u8(msg, NL80211_ATTR_MAX_NUM_PMKIDS,
rdev->wiphy.max_num_pmkids))
goto nla_put_failure;
if ((rdev->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,
rdev->wiphy.available_antennas_tx) ||
nla_put_u32(msg, NL80211_ATTR_WIPHY_ANTENNA_AVAIL_RX,
rdev->wiphy.available_antennas_rx))
goto nla_put_failure;
if ((rdev->wiphy.flags & WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD) &&
nla_put_u32(msg, NL80211_ATTR_PROBE_RESP_OFFLOAD,
rdev->wiphy.probe_resp_offload))
goto nla_put_failure;
if ((rdev->wiphy.available_antennas_tx ||
rdev->wiphy.available_antennas_rx) &&
rdev->ops->get_antenna) {
u32 tx_ant = 0, rx_ant = 0;
int res;
res = rdev_get_antenna(rdev, &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;
}
}
state->split_start++;
if (state->split)
break;
fallthrough;
case 2:
if (nl80211_put_iftypes(msg, NL80211_ATTR_SUPPORTED_IFTYPES,
rdev->wiphy.interface_modes))
goto nla_put_failure;
state->split_start++;
if (state->split)
break;
fallthrough;
case 3:
nl_bands = nla_nest_start_noflag(msg,
NL80211_ATTR_WIPHY_BANDS);
if (!nl_bands)
goto nla_put_failure;
for (band = state->band_start;
band < (state->split ?
NUM_NL80211_BANDS :
NL80211_BAND_60GHZ + 1);
band++) {
struct ieee80211_supported_band *sband;
if (band > NL80211_BAND_5GHZ && !state->split)
break;
sband = rdev->wiphy.bands[band];
if (!sband)
continue;
nl_band = nla_nest_start_noflag(msg, band);
if (!nl_band)
goto nla_put_failure;
switch (state->chan_start) {
case 0:
if (nl80211_send_band_rateinfo(msg, sband,
state->split))
goto nla_put_failure;
state->chan_start++;
if (state->split)
break;
fallthrough;
default:
nl_freqs = nla_nest_start_noflag(msg,
NL80211_BAND_ATTR_FREQS);
if (!nl_freqs)
goto nla_put_failure;
for (i = state->chan_start - 1;
i < sband->n_channels;
i++) {
nl_freq = nla_nest_start_noflag(msg,
i);
if (!nl_freq)
goto nla_put_failure;
chan = &sband->channels[i];
if (nl80211_msg_put_channel(
msg, &rdev->wiphy, chan,
state->split))
goto nla_put_failure;
nla_nest_end(msg, nl_freq);
if (state->split)
break;
}
if (i < sband->n_channels)
state->chan_start = i + 2;
else
state->chan_start = 0;
nla_nest_end(msg, nl_freqs);
}
nla_nest_end(msg, nl_band);
if (state->split) {
if (state->chan_start)
band--;
break;
}
}
nla_nest_end(msg, nl_bands);
if (band < NUM_NL80211_BANDS)
state->band_start = band + 1;
else
state->band_start = 0;
if (state->band_start == 0 && state->chan_start == 0)
state->split_start++;
if (state->split)
break;
fallthrough;
case 4:
nl_cmds = nla_nest_start_noflag(msg,
NL80211_ATTR_SUPPORTED_COMMANDS);
if (!nl_cmds)
goto nla_put_failure;
i = nl80211_add_commands_unsplit(rdev, msg);
if (i < 0)
goto nla_put_failure;
if (state->split) {
CMD(crit_proto_start, CRIT_PROTOCOL_START);
CMD(crit_proto_stop, CRIT_PROTOCOL_STOP);
if (rdev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH)
CMD(channel_switch, CHANNEL_SWITCH);
CMD(set_qos_map, SET_QOS_MAP);
if (rdev->wiphy.features &
NL80211_FEATURE_SUPPORTS_WMM_ADMISSION)
CMD(add_tx_ts, ADD_TX_TS);
CMD(set_multicast_to_unicast, SET_MULTICAST_TO_UNICAST);
CMD(update_connect_params, UPDATE_CONNECT_PARAMS);
CMD(update_ft_ies, UPDATE_FT_IES);
if (rdev->wiphy.sar_capa)
CMD(set_sar_specs, SET_SAR_SPECS);
}
#undef CMD
nla_nest_end(msg, nl_cmds);
state->split_start++;
if (state->split)
break;
fallthrough;
case 5:
if (rdev->ops->remain_on_channel &&
(rdev->wiphy.flags & WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL) &&
nla_put_u32(msg,
NL80211_ATTR_MAX_REMAIN_ON_CHANNEL_DURATION,
rdev->wiphy.max_remain_on_channel_duration))
goto nla_put_failure;
if ((rdev->wiphy.flags & WIPHY_FLAG_OFFCHAN_TX) &&
nla_put_flag(msg, NL80211_ATTR_OFFCHANNEL_TX_OK))
goto nla_put_failure;
state->split_start++;
if (state->split)
break;
fallthrough;
case 6:
#ifdef CONFIG_PM
if (nl80211_send_wowlan(msg, rdev, state->split))
goto nla_put_failure;
state->split_start++;
if (state->split)
break;
#else
state->split_start++;
#endif
fallthrough;
case 7:
if (nl80211_put_iftypes(msg, NL80211_ATTR_SOFTWARE_IFTYPES,
rdev->wiphy.software_iftypes))
goto nla_put_failure;
if (nl80211_put_iface_combinations(&rdev->wiphy, msg,
state->split))
goto nla_put_failure;
state->split_start++;
if (state->split)
break;
fallthrough;
case 8:
if ((rdev->wiphy.flags & WIPHY_FLAG_HAVE_AP_SME) &&
nla_put_u32(msg, NL80211_ATTR_DEVICE_AP_SME,
rdev->wiphy.ap_sme_capa))
goto nla_put_failure;
features = rdev->wiphy.features;
if (state->split)
features |= NL80211_FEATURE_ADVERTISE_CHAN_LIMITS;
if (nla_put_u32(msg, NL80211_ATTR_FEATURE_FLAGS, features))
goto nla_put_failure;
if (rdev->wiphy.ht_capa_mod_mask &&
nla_put(msg, NL80211_ATTR_HT_CAPABILITY_MASK,
sizeof(*rdev->wiphy.ht_capa_mod_mask),
rdev->wiphy.ht_capa_mod_mask))
goto nla_put_failure;
if (rdev->wiphy.flags & WIPHY_FLAG_HAVE_AP_SME &&
rdev->wiphy.max_acl_mac_addrs &&
nla_put_u32(msg, NL80211_ATTR_MAC_ACL_MAX,
rdev->wiphy.max_acl_mac_addrs))
goto nla_put_failure;
if (state->split)
state->split_start++;
else
state->split_start = 0;
break;
case 9:
if (nl80211_send_mgmt_stypes(msg, mgmt_stypes))
goto nla_put_failure;
if (nla_put_u32(msg, NL80211_ATTR_MAX_NUM_SCHED_SCAN_PLANS,
rdev->wiphy.max_sched_scan_plans) ||
nla_put_u32(msg, NL80211_ATTR_MAX_SCAN_PLAN_INTERVAL,
rdev->wiphy.max_sched_scan_plan_interval) ||
nla_put_u32(msg, NL80211_ATTR_MAX_SCAN_PLAN_ITERATIONS,
rdev->wiphy.max_sched_scan_plan_iterations))
goto nla_put_failure;
if (rdev->wiphy.extended_capabilities &&
(nla_put(msg, NL80211_ATTR_EXT_CAPA,
rdev->wiphy.extended_capabilities_len,
rdev->wiphy.extended_capabilities) ||
nla_put(msg, NL80211_ATTR_EXT_CAPA_MASK,
rdev->wiphy.extended_capabilities_len,
rdev->wiphy.extended_capabilities_mask)))
goto nla_put_failure;
if (rdev->wiphy.vht_capa_mod_mask &&
nla_put(msg, NL80211_ATTR_VHT_CAPABILITY_MASK,
sizeof(*rdev->wiphy.vht_capa_mod_mask),
rdev->wiphy.vht_capa_mod_mask))
goto nla_put_failure;
if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN,
rdev->wiphy.perm_addr))
goto nla_put_failure;
if (!is_zero_ether_addr(rdev->wiphy.addr_mask) &&
nla_put(msg, NL80211_ATTR_MAC_MASK, ETH_ALEN,
rdev->wiphy.addr_mask))
goto nla_put_failure;
if (rdev->wiphy.n_addresses > 1) {
void *attr;
attr = nla_nest_start(msg, NL80211_ATTR_MAC_ADDRS);
if (!attr)
goto nla_put_failure;
for (i = 0; i < rdev->wiphy.n_addresses; i++)
if (nla_put(msg, i + 1, ETH_ALEN,
rdev->wiphy.addresses[i].addr))
goto nla_put_failure;
nla_nest_end(msg, attr);
}
state->split_start++;
break;
case 10:
if (nl80211_send_coalesce(msg, rdev))
goto nla_put_failure;
if ((rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_5_10_MHZ) &&
(nla_put_flag(msg, NL80211_ATTR_SUPPORT_5_MHZ) ||
nla_put_flag(msg, NL80211_ATTR_SUPPORT_10_MHZ)))
goto nla_put_failure;
if (rdev->wiphy.max_ap_assoc_sta &&
nla_put_u32(msg, NL80211_ATTR_MAX_AP_ASSOC_STA,
rdev->wiphy.max_ap_assoc_sta))
goto nla_put_failure;
state->split_start++;
break;
case 11:
if (rdev->wiphy.n_vendor_commands) {
const struct nl80211_vendor_cmd_info *info;
struct nlattr *nested;
nested = nla_nest_start_noflag(msg,
NL80211_ATTR_VENDOR_DATA);
if (!nested)
goto nla_put_failure;
for (i = 0; i < rdev->wiphy.n_vendor_commands; i++) {
info = &rdev->wiphy.vendor_commands[i].info;
if (nla_put(msg, i + 1, sizeof(*info), info))
goto nla_put_failure;
}
nla_nest_end(msg, nested);
}
if (rdev->wiphy.n_vendor_events) {
const struct nl80211_vendor_cmd_info *info;
struct nlattr *nested;
nested = nla_nest_start_noflag(msg,
NL80211_ATTR_VENDOR_EVENTS);
if (!nested)
goto nla_put_failure;
for (i = 0; i < rdev->wiphy.n_vendor_events; i++) {
info = &rdev->wiphy.vendor_events[i];
if (nla_put(msg, i + 1, sizeof(*info), info))
goto nla_put_failure;
}
nla_nest_end(msg, nested);
}
state->split_start++;
break;
case 12:
if (rdev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH &&
nla_put_u8(msg, NL80211_ATTR_MAX_CSA_COUNTERS,
rdev->wiphy.max_num_csa_counters))
goto nla_put_failure;
if (rdev->wiphy.regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED &&
nla_put_flag(msg, NL80211_ATTR_WIPHY_SELF_MANAGED_REG))
goto nla_put_failure;
if (rdev->wiphy.max_sched_scan_reqs &&
nla_put_u32(msg, NL80211_ATTR_SCHED_SCAN_MAX_REQS,
rdev->wiphy.max_sched_scan_reqs))
goto nla_put_failure;
if (nla_put(msg, NL80211_ATTR_EXT_FEATURES,
sizeof(rdev->wiphy.ext_features),
rdev->wiphy.ext_features))
goto nla_put_failure;
if (rdev->wiphy.bss_select_support) {
struct nlattr *nested;
u32 bss_select_support = rdev->wiphy.bss_select_support;
nested = nla_nest_start_noflag(msg,
NL80211_ATTR_BSS_SELECT);
if (!nested)
goto nla_put_failure;
i = 0;
while (bss_select_support) {
if ((bss_select_support & 1) &&
nla_put_flag(msg, i))
goto nla_put_failure;
i++;
bss_select_support >>= 1;
}
nla_nest_end(msg, nested);
}
state->split_start++;
break;
case 13:
if (rdev->wiphy.num_iftype_ext_capab &&
rdev->wiphy.iftype_ext_capab) {
struct nlattr *nested_ext_capab, *nested;
nested = nla_nest_start_noflag(msg,
NL80211_ATTR_IFTYPE_EXT_CAPA);
if (!nested)
goto nla_put_failure;
for (i = state->capa_start;
i < rdev->wiphy.num_iftype_ext_capab; i++) {
const struct wiphy_iftype_ext_capab *capab;
capab = &rdev->wiphy.iftype_ext_capab[i];
nested_ext_capab = nla_nest_start_noflag(msg,
i);
if (!nested_ext_capab ||
nla_put_u32(msg, NL80211_ATTR_IFTYPE,
capab->iftype) ||
nla_put(msg, NL80211_ATTR_EXT_CAPA,
capab->extended_capabilities_len,
capab->extended_capabilities) ||
nla_put(msg, NL80211_ATTR_EXT_CAPA_MASK,
capab->extended_capabilities_len,
capab->extended_capabilities_mask))
goto nla_put_failure;
if (rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_MLO &&
(nla_put_u16(msg,
NL80211_ATTR_EML_CAPABILITY,
capab->eml_capabilities) ||
nla_put_u16(msg,
NL80211_ATTR_MLD_CAPA_AND_OPS,
capab->mld_capa_and_ops)))
goto nla_put_failure;
nla_nest_end(msg, nested_ext_capab);
if (state->split)
break;
}
nla_nest_end(msg, nested);
if (i < rdev->wiphy.num_iftype_ext_capab) {
state->capa_start = i + 1;
break;
}
}
if (nla_put_u32(msg, NL80211_ATTR_BANDS,
rdev->wiphy.nan_supported_bands))
goto nla_put_failure;
if (wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_TXQS)) {
struct cfg80211_txq_stats txqstats = {};
int res;
res = rdev_get_txq_stats(rdev, NULL, &txqstats);
if (!res &&
!nl80211_put_txq_stats(msg, &txqstats,
NL80211_ATTR_TXQ_STATS))
goto nla_put_failure;
if (nla_put_u32(msg, NL80211_ATTR_TXQ_LIMIT,
rdev->wiphy.txq_limit))
goto nla_put_failure;
if (nla_put_u32(msg, NL80211_ATTR_TXQ_MEMORY_LIMIT,
rdev->wiphy.txq_memory_limit))
goto nla_put_failure;
if (nla_put_u32(msg, NL80211_ATTR_TXQ_QUANTUM,
rdev->wiphy.txq_quantum))
goto nla_put_failure;
}
state->split_start++;
break;
case 14:
if (nl80211_send_pmsr_capa(rdev, msg))
goto nla_put_failure;
state->split_start++;
break;
case 15:
if (rdev->wiphy.akm_suites &&
nla_put(msg, NL80211_ATTR_AKM_SUITES,
sizeof(u32) * rdev->wiphy.n_akm_suites,
rdev->wiphy.akm_suites))
goto nla_put_failure;
if (nl80211_put_iftype_akm_suites(rdev, msg))
goto nla_put_failure;
if (nl80211_put_tid_config_support(rdev, msg))
goto nla_put_failure;
state->split_start++;
break;
case 16:
if (nl80211_put_sar_specs(rdev, msg))
goto nla_put_failure;
if (nl80211_put_mbssid_support(&rdev->wiphy, msg))
goto nla_put_failure;
if (nla_put_u16(msg, NL80211_ATTR_MAX_NUM_AKM_SUITES,
rdev->wiphy.max_num_akm_suites))
goto nla_put_failure;
if (rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_MLO)
nla_put_flag(msg, NL80211_ATTR_MLO_SUPPORT);
state->split_start = 0;
break;
}
finish:
genlmsg_end(msg, hdr);
return 0;
nla_put_failure:
genlmsg_cancel(msg, hdr);
return -EMSGSIZE;
}
static int nl80211_dump_wiphy_parse(struct sk_buff *skb,
struct netlink_callback *cb,
struct nl80211_dump_wiphy_state *state)
{
struct nlattr **tb = kcalloc(NUM_NL80211_ATTR, sizeof(*tb), GFP_KERNEL);
int ret;
if (!tb)
return -ENOMEM;
ret = nlmsg_parse_deprecated(cb->nlh,
GENL_HDRLEN + nl80211_fam.hdrsize,
tb, nl80211_fam.maxattr,
nl80211_policy, NULL);
if (ret) {
ret = 0;
goto out;
}
state->split = tb[NL80211_ATTR_SPLIT_WIPHY_DUMP];
if (tb[NL80211_ATTR_WIPHY])
state->filter_wiphy = nla_get_u32(tb[NL80211_ATTR_WIPHY]);
if (tb[NL80211_ATTR_WDEV])
state->filter_wiphy = nla_get_u64(tb[NL80211_ATTR_WDEV]) >> 32;
if (tb[NL80211_ATTR_IFINDEX]) {
struct net_device *netdev;
struct cfg80211_registered_device *rdev;
int ifidx = nla_get_u32(tb[NL80211_ATTR_IFINDEX]);
netdev = __dev_get_by_index(sock_net(skb->sk), ifidx);
if (!netdev) {
ret = -ENODEV;
goto out;
}
if (netdev->ieee80211_ptr) {
rdev = wiphy_to_rdev(
netdev->ieee80211_ptr->wiphy);
state->filter_wiphy = rdev->wiphy_idx;
}
}
ret = 0;
out:
kfree(tb);
return ret;
}
static int nl80211_dump_wiphy(struct sk_buff *skb, struct netlink_callback *cb)
{
int idx = 0, ret;
struct nl80211_dump_wiphy_state *state = (void *)cb->args[0];
struct cfg80211_registered_device *rdev;
rtnl_lock();
if (!state) {
state = kzalloc(sizeof(*state), GFP_KERNEL);
if (!state) {
rtnl_unlock();
return -ENOMEM;
}
state->filter_wiphy = -1;
ret = nl80211_dump_wiphy_parse(skb, cb, state);
if (ret) {
kfree(state);
rtnl_unlock();
return ret;
}
cb->args[0] = (long)state;
}
list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
if (!net_eq(wiphy_net(&rdev->wiphy), sock_net(skb->sk)))
continue;
if (++idx <= state->start)
continue;
if (state->filter_wiphy != -1 &&
state->filter_wiphy != rdev->wiphy_idx)
continue;
do {
ret = nl80211_send_wiphy(rdev, NL80211_CMD_NEW_WIPHY,
skb,
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq,
NLM_F_MULTI, state);
if (ret < 0) {
if ((ret == -ENOBUFS || ret == -EMSGSIZE) &&
!skb->len && !state->split &&
cb->min_dump_alloc < 4096) {
cb->min_dump_alloc = 4096;
state->split_start = 0;
rtnl_unlock();
return 1;
}
idx--;
break;
}
} while (state->split_start > 0);
break;
}
rtnl_unlock();
state->start = idx;
return skb->len;
}
static int nl80211_dump_wiphy_done(struct netlink_callback *cb)
{
kfree((void *)cb->args[0]);
return 0;
}
static int nl80211_get_wiphy(struct sk_buff *skb, struct genl_info *info)
{
struct sk_buff *msg;
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct nl80211_dump_wiphy_state state = {};
msg = nlmsg_new(4096, GFP_KERNEL);
if (!msg)
return -ENOMEM;
if (nl80211_send_wiphy(rdev, NL80211_CMD_NEW_WIPHY, msg,
info->snd_portid, info->snd_seq, 0,
&state) < 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)
{
u8 ac;
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;
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 (ac >= NL80211_NUM_ACS)
return -EINVAL;
txq_params->ac = array_index_nospec(ac, NL80211_NUM_ACS);
return 0;
}
static bool nl80211_can_set_dev_channel(struct wireless_dev *wdev)
{
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_punct_bitmap(struct cfg80211_registered_device *rdev,
struct genl_info *info,
const struct cfg80211_chan_def *chandef,
u16 *punct_bitmap)
{
if (!wiphy_ext_feature_isset(&rdev->wiphy, NL80211_EXT_FEATURE_PUNCT))
return -EINVAL;
*punct_bitmap = nla_get_u32(info->attrs[NL80211_ATTR_PUNCT_BITMAP]);
if (!cfg80211_valid_disable_subchannel_bitmap(punct_bitmap, chandef))
return -EINVAL;
return 0;
}
int nl80211_parse_chandef(struct cfg80211_registered_device *rdev,
struct genl_info *info,
struct cfg80211_chan_def *chandef)
{
struct netlink_ext_ack *extack = info->extack;
struct nlattr **attrs = info->attrs;
u32 control_freq;
if (!attrs[NL80211_ATTR_WIPHY_FREQ]) {
NL_SET_ERR_MSG_ATTR(extack, attrs[NL80211_ATTR_WIPHY_FREQ],
"Frequency is missing");
return -EINVAL;
}
control_freq = MHZ_TO_KHZ(
nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]));
if (info->attrs[NL80211_ATTR_WIPHY_FREQ_OFFSET])
control_freq +=
nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ_OFFSET]);
memset(chandef, 0, sizeof(*chandef));
chandef->chan = ieee80211_get_channel_khz(&rdev->wiphy, control_freq);
chandef->width = NL80211_CHAN_WIDTH_20_NOHT;
chandef->center_freq1 = KHZ_TO_MHZ(control_freq);
chandef->freq1_offset = control_freq % 1000;
chandef->center_freq2 = 0;
if (!chandef->chan || chandef->chan->flags & IEEE80211_CHAN_DISABLED) {
NL_SET_ERR_MSG_ATTR(extack, attrs[NL80211_ATTR_WIPHY_FREQ],
"Channel is disabled");
return -EINVAL;
}
if (attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) {
enum nl80211_channel_type chantype;
chantype = nla_get_u32(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);
if (attrs[NL80211_ATTR_CENTER_FREQ1] &&
chandef->center_freq1 != nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ1])) {
NL_SET_ERR_MSG_ATTR(extack,
attrs[NL80211_ATTR_CENTER_FREQ1],
"bad center frequency 1");
return -EINVAL;
}
if (attrs[NL80211_ATTR_CENTER_FREQ2] &&
nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ2])) {
NL_SET_ERR_MSG_ATTR(extack,
attrs[NL80211_ATTR_CENTER_FREQ2],
"center frequency 2 can't be used");
return -EINVAL;
}
break;
default:
NL_SET_ERR_MSG_ATTR(extack,
attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE],
"invalid channel type");
return -EINVAL;
}
} else if (attrs[NL80211_ATTR_CHANNEL_WIDTH]) {
chandef->width =
nla_get_u32(attrs[NL80211_ATTR_CHANNEL_WIDTH]);
if (chandef->chan->band == NL80211_BAND_S1GHZ) {
if (chandef->width != ieee80211_s1g_channel_width(chandef->chan)) {
NL_SET_ERR_MSG_ATTR(extack,
attrs[NL80211_ATTR_CHANNEL_WIDTH],
"bad channel width");
return -EINVAL;
}
}
if (attrs[NL80211_ATTR_CENTER_FREQ1]) {
chandef->center_freq1 =
nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ1]);
if (attrs[NL80211_ATTR_CENTER_FREQ1_OFFSET])
chandef->freq1_offset = nla_get_u32(
attrs[NL80211_ATTR_CENTER_FREQ1_OFFSET]);
else
chandef->freq1_offset = 0;
}
if (attrs[NL80211_ATTR_CENTER_FREQ2])
chandef->center_freq2 =
nla_get_u32(attrs[NL80211_ATTR_CENTER_FREQ2]);
}
if (info->attrs[NL80211_ATTR_WIPHY_EDMG_CHANNELS]) {
chandef->edmg.channels =
nla_get_u8(info->attrs[NL80211_ATTR_WIPHY_EDMG_CHANNELS]);
if (info->attrs[NL80211_ATTR_WIPHY_EDMG_BW_CONFIG])
chandef->edmg.bw_config =
nla_get_u8(info->attrs[NL80211_ATTR_WIPHY_EDMG_BW_CONFIG]);
} else {
chandef->edmg.bw_config = 0;
chandef->edmg.channels = 0;
}
if (!cfg80211_chandef_valid(chandef)) {
NL_SET_ERR_MSG(extack, "invalid channel definition");
return -EINVAL;
}
if (!cfg80211_chandef_usable(&rdev->wiphy, chandef,
IEEE80211_CHAN_DISABLED)) {
NL_SET_ERR_MSG(extack, "(extension) channel is disabled");
return -EINVAL;
}
if ((chandef->width == NL80211_CHAN_WIDTH_5 ||
chandef->width == NL80211_CHAN_WIDTH_10) &&
!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_5_10_MHZ)) {
NL_SET_ERR_MSG(extack, "5/10 MHz not supported");
return -EINVAL;
}
return 0;
}
static int __nl80211_set_channel(struct cfg80211_registered_device *rdev,
struct net_device *dev,
struct genl_info *info,
int _link_id)
{
struct cfg80211_chan_def chandef;
int result;
enum nl80211_iftype iftype = NL80211_IFTYPE_MONITOR;
struct wireless_dev *wdev = NULL;
int link_id = _link_id;
if (dev)
wdev = dev->ieee80211_ptr;
if (!nl80211_can_set_dev_channel(wdev))
return -EOPNOTSUPP;
if (wdev)
iftype = wdev->iftype;
if (link_id < 0) {
if (wdev && wdev->valid_links)
return -EINVAL;
link_id = 0;
}
result = nl80211_parse_chandef(rdev, info, &chandef);
if (result)
return result;
switch (iftype) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_P2P_GO:
if (!cfg80211_reg_can_beacon_relax(&rdev->wiphy, &chandef,
iftype))
return -EINVAL;
if (wdev->links[link_id].ap.beacon_interval) {
struct ieee80211_channel *cur_chan;
if (!dev || !rdev->ops->set_ap_chanwidth ||
!(rdev->wiphy.features &
NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE))
return -EBUSY;
cur_chan = wdev->links[link_id].ap.chandef.chan;
if (chandef.chan != cur_chan)
return -EBUSY;
result = rdev_set_ap_chanwidth(rdev, dev, link_id,
&chandef);
if (result)
return result;
wdev->links[link_id].ap.chandef = chandef;
} else {
wdev->u.ap.preset_chandef = chandef;
}
return 0;
case NL80211_IFTYPE_MESH_POINT:
return cfg80211_set_mesh_channel(rdev, wdev, &chandef);
case NL80211_IFTYPE_MONITOR:
return cfg80211_set_monitor_channel(rdev, &chandef);
default:
break;
}
return -EINVAL;
}
static int nl80211_set_channel(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
int link_id = nl80211_link_id_or_invalid(info->attrs);
struct net_device *netdev = info->user_ptr[1];
int ret;
wdev_lock(netdev->ieee80211_ptr);
ret = __nl80211_set_channel(rdev, netdev, info, link_id);
wdev_unlock(netdev->ieee80211_ptr);
return ret;
}
static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = NULL;
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;
u32 txq_limit = 0, txq_memory_limit = 0, txq_quantum = 0;
rtnl_lock();
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_rdev(netdev->ieee80211_ptr->wiphy);
else
netdev = NULL;
}
if (!netdev) {
rdev = __cfg80211_rdev_from_attrs(genl_info_net(info),
info->attrs);
if (IS_ERR(rdev)) {
rtnl_unlock();
return PTR_ERR(rdev);
}
wdev = NULL;
netdev = NULL;
result = 0;
} else
wdev = netdev->ieee80211_ptr;
wiphy_lock(&rdev->wiphy);
if (info->attrs[NL80211_ATTR_WIPHY_NAME])
result = cfg80211_dev_rename(
rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME]));
rtnl_unlock();
if (result)
goto out;
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 out;
}
if (!netdev) {
result = -EINVAL;
goto out;
}
if (netdev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
netdev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) {
result = -EINVAL;
goto out;
}
if (!netif_running(netdev)) {
result = -ENETDOWN;
goto out;
}
nla_for_each_nested(nl_txq_params,
info->attrs[NL80211_ATTR_WIPHY_TXQ_PARAMS],
rem_txq_params) {
result = nla_parse_nested_deprecated(tb,
NL80211_TXQ_ATTR_MAX,
nl_txq_params,
txq_params_policy,
info->extack);
if (result)
goto out;
result = parse_txq_params(tb, &txq_params);
if (result)
goto out;
txq_params.link_id =
nl80211_link_id_or_invalid(info->attrs);
wdev_lock(netdev->ieee80211_ptr);
if (txq_params.link_id >= 0 &&
!(netdev->ieee80211_ptr->valid_links &
BIT(txq_params.link_id)))
result = -ENOLINK;
else if (txq_params.link_id >= 0 &&
!netdev->ieee80211_ptr->valid_links)
result = -EINVAL;
else
result = rdev_set_txq_params(rdev, netdev,
&txq_params);
wdev_unlock(netdev->ieee80211_ptr);
if (result)
goto out;
}
}
if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
int link_id = nl80211_link_id_or_invalid(info->attrs);
if (wdev) {
wdev_lock(wdev);
result = __nl80211_set_channel(
rdev,
nl80211_can_set_dev_channel(wdev) ? netdev : NULL,
info, link_id);
wdev_unlock(wdev);
} else {
result = __nl80211_set_channel(rdev, netdev, info, link_id);
}
if (result)
goto out;
}
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 out;
}
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 out;
}
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 out;
}
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 out;
}
tx_ant = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_ANTENNA_TX]);
rx_ant = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_ANTENNA_RX]);
if ((~tx_ant && (tx_ant & ~rdev->wiphy.available_antennas_tx)) ||
(~rx_ant && (rx_ant & ~rdev->wiphy.available_antennas_rx))) {
result = -EINVAL;
goto out;
}
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 out;
}
changed = 0;
if (info->attrs[NL80211_ATTR_WIPHY_RETRY_SHORT]) {
retry_short = nla_get_u8(
info->attrs[NL80211_ATTR_WIPHY_RETRY_SHORT]);
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]);
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 out;
}
if (frag_threshold != (u32) -1) {
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]) {
if (info->attrs[NL80211_ATTR_WIPHY_DYN_ACK]) {
result = -EINVAL;
goto out;
}
coverage_class = nla_get_u8(
info->attrs[NL80211_ATTR_WIPHY_COVERAGE_CLASS]);
changed |= WIPHY_PARAM_COVERAGE_CLASS;
}
if (info->attrs[NL80211_ATTR_WIPHY_DYN_ACK]) {
if (!(rdev->wiphy.features & NL80211_FEATURE_ACKTO_ESTIMATION)) {
result = -EOPNOTSUPP;
goto out;
}
changed |= WIPHY_PARAM_DYN_ACK;
}
if (info->attrs[NL80211_ATTR_TXQ_LIMIT]) {
if (!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_TXQS)) {
result = -EOPNOTSUPP;
goto out;
}
txq_limit = nla_get_u32(
info->attrs[NL80211_ATTR_TXQ_LIMIT]);
changed |= WIPHY_PARAM_TXQ_LIMIT;
}
if (info->attrs[NL80211_ATTR_TXQ_MEMORY_LIMIT]) {
if (!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_TXQS)) {
result = -EOPNOTSUPP;
goto out;
}
txq_memory_limit = nla_get_u32(
info->attrs[NL80211_ATTR_TXQ_MEMORY_LIMIT]);
changed |= WIPHY_PARAM_TXQ_MEMORY_LIMIT;
}
if (info->attrs[NL80211_ATTR_TXQ_QUANTUM]) {
if (!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_TXQS)) {
result = -EOPNOTSUPP;
goto out;
}
txq_quantum = nla_get_u32(
info->attrs[NL80211_ATTR_TXQ_QUANTUM]);
changed |= WIPHY_PARAM_TXQ_QUANTUM;
}
if (changed) {
u8 old_retry_short, old_retry_long;
u32 old_frag_threshold, old_rts_threshold;
u8 old_coverage_class;
u32 old_txq_limit, old_txq_memory_limit, old_txq_quantum;
if (!rdev->ops->set_wiphy_params) {
result = -EOPNOTSUPP;
goto out;
}
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;
old_txq_limit = rdev->wiphy.txq_limit;
old_txq_memory_limit = rdev->wiphy.txq_memory_limit;
old_txq_quantum = rdev->wiphy.txq_quantum;
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;
if (changed & WIPHY_PARAM_TXQ_LIMIT)
rdev->wiphy.txq_limit = txq_limit;
if (changed & WIPHY_PARAM_TXQ_MEMORY_LIMIT)
rdev->wiphy.txq_memory_limit = txq_memory_limit;
if (changed & WIPHY_PARAM_TXQ_QUANTUM)
rdev->wiphy.txq_quantum = txq_quantum;
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;
rdev->wiphy.txq_limit = old_txq_limit;
rdev->wiphy.txq_memory_limit = old_txq_memory_limit;
rdev->wiphy.txq_quantum = old_txq_quantum;
goto out;
}
}
result = 0;
out:
wiphy_unlock(&rdev->wiphy);
return result;
}
static int nl80211_send_chandef(struct sk_buff *msg,
const struct cfg80211_chan_def *chandef)
{
if (WARN_ON(!cfg80211_chandef_valid(chandef)))
return -EINVAL;
if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ,
chandef->chan->center_freq))
return -ENOBUFS;
if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ_OFFSET,
chandef->chan->freq_offset))
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,
enum nl80211_commands cmd)
{
struct net_device *dev = wdev->netdev;
void *hdr;
WARN_ON(cmd != NL80211_CMD_NEW_INTERFACE &&
cmd != NL80211_CMD_DEL_INTERFACE &&
cmd != NL80211_CMD_SET_INTERFACE);
hdr = nl80211hdr_put(msg, portid, seq, flags, cmd);
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_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
NL80211_ATTR_PAD) ||
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)) ||
nla_put_u8(msg, NL80211_ATTR_4ADDR, wdev->use_4addr))
goto nla_put_failure;
if (rdev->ops->get_channel && !wdev->valid_links) {
struct cfg80211_chan_def chandef = {};
int ret;
ret = rdev_get_channel(rdev, wdev, 0, &chandef);
if (ret == 0 && nl80211_send_chandef(msg, &chandef))
goto nla_put_failure;
}
if (rdev->ops->get_tx_power) {
int dbm, ret;
ret = rdev_get_tx_power(rdev, wdev, &dbm);
if (ret == 0 &&
nla_put_u32(msg, NL80211_ATTR_WIPHY_TX_POWER_LEVEL,
DBM_TO_MBM(dbm)))
goto nla_put_failure;
}
wdev_lock(wdev);
switch (wdev->iftype) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_P2P_GO:
if (wdev->u.ap.ssid_len &&
nla_put(msg, NL80211_ATTR_SSID, wdev->u.ap.ssid_len,
wdev->u.ap.ssid))
goto nla_put_failure_locked;
break;
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_P2P_CLIENT:
if (wdev->u.client.ssid_len &&
nla_put(msg, NL80211_ATTR_SSID, wdev->u.client.ssid_len,
wdev->u.client.ssid))
goto nla_put_failure_locked;
break;
case NL80211_IFTYPE_ADHOC:
if (wdev->u.ibss.ssid_len &&
nla_put(msg, NL80211_ATTR_SSID, wdev->u.ibss.ssid_len,
wdev->u.ibss.ssid))
goto nla_put_failure_locked;
break;
default:
break;
}
wdev_unlock(wdev);
if (rdev->ops->get_txq_stats) {
struct cfg80211_txq_stats txqstats = {};
int ret = rdev_get_txq_stats(rdev, wdev, &txqstats);
if (ret == 0 &&
!nl80211_put_txq_stats(msg, &txqstats,
NL80211_ATTR_TXQ_STATS))
goto nla_put_failure;
}
if (wdev->valid_links) {
unsigned int link_id;
struct nlattr *links = nla_nest_start(msg,
NL80211_ATTR_MLO_LINKS);
if (!links)
goto nla_put_failure;
for_each_valid_link(wdev, link_id) {
struct nlattr *link = nla_nest_start(msg, link_id + 1);
struct cfg80211_chan_def chandef = {};
int ret;
if (!link)
goto nla_put_failure;
if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id))
goto nla_put_failure;
if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN,
wdev->links[link_id].addr))
goto nla_put_failure;
ret = rdev_get_channel(rdev, wdev, link_id, &chandef);
if (ret == 0 && nl80211_send_chandef(msg, &chandef))
goto nla_put_failure;
nla_nest_end(msg, link);
}
nla_nest_end(msg, links);
}
genlmsg_end(msg, hdr);
return 0;
nla_put_failure_locked:
wdev_unlock(wdev);
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];
int filter_wiphy = -1;
struct cfg80211_registered_device *rdev;
struct wireless_dev *wdev;
int ret;
rtnl_lock();
if (!cb->args[2]) {
struct nl80211_dump_wiphy_state state = {
.filter_wiphy = -1,
};
ret = nl80211_dump_wiphy_parse(skb, cb, &state);
if (ret)
goto out_unlock;
filter_wiphy = state.filter_wiphy;
if (filter_wiphy >= 0)
cb->args[2] = filter_wiphy + 1;
else
cb->args[2] = -1;
} else if (cb->args[2] > 0) {
filter_wiphy = cb->args[2] - 1;
}
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 (filter_wiphy >= 0 && filter_wiphy != rdev->wiphy_idx)
continue;
if_idx = 0;
list_for_each_entry(wdev, &rdev->wiphy.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,
NL80211_CMD_NEW_INTERFACE) < 0) {
goto out;
}
if_idx++;
}
wp_idx++;
}
out:
cb->args[0] = wp_idx;
cb->args[1] = if_idx;
ret = skb->len;
out_unlock:
rtnl_unlock();
return ret;
}
static int nl80211_get_interface(struct sk_buff *skb, struct genl_info *info)
{
struct sk_buff *msg;
struct cfg80211_registered_device *rdev = 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,
rdev, wdev, NL80211_CMD_NEW_INTERFACE) < 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 },
[NL80211_MNTR_FLAG_ACTIVE] = { .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_deprecated(flags, NL80211_MNTR_FLAG_MAX, nla, mntr_flags_policy, NULL))
return -EINVAL;
for (flag = 1; flag <= NL80211_MNTR_FLAG_MAX; flag++)
if (flags[flag])
*mntrflags |= (1<<flag);
*mntrflags |= MONITOR_FLAG_CHANGED;
return 0;
}
static int nl80211_parse_mon_options(struct cfg80211_registered_device *rdev,
enum nl80211_iftype type,
struct genl_info *info,
struct vif_params *params)
{
bool change = false;
int err;
if (info->attrs[NL80211_ATTR_MNTR_FLAGS]) {
if (type != NL80211_IFTYPE_MONITOR)
return -EINVAL;
err = parse_monitor_flags(info->attrs[NL80211_ATTR_MNTR_FLAGS],
¶ms->flags);
if (err)
return err;
change = true;
}
if (params->flags & MONITOR_FLAG_ACTIVE &&
!(rdev->wiphy.features & NL80211_FEATURE_ACTIVE_MONITOR))
return -EOPNOTSUPP;
if (info->attrs[NL80211_ATTR_MU_MIMO_GROUP_DATA]) {
const u8 *mumimo_groups;
u32 cap_flag = NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER;
if (type != NL80211_IFTYPE_MONITOR)
return -EINVAL;
if (!wiphy_ext_feature_isset(&rdev->wiphy, cap_flag))
return -EOPNOTSUPP;
mumimo_groups =
nla_data(info->attrs[NL80211_ATTR_MU_MIMO_GROUP_DATA]);
if ((mumimo_groups[0] & BIT(0)) ||
(mumimo_groups[VHT_MUMIMO_GROUPS_DATA_LEN - 1] & BIT(7)))
return -EINVAL;
params->vht_mumimo_groups = mumimo_groups;
change = true;
}
if (info->attrs[NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR]) {
u32 cap_flag = NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER;
if (type != NL80211_IFTYPE_MONITOR)
return -EINVAL;
if (!wiphy_ext_feature_isset(&rdev->wiphy, cap_flag))
return -EOPNOTSUPP;
params->vht_mumimo_follow_addr =
nla_data(info->attrs[NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR]);
change = true;
}
return change ? 1 : 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 && netif_is_bridge_port(netdev))
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];
bool change = false;
memset(¶ms, 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 (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->u.mesh.id_up_len =
nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
memcpy(wdev->u.mesh.id,
nla_data(info->attrs[NL80211_ATTR_MESH_ID]),
wdev->u.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;
}
err = nl80211_parse_mon_options(rdev, ntype, info, ¶ms);
if (err < 0)
return err;
if (err > 0)
change = true;
if (change)
err = cfg80211_change_iface(rdev, dev, ntype, ¶ms);
else
err = 0;
if (!err && params.use_4addr != -1)
dev->ieee80211_ptr->use_4addr = params.use_4addr;
if (change && !err) {
struct wireless_dev *wdev = dev->ieee80211_ptr;
nl80211_notify_iface(rdev, wdev, NL80211_CMD_SET_INTERFACE);
}
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;
memset(¶ms, 0, sizeof(params));
if (!info->attrs[NL80211_ATTR_IFNAME])
return -EINVAL;
if (info->attrs[NL80211_ATTR_IFTYPE])
type = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]);
if (!rdev->ops->add_virtual_intf)
return -EOPNOTSUPP;
if ((type == NL80211_IFTYPE_P2P_DEVICE || type == NL80211_IFTYPE_NAN ||
rdev->wiphy.features & NL80211_FEATURE_MAC_ON_CREATE) &&
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;
}
if (!cfg80211_iftype_allowed(&rdev->wiphy, type, params.use_4addr, 0))
return -EOPNOTSUPP;
err = nl80211_parse_mon_options(rdev, type, info, ¶ms);
if (err < 0)
return err;
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg)
return -ENOMEM;
wdev = rdev_add_virtual_intf(rdev,
nla_data(info->attrs[NL80211_ATTR_IFNAME]),
NET_NAME_USER, type, ¶ms);
if (WARN_ON(!wdev)) {
nlmsg_free(msg);
return -EPROTO;
} else if (IS_ERR(wdev)) {
nlmsg_free(msg);
return PTR_ERR(wdev);
}
if (info->attrs[NL80211_ATTR_SOCKET_OWNER])
wdev->owner_nlportid = info->snd_portid;
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->u.mesh.id_up_len =
nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
memcpy(wdev->u.mesh.id,
nla_data(info->attrs[NL80211_ATTR_MESH_ID]),
wdev->u.mesh.id_up_len);
wdev_unlock(wdev);
break;
case NL80211_IFTYPE_NAN:
case NL80211_IFTYPE_P2P_DEVICE:
cfg80211_init_wdev(wdev);
cfg80211_register_wdev(rdev, wdev);
break;
default:
break;
}
if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0,
rdev, wdev, NL80211_CMD_NEW_INTERFACE) < 0) {
nlmsg_free(msg);
return -ENOBUFS;
}
return genlmsg_reply(msg, info);
}
static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
int ret;
cfg80211_destroy_ifaces(rdev);
wiphy_lock(&rdev->wiphy);
ret = _nl80211_new_interface(skb, info);
wiphy_unlock(&rdev->wiphy);
return ret;
}
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;
mutex_unlock(&rdev->wiphy.mtx);
if (!wdev->netdev)
info->user_ptr[1] = NULL;
else
dev_close(wdev->netdev);
mutex_lock(&rdev->wiphy.mtx);
return cfg80211_remove_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);
}
static int nl80211_validate_key_link_id(struct genl_info *info,
struct wireless_dev *wdev,
int link_id, bool pairwise)
{
if (pairwise) {
if (link_id != -1) {
GENL_SET_ERR_MSG(info,
"link ID not allowed for pairwise key");
return -EINVAL;
}
return 0;
}
if (wdev->valid_links) {
if (link_id == -1) {
GENL_SET_ERR_MSG(info,
"link ID must for MLO group key");
return -EINVAL;
}
if (!(wdev->valid_links & BIT(link_id))) {
GENL_SET_ERR_MSG(info, "invalid link ID for MLO group key");
return -EINVAL;
}
} else if (link_id != -1) {
GENL_SET_ERR_MSG(info, "link ID not allowed for non-MLO group key");
return -EINVAL;
}
return 0;
}
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_noflag(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_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;
bool bigtk_support = false;
int link_id = nl80211_link_id_or_invalid(info->attrs);
struct wireless_dev *wdev = dev->ieee80211_ptr;
if (wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_BEACON_PROTECTION))
bigtk_support = true;
if ((wdev->iftype == NL80211_IFTYPE_STATION ||
wdev->iftype == NL80211_IFTYPE_P2P_CLIENT) &&
wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT))
bigtk_support = true;
if (info->attrs[NL80211_ATTR_KEY_IDX]) {
key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);
if (key_idx >= 6 && key_idx <= 7 && !bigtk_support) {
GENL_SET_ERR_MSG(info, "BIGTK not supported");
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 != NL80211_KEYTYPE_GROUP &&
kt != NL80211_KEYTYPE_PAIRWISE)
return -EINVAL;
pairwise = kt == NL80211_KEYTYPE_PAIRWISE;
}
if (!rdev->ops->get_key)
return -EOPNOTSUPP;
if (!pairwise && mac_addr && !(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN))
return -ENOENT;
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;
err = nl80211_validate_key_link_id(info, wdev, link_id, pairwise);
if (err)
goto free_msg;
err = rdev_get_key(rdev, dev, link_id, 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];
int link_id = nl80211_link_id_or_invalid(info->attrs);
struct wireless_dev *wdev = dev->ieee80211_ptr;
err = nl80211_parse_key(info, &key);
if (err)
return err;
if (key.idx < 0)
return -EINVAL;
if (!key.def && !key.defmgmt && !key.defbeacon &&
!(key.p.mode == NL80211_KEY_SET_TX))
return -EINVAL;
wdev_lock(wdev);
if (key.def) {
if (!rdev->ops->set_default_key) {
err = -EOPNOTSUPP;
goto out;
}
err = nl80211_key_allowed(wdev);
if (err)
goto out;
err = nl80211_validate_key_link_id(info, wdev, link_id, false);
if (err)
goto out;
err = rdev_set_default_key(rdev, dev, link_id, key.idx,
key.def_uni, key.def_multi);
if (err)
goto out;
#ifdef CONFIG_CFG80211_WEXT
wdev->wext.default_key = key.idx;
#endif
} else if (key.defmgmt) {
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(wdev);
if (err)
goto out;
err = nl80211_validate_key_link_id(info, wdev, link_id, false);
if (err)
goto out;
err = rdev_set_default_mgmt_key(rdev, dev, link_id, key.idx);
if (err)
goto out;
#ifdef CONFIG_CFG80211_WEXT
wdev->wext.default_mgmt_key = key.idx;
#endif
} else if (key.defbeacon) {
if (key.def_uni || !key.def_multi) {
err = -EINVAL;
goto out;
}
if (!rdev->ops->set_default_beacon_key) {
err = -EOPNOTSUPP;
goto out;
}
err = nl80211_key_allowed(wdev);
if (err)
goto out;
err = nl80211_validate_key_link_id(info, wdev, link_id, false);
if (err)
goto out;
err = rdev_set_default_beacon_key(rdev, dev, link_id, key.idx);
if (err)
goto out;
} else if (key.p.mode == NL80211_KEY_SET_TX &&
wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_EXT_KEY_ID)) {
u8 *mac_addr = NULL;
if (info->attrs[NL80211_ATTR_MAC])
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
if (!mac_addr || key.idx < 0 || key.idx > 1) {
err = -EINVAL;
goto out;
}
err = nl80211_validate_key_link_id(info, wdev, link_id, true);
if (err)
goto out;
err = rdev_add_key(rdev, dev, link_id, key.idx,
NL80211_KEYTYPE_PAIRWISE,
mac_addr, &key.p);
} else {
err = -EINVAL;
}
out:
wdev_unlock(wdev);
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;
int link_id = nl80211_link_id_or_invalid(info->attrs);
struct wireless_dev *wdev = dev->ieee80211_ptr;
err = nl80211_parse_key(info, &key);
if (err)
return err;
if (!key.p.key) {
GENL_SET_ERR_MSG(info, "no 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;
}
if (key.type != NL80211_KEYTYPE_PAIRWISE &&
key.type != NL80211_KEYTYPE_GROUP) {
GENL_SET_ERR_MSG(info, "key type not pairwise or group");
return -EINVAL;
}
if (key.type == NL80211_KEYTYPE_GROUP &&
info->attrs[NL80211_ATTR_VLAN_ID])
key.p.vlan_id = nla_get_u16(info->attrs[NL80211_ATTR_VLAN_ID]);
if (!rdev->ops->add_key)
return -EOPNOTSUPP;
if (cfg80211_validate_key_settings(rdev, &key.p, key.idx,
key.type == NL80211_KEYTYPE_PAIRWISE,
mac_addr)) {
GENL_SET_ERR_MSG(info, "key setting validation failed");
return -EINVAL;
}
wdev_lock(wdev);
err = nl80211_key_allowed(wdev);
if (err)
GENL_SET_ERR_MSG(info, "key not allowed");
if (!err)
err = nl80211_validate_key_link_id(info, wdev, link_id,
key.type == NL80211_KEYTYPE_PAIRWISE);
if (!err) {
err = rdev_add_key(rdev, dev, link_id, key.idx,
key.type == NL80211_KEYTYPE_PAIRWISE,
mac_addr, &key.p);
if (err)
GENL_SET_ERR_MSG(info, "key addition failed");
}
wdev_unlock(wdev);
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;
int link_id = nl80211_link_id_or_invalid(info->attrs);
struct wireless_dev *wdev = dev->ieee80211_ptr;
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;
}
if (key.type != NL80211_KEYTYPE_PAIRWISE &&
key.type != NL80211_KEYTYPE_GROUP)
return -EINVAL;
if (!cfg80211_valid_key_idx(rdev, key.idx,
key.type == NL80211_KEYTYPE_PAIRWISE))
return -EINVAL;
if (!rdev->ops->del_key)
return -EOPNOTSUPP;
wdev_lock(wdev);
err = nl80211_key_allowed(wdev);
if (key.type == NL80211_KEYTYPE_GROUP && mac_addr &&
!(rdev->wiphy.flags & WIPHY_FLAG_IBSS_RSN))
err = -ENOENT;
if (!err)
err = nl80211_validate_key_link_id(info, wdev, link_id,
key.type == NL80211_KEYTYPE_PAIRWISE);
if (!err)
err = rdev_del_key(rdev, dev, link_id, key.idx,
key.type == NL80211_KEYTYPE_PAIRWISE,
mac_addr);
#ifdef CONFIG_CFG80211_WEXT
if (!err) {
if (key.idx == wdev->wext.default_key)
wdev->wext.default_key = -1;
else if (key.idx == wdev->wext.default_mgmt_key)
wdev->wext.default_mgmt_key = -1;
}
#endif
wdev_unlock(wdev);
return err;
}
static int validate_acl_mac_addrs(struct nlattr *nl_attr)
{
struct nlattr *attr;
int n_entries = 0, tmp;
nla_for_each_nested(attr, nl_attr, tmp) {
if (nla_len(attr) != ETH_ALEN)
return -EINVAL;
n_entries++;
}
return n_entries;
}
static struct cfg80211_acl_data *parse_acl_data(struct wiphy *wiphy,
struct genl_info *info)
{
enum nl80211_acl_policy acl_policy;
struct nlattr *attr;
struct cfg80211_acl_data *acl;
int i = 0, n_entries, tmp;
if (!wiphy->max_acl_mac_addrs)
return ERR_PTR(-EOPNOTSUPP);
if (!info->attrs[NL80211_ATTR_ACL_POLICY])
return ERR_PTR(-EINVAL);
acl_policy = nla_get_u32(info->attrs[NL80211_ATTR_ACL_POLICY]);
if (acl_policy != NL80211_ACL_POLICY_ACCEPT_UNLESS_LISTED &&
acl_policy != NL80211_ACL_POLICY_DENY_UNLESS_LISTED)
return ERR_PTR(-EINVAL);
if (!info->attrs[NL80211_ATTR_MAC_ADDRS])
return ERR_PTR(-EINVAL);
n_entries = validate_acl_mac_addrs(info->attrs[NL80211_ATTR_MAC_ADDRS]);
if (n_entries < 0)
return ERR_PTR(n_entries);
if (n_entries > wiphy->max_acl_mac_addrs)
return ERR_PTR(-ENOTSUPP);
acl = kzalloc(struct_size(acl, mac_addrs, n_entries), GFP_KERNEL);
if (!acl)
return ERR_PTR(-ENOMEM);
nla_for_each_nested(attr, info->attrs[NL80211_ATTR_MAC_ADDRS], tmp) {
memcpy(acl->mac_addrs[i].addr, nla_data(attr), ETH_ALEN);
i++;
}
acl->n_acl_entries = n_entries;
acl->acl_policy = acl_policy;
return acl;
}
static int nl80211_set_mac_acl(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 cfg80211_acl_data *acl;
int err;
if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
return -EOPNOTSUPP;
if (!dev->ieee80211_ptr->links[0].ap.beacon_interval)
return -EINVAL;
acl = parse_acl_data(&rdev->wiphy, info);
if (IS_ERR(acl))
return PTR_ERR(acl);
err = rdev_set_mac_acl(rdev, dev, acl);
kfree(acl);
return err;
}
static u32 rateset_to_mask(struct ieee80211_supported_band *sband,
u8 *rates, u8 rates_len)
{
u8 i;
u32 mask = 0;
for (i = 0; i < rates_len; i++) {
int rate = (rates[i] & 0x7f) * 5;
int ridx;
for (ridx = 0; ridx < sband->n_bitrates; ridx++) {
struct ieee80211_rate *srate =
&sband->bitrates[ridx];
if (rate == srate->bitrate) {
mask |= 1 << ridx;
break;
}
}
if (ridx == sband->n_bitrates)
return 0;
}
return mask;
}
static bool ht_rateset_to_mask(struct ieee80211_supported_band *sband,
u8 *rates, u8 rates_len,
u8 mcs[IEEE80211_HT_MCS_MASK_LEN])
{
u8 i;
memset(mcs, 0, IEEE80211_HT_MCS_MASK_LEN);
for (i = 0; i < rates_len; i++) {
int ridx, rbit;
ridx = rates[i] / 8;
rbit = BIT(rates[i] % 8);
if ((ridx < 0) || (ridx >= IEEE80211_HT_MCS_MASK_LEN))
return false;
ridx = array_index_nospec(ridx, IEEE80211_HT_MCS_MASK_LEN);
if (sband->ht_cap.mcs.rx_mask[ridx] & rbit)
mcs[ridx] |= rbit;
else
return false;
}
return true;
}
static u16 vht_mcs_map_to_mcs_mask(u8 vht_mcs_map)
{
u16 mcs_mask = 0;
switch (vht_mcs_map) {
case IEEE80211_VHT_MCS_NOT_SUPPORTED:
break;
case IEEE80211_VHT_MCS_SUPPORT_0_7:
mcs_mask = 0x00FF;
break;
case IEEE80211_VHT_MCS_SUPPORT_0_8:
mcs_mask = 0x01FF;
break;
case IEEE80211_VHT_MCS_SUPPORT_0_9:
mcs_mask = 0x03FF;
break;
default:
break;
}
return mcs_mask;
}
static void vht_build_mcs_mask(u16 vht_mcs_map,
u16 vht_mcs_mask[NL80211_VHT_NSS_MAX])
{
u8 nss;
for (nss = 0; nss < NL80211_VHT_NSS_MAX; nss++) {
vht_mcs_mask[nss] = vht_mcs_map_to_mcs_mask(vht_mcs_map & 0x03);
vht_mcs_map >>= 2;
}
}
static bool vht_set_mcs_mask(struct ieee80211_supported_band *sband,
struct nl80211_txrate_vht *txrate,
u16 mcs[NL80211_VHT_NSS_MAX])
{
u16 tx_mcs_map = le16_to_cpu(sband->vht_cap.vht_mcs.tx_mcs_map);
u16 tx_mcs_mask[NL80211_VHT_NSS_MAX] = {};
u8 i;
if (!sband->vht_cap.vht_supported)
return false;
memset(mcs, 0, sizeof(u16) * NL80211_VHT_NSS_MAX);
vht_build_mcs_mask(tx_mcs_map, tx_mcs_mask);
for (i = 0; i < NL80211_VHT_NSS_MAX; i++) {
if ((tx_mcs_mask[i] & txrate->mcs[i]) == txrate->mcs[i])
mcs[i] = txrate->mcs[i];
else
return false;
}
return true;
}
static u16 he_mcs_map_to_mcs_mask(u8 he_mcs_map)
{
switch (he_mcs_map) {
case IEEE80211_HE_MCS_NOT_SUPPORTED:
return 0;
case IEEE80211_HE_MCS_SUPPORT_0_7:
return 0x00FF;
case IEEE80211_HE_MCS_SUPPORT_0_9:
return 0x03FF;
case IEEE80211_HE_MCS_SUPPORT_0_11:
return 0xFFF;
default:
break;
}
return 0;
}
static void he_build_mcs_mask(u16 he_mcs_map,
u16 he_mcs_mask[NL80211_HE_NSS_MAX])
{
u8 nss;
for (nss = 0; nss < NL80211_HE_NSS_MAX; nss++) {
he_mcs_mask[nss] = he_mcs_map_to_mcs_mask(he_mcs_map & 0x03);
he_mcs_map >>= 2;
}
}
static u16 he_get_txmcsmap(struct genl_info *info, unsigned int link_id,
const struct ieee80211_sta_he_cap *he_cap)
{
struct net_device *dev = info->user_ptr[1];
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_chan_def *chandef;
__le16 tx_mcs;
chandef = wdev_chandef(wdev, link_id);
if (!chandef) {
return le16_to_cpu(he_cap->he_mcs_nss_supp.tx_mcs_80);
}
switch (chandef->width) {
case NL80211_CHAN_WIDTH_80P80:
tx_mcs = he_cap->he_mcs_nss_supp.tx_mcs_80p80;
break;
case NL80211_CHAN_WIDTH_160:
tx_mcs = he_cap->he_mcs_nss_supp.tx_mcs_160;
break;
default:
tx_mcs = he_cap->he_mcs_nss_supp.tx_mcs_80;
break;
}
return le16_to_cpu(tx_mcs);
}
static bool he_set_mcs_mask(struct genl_info *info,
struct wireless_dev *wdev,
struct ieee80211_supported_band *sband,
struct nl80211_txrate_he *txrate,
u16 mcs[NL80211_HE_NSS_MAX],
unsigned int link_id)
{
const struct ieee80211_sta_he_cap *he_cap;
u16 tx_mcs_mask[NL80211_HE_NSS_MAX] = {};
u16 tx_mcs_map = 0;
u8 i;
he_cap = ieee80211_get_he_iftype_cap(sband, wdev->iftype);
if (!he_cap)
return false;
memset(mcs, 0, sizeof(u16) * NL80211_HE_NSS_MAX);
tx_mcs_map = he_get_txmcsmap(info, link_id, he_cap);
he_build_mcs_mask(tx_mcs_map, tx_mcs_mask);
for (i = 0; i < NL80211_HE_NSS_MAX; i++) {
if ((tx_mcs_mask[i] & txrate->mcs[i]) == txrate->mcs[i])
mcs[i] = txrate->mcs[i];
else
return false;
}
return true;
}
static int nl80211_parse_tx_bitrate_mask(struct genl_info *info,
struct nlattr *attrs[],
enum nl80211_attrs attr,
struct cfg80211_bitrate_mask *mask,
struct net_device *dev,
bool default_all_enabled,
unsigned int link_id)
{
struct nlattr *tb[NL80211_TXRATE_MAX + 1];
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct wireless_dev *wdev = dev->ieee80211_ptr;
int rem, i;
struct nlattr *tx_rates;
struct ieee80211_supported_band *sband;
u16 vht_tx_mcs_map, he_tx_mcs_map;
memset(mask, 0, sizeof(*mask));
for (i = 0; i < NUM_NL80211_BANDS; i++) {
const struct ieee80211_sta_he_cap *he_cap;
if (!default_all_enabled)
break;
sband = rdev->wiphy.bands[i];
if (!sband)
continue;
mask->control[i].legacy = (1 << sband->n_bitrates) - 1;
memcpy(mask->control[i].ht_mcs,
sband->ht_cap.mcs.rx_mask,
sizeof(mask->control[i].ht_mcs));
if (sband->vht_cap.vht_supported) {
vht_tx_mcs_map = le16_to_cpu(sband->vht_cap.vht_mcs.tx_mcs_map);
vht_build_mcs_mask(vht_tx_mcs_map, mask->control[i].vht_mcs);
}
he_cap = ieee80211_get_he_iftype_cap(sband, wdev->iftype);
if (!he_cap)
continue;
he_tx_mcs_map = he_get_txmcsmap(info, link_id, he_cap);
he_build_mcs_mask(he_tx_mcs_map, mask->control[i].he_mcs);
mask->control[i].he_gi = 0xFF;
mask->control[i].he_ltf = 0xFF;
}
if (!attrs[attr])
goto out;
BUILD_BUG_ON(NL80211_MAX_SUPP_HT_RATES > IEEE80211_HT_MCS_MASK_LEN * 8);
nla_for_each_nested(tx_rates, attrs[attr], rem) {
enum nl80211_band band = nla_type(tx_rates);
int err;
if (band < 0 || band >= NUM_NL80211_BANDS)
return -EINVAL;
sband = rdev->wiphy.bands[band];
if (sband == NULL)
return -EINVAL;
err = nla_parse_nested_deprecated(tb, NL80211_TXRATE_MAX,
tx_rates,
nl80211_txattr_policy,
info->extack);
if (err)
return err;
if (tb[NL80211_TXRATE_LEGACY]) {
mask->control[band].legacy = rateset_to_mask(
sband,
nla_data(tb[NL80211_TXRATE_LEGACY]),
nla_len(tb[NL80211_TXRATE_LEGACY]));
if ((mask->control[band].legacy == 0) &&
nla_len(tb[NL80211_TXRATE_LEGACY]))
return -EINVAL;
}
if (tb[NL80211_TXRATE_HT]) {
if (!ht_rateset_to_mask(
sband,
nla_data(tb[NL80211_TXRATE_HT]),
nla_len(tb[NL80211_TXRATE_HT]),
mask->control[band].ht_mcs))
return -EINVAL;
}
if (tb[NL80211_TXRATE_VHT]) {
if (!vht_set_mcs_mask(
sband,
nla_data(tb[NL80211_TXRATE_VHT]),
mask->control[band].vht_mcs))
return -EINVAL;
}
if (tb[NL80211_TXRATE_GI]) {
mask->control[band].gi =
nla_get_u8(tb[NL80211_TXRATE_GI]);
if (mask->control[band].gi > NL80211_TXRATE_FORCE_LGI)
return -EINVAL;
}
if (tb[NL80211_TXRATE_HE] &&
!he_set_mcs_mask(info, wdev, sband,
nla_data(tb[NL80211_TXRATE_HE]),
mask->control[band].he_mcs,
link_id))
return -EINVAL;
if (tb[NL80211_TXRATE_HE_GI])
mask->control[band].he_gi =
nla_get_u8(tb[NL80211_TXRATE_HE_GI]);
if (tb[NL80211_TXRATE_HE_LTF])
mask->control[band].he_ltf =
nla_get_u8(tb[NL80211_TXRATE_HE_LTF]);
if (mask->control[band].legacy == 0) {
if (!(rdev->wiphy.bands[band]->ht_cap.ht_supported ||
rdev->wiphy.bands[band]->vht_cap.vht_supported ||
ieee80211_get_he_iftype_cap(sband, wdev->iftype)))
return -EINVAL;
for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++)
if (mask->control[band].ht_mcs[i])
goto out;
for (i = 0; i < NL80211_VHT_NSS_MAX; i++)
if (mask->control[band].vht_mcs[i])
goto out;
for (i = 0; i < NL80211_HE_NSS_MAX; i++)
if (mask->control[band].he_mcs[i])
goto out;
return -EINVAL;
}
}
out:
return 0;
}
static int validate_beacon_tx_rate(struct cfg80211_registered_device *rdev,
enum nl80211_band band,
struct cfg80211_bitrate_mask *beacon_rate)
{
u32 count_ht, count_vht, count_he, i;
u32 rate = beacon_rate->control[band].legacy;
if (hweight32(rate) > 1)
return -EINVAL;
count_ht = 0;
for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++) {
if (hweight8(beacon_rate->control[band].ht_mcs[i]) > 1) {
return -EINVAL;
} else if (beacon_rate->control[band].ht_mcs[i]) {
count_ht++;
if (count_ht > 1)
return -EINVAL;
}
if (count_ht && rate)
return -EINVAL;
}
count_vht = 0;
for (i = 0; i < NL80211_VHT_NSS_MAX; i++) {
if (hweight16(beacon_rate->control[band].vht_mcs[i]) > 1) {
return -EINVAL;
} else if (beacon_rate->control[band].vht_mcs[i]) {
count_vht++;
if (count_vht > 1)
return -EINVAL;
}
if (count_vht && rate)
return -EINVAL;
}
count_he = 0;
for (i = 0; i < NL80211_HE_NSS_MAX; i++) {
if (hweight16(beacon_rate->control[band].he_mcs[i]) > 1) {
return -EINVAL;
} else if (beacon_rate->control[band].he_mcs[i]) {
count_he++;
if (count_he > 1)
return -EINVAL;
}
if (count_he && rate)
return -EINVAL;
}
if ((count_ht && count_vht && count_he) ||
(!rate && !count_ht && !count_vht && !count_he))
return -EINVAL;
if (rate &&
!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_BEACON_RATE_LEGACY))
return -EINVAL;
if (count_ht &&
!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_BEACON_RATE_HT))
return -EINVAL;
if (count_vht &&
!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_BEACON_RATE_VHT))
return -EINVAL;
if (count_he &&
!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_BEACON_RATE_HE))
return -EINVAL;
return 0;
}
static int nl80211_parse_mbssid_config(struct wiphy *wiphy,
struct net_device *dev,
struct nlattr *attrs,
struct cfg80211_mbssid_config *config,
u8 num_elems)
{
struct nlattr *tb[NL80211_MBSSID_CONFIG_ATTR_MAX + 1];
if (!wiphy->mbssid_max_interfaces)
return -EOPNOTSUPP;
if (nla_parse_nested(tb, NL80211_MBSSID_CONFIG_ATTR_MAX, attrs, NULL,
NULL) ||
!tb[NL80211_MBSSID_CONFIG_ATTR_INDEX])
return -EINVAL;
config->ema = nla_get_flag(tb[NL80211_MBSSID_CONFIG_ATTR_EMA]);
if (config->ema) {
if (!wiphy->ema_max_profile_periodicity)
return -EOPNOTSUPP;
if (num_elems > wiphy->ema_max_profile_periodicity)
return -EINVAL;
}
config->index = nla_get_u8(tb[NL80211_MBSSID_CONFIG_ATTR_INDEX]);
if (config->index >= wiphy->mbssid_max_interfaces ||
(!config->index && !num_elems))
return -EINVAL;
if (tb[NL80211_MBSSID_CONFIG_ATTR_TX_IFINDEX]) {
u32 tx_ifindex =
nla_get_u32(tb[NL80211_MBSSID_CONFIG_ATTR_TX_IFINDEX]);
if ((!config->index && tx_ifindex != dev->ifindex) ||
(config->index && tx_ifindex == dev->ifindex))
return -EINVAL;
if (tx_ifindex != dev->ifindex) {
struct net_device *tx_netdev =
dev_get_by_index(wiphy_net(wiphy), tx_ifindex);
if (!tx_netdev || !tx_netdev->ieee80211_ptr ||
tx_netdev->ieee80211_ptr->wiphy != wiphy ||
tx_netdev->ieee80211_ptr->iftype !=
NL80211_IFTYPE_AP) {
dev_put(tx_netdev);
return -EINVAL;
}
config->tx_wdev = tx_netdev->ieee80211_ptr;
} else {
config->tx_wdev = dev->ieee80211_ptr;
}
} else if (!config->index) {
config->tx_wdev = dev->ieee80211_ptr;
} else {
return -EINVAL;
}
return 0;
}
static struct cfg80211_mbssid_elems *
nl80211_parse_mbssid_elems(struct wiphy *wiphy, struct nlattr *attrs)
{
struct nlattr *nl_elems;
struct cfg80211_mbssid_elems *elems;
int rem_elems;
u8 i = 0, num_elems = 0;
if (!wiphy->mbssid_max_interfaces)
return ERR_PTR(-EINVAL);
nla_for_each_nested(nl_elems, attrs, rem_elems)
num_elems++;
elems = kzalloc(struct_size(elems, elem, num_elems), GFP_KERNEL);
if (!elems)
return ERR_PTR(-ENOMEM);
nla_for_each_nested(nl_elems, attrs, rem_elems) {
elems->elem[i].data = nla_data(nl_elems);
elems->elem[i].len = nla_len(nl_elems);
i++;
}
elems->cnt = num_elems;
return elems;
}
static int nl80211_parse_he_bss_color(struct nlattr *attrs,
struct cfg80211_he_bss_color *he_bss_color)
{
struct nlattr *tb[NL80211_HE_BSS_COLOR_ATTR_MAX + 1];
int err;
err = nla_parse_nested(tb, NL80211_HE_BSS_COLOR_ATTR_MAX, attrs,
he_bss_color_policy, NULL);
if (err)
return err;
if (!tb[NL80211_HE_BSS_COLOR_ATTR_COLOR])
return -EINVAL;
he_bss_color->color =
nla_get_u8(tb[NL80211_HE_BSS_COLOR_ATTR_COLOR]);
he_bss_color->enabled =
!nla_get_flag(tb[NL80211_HE_BSS_COLOR_ATTR_DISABLED]);
he_bss_color->partial =
nla_get_flag(tb[NL80211_HE_BSS_COLOR_ATTR_PARTIAL]);
return 0;
}
static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev,
struct nlattr *attrs[],
struct cfg80211_beacon_data *bcn)
{
bool haveinfo = false;
int err;
memset(bcn, 0, sizeof(*bcn));
bcn->link_id = nl80211_link_id(attrs);
if (attrs[NL80211_ATTR_BEACON_HEAD]) {
bcn->head = nla_data(attrs[NL80211_ATTR_BEACON_HEAD]);
bcn->head_len = nla_len(attrs[NL80211_ATTR_BEACON_HEAD]);
if (!bcn->head_len)
return -EINVAL;
haveinfo = true;
}
if (attrs[NL80211_ATTR_BEACON_TAIL]) {
bcn->tail = nla_data(attrs[NL80211_ATTR_BEACON_TAIL]);
bcn->tail_len = nla_len(attrs[NL80211_ATTR_BEACON_TAIL]);
haveinfo = true;
}
if (!haveinfo)
return -EINVAL;
if (attrs[NL80211_ATTR_IE]) {
bcn->beacon_ies = nla_data(attrs[NL80211_ATTR_IE]);
bcn->beacon_ies_len = nla_len(attrs[NL80211_ATTR_IE]);
}
if (attrs[NL80211_ATTR_IE_PROBE_RESP]) {
bcn->proberesp_ies =
nla_data(attrs[NL80211_ATTR_IE_PROBE_RESP]);
bcn->proberesp_ies_len =
nla_len(attrs[NL80211_ATTR_IE_PROBE_RESP]);
}
if (attrs[NL80211_ATTR_IE_ASSOC_RESP]) {
bcn->assocresp_ies =
nla_data(attrs[NL80211_ATTR_IE_ASSOC_RESP]);
bcn->assocresp_ies_len =
nla_len(attrs[NL80211_ATTR_IE_ASSOC_RESP]);
}
if (attrs[NL80211_ATTR_PROBE_RESP]) {
bcn->probe_resp = nla_data(attrs[NL80211_ATTR_PROBE_RESP]);
bcn->probe_resp_len = nla_len(attrs[NL80211_ATTR_PROBE_RESP]);
}
if (attrs[NL80211_ATTR_FTM_RESPONDER]) {
struct nlattr *tb[NL80211_FTM_RESP_ATTR_MAX + 1];
err = nla_parse_nested_deprecated(tb,
NL80211_FTM_RESP_ATTR_MAX,
attrs[NL80211_ATTR_FTM_RESPONDER],
NULL, NULL);
if (err)
return err;
if (tb[NL80211_FTM_RESP_ATTR_ENABLED] &&
wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER))
bcn->ftm_responder = 1;
else
return -EOPNOTSUPP;
if (tb[NL80211_FTM_RESP_ATTR_LCI]) {
bcn->lci = nla_data(tb[NL80211_FTM_RESP_ATTR_LCI]);
bcn->lci_len = nla_len(tb[NL80211_FTM_RESP_ATTR_LCI]);
}
if (tb[NL80211_FTM_RESP_ATTR_CIVICLOC]) {
bcn->civicloc = nla_data(tb[NL80211_FTM_RESP_ATTR_CIVICLOC]);
bcn->civicloc_len = nla_len(tb[NL80211_FTM_RESP_ATTR_CIVICLOC]);
}
} else {
bcn->ftm_responder = -1;
}
if (attrs[NL80211_ATTR_HE_BSS_COLOR]) {
err = nl80211_parse_he_bss_color(attrs[NL80211_ATTR_HE_BSS_COLOR],
&bcn->he_bss_color);
if (err)
return err;
bcn->he_bss_color_valid = true;
}
if (attrs[NL80211_ATTR_MBSSID_ELEMS]) {
struct cfg80211_mbssid_elems *mbssid =
nl80211_parse_mbssid_elems(&rdev->wiphy,
attrs[NL80211_ATTR_MBSSID_ELEMS]);
if (IS_ERR(mbssid))
return PTR_ERR(mbssid);
bcn->mbssid_ies = mbssid;
}
return 0;
}
static int nl80211_parse_he_obss_pd(struct nlattr *attrs,
struct ieee80211_he_obss_pd *he_obss_pd)
{
struct nlattr *tb[NL80211_HE_OBSS_PD_ATTR_MAX + 1];
int err;
err = nla_parse_nested(tb, NL80211_HE_OBSS_PD_ATTR_MAX, attrs,
he_obss_pd_policy, NULL);
if (err)
return err;
if (!tb[NL80211_HE_OBSS_PD_ATTR_SR_CTRL])
return -EINVAL;
he_obss_pd->sr_ctrl = nla_get_u8(tb[NL80211_HE_OBSS_PD_ATTR_SR_CTRL]);
if (tb[NL80211_HE_OBSS_PD_ATTR_MIN_OFFSET])
he_obss_pd->min_offset =
nla_get_u8(tb[NL80211_HE_OBSS_PD_ATTR_MIN_OFFSET]);
if (tb[NL80211_HE_OBSS_PD_ATTR_MAX_OFFSET])
he_obss_pd->max_offset =
nla_get_u8(tb[NL80211_HE_OBSS_PD_ATTR_MAX_OFFSET]);
if (tb[NL80211_HE_OBSS_PD_ATTR_NON_SRG_MAX_OFFSET])
he_obss_pd->non_srg_max_offset =
nla_get_u8(tb[NL80211_HE_OBSS_PD_ATTR_NON_SRG_MAX_OFFSET]);
if (he_obss_pd->min_offset > he_obss_pd->max_offset)
return -EINVAL;
if (tb[NL80211_HE_OBSS_PD_ATTR_BSS_COLOR_BITMAP])
memcpy(he_obss_pd->bss_color_bitmap,
nla_data(tb[NL80211_HE_OBSS_PD_ATTR_BSS_COLOR_BITMAP]),
sizeof(he_obss_pd->bss_color_bitmap));
if (tb[NL80211_HE_OBSS_PD_ATTR_PARTIAL_BSSID_BITMAP])
memcpy(he_obss_pd->partial_bssid_bitmap,
nla_data(tb[NL80211_HE_OBSS_PD_ATTR_PARTIAL_BSSID_BITMAP]),
sizeof(he_obss_pd->partial_bssid_bitmap));
he_obss_pd->enable = true;
return 0;
}
static int nl80211_parse_fils_discovery(struct cfg80211_registered_device *rdev,
struct nlattr *attrs,
struct cfg80211_ap_settings *params)
{
struct nlattr *tb[NL80211_FILS_DISCOVERY_ATTR_MAX + 1];
int ret;
struct cfg80211_fils_discovery *fd = ¶ms->fils_discovery;
if (!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_FILS_DISCOVERY))
return -EINVAL;
ret = nla_parse_nested(tb, NL80211_FILS_DISCOVERY_ATTR_MAX, attrs,
NULL, NULL);
if (ret)
return ret;
if (!tb[NL80211_FILS_DISCOVERY_ATTR_INT_MIN] ||
!tb[NL80211_FILS_DISCOVERY_ATTR_INT_MAX] ||
!tb[NL80211_FILS_DISCOVERY_ATTR_TMPL])
return -EINVAL;
fd->tmpl_len = nla_len(tb[NL80211_FILS_DISCOVERY_ATTR_TMPL]);
fd->tmpl = nla_data(tb[NL80211_FILS_DISCOVERY_ATTR_TMPL]);
fd->min_interval = nla_get_u32(tb[NL80211_FILS_DISCOVERY_ATTR_INT_MIN]);
fd->max_interval = nla_get_u32(tb[NL80211_FILS_DISCOVERY_ATTR_INT_MAX]);
return 0;
}
static int
nl80211_parse_unsol_bcast_probe_resp(struct cfg80211_registered_device *rdev,
struct nlattr *attrs,
struct cfg80211_ap_settings *params)
{
struct nlattr *tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_MAX + 1];
int ret;
struct cfg80211_unsol_bcast_probe_resp *presp =
¶ms->unsol_bcast_probe_resp;
if (!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_UNSOL_BCAST_PROBE_RESP))
return -EINVAL;
ret = nla_parse_nested(tb, NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_MAX,
attrs, NULL, NULL);
if (ret)
return ret;
if (!tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_INT] ||
!tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_TMPL])
return -EINVAL;
presp->tmpl = nla_data(tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_TMPL]);
presp->tmpl_len = nla_len(tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_TMPL]);
presp->interval = nla_get_u32(tb[NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_INT]);
return 0;
}
static void nl80211_check_ap_rate_selectors(struct cfg80211_ap_settings *params,
const struct element *rates)
{
int i;
if (!rates)
return;
for (i = 0; i < rates->datalen; i++) {
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HT_PHY)
params->ht_required = true;
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY)
params->vht_required = true;
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HE_PHY)
params->he_required = true;
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_SAE_H2E)
params->sae_h2e_required = true;
}
}
static int nl80211_calculate_ap_params(struct cfg80211_ap_settings *params)
{
const struct cfg80211_beacon_data *bcn = ¶ms->beacon;
size_t ies_len = bcn->tail_len;
const u8 *ies = bcn->tail;
const struct element *rates;
const struct element *cap;
rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len);
nl80211_check_ap_rate_selectors(params, rates);
rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len);
nl80211_check_ap_rate_selectors(params, rates);
cap = cfg80211_find_elem(WLAN_EID_HT_CAPABILITY, ies, ies_len);
if (cap && cap->datalen >= sizeof(*params->ht_cap))
params->ht_cap = (void *)cap->data;
cap = cfg80211_find_elem(WLAN_EID_VHT_CAPABILITY, ies, ies_len);
if (cap && cap->datalen >= sizeof(*params->vht_cap))
params->vht_cap = (void *)cap->data;
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, ies, ies_len);
if (cap && cap->datalen >= sizeof(*params->he_cap) + 1)
params->he_cap = (void *)(cap->data + 1);
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
if (cap && cap->datalen >= sizeof(*params->he_oper) + 1)
params->he_oper = (void *)(cap->data + 1);
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_CAPABILITY, ies, ies_len);
if (cap) {
if (!cap->datalen)
return -EINVAL;
params->eht_cap = (void *)(cap->data + 1);
if (!ieee80211_eht_capa_size_ok((const u8 *)params->he_cap,
(const u8 *)params->eht_cap,
cap->datalen - 1, true))
return -EINVAL;
}
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
if (cap) {
if (!cap->datalen)
return -EINVAL;
params->eht_oper = (void *)(cap->data + 1);
if (!ieee80211_eht_oper_size_ok((const u8 *)params->eht_oper,
cap->datalen - 1))
return -EINVAL;
}
return 0;
}
static bool nl80211_get_ap_channel(struct cfg80211_registered_device *rdev,
struct cfg80211_ap_settings *params)
{
struct wireless_dev *wdev;
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
if (wdev->iftype != NL80211_IFTYPE_AP &&
wdev->iftype != NL80211_IFTYPE_P2P_GO)
continue;
if (!wdev->u.ap.preset_chandef.chan)
continue;
params->chandef = wdev->u.ap.preset_chandef;
return true;
}
return false;
}
static bool nl80211_valid_auth_type(struct cfg80211_registered_device *rdev,
enum nl80211_auth_type auth_type,
enum nl80211_commands cmd)
{
if (auth_type > NL80211_AUTHTYPE_MAX)
return false;
switch (cmd) {
case NL80211_CMD_AUTHENTICATE:
if (!(rdev->wiphy.features & NL80211_FEATURE_SAE) &&
auth_type == NL80211_AUTHTYPE_SAE)
return false;
if (!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_FILS_STA) &&
(auth_type == NL80211_AUTHTYPE_FILS_SK ||
auth_type == NL80211_AUTHTYPE_FILS_SK_PFS ||
auth_type == NL80211_AUTHTYPE_FILS_PK))
return false;
return true;
case NL80211_CMD_CONNECT:
if (!(rdev->wiphy.features & NL80211_FEATURE_SAE) &&
!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_SAE_OFFLOAD) &&
auth_type == NL80211_AUTHTYPE_SAE)
return false;
if (auth_type == NL80211_AUTHTYPE_FILS_SK_PFS ||
auth_type == NL80211_AUTHTYPE_FILS_PK)
return false;
if (!wiphy_ext_feature_isset(
&rdev->wiphy,
NL80211_EXT_FEATURE_FILS_SK_OFFLOAD) &&
auth_type == NL80211_AUTHTYPE_FILS_SK)
return false;
return true;
case NL80211_CMD_START_AP:
if (!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_SAE_OFFLOAD_AP) &&
auth_type == NL80211_AUTHTYPE_SAE)
return false;
if (auth_type == NL80211_AUTHTYPE_FILS_SK ||
auth_type == NL80211_AUTHTYPE_FILS_SK_PFS ||
auth_type == NL80211_AUTHTYPE_FILS_PK)
return false;
return true;
default:
return false;
}
}
static void nl80211_send_ap_started(struct wireless_dev *wdev,
unsigned int link_id)
{
struct wiphy *wiphy = wdev->wiphy;
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
struct sk_buff *msg;
void *hdr;
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg)
return;
hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_START_AP);
if (!hdr)
goto out;
if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
nla_put_u32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex) ||
nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
NL80211_ATTR_PAD) ||
(wdev->u.ap.ssid_len &&
nla_put(msg, NL80211_ATTR_SSID, wdev->u.ap.ssid_len,
wdev->u.ap.ssid)) ||
(wdev->valid_links &&
nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)))
goto out;
genlmsg_end(msg, hdr);
genlmsg_multicast_netns(&nl80211_fam, wiphy_net(wiphy), msg, 0,
NL80211_MCGRP_MLME, GFP_KERNEL);
return;
out:
nlmsg_free(msg);
}
static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
unsigned int link_id = nl80211_link_id(info->attrs);
struct net_device *dev = info->user_ptr[1];
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_ap_settings *params;
int err;
if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
return -EOPNOTSUPP;
if (!rdev->ops->start_ap)
return -EOPNOTSUPP;
if (wdev->links[link_id].ap.beacon_interval)
return -EALREADY;
if (!info->attrs[NL80211_ATTR_BEACON_INTERVAL] ||
!info->attrs[NL80211_ATTR_DTIM_PERIOD] ||
!info->attrs[NL80211_ATTR_BEACON_HEAD])
return -EINVAL;
params = kzalloc(sizeof(*params), GFP_KERNEL);
if (!params)
return -ENOMEM;
err = nl80211_parse_beacon(rdev, info->attrs, ¶ms->beacon);
if (err)
goto out;
params->beacon_interval =
nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]);
params->dtim_period =
nla_get_u32(info->attrs[NL80211_ATTR_DTIM_PERIOD]);
err = cfg80211_validate_beacon_int(rdev, dev->ieee80211_ptr->iftype,
params->beacon_interval);
if (err)
goto out;
if (info->attrs[NL80211_ATTR_SSID]) {
params->ssid = nla_data(info->attrs[NL80211_ATTR_SSID]);
params->ssid_len =
nla_len(info->attrs[NL80211_ATTR_SSID]);
if (params->ssid_len == 0) {
err = -EINVAL;
goto out;
}
if (wdev->u.ap.ssid_len &&
(wdev->u.ap.ssid_len != params->ssid_len ||
memcmp(wdev->u.ap.ssid, params->ssid, params->ssid_len))) {
err = -EINVAL;
goto out;
}
} else if (wdev->valid_links) {
err = -EINVAL;
goto out;
}
if (info->attrs[NL80211_ATTR_HIDDEN_SSID])
params->hidden_ssid = nla_get_u32(
info->attrs[NL80211_ATTR_HIDDEN_SSID]);
params->privacy = !!info->attrs[NL80211_ATTR_PRIVACY];
if (info->attrs[NL80211_ATTR_AUTH_TYPE]) {
params->auth_type = nla_get_u32(
info->attrs[NL80211_ATTR_AUTH_TYPE]);
if (!nl80211_valid_auth_type(rdev, params->auth_type,
NL80211_CMD_START_AP)) {
err = -EINVAL;
goto out;
}
} else
params->auth_type = NL80211_AUTHTYPE_AUTOMATIC;
err = nl80211_crypto_settings(rdev, info, ¶ms->crypto,
NL80211_MAX_NR_CIPHER_SUITES);
if (err)
goto out;
if (info->attrs[NL80211_ATTR_INACTIVITY_TIMEOUT]) {
if (!(rdev->wiphy.features & NL80211_FEATURE_INACTIVITY_TIMER)) {
err = -EOPNOTSUPP;
goto out;
}
params->inactivity_timeout = nla_get_u16(
info->attrs[NL80211_ATTR_INACTIVITY_TIMEOUT]);
}
if (info->attrs[NL80211_ATTR_P2P_CTWINDOW]) {
if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) {
err = -EINVAL;
goto out;
}
params->p2p_ctwindow =
nla_get_u8(info->attrs[NL80211_ATTR_P2P_CTWINDOW]);
if (params->p2p_ctwindow != 0 &&
!(rdev->wiphy.features & NL80211_FEATURE_P2P_GO_CTWIN)) {
err = -EINVAL;
goto out;
}
}
if (info->attrs[NL80211_ATTR_P2P_OPPPS]) {
u8 tmp;
if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) {
err = -EINVAL;
goto out;
}
tmp = nla_get_u8(info->attrs[NL80211_ATTR_P2P_OPPPS]);
params->p2p_opp_ps = tmp;
if (params->p2p_opp_ps != 0 &&
!(rdev->wiphy.features & NL80211_FEATURE_P2P_GO_OPPPS)) {
err = -EINVAL;
goto out;
}
}
if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
err = nl80211_parse_chandef(rdev, info, ¶ms->chandef);
if (err)
goto out;
} else if (wdev->valid_links) {
err = -EINVAL;
goto out;
} else if (wdev->u.ap.preset_chandef.chan) {
params->chandef = wdev->u.ap.preset_chandef;
} else if (!nl80211_get_ap_channel(rdev, params)) {
err = -EINVAL;
goto out;
}
if (info->attrs[NL80211_ATTR_PUNCT_BITMAP]) {
err = nl80211_parse_punct_bitmap(rdev, info,
¶ms->chandef,
¶ms->punct_bitmap);
if (err)
goto out;
}
if (!cfg80211_reg_can_beacon_relax(&rdev->wiphy, ¶ms->chandef,
wdev->iftype)) {
err = -EINVAL;
goto out;
}
wdev_lock(wdev);
if (info->attrs[NL80211_ATTR_TX_RATES]) {
err = nl80211_parse_tx_bitrate_mask(info, info->attrs,
NL80211_ATTR_TX_RATES,
¶ms->beacon_rate,
dev, false, link_id);
if (err)
goto out_unlock;
err = validate_beacon_tx_rate(rdev, params->chandef.chan->band,
¶ms->beacon_rate);
if (err)
goto out_unlock;
}
if (info->attrs[NL80211_ATTR_SMPS_MODE]) {
params->smps_mode =
nla_get_u8(info->attrs[NL80211_ATTR_SMPS_MODE]);
switch (params->smps_mode) {
case NL80211_SMPS_OFF:
break;
case NL80211_SMPS_STATIC:
if (!(rdev->wiphy.features &
NL80211_FEATURE_STATIC_SMPS)) {
err = -EINVAL;
goto out_unlock;
}
break;
case NL80211_SMPS_DYNAMIC:
if (!(rdev->wiphy.features &
NL80211_FEATURE_DYNAMIC_SMPS)) {
err = -EINVAL;
goto out_unlock;
}
break;
default:
err = -EINVAL;
goto out_unlock;
}
} else {
params->smps_mode = NL80211_SMPS_OFF;
}
params->pbss = nla_get_flag(info->attrs[NL80211_ATTR_PBSS]);
if (params->pbss && !rdev->