|
@@ -56,6 +56,7 @@ enum nl80211_multicast_groups {
|
|
|
NL80211_MCGRP_REGULATORY,
|
|
|
NL80211_MCGRP_MLME,
|
|
|
NL80211_MCGRP_VENDOR,
|
|
|
+ NL80211_MCGRP_NAN,
|
|
|
NL80211_MCGRP_TESTMODE /* keep last - ifdef! */
|
|
|
};
|
|
|
|
|
@@ -65,6 +66,7 @@ static const struct genl_multicast_group nl80211_mcgrps[] = {
|
|
|
[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
|
|
@@ -409,6 +411,9 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
|
|
|
.len = VHT_MUMIMO_GROUPS_DATA_LEN
|
|
|
},
|
|
|
[NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR] = { .len = ETH_ALEN },
|
|
|
+ [NL80211_ATTR_NAN_MASTER_PREF] = { .type = NLA_U8 },
|
|
|
+ [NL80211_ATTR_NAN_DUAL] = { .type = NLA_U8 },
|
|
|
+ [NL80211_ATTR_NAN_FUNC] = { .type = NLA_NESTED },
|
|
|
};
|
|
|
|
|
|
/* policy for the key attributes */
|
|
@@ -502,6 +507,39 @@ nl80211_bss_select_policy[NL80211_BSS_SELECT_ATTR_MAX + 1] = {
|
|
|
},
|
|
|
};
|
|
|
|
|
|
+/* policy for NAN function attributes */
|
|
|
+static const struct nla_policy
|
|
|
+nl80211_nan_func_policy[NL80211_NAN_FUNC_ATTR_MAX + 1] = {
|
|
|
+ [NL80211_NAN_FUNC_TYPE] = { .type = NLA_U8 },
|
|
|
+ [NL80211_NAN_FUNC_SERVICE_ID] = { .type = NLA_BINARY,
|
|
|
+ .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] = { .len = 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 },
|
|
|
+};
|
|
|
+
|
|
|
static int nl80211_prepare_wdev_dump(struct sk_buff *skb,
|
|
|
struct netlink_callback *cb,
|
|
|
struct cfg80211_registered_device **rdev,
|
|
@@ -934,6 +972,7 @@ static int nl80211_key_allowed(struct wireless_dev *wdev)
|
|
|
case NL80211_IFTYPE_UNSPECIFIED:
|
|
|
case NL80211_IFTYPE_OCB:
|
|
|
case NL80211_IFTYPE_MONITOR:
|
|
|
+ case NL80211_IFTYPE_NAN:
|
|
|
case NL80211_IFTYPE_P2P_DEVICE:
|
|
|
case NL80211_IFTYPE_WDS:
|
|
|
case NUM_NL80211_IFTYPES:
|
|
@@ -2819,7 +2858,7 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
|
|
|
!(rdev->wiphy.interface_modes & (1 << type)))
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
- if ((type == NL80211_IFTYPE_P2P_DEVICE ||
|
|
|
+ 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],
|
|
@@ -2875,9 +2914,10 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
|
|
|
wdev->mesh_id_up_len);
|
|
|
wdev_unlock(wdev);
|
|
|
break;
|
|
|
+ case NL80211_IFTYPE_NAN:
|
|
|
case NL80211_IFTYPE_P2P_DEVICE:
|
|
|
/*
|
|
|
- * P2P Device doesn't have a netdev, so doesn't go
|
|
|
+ * P2P Device and NAN do not have a netdev, so don't go
|
|
|
* through the netdev notifier and must be added here
|
|
|
*/
|
|
|
mutex_init(&wdev->mtx);
|
|
@@ -3340,6 +3380,291 @@ static int nl80211_set_mac_acl(struct sk_buff *skb, struct genl_info *info)
|
|
|
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; /* rate not found */
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+
|
|
|
+ /* check validity */
|
|
|
+ if ((ridx < 0) || (ridx >= IEEE80211_HT_MCS_MASK_LEN))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ /* check availability */
|
|
|
+ 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);
|
|
|
+
|
|
|
+ /* Build vht_mcs_mask from VHT capabilities */
|
|
|
+ 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 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] = { .len = sizeof(struct nl80211_txrate_vht)},
|
|
|
+ [NL80211_TXRATE_GI] = { .type = NLA_U8 },
|
|
|
+};
|
|
|
+
|
|
|
+static int nl80211_parse_tx_bitrate_mask(struct genl_info *info,
|
|
|
+ struct cfg80211_bitrate_mask *mask)
|
|
|
+{
|
|
|
+ struct nlattr *tb[NL80211_TXRATE_MAX + 1];
|
|
|
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
|
|
+ int rem, i;
|
|
|
+ struct nlattr *tx_rates;
|
|
|
+ struct ieee80211_supported_band *sband;
|
|
|
+ u16 vht_tx_mcs_map;
|
|
|
+
|
|
|
+ memset(mask, 0, sizeof(*mask));
|
|
|
+ /* Default to all rates enabled */
|
|
|
+ for (i = 0; i < NUM_NL80211_BANDS; i++) {
|
|
|
+ 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)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* if no rates are given set it back to the defaults */
|
|
|
+ if (!info->attrs[NL80211_ATTR_TX_RATES])
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ /* The nested attribute uses enum nl80211_band as the index. This maps
|
|
|
+ * directly to the enum nl80211_band values used in cfg80211.
|
|
|
+ */
|
|
|
+ BUILD_BUG_ON(NL80211_MAX_SUPP_HT_RATES > IEEE80211_HT_MCS_MASK_LEN * 8);
|
|
|
+ nla_for_each_nested(tx_rates, info->attrs[NL80211_ATTR_TX_RATES], 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(tb, NL80211_TXRATE_MAX, nla_data(tx_rates),
|
|
|
+ nla_len(tx_rates), nl80211_txattr_policy);
|
|
|
+ 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 (mask->control[band].legacy == 0) {
|
|
|
+ /* don't allow empty legacy rates if HT or VHT
|
|
|
+ * are not even supported.
|
|
|
+ */
|
|
|
+ if (!(rdev->wiphy.bands[band]->ht_cap.ht_supported ||
|
|
|
+ rdev->wiphy.bands[band]->vht_cap.vht_supported))
|
|
|
+ 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;
|
|
|
+
|
|
|
+ /* legacy and mcs rates may not be both empty */
|
|
|
+ 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, i;
|
|
|
+ u32 rate = beacon_rate->control[band].legacy;
|
|
|
+
|
|
|
+ /* Allow only one rate */
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((count_ht && count_vht) || (!rate && !count_ht && !count_vht))
|
|
|
+ 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;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static int nl80211_parse_beacon(struct nlattr *attrs[],
|
|
|
struct cfg80211_beacon_data *bcn)
|
|
|
{
|
|
@@ -3569,6 +3894,17 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
|
|
|
wdev->iftype))
|
|
|
return -EINVAL;
|
|
|
|
|
|
+ if (info->attrs[NL80211_ATTR_TX_RATES]) {
|
|
|
+ err = nl80211_parse_tx_bitrate_mask(info, ¶ms.beacon_rate);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ err = validate_beacon_tx_rate(rdev, params.chandef.chan->band,
|
|
|
+ ¶ms.beacon_rate);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+
|
|
|
if (info->attrs[NL80211_ATTR_SMPS_MODE]) {
|
|
|
params.smps_mode =
|
|
|
nla_get_u8(info->attrs[NL80211_ATTR_SMPS_MODE]);
|
|
@@ -6138,6 +6474,9 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
|
|
|
|
|
|
wiphy = &rdev->wiphy;
|
|
|
|
|
|
+ if (wdev->iftype == NL80211_IFTYPE_NAN)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
if (!rdev->ops->scan)
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
@@ -8641,238 +8980,21 @@ static int nl80211_cancel_remain_on_channel(struct sk_buff *skb,
|
|
|
return rdev_cancel_remain_on_channel(rdev, wdev, cookie);
|
|
|
}
|
|
|
|
|
|
-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; /* rate not found */
|
|
|
- }
|
|
|
-
|
|
|
- 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);
|
|
|
-
|
|
|
- /* check validity */
|
|
|
- if ((ridx < 0) || (ridx >= IEEE80211_HT_MCS_MASK_LEN))
|
|
|
- return false;
|
|
|
-
|
|
|
- /* check availability */
|
|
|
- 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);
|
|
|
-
|
|
|
- /* Build vht_mcs_mask from VHT capabilities */
|
|
|
- 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 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] = { .len = sizeof(struct nl80211_txrate_vht)},
|
|
|
- [NL80211_TXRATE_GI] = { .type = NLA_U8 },
|
|
|
-};
|
|
|
-
|
|
|
static int nl80211_set_tx_bitrate_mask(struct sk_buff *skb,
|
|
|
struct genl_info *info)
|
|
|
{
|
|
|
- struct nlattr *tb[NL80211_TXRATE_MAX + 1];
|
|
|
- struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
|
|
struct cfg80211_bitrate_mask mask;
|
|
|
- int rem, i;
|
|
|
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
|
|
struct net_device *dev = info->user_ptr[1];
|
|
|
- struct nlattr *tx_rates;
|
|
|
- struct ieee80211_supported_band *sband;
|
|
|
- u16 vht_tx_mcs_map;
|
|
|
+ int err;
|
|
|
|
|
|
if (!rdev->ops->set_bitrate_mask)
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
- memset(&mask, 0, sizeof(mask));
|
|
|
- /* Default to all rates enabled */
|
|
|
- for (i = 0; i < NUM_NL80211_BANDS; i++) {
|
|
|
- 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)
|
|
|
- continue;
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
-
|
|
|
- /* if no rates are given set it back to the defaults */
|
|
|
- if (!info->attrs[NL80211_ATTR_TX_RATES])
|
|
|
- goto out;
|
|
|
-
|
|
|
- /*
|
|
|
- * The nested attribute uses enum nl80211_band as the index. This maps
|
|
|
- * directly to the enum nl80211_band values used in cfg80211.
|
|
|
- */
|
|
|
- BUILD_BUG_ON(NL80211_MAX_SUPP_HT_RATES > IEEE80211_HT_MCS_MASK_LEN * 8);
|
|
|
- nla_for_each_nested(tx_rates, info->attrs[NL80211_ATTR_TX_RATES], 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(tb, NL80211_TXRATE_MAX, nla_data(tx_rates),
|
|
|
- nla_len(tx_rates), nl80211_txattr_policy);
|
|
|
- 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 (mask.control[band].legacy == 0) {
|
|
|
- /* don't allow empty legacy rates if HT or VHT
|
|
|
- * are not even supported.
|
|
|
- */
|
|
|
- if (!(rdev->wiphy.bands[band]->ht_cap.ht_supported ||
|
|
|
- rdev->wiphy.bands[band]->vht_cap.vht_supported))
|
|
|
- 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;
|
|
|
-
|
|
|
- /* legacy and mcs rates may not be both empty */
|
|
|
- return -EINVAL;
|
|
|
- }
|
|
|
- }
|
|
|
+ err = nl80211_parse_tx_bitrate_mask(info, &mask);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
|
|
|
-out:
|
|
|
return rdev_set_bitrate_mask(rdev, dev, NULL, &mask);
|
|
|
}
|
|
|
|
|
@@ -8898,6 +9020,7 @@ static int nl80211_register_mgmt(struct sk_buff *skb, struct genl_info *info)
|
|
|
case NL80211_IFTYPE_P2P_GO:
|
|
|
case NL80211_IFTYPE_P2P_DEVICE:
|
|
|
break;
|
|
|
+ case NL80211_IFTYPE_NAN:
|
|
|
default:
|
|
|
return -EOPNOTSUPP;
|
|
|
}
|
|
@@ -8943,6 +9066,7 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)
|
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
|
case NL80211_IFTYPE_P2P_GO:
|
|
|
break;
|
|
|
+ case NL80211_IFTYPE_NAN:
|
|
|
default:
|
|
|
return -EOPNOTSUPP;
|
|
|
}
|
|
@@ -9059,6 +9183,7 @@ static int nl80211_tx_mgmt_cancel_wait(struct sk_buff *skb, struct genl_info *in
|
|
|
case NL80211_IFTYPE_P2P_GO:
|
|
|
case NL80211_IFTYPE_P2P_DEVICE:
|
|
|
break;
|
|
|
+ case NL80211_IFTYPE_NAN:
|
|
|
default:
|
|
|
return -EOPNOTSUPP;
|
|
|
}
|
|
@@ -9340,6 +9465,17 @@ static int nl80211_join_mesh(struct sk_buff *skb, struct genl_info *info)
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
+ if (info->attrs[NL80211_ATTR_TX_RATES]) {
|
|
|
+ err = nl80211_parse_tx_bitrate_mask(info, &setup.beacon_rate);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ err = validate_beacon_tx_rate(rdev, setup.chandef.chan->band,
|
|
|
+ &setup.beacon_rate);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+
|
|
|
return cfg80211_join_mesh(rdev, dev, &setup, &cfg);
|
|
|
}
|
|
|
|
|
@@ -10414,38 +10550,581 @@ static int nl80211_stop_p2p_device(struct sk_buff *skb, struct genl_info *info)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static int nl80211_get_protocol_features(struct sk_buff *skb,
|
|
|
- struct genl_info *info)
|
|
|
+static int nl80211_start_nan(struct sk_buff *skb, struct genl_info *info)
|
|
|
{
|
|
|
- void *hdr;
|
|
|
- struct sk_buff *msg;
|
|
|
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
|
|
+ struct wireless_dev *wdev = info->user_ptr[1];
|
|
|
+ struct cfg80211_nan_conf conf = {};
|
|
|
+ int err;
|
|
|
|
|
|
- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
|
|
- if (!msg)
|
|
|
- return -ENOMEM;
|
|
|
+ if (wdev->iftype != NL80211_IFTYPE_NAN)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
|
|
|
- hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
|
|
|
- NL80211_CMD_GET_PROTOCOL_FEATURES);
|
|
|
- if (!hdr)
|
|
|
- goto nla_put_failure;
|
|
|
+ if (wdev->nan_started)
|
|
|
+ return -EEXIST;
|
|
|
|
|
|
- if (nla_put_u32(msg, NL80211_ATTR_PROTOCOL_FEATURES,
|
|
|
- NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP))
|
|
|
- goto nla_put_failure;
|
|
|
+ if (rfkill_blocked(rdev->rfkill))
|
|
|
+ return -ERFKILL;
|
|
|
|
|
|
- genlmsg_end(msg, hdr);
|
|
|
- return genlmsg_reply(msg, info);
|
|
|
+ if (!info->attrs[NL80211_ATTR_NAN_MASTER_PREF])
|
|
|
+ return -EINVAL;
|
|
|
|
|
|
- nla_put_failure:
|
|
|
- kfree_skb(msg);
|
|
|
- return -ENOBUFS;
|
|
|
+ if (!info->attrs[NL80211_ATTR_NAN_DUAL])
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ conf.master_pref =
|
|
|
+ nla_get_u8(info->attrs[NL80211_ATTR_NAN_MASTER_PREF]);
|
|
|
+ if (!conf.master_pref)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ conf.dual = nla_get_u8(info->attrs[NL80211_ATTR_NAN_DUAL]);
|
|
|
+
|
|
|
+ err = rdev_start_nan(rdev, wdev, &conf);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ wdev->nan_started = true;
|
|
|
+ rdev->opencount++;
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
-static int nl80211_update_ft_ies(struct sk_buff *skb, struct genl_info *info)
|
|
|
+static int nl80211_stop_nan(struct sk_buff *skb, struct genl_info *info)
|
|
|
{
|
|
|
struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
|
|
- struct cfg80211_update_ft_ies_params ft_params;
|
|
|
- struct net_device *dev = info->user_ptr[1];
|
|
|
+ struct wireless_dev *wdev = info->user_ptr[1];
|
|
|
+
|
|
|
+ if (wdev->iftype != NL80211_IFTYPE_NAN)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ cfg80211_stop_nan(rdev, wdev);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int validate_nan_filter(struct nlattr *filter_attr)
|
|
|
+{
|
|
|
+ struct nlattr *attr;
|
|
|
+ int len = 0, n_entries = 0, rem;
|
|
|
+
|
|
|
+ nla_for_each_nested(attr, filter_attr, rem) {
|
|
|
+ len += nla_len(attr);
|
|
|
+ n_entries++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (len >= U8_MAX)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ return n_entries;
|
|
|
+}
|
|
|
+
|
|
|
+static int handle_nan_filter(struct nlattr *attr_filter,
|
|
|
+ struct cfg80211_nan_func *func,
|
|
|
+ bool tx)
|
|
|
+{
|
|
|
+ struct nlattr *attr;
|
|
|
+ int n_entries, rem, i;
|
|
|
+ struct cfg80211_nan_func_filter *filter;
|
|
|
+
|
|
|
+ n_entries = validate_nan_filter(attr_filter);
|
|
|
+ if (n_entries < 0)
|
|
|
+ return n_entries;
|
|
|
+
|
|
|
+ BUILD_BUG_ON(sizeof(*func->rx_filters) != sizeof(*func->tx_filters));
|
|
|
+
|
|
|
+ filter = kcalloc(n_entries, sizeof(*func->rx_filters), GFP_KERNEL);
|
|
|
+ if (!filter)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ i = 0;
|
|
|
+ nla_for_each_nested(attr, attr_filter, rem) {
|
|
|
+ filter[i].filter = kmemdup(nla_data(attr), nla_len(attr),
|
|
|
+ GFP_KERNEL);
|
|
|
+ filter[i].len = nla_len(attr);
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ if (tx) {
|
|
|
+ func->num_tx_filters = n_entries;
|
|
|
+ func->tx_filters = filter;
|
|
|
+ } else {
|
|
|
+ func->num_rx_filters = n_entries;
|
|
|
+ func->rx_filters = filter;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int nl80211_nan_add_func(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];
|
|
|
+ struct nlattr *tb[NUM_NL80211_NAN_FUNC_ATTR], *func_attr;
|
|
|
+ struct cfg80211_nan_func *func;
|
|
|
+ struct sk_buff *msg = NULL;
|
|
|
+ void *hdr = NULL;
|
|
|
+ int err = 0;
|
|
|
+
|
|
|
+ if (wdev->iftype != NL80211_IFTYPE_NAN)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ if (!wdev->nan_started)
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ if (!info->attrs[NL80211_ATTR_NAN_FUNC])
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (wdev->owner_nlportid &&
|
|
|
+ wdev->owner_nlportid != info->snd_portid)
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ err = nla_parse(tb, NL80211_NAN_FUNC_ATTR_MAX,
|
|
|
+ nla_data(info->attrs[NL80211_ATTR_NAN_FUNC]),
|
|
|
+ nla_len(info->attrs[NL80211_ATTR_NAN_FUNC]),
|
|
|
+ nl80211_nan_func_policy);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ func = kzalloc(sizeof(*func), GFP_KERNEL);
|
|
|
+ if (!func)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ func->cookie = wdev->wiphy->cookie_counter++;
|
|
|
+
|
|
|
+ if (!tb[NL80211_NAN_FUNC_TYPE] ||
|
|
|
+ nla_get_u8(tb[NL80211_NAN_FUNC_TYPE]) > NL80211_NAN_FUNC_MAX_TYPE) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func->type = nla_get_u8(tb[NL80211_NAN_FUNC_TYPE]);
|
|
|
+
|
|
|
+ if (!tb[NL80211_NAN_FUNC_SERVICE_ID]) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ memcpy(func->service_id, nla_data(tb[NL80211_NAN_FUNC_SERVICE_ID]),
|
|
|
+ sizeof(func->service_id));
|
|
|
+
|
|
|
+ func->close_range =
|
|
|
+ nla_get_flag(tb[NL80211_NAN_FUNC_CLOSE_RANGE]);
|
|
|
+
|
|
|
+ if (tb[NL80211_NAN_FUNC_SERVICE_INFO]) {
|
|
|
+ func->serv_spec_info_len =
|
|
|
+ nla_len(tb[NL80211_NAN_FUNC_SERVICE_INFO]);
|
|
|
+ func->serv_spec_info =
|
|
|
+ kmemdup(nla_data(tb[NL80211_NAN_FUNC_SERVICE_INFO]),
|
|
|
+ func->serv_spec_info_len,
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!func->serv_spec_info) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tb[NL80211_NAN_FUNC_TTL])
|
|
|
+ func->ttl = nla_get_u32(tb[NL80211_NAN_FUNC_TTL]);
|
|
|
+
|
|
|
+ switch (func->type) {
|
|
|
+ case NL80211_NAN_FUNC_PUBLISH:
|
|
|
+ if (!tb[NL80211_NAN_FUNC_PUBLISH_TYPE]) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ func->publish_type =
|
|
|
+ nla_get_u8(tb[NL80211_NAN_FUNC_PUBLISH_TYPE]);
|
|
|
+ func->publish_bcast =
|
|
|
+ nla_get_flag(tb[NL80211_NAN_FUNC_PUBLISH_BCAST]);
|
|
|
+
|
|
|
+ if ((!(func->publish_type & NL80211_NAN_SOLICITED_PUBLISH)) &&
|
|
|
+ func->publish_bcast) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case NL80211_NAN_FUNC_SUBSCRIBE:
|
|
|
+ func->subscribe_active =
|
|
|
+ nla_get_flag(tb[NL80211_NAN_FUNC_SUBSCRIBE_ACTIVE]);
|
|
|
+ break;
|
|
|
+ case NL80211_NAN_FUNC_FOLLOW_UP:
|
|
|
+ if (!tb[NL80211_NAN_FUNC_FOLLOW_UP_ID] ||
|
|
|
+ !tb[NL80211_NAN_FUNC_FOLLOW_UP_REQ_ID]) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ func->followup_id =
|
|
|
+ nla_get_u8(tb[NL80211_NAN_FUNC_FOLLOW_UP_ID]);
|
|
|
+ func->followup_reqid =
|
|
|
+ nla_get_u8(tb[NL80211_NAN_FUNC_FOLLOW_UP_REQ_ID]);
|
|
|
+ memcpy(func->followup_dest.addr,
|
|
|
+ nla_data(tb[NL80211_NAN_FUNC_FOLLOW_UP_DEST]),
|
|
|
+ sizeof(func->followup_dest.addr));
|
|
|
+ if (func->ttl) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tb[NL80211_NAN_FUNC_SRF]) {
|
|
|
+ struct nlattr *srf_tb[NUM_NL80211_NAN_SRF_ATTR];
|
|
|
+
|
|
|
+ err = nla_parse(srf_tb, NL80211_NAN_SRF_ATTR_MAX,
|
|
|
+ nla_data(tb[NL80211_NAN_FUNC_SRF]),
|
|
|
+ nla_len(tb[NL80211_NAN_FUNC_SRF]), NULL);
|
|
|
+ if (err)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ func->srf_include =
|
|
|
+ nla_get_flag(srf_tb[NL80211_NAN_SRF_INCLUDE]);
|
|
|
+
|
|
|
+ if (srf_tb[NL80211_NAN_SRF_BF]) {
|
|
|
+ if (srf_tb[NL80211_NAN_SRF_MAC_ADDRS] ||
|
|
|
+ !srf_tb[NL80211_NAN_SRF_BF_IDX]) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ func->srf_bf_len =
|
|
|
+ nla_len(srf_tb[NL80211_NAN_SRF_BF]);
|
|
|
+ func->srf_bf =
|
|
|
+ kmemdup(nla_data(srf_tb[NL80211_NAN_SRF_BF]),
|
|
|
+ func->srf_bf_len, GFP_KERNEL);
|
|
|
+ if (!func->srf_bf) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ func->srf_bf_idx =
|
|
|
+ nla_get_u8(srf_tb[NL80211_NAN_SRF_BF_IDX]);
|
|
|
+ } else {
|
|
|
+ struct nlattr *attr, *mac_attr =
|
|
|
+ srf_tb[NL80211_NAN_SRF_MAC_ADDRS];
|
|
|
+ int n_entries, rem, i = 0;
|
|
|
+
|
|
|
+ if (!mac_attr) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ n_entries = validate_acl_mac_addrs(mac_attr);
|
|
|
+ if (n_entries <= 0) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ func->srf_num_macs = n_entries;
|
|
|
+ func->srf_macs =
|
|
|
+ kzalloc(sizeof(*func->srf_macs) * n_entries,
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!func->srf_macs) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ nla_for_each_nested(attr, mac_attr, rem)
|
|
|
+ memcpy(func->srf_macs[i++].addr, nla_data(attr),
|
|
|
+ sizeof(*func->srf_macs));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tb[NL80211_NAN_FUNC_TX_MATCH_FILTER]) {
|
|
|
+ err = handle_nan_filter(tb[NL80211_NAN_FUNC_TX_MATCH_FILTER],
|
|
|
+ func, true);
|
|
|
+ if (err)
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tb[NL80211_NAN_FUNC_RX_MATCH_FILTER]) {
|
|
|
+ err = handle_nan_filter(tb[NL80211_NAN_FUNC_RX_MATCH_FILTER],
|
|
|
+ func, false);
|
|
|
+ if (err)
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
|
|
+ if (!msg) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
|
|
|
+ NL80211_CMD_ADD_NAN_FUNCTION);
|
|
|
+ /* This can't really happen - we just allocated 4KB */
|
|
|
+ if (WARN_ON(!hdr)) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = rdev_add_nan_func(rdev, wdev, func);
|
|
|
+out:
|
|
|
+ if (err < 0) {
|
|
|
+ cfg80211_free_nan_func(func);
|
|
|
+ nlmsg_free(msg);
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* propagate the instance id and cookie to userspace */
|
|
|
+ if (nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, func->cookie,
|
|
|
+ NL80211_ATTR_PAD))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ func_attr = nla_nest_start(msg, NL80211_ATTR_NAN_FUNC);
|
|
|
+ if (!func_attr)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (nla_put_u8(msg, NL80211_NAN_FUNC_INSTANCE_ID,
|
|
|
+ func->instance_id))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ nla_nest_end(msg, func_attr);
|
|
|
+
|
|
|
+ genlmsg_end(msg, hdr);
|
|
|
+ return genlmsg_reply(msg, info);
|
|
|
+
|
|
|
+nla_put_failure:
|
|
|
+ nlmsg_free(msg);
|
|
|
+ return -ENOBUFS;
|
|
|
+}
|
|
|
+
|
|
|
+static int nl80211_nan_del_func(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];
|
|
|
+ u64 cookie;
|
|
|
+
|
|
|
+ if (wdev->iftype != NL80211_IFTYPE_NAN)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ if (!wdev->nan_started)
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ if (!info->attrs[NL80211_ATTR_COOKIE])
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (wdev->owner_nlportid &&
|
|
|
+ wdev->owner_nlportid != info->snd_portid)
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ cookie = nla_get_u64(info->attrs[NL80211_ATTR_COOKIE]);
|
|
|
+
|
|
|
+ rdev_del_nan_func(rdev, wdev, cookie);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int nl80211_nan_change_config(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];
|
|
|
+ struct cfg80211_nan_conf conf = {};
|
|
|
+ u32 changed = 0;
|
|
|
+
|
|
|
+ if (wdev->iftype != NL80211_IFTYPE_NAN)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ if (!wdev->nan_started)
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ if (info->attrs[NL80211_ATTR_NAN_MASTER_PREF]) {
|
|
|
+ conf.master_pref =
|
|
|
+ nla_get_u8(info->attrs[NL80211_ATTR_NAN_MASTER_PREF]);
|
|
|
+ if (conf.master_pref <= 1 || conf.master_pref == 255)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ changed |= CFG80211_NAN_CONF_CHANGED_PREF;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (info->attrs[NL80211_ATTR_NAN_DUAL]) {
|
|
|
+ conf.dual = nla_get_u8(info->attrs[NL80211_ATTR_NAN_DUAL]);
|
|
|
+ changed |= CFG80211_NAN_CONF_CHANGED_DUAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!changed)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ return rdev_nan_change_conf(rdev, wdev, &conf, changed);
|
|
|
+}
|
|
|
+
|
|
|
+void cfg80211_nan_match(struct wireless_dev *wdev,
|
|
|
+ struct cfg80211_nan_match_params *match, gfp_t gfp)
|
|
|
+{
|
|
|
+ struct wiphy *wiphy = wdev->wiphy;
|
|
|
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
|
|
|
+ struct nlattr *match_attr, *local_func_attr, *peer_func_attr;
|
|
|
+ struct sk_buff *msg;
|
|
|
+ void *hdr;
|
|
|
+
|
|
|
+ if (WARN_ON(!match->inst_id || !match->peer_inst_id || !match->addr))
|
|
|
+ return;
|
|
|
+
|
|
|
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
|
|
|
+ if (!msg)
|
|
|
+ return;
|
|
|
+
|
|
|
+ hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_NAN_MATCH);
|
|
|
+ if (!hdr) {
|
|
|
+ nlmsg_free(msg);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
|
|
|
+ (wdev->netdev && nla_put_u32(msg, NL80211_ATTR_IFINDEX,
|
|
|
+ wdev->netdev->ifindex)) ||
|
|
|
+ nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
|
|
|
+ NL80211_ATTR_PAD))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, match->cookie,
|
|
|
+ NL80211_ATTR_PAD) ||
|
|
|
+ nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, match->addr))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ match_attr = nla_nest_start(msg, NL80211_ATTR_NAN_MATCH);
|
|
|
+ if (!match_attr)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ local_func_attr = nla_nest_start(msg, NL80211_NAN_MATCH_FUNC_LOCAL);
|
|
|
+ if (!local_func_attr)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (nla_put_u8(msg, NL80211_NAN_FUNC_INSTANCE_ID, match->inst_id))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ nla_nest_end(msg, local_func_attr);
|
|
|
+
|
|
|
+ peer_func_attr = nla_nest_start(msg, NL80211_NAN_MATCH_FUNC_PEER);
|
|
|
+ if (!peer_func_attr)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (nla_put_u8(msg, NL80211_NAN_FUNC_TYPE, match->type) ||
|
|
|
+ nla_put_u8(msg, NL80211_NAN_FUNC_INSTANCE_ID, match->peer_inst_id))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (match->info && match->info_len &&
|
|
|
+ nla_put(msg, NL80211_NAN_FUNC_SERVICE_INFO, match->info_len,
|
|
|
+ match->info))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ nla_nest_end(msg, peer_func_attr);
|
|
|
+ nla_nest_end(msg, match_attr);
|
|
|
+ genlmsg_end(msg, hdr);
|
|
|
+
|
|
|
+ if (!wdev->owner_nlportid)
|
|
|
+ genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy),
|
|
|
+ msg, 0, NL80211_MCGRP_NAN, gfp);
|
|
|
+ else
|
|
|
+ genlmsg_unicast(wiphy_net(&rdev->wiphy), msg,
|
|
|
+ wdev->owner_nlportid);
|
|
|
+
|
|
|
+ return;
|
|
|
+
|
|
|
+nla_put_failure:
|
|
|
+ nlmsg_free(msg);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(cfg80211_nan_match);
|
|
|
+
|
|
|
+void cfg80211_nan_func_terminated(struct wireless_dev *wdev,
|
|
|
+ u8 inst_id,
|
|
|
+ enum nl80211_nan_func_term_reason reason,
|
|
|
+ u64 cookie, gfp_t gfp)
|
|
|
+{
|
|
|
+ struct wiphy *wiphy = wdev->wiphy;
|
|
|
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
|
|
|
+ struct sk_buff *msg;
|
|
|
+ struct nlattr *func_attr;
|
|
|
+ void *hdr;
|
|
|
+
|
|
|
+ if (WARN_ON(!inst_id))
|
|
|
+ return;
|
|
|
+
|
|
|
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
|
|
|
+ if (!msg)
|
|
|
+ return;
|
|
|
+
|
|
|
+ hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_DEL_NAN_FUNCTION);
|
|
|
+ if (!hdr) {
|
|
|
+ nlmsg_free(msg);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
|
|
|
+ (wdev->netdev && nla_put_u32(msg, NL80211_ATTR_IFINDEX,
|
|
|
+ wdev->netdev->ifindex)) ||
|
|
|
+ nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
|
|
|
+ NL80211_ATTR_PAD))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, cookie,
|
|
|
+ NL80211_ATTR_PAD))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ func_attr = nla_nest_start(msg, NL80211_ATTR_NAN_FUNC);
|
|
|
+ if (!func_attr)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (nla_put_u8(msg, NL80211_NAN_FUNC_INSTANCE_ID, inst_id) ||
|
|
|
+ nla_put_u8(msg, NL80211_NAN_FUNC_TERM_REASON, reason))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ nla_nest_end(msg, func_attr);
|
|
|
+ genlmsg_end(msg, hdr);
|
|
|
+
|
|
|
+ if (!wdev->owner_nlportid)
|
|
|
+ genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy),
|
|
|
+ msg, 0, NL80211_MCGRP_NAN, gfp);
|
|
|
+ else
|
|
|
+ genlmsg_unicast(wiphy_net(&rdev->wiphy), msg,
|
|
|
+ wdev->owner_nlportid);
|
|
|
+
|
|
|
+ return;
|
|
|
+
|
|
|
+nla_put_failure:
|
|
|
+ nlmsg_free(msg);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(cfg80211_nan_func_terminated);
|
|
|
+
|
|
|
+static int nl80211_get_protocol_features(struct sk_buff *skb,
|
|
|
+ struct genl_info *info)
|
|
|
+{
|
|
|
+ void *hdr;
|
|
|
+ struct sk_buff *msg;
|
|
|
+
|
|
|
+ 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_GET_PROTOCOL_FEATURES);
|
|
|
+ if (!hdr)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (nla_put_u32(msg, NL80211_ATTR_PROTOCOL_FEATURES,
|
|
|
+ NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ genlmsg_end(msg, hdr);
|
|
|
+ return genlmsg_reply(msg, info);
|
|
|
+
|
|
|
+ nla_put_failure:
|
|
|
+ kfree_skb(msg);
|
|
|
+ return -ENOBUFS;
|
|
|
+}
|
|
|
+
|
|
|
+static int nl80211_update_ft_ies(struct sk_buff *skb, struct genl_info *info)
|
|
|
+{
|
|
|
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
|
|
+ struct cfg80211_update_ft_ies_params ft_params;
|
|
|
+ struct net_device *dev = info->user_ptr[1];
|
|
|
|
|
|
if (!rdev->ops->update_ft_ies)
|
|
|
return -EOPNOTSUPP;
|
|
@@ -11115,7 +11794,14 @@ static int nl80211_pre_doit(const struct genl_ops *ops, struct sk_buff *skb,
|
|
|
|
|
|
dev_hold(dev);
|
|
|
} else if (ops->internal_flags & NL80211_FLAG_CHECK_NETDEV_UP) {
|
|
|
- if (!wdev->p2p_started) {
|
|
|
+ if (wdev->iftype == NL80211_IFTYPE_P2P_DEVICE &&
|
|
|
+ !wdev->p2p_started) {
|
|
|
+ if (rtnl)
|
|
|
+ rtnl_unlock();
|
|
|
+ return -ENETDOWN;
|
|
|
+ }
|
|
|
+ if (wdev->iftype == NL80211_IFTYPE_NAN &&
|
|
|
+ !wdev->nan_started) {
|
|
|
if (rtnl)
|
|
|
rtnl_unlock();
|
|
|
return -ENETDOWN;
|
|
@@ -11748,6 +12434,46 @@ static const struct genl_ops nl80211_ops[] = {
|
|
|
.internal_flags = NL80211_FLAG_NEED_WDEV_UP |
|
|
|
NL80211_FLAG_NEED_RTNL,
|
|
|
},
|
|
|
+ {
|
|
|
+ .cmd = NL80211_CMD_START_NAN,
|
|
|
+ .doit = nl80211_start_nan,
|
|
|
+ .policy = nl80211_policy,
|
|
|
+ .flags = GENL_ADMIN_PERM,
|
|
|
+ .internal_flags = NL80211_FLAG_NEED_WDEV |
|
|
|
+ NL80211_FLAG_NEED_RTNL,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .cmd = NL80211_CMD_STOP_NAN,
|
|
|
+ .doit = nl80211_stop_nan,
|
|
|
+ .policy = nl80211_policy,
|
|
|
+ .flags = GENL_ADMIN_PERM,
|
|
|
+ .internal_flags = NL80211_FLAG_NEED_WDEV_UP |
|
|
|
+ NL80211_FLAG_NEED_RTNL,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .cmd = NL80211_CMD_ADD_NAN_FUNCTION,
|
|
|
+ .doit = nl80211_nan_add_func,
|
|
|
+ .policy = nl80211_policy,
|
|
|
+ .flags = GENL_ADMIN_PERM,
|
|
|
+ .internal_flags = NL80211_FLAG_NEED_WDEV_UP |
|
|
|
+ NL80211_FLAG_NEED_RTNL,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .cmd = NL80211_CMD_DEL_NAN_FUNCTION,
|
|
|
+ .doit = nl80211_nan_del_func,
|
|
|
+ .policy = nl80211_policy,
|
|
|
+ .flags = GENL_ADMIN_PERM,
|
|
|
+ .internal_flags = NL80211_FLAG_NEED_WDEV_UP |
|
|
|
+ NL80211_FLAG_NEED_RTNL,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .cmd = NL80211_CMD_CHANGE_NAN_CONFIG,
|
|
|
+ .doit = nl80211_nan_change_config,
|
|
|
+ .policy = nl80211_policy,
|
|
|
+ .flags = GENL_ADMIN_PERM,
|
|
|
+ .internal_flags = NL80211_FLAG_NEED_WDEV_UP |
|
|
|
+ NL80211_FLAG_NEED_RTNL,
|
|
|
+ },
|
|
|
{
|
|
|
.cmd = NL80211_CMD_SET_MCAST_RATE,
|
|
|
.doit = nl80211_set_mcast_rate,
|