| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * This is the new netlink-based wireless configuration interface. |
| * |
| * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net> |
| * Copyright 2013-2014 Intel Mobile Communications GmbH |
| * Copyright 2015-2017 Intel Deutschland GmbH |
| * Copyright (C) 2018-2024 Intel Corporation |
| */ |
| |
| #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); |
| |
| /* the netlink family */ |
| static struct genl_family nl80211_fam; |
| |
| /* multicast groups */ |
| 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 /* keep last - ifdef! */ |
| }; |
| |
| 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 |
| }; |
| |
| /* returns ERR_PTR values */ |
| 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(); |
| |
| for_each_rdev(rdev) { |
| 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) { |
| /* make sure wdev exists */ |
| 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; |
| |
| /* not wireless device -- return error */ |
| if (!tmp) |
| return ERR_PTR(-EINVAL); |
| |
| /* mismatch -- return error */ |
| if (rdev && tmp != rdev) |
| return ERR_PTR(-EINVAL); |
| |
| rdev = tmp; |
| } |
| } |
| |
| if (!rdev) |
| return ERR_PTR(-ENODEV); |
| |
| if (netns != wiphy_net(&rdev->wiphy)) |
| return ERR_PTR(-ENODEV); |
| |
| return rdev; |
| } |
| |
| /* |
| * This function returns a pointer to the driver |
| * that the genl_info item that is passed refers to. |
| * |
| * The result of this can be a PTR_ERR and hence must |
| * be checked with IS_ERR() for errors. |
| */ |
| static struct cfg80211_registered_device * |
| cfg80211_get_dev_from_info(struct net *netns, struct genl_info *info) |
| { |
| 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) { |
| /* nothing */ |
| } |
| |
| 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) { |
| /* nothing */ |
| } |
| |
| 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; |
| } |
| |
| /* policy for the attributes */ |
| 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] = { .type = NLA_U8 }, |
| [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 }, |
| [NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR] = { .type = NLA_U8 }, |
| }; |
| |
| 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 const struct netlink_range_validation nl80211_punct_bitmap_range = { |
| .min = 0, |
| .max = 0xffff, |
| }; |
| |
| static const struct netlink_range_validation q_range = { |
| .max = INT_MAX, |
| }; |
| |
| 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] = { /* NLA_NESTED can't be empty */ }, |
| [NL80211_ATTR_MESH_ID] = { .type = NLA_BINARY, |
| .len = IEEE80211_MAX_MESH_ID_LEN }, |
| [NL80211_ATTR_MPATH_NEXT_HOP] = NLA_POLICY_ETH_ADDR_COMPAT, |
| |
| /* allow 3 for NUL-termination, we used to declare this NLA_STRING */ |
| [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] = |
| NLA_POLICY_RANGE(NLA_U32, 0, |
| NL80211_WPA_VERSION_1 | |
| NL80211_WPA_VERSION_2 | |
| NL80211_WPA_VERSION_3), |
| [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 }, |
| |
| /* need to include at least Auth Transaction and Status Code */ |
| [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), |
| /* |
| * The value of the Length field of the Supported Operating |
| * Classes element is between 2 and 253. |
| */ |
| [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] = NLA_POLICY_FULL_RANGE(NLA_U32, &q_range), |
| [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), |
| |
| [NL80211_ATTR_MAX_HW_TIMESTAMP_PEERS] = { .type = NLA_U16 }, |
| [NL80211_ATTR_HW_TIMESTAMP_ENABLED] = { .type = NLA_FLAG }, |
| [NL80211_ATTR_EMA_RNR_ELEMS] = { .type = NLA_NESTED }, |
| [NL80211_ATTR_MLO_LINK_DISABLED] = { .type = NLA_FLAG }, |
| [NL80211_ATTR_BSS_DUMP_INCLUDE_USE_DATA] = { .type = NLA_FLAG }, |
| [NL80211_ATTR_MLO_TTLM_DLINK] = NLA_POLICY_EXACT_LEN(sizeof(u16) * 8), |
| [NL80211_ATTR_MLO_TTLM_ULINK] = NLA_POLICY_EXACT_LEN(sizeof(u16) * 8), |
| [NL80211_ATTR_ASSOC_SPP_AMSDU] = { .type = NLA_FLAG }, |
| }; |
| |
| /* policy for the key attributes */ |
| static const struct nla_policy nl80211_key_policy[NL80211_KEY_MAX + 1] = { |
| [NL80211_KEY_DATA] = { .type = NLA_BINARY, .len = WLAN_MAX_KEY_LEN }, |
| [NL80211_KEY_IDX] = { .type = NLA_U8 }, |
| [NL80211_KEY_CIPHER] = { .type = NLA_U32 }, |
| [NL80211_KEY_SEQ] = { .type = NLA_BINARY, .len = 16 }, |
| [NL80211_KEY_DEFAULT] = { .type = NLA_FLAG }, |
| [NL80211_KEY_DEFAULT_MGMT] = { .type = NLA_FLAG }, |
| [NL80211_KEY_TYPE] = 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), |
| }; |
| |
| /* policy for the key default flags */ |
| static const struct nla_policy |
| nl80211_key_default_policy[NUM_NL80211_KEY_DEFAULT_TYPES] = { |
| [NL80211_KEY_DEFAULT_TYPE_UNICAST] = { .type = NLA_FLAG }, |
| [NL80211_KEY_DEFAULT_TYPE_MULTICAST] = { .type = NLA_FLAG }, |
| }; |
| |
| #ifdef CONFIG_PM |
| /* policy for WoWLAN attributes */ |
| static const struct nla_policy |
| nl80211_wowlan_policy[NUM_NL80211_WOWLAN_TRIG] = { |
| [NL80211_WOWLAN_TRIG_ANY] = { .type = NLA_FLAG }, |
| [NL80211_WOWLAN_TRIG_DISCONNECT] = { .type = NLA_FLAG }, |
| [NL80211_WOWLAN_TRIG_MAGIC_PKT] = { .type = NLA_FLAG }, |
| [NL80211_WOWLAN_TRIG_PKT_PATTERN] = { .type = NLA_NESTED }, |
| [NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE] = { .type = NLA_FLAG }, |
| [NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST] = { .type = NLA_FLAG }, |
| [NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE] = { .type = NLA_FLAG }, |
| [NL80211_WOWLAN_TRIG_RFKILL_RELEASE] = { .type = NLA_FLAG }, |
| [NL80211_WOWLAN_TRIG_TCP_CONNECTION] = { .type = NLA_NESTED }, |
| [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 */ |
| |
| /* policy for coalesce rule attributes */ |
| 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 }, |
| }; |
| |
| /* policy for GTK rekey offload attributes */ |
| 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_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 }, |
| }; |
| |
| 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) |
| }, |
| }; |
| |
| /* policy for NAN function attributes */ |
| 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 }, |
| }; |
| |
| /* policy for Service Response Filter attributes */ |
| 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 }, |
| }; |
| |
| /* policy for packet pattern attributes */ |
| 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(); |
| /* 0 is the first index - add 1 to parse only once */ |
| cb->args[0] = (*rdev)->wiphy_idx + 1; |
| cb->args[1] = (*wdev)->identifier; |
| } else { |
| /* subtract the 1 again here */ |
| struct wiphy *wiphy; |
| 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; |
| } |
| |
| /* message building helper */ |
| void *nl80211hdr_put(struct sk_buff *skb, u32 portid, u32 seq, |
| int flags, u8 cmd) |
| { |
| /* since there is no private header just add the generic one */ |
| return genlmsg_put(skb, portid, seq, &nl80211_fam, flags, cmd); |
| } |
| |
| static int nl80211_msg_put_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) |
| { |
| /* Some channels must be completely excluded from the |
| * list to protect old user-space tools from breaking |
| */ |
| 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_PSD) && |
| nla_put_s8(msg, NL80211_FREQUENCY_ATTR_PSD, chan->psd)) |
| 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 ((chan->flags & IEEE80211_CHAN_DFS_CONCURRENT) && |
| nla_put_flag(msg, NL80211_FREQUENCY_ATTR_DFS_CONCURRENT)) |
| goto nla_put_failure; |
| if ((chan->flags & IEEE80211_CHAN_NO_6GHZ_VLP_CLIENT) && |
| nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_6GHZ_VLP_CLIENT)) |
| goto nla_put_failure; |
| if ((chan->flags & IEEE80211_CHAN_NO_6GHZ_AFC_CLIENT) && |
| nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_6GHZ_AFC_CLIENT)) |
| goto nla_put_failure; |
| if ((chan->flags & IEEE80211_CHAN_CAN_MONITOR) && |
| nla_put_flag(msg, NL80211_FREQUENCY_ATTR_CAN_MONITOR)) |
| goto nla_put_failure; |
| if ((chan->flags & IEEE80211_CHAN_ALLOW_6GHZ_VLP_AP) && |
| nla_put_flag(msg, NL80211_FREQUENCY_ATTR_ALLOW_6GHZ_VLP_AP)) |
| 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; |
| } |
| |
| /* netlink command implementations */ |
| |
| /** |
| * nl80211_link_id - return link ID |
| * @attrs: attributes to look at |
| * |
| * Returns: the link ID or 0 if not given |
| * |
| * Note this function doesn't do any validation of the link |
| * ID validity wrt. links that were actually added, so it must |
| * be called only from ops with %NL80211_FLAG_MLO_VALID_LINK_ID |
| * or if additional validation is done. |
| */ |
| 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); |
| |
| /* must be WEP key if we got here */ |
| 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) |
| { |
| lockdep_assert_wiphy(wdev->wiphy); |
| |
| 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_ifcomb_data(struct sk_buff *msg, bool large, int idx, |
| const struct ieee80211_iface_combination *c, |
| u16 nested) |
| { |
| struct nlattr *nl_combi, *nl_limits; |
| int i; |
| |
| nl_combi = nla_nest_start_noflag(msg, idx | nested); |
| if (!nl_combi) |
| goto nla_put_failure; |
| |
| nl_limits = nla_nest_start_noflag(msg, NL80211_IFACE_COMB_LIMITS | |
| nested); |
| if (!nl_limits) |
| goto nla_put_failure; |
| |
| for (i = 0; i < c->n_limits; i++) { |
| struct nlattr *nl_limit; |
| |
| nl_limit = nla_nest_start_noflag(msg, i + 1); |
| if (!nl_limit) |
| goto nla_put_failure; |
| if (nla_put_u32(msg, NL80211_IFACE_LIMIT_MAX, c->limits[i].max)) |
| goto nla_put_failure; |
| if (nl80211_put_iftypes(msg, NL80211_IFACE_LIMIT_TYPES, |
| c->limits[i].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); |
| |
| return 0; |
| nla_put_failure: |
| return -ENOBUFS; |
| } |
| |
| static int nl80211_put_iface_combinations(struct wiphy *wiphy, |
| struct sk_buff *msg, |
| int attr, int radio, |
| bool large, u16 nested) |
| { |
| const struct ieee80211_iface_combination *c; |
| struct nlattr *nl_combis; |
| int i, n; |
| |
| nl_combis = nla_nest_start_noflag(msg, attr | nested); |
| if (!nl_combis) |
| goto nla_put_failure; |
| |
| if (radio >= 0) { |
| c = wiphy->radio[0].iface_combinations; |
| n = wiphy->radio[0].n_iface_combinations; |
| } else { |
| c = wiphy->iface_combinations; |
| n = wiphy->n_iface_combinations; |
| } |
| for (i = 0; i < n; i++) |
| if (nl80211_put_ifcomb_data(msg, large, i + 1, &c[i], nested)) |
| goto nla_put_failure; |
| |
| 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; |
| |
| /* add HT info */ |
| if (sband->ht_cap.ht_supported && |
| (nla_put(msg, NL80211_BAND_ATTR_HT_MCS_SET, |
| sizeof(sband->ht_cap.mcs), |
| &sband->ht_cap.mcs) || |
| nla_put_u16(msg, NL80211_BAND_ATTR_HT_CAPA, |
| sband->ht_cap.cap) || |
| nla_put_u8(msg, NL80211_BAND_ATTR_HT_AMPDU_FACTOR, |
| sband->ht_cap.ampdu_factor) || |
| nla_put_u8(msg, NL80211_BAND_ATTR_HT_AMPDU_DENSITY, |
| sband->ht_cap.ampdu_density))) |
| return -ENOBUFS; |
| |
| /* add VHT info */ |
| if (sband->vht_cap.vht_supported && |
| (nla_put(msg, NL80211_BAND_ATTR_VHT_MCS_SET, |
| sizeof(sband->vht_cap.vht_mcs), |
| &sband->vht_cap.vht_mcs) || |
| nla_put_u32(msg, NL80211_BAND_ATTR_VHT_CAPA, |
| sband->vht_cap.cap))) |
| return -ENOBUFS; |
| |
| if (large && sband->n_iftype_data) { |
| struct nlattr *nl_iftype_data = |
| nla_nest_start_noflag(msg, |
| NL80211_BAND_ATTR_IFTYPE_DATA); |
| const struct ieee80211_sband_iftype_data *iftd; |
| int err; |
| |
| if (!nl_iftype_data) |
| return -ENOBUFS; |
| |
| for_each_sband_iftype_data(sband, i, iftd) { |
| struct nlattr *iftdata; |
| |
| iftdata = nla_nest_start_noflag(msg, i + 1); |
| if (!iftdata) |
| return -ENOBUFS; |
| |
| err = nl80211_send_iftype_data(msg, sband, iftd); |
| if (err) |
| return err; |
| |
| nla_nest_end(msg, iftdata); |
| } |
| |
| nla_nest_end(msg, nl_iftype_data); |
| } |
| |
| /* add EDMG info */ |
| 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; |
| |
| /* add bitrates */ |
| 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); |
| |
| /* S1G capabilities */ |
| if (sband->band == NL80211_BAND_S1GHZ && sband->s1g_cap.s1g && |
| (nla_put(msg, NL80211_BAND_ATTR_S1G_CAPA, |
| sizeof(sband->s1g_cap.cap), |
| sband->s1g_cap.cap) || |
| nla_put(msg, NL80211_BAND_ATTR_S1G_MCS_NSS_SET, |
| sizeof(sband->s1g_cap.nss_mcs), |
| sband->s1g_cap.nss_mcs))) |
| return -ENOBUFS; |
| |
| 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; |
| |
| /* |
| * do *NOT* add anything into this function, new things need to be |
| * advertised only to new versions of userspace that can deal with |
| * the split (and they can't possibly care about new features... |
| */ |
| 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; |
| |
| /* |
| * we don't need to clean up anything here since the caller |
| * will genlmsg_cancel() if we fail |
| */ |
| |
| 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; |
| |
| /* for now we just use the same value ... makes more sense */ |
| 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; |
| |
| /* report supported freq_ranges */ |
| 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; |
| } |
| |
| static int nl80211_put_radio(struct wiphy *wiphy, struct sk_buff *msg, int idx) |
| { |
| const struct wiphy_radio *r = &wiphy->radio[idx]; |
| struct nlattr *radio, *freq; |
| int i; |
| |
| radio = nla_nest_start(msg, idx); |
| if (!radio) |
| return -ENOBUFS; |
| |
| if (nla_put_u32(msg, NL80211_WIPHY_RADIO_ATTR_INDEX, idx)) |
| goto nla_put_failure; |
| |
| for (i = 0; i < r->n_freq_range; i++) { |
| const struct wiphy_radio_freq_range *range = &r->freq_range[i]; |
| |
| freq = nla_nest_start(msg, NL80211_WIPHY_RADIO_ATTR_FREQ_RANGE); |
| if (!freq) |
| goto nla_put_failure; |
| |
| if (nla_put_u32(msg, NL80211_WIPHY_RADIO_FREQ_ATTR_START, |
| range->start_freq) || |
| nla_put_u32(msg, NL80211_WIPHY_RADIO_FREQ_ATTR_END, |
| range->end_freq)) |
| goto nla_put_failure; |
| |
| nla_nest_end(msg, freq); |
| } |
| |
| for (i = 0; i < r->n_iface_combinations; i++) |
| if (nl80211_put_ifcomb_data(msg, true, |
| NL80211_WIPHY_RADIO_ATTR_INTERFACE_COMBINATION, |
| &r->iface_combinations[i], |
| NLA_F_NESTED)) |
| goto nla_put_failure; |
| |
| nla_nest_end(msg, radio); |
| |
| return 0; |
| |
| nla_put_failure: |
| return -ENOBUFS; |
| } |
| |
| static int nl80211_put_radios(struct wiphy *wiphy, struct sk_buff *msg) |
| { |
| struct nlattr *radios; |
| int i; |
| |
| if (!wiphy->n_radio) |
| return 0; |
| |
| radios = nla_nest_start(msg, NL80211_ATTR_WIPHY_RADIOS); |
| if (!radios) |
| return -ENOBUFS; |
| |
| for (i = 0; i < wiphy->n_radio; i++) |
| if (nl80211_put_radio(wiphy, msg, i)) |
| goto fail; |
| |
| nla_nest_end(msg, radios); |
| |
| if (nl80211_put_iface_combinations(wiphy, msg, |
| NL80211_ATTR_WIPHY_INTERFACE_COMBINATIONS, |
| -1, true, NLA_F_NESTED)) |
| return -ENOBUFS; |
| |
| return 0; |
| |
| fail: |
| nla_nest_cancel(msg, radios); |
| 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; |
| |
| /* omit higher bands for ancient software */ |
| 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: |
| /* add frequencies */ |
| 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) { |
| /* start again here */ |
| 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 bands & channels are done, continue outside */ |
| 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++; |
|