|
@@ -411,6 +411,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
|
|
[NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR] = { .len = ETH_ALEN },
|
|
[NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR] = { .len = ETH_ALEN },
|
|
[NL80211_ATTR_NAN_MASTER_PREF] = { .type = NLA_U8 },
|
|
[NL80211_ATTR_NAN_MASTER_PREF] = { .type = NLA_U8 },
|
|
[NL80211_ATTR_NAN_DUAL] = { .type = NLA_U8 },
|
|
[NL80211_ATTR_NAN_DUAL] = { .type = NLA_U8 },
|
|
|
|
+ [NL80211_ATTR_NAN_FUNC] = { .type = NLA_NESTED },
|
|
};
|
|
};
|
|
|
|
|
|
/* policy for the key attributes */
|
|
/* policy for the key attributes */
|
|
@@ -504,6 +505,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,
|
|
static int nl80211_prepare_wdev_dump(struct sk_buff *skb,
|
|
struct netlink_callback *cb,
|
|
struct netlink_callback *cb,
|
|
struct cfg80211_registered_device **rdev,
|
|
struct cfg80211_registered_device **rdev,
|
|
@@ -10566,6 +10600,325 @@ static int nl80211_stop_nan(struct sk_buff *skb, struct genl_info *info)
|
|
return 0;
|
|
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_get_protocol_features(struct sk_buff *skb,
|
|
static int nl80211_get_protocol_features(struct sk_buff *skb,
|
|
struct genl_info *info)
|
|
struct genl_info *info)
|
|
{
|
|
{
|
|
@@ -11923,6 +12276,22 @@ static const struct genl_ops nl80211_ops[] = {
|
|
.internal_flags = NL80211_FLAG_NEED_WDEV_UP |
|
|
.internal_flags = NL80211_FLAG_NEED_WDEV_UP |
|
|
NL80211_FLAG_NEED_RTNL,
|
|
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_SET_MCAST_RATE,
|
|
.cmd = NL80211_CMD_SET_MCAST_RATE,
|
|
.doit = nl80211_set_mcast_rate,
|
|
.doit = nl80211_set_mcast_rate,
|