cfg80211: allow survey data to return global data
[deliverable/linux.git] / net / wireless / nl80211.c
index a17d6bc6b22ca7ccda9716af9a3367b66bc47632..9555ef9fd99eead9f1a50420629c5e834e0f8b19 100644 (file)
@@ -59,13 +59,13 @@ enum nl80211_multicast_groups {
 };
 
 static const struct genl_multicast_group nl80211_mcgrps[] = {
-       [NL80211_MCGRP_CONFIG] = { .name = "config", },
-       [NL80211_MCGRP_SCAN] = { .name = "scan", },
-       [NL80211_MCGRP_REGULATORY] = { .name = "regulatory", },
-       [NL80211_MCGRP_MLME] = { .name = "mlme", },
-       [NL80211_MCGRP_VENDOR] = { .name = "vendor", },
+       [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 },
 #ifdef CONFIG_NL80211_TESTMODE
-       [NL80211_MCGRP_TESTMODE] = { .name = "testmode", }
+       [NL80211_MCGRP_TESTMODE] = { .name = NL80211_MULTICAST_GROUP_TESTMODE }
 #endif
 };
 
@@ -396,6 +396,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
        [NL80211_ATTR_ADMITTED_TIME] = { .type = NLA_U16 },
        [NL80211_ATTR_SMPS_MODE] = { .type = NLA_U8 },
        [NL80211_ATTR_MAC_MASK] = { .len = ETH_ALEN },
+       [NL80211_ATTR_WIPHY_SELF_MANAGED_REG] = { .type = NLA_FLAG },
 };
 
 /* policy for the key attributes */
@@ -1701,6 +1702,15 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev,
                               rdev->wiphy.max_num_csa_counters))
                        goto nla_put_failure;
 
+               if (rdev->wiphy.regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED &&
+                   nla_put_flag(msg, NL80211_ATTR_WIPHY_SELF_MANAGED_REG))
+                       goto nla_put_failure;
+
+               if (nla_put(msg, NL80211_ATTR_EXT_FEATURES,
+                           sizeof(rdev->wiphy.ext_features),
+                           rdev->wiphy.ext_features))
+                       goto nla_put_failure;
+
                /* done */
                state->split_start = 0;
                break;
@@ -5327,42 +5337,20 @@ static int nl80211_update_mesh_config(struct sk_buff *skb,
        return err;
 }
 
-static int nl80211_get_reg(struct sk_buff *skb, struct genl_info *info)
+static int nl80211_put_regdom(const struct ieee80211_regdomain *regdom,
+                             struct sk_buff *msg)
 {
-       const struct ieee80211_regdomain *regdom;
-       struct sk_buff *msg;
-       void *hdr = NULL;
        struct nlattr *nl_reg_rules;
        unsigned int i;
 
-       if (!cfg80211_regdomain)
-               return -EINVAL;
-
-       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
-       if (!msg)
-               return -ENOBUFS;
-
-       hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
-                            NL80211_CMD_GET_REG);
-       if (!hdr)
-               goto put_failure;
-
-       if (reg_last_request_cell_base() &&
-           nla_put_u32(msg, NL80211_ATTR_USER_REG_HINT_TYPE,
-                       NL80211_USER_REG_HINT_CELL_BASE))
-               goto nla_put_failure;
-
-       rcu_read_lock();
-       regdom = rcu_dereference(cfg80211_regdomain);
-
        if (nla_put_string(msg, NL80211_ATTR_REG_ALPHA2, regdom->alpha2) ||
            (regdom->dfs_region &&
             nla_put_u8(msg, NL80211_ATTR_DFS_REGION, regdom->dfs_region)))
-               goto nla_put_failure_rcu;
+               goto nla_put_failure;
 
        nl_reg_rules = nla_nest_start(msg, NL80211_ATTR_REG_RULES);
        if (!nl_reg_rules)
-               goto nla_put_failure_rcu;
+               goto nla_put_failure;
 
        for (i = 0; i < regdom->n_reg_rules; i++) {
                struct nlattr *nl_reg_rule;
@@ -5377,7 +5365,7 @@ static int nl80211_get_reg(struct sk_buff *skb, struct genl_info *info)
 
                nl_reg_rule = nla_nest_start(msg, i);
                if (!nl_reg_rule)
-                       goto nla_put_failure_rcu;
+                       goto nla_put_failure;
 
                max_bandwidth_khz = freq_range->max_bandwidth_khz;
                if (!max_bandwidth_khz)
@@ -5398,13 +5386,74 @@ static int nl80211_get_reg(struct sk_buff *skb, struct genl_info *info)
                                power_rule->max_eirp) ||
                    nla_put_u32(msg, NL80211_ATTR_DFS_CAC_TIME,
                                reg_rule->dfs_cac_ms))
-                       goto nla_put_failure_rcu;
+                       goto nla_put_failure;
 
                nla_nest_end(msg, nl_reg_rule);
        }
-       rcu_read_unlock();
 
        nla_nest_end(msg, nl_reg_rules);
+       return 0;
+
+nla_put_failure:
+       return -EMSGSIZE;
+}
+
+static int nl80211_get_reg_do(struct sk_buff *skb, struct genl_info *info)
+{
+       const struct ieee80211_regdomain *regdom = NULL;
+       struct cfg80211_registered_device *rdev;
+       struct wiphy *wiphy = NULL;
+       struct sk_buff *msg;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOBUFS;
+
+       hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
+                            NL80211_CMD_GET_REG);
+       if (!hdr)
+               goto put_failure;
+
+       if (info->attrs[NL80211_ATTR_WIPHY]) {
+               bool self_managed;
+
+               rdev = cfg80211_get_dev_from_info(genl_info_net(info), info);
+               if (IS_ERR(rdev)) {
+                       nlmsg_free(msg);
+                       return PTR_ERR(rdev);
+               }
+
+               wiphy = &rdev->wiphy;
+               self_managed = wiphy->regulatory_flags &
+                              REGULATORY_WIPHY_SELF_MANAGED;
+               regdom = get_wiphy_regdom(wiphy);
+
+               /* a self-managed-reg device must have a private regdom */
+               if (WARN_ON(!regdom && self_managed)) {
+                       nlmsg_free(msg);
+                       return -EINVAL;
+               }
+
+               if (regdom &&
+                   nla_put_u32(msg, NL80211_ATTR_WIPHY, get_wiphy_idx(wiphy)))
+                       goto nla_put_failure;
+       }
+
+       if (!wiphy && reg_last_request_cell_base() &&
+           nla_put_u32(msg, NL80211_ATTR_USER_REG_HINT_TYPE,
+                       NL80211_USER_REG_HINT_CELL_BASE))
+               goto nla_put_failure;
+
+       rcu_read_lock();
+
+       if (!regdom)
+               regdom = rcu_dereference(cfg80211_regdomain);
+
+       if (nl80211_put_regdom(regdom, msg))
+               goto nla_put_failure_rcu;
+
+       rcu_read_unlock();
 
        genlmsg_end(msg, hdr);
        return genlmsg_reply(msg, info);
@@ -5418,6 +5467,83 @@ put_failure:
        return -EMSGSIZE;
 }
 
+static int nl80211_send_regdom(struct sk_buff *msg, struct netlink_callback *cb,
+                              u32 seq, int flags, struct wiphy *wiphy,
+                              const struct ieee80211_regdomain *regdom)
+{
+       void *hdr = nl80211hdr_put(msg, NETLINK_CB(cb->skb).portid, seq, flags,
+                                  NL80211_CMD_GET_REG);
+
+       if (!hdr)
+               return -1;
+
+       genl_dump_check_consistent(cb, hdr, &nl80211_fam);
+
+       if (nl80211_put_regdom(regdom, msg))
+               goto nla_put_failure;
+
+       if (!wiphy && reg_last_request_cell_base() &&
+           nla_put_u32(msg, NL80211_ATTR_USER_REG_HINT_TYPE,
+                       NL80211_USER_REG_HINT_CELL_BASE))
+               goto nla_put_failure;
+
+       if (wiphy &&
+           nla_put_u32(msg, NL80211_ATTR_WIPHY, get_wiphy_idx(wiphy)))
+               goto nla_put_failure;
+
+       if (wiphy && wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED &&
+           nla_put_flag(msg, NL80211_ATTR_WIPHY_SELF_MANAGED_REG))
+               goto nla_put_failure;
+
+       return genlmsg_end(msg, hdr);
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
+}
+
+static int nl80211_get_reg_dump(struct sk_buff *skb,
+                               struct netlink_callback *cb)
+{
+       const struct ieee80211_regdomain *regdom = NULL;
+       struct cfg80211_registered_device *rdev;
+       int err, reg_idx, start = cb->args[2];
+
+       rtnl_lock();
+
+       if (cfg80211_regdomain && start == 0) {
+               err = nl80211_send_regdom(skb, cb, cb->nlh->nlmsg_seq,
+                                         NLM_F_MULTI, NULL,
+                                         rtnl_dereference(cfg80211_regdomain));
+               if (err < 0)
+                       goto out_err;
+       }
+
+       /* the global regdom is idx 0 */
+       reg_idx = 1;
+       list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
+               regdom = get_wiphy_regdom(&rdev->wiphy);
+               if (!regdom)
+                       continue;
+
+               if (++reg_idx <= start)
+                       continue;
+
+               err = nl80211_send_regdom(skb, cb, cb->nlh->nlmsg_seq,
+                                         NLM_F_MULTI, &rdev->wiphy, regdom);
+               if (err < 0) {
+                       reg_idx--;
+                       break;
+               }
+       }
+
+       cb->args[2] = reg_idx;
+       err = skb->len;
+out_err:
+       rtnl_unlock();
+       return err;
+}
+
 static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
 {
        struct nlattr *tb[NL80211_REG_RULE_ATTR_MAX + 1];
@@ -6002,7 +6128,7 @@ nl80211_parse_sched_scan(struct wiphy *wiphy, struct wireless_dev *wdev,
                }
 
                /* there was no other matchset, so the RSSI one is alone */
-               if (i == 0)
+               if (i == 0 && n_match_sets)
                        request->match_sets[0].rssi_thold = default_match_rssi;
 
                request->min_rssi_thold = INT_MAX;
@@ -6069,6 +6195,7 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
        struct net_device *dev = info->user_ptr[1];
        struct wireless_dev *wdev = dev->ieee80211_ptr;
+       struct cfg80211_sched_scan_request *sched_scan_req;
        int err;
 
        if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) ||
@@ -6078,27 +6205,32 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
        if (rdev->sched_scan_req)
                return -EINPROGRESS;
 
-       rdev->sched_scan_req = nl80211_parse_sched_scan(&rdev->wiphy, wdev,
-                                                       info->attrs);
-       err = PTR_ERR_OR_ZERO(rdev->sched_scan_req);
+       sched_scan_req = nl80211_parse_sched_scan(&rdev->wiphy, wdev,
+                                                 info->attrs);
+
+       err = PTR_ERR_OR_ZERO(sched_scan_req);
        if (err)
                goto out_err;
 
-       err = rdev_sched_scan_start(rdev, dev, rdev->sched_scan_req);
+       err = rdev_sched_scan_start(rdev, dev, sched_scan_req);
        if (err)
                goto out_free;
 
-       rdev->sched_scan_req->dev = dev;
-       rdev->sched_scan_req->wiphy = &rdev->wiphy;
+       sched_scan_req->dev = dev;
+       sched_scan_req->wiphy = &rdev->wiphy;
+
+       if (info->attrs[NL80211_ATTR_SOCKET_OWNER])
+               sched_scan_req->owner_nlportid = info->snd_portid;
+
+       rcu_assign_pointer(rdev->sched_scan_req, sched_scan_req);
 
        nl80211_send_sched_scan(rdev, dev,
                                NL80211_CMD_START_SCHED_SCAN);
        return 0;
 
 out_free:
-       kfree(rdev->sched_scan_req);
+       kfree(sched_scan_req);
 out_err:
-       rdev->sched_scan_req = NULL;
        return err;
 }
 
@@ -6481,12 +6613,17 @@ static int nl80211_dump_scan(struct sk_buff *skb, struct netlink_callback *cb)
 }
 
 static int nl80211_send_survey(struct sk_buff *msg, u32 portid, u32 seq,
-                               int flags, struct net_device *dev,
-                               struct survey_info *survey)
+                              int flags, struct net_device *dev,
+                              bool allow_radio_stats,
+                              struct survey_info *survey)
 {
        void *hdr;
        struct nlattr *infoattr;
 
+       /* skip radio stats if userspace didn't request them */
+       if (!survey->channel && !allow_radio_stats)
+               return 0;
+
        hdr = nl80211hdr_put(msg, portid, seq, flags,
                             NL80211_CMD_NEW_SURVEY_RESULTS);
        if (!hdr)
@@ -6499,7 +6636,8 @@ static int nl80211_send_survey(struct sk_buff *msg, u32 portid, u32 seq,
        if (!infoattr)
                goto nla_put_failure;
 
-       if (nla_put_u32(msg, NL80211_SURVEY_INFO_FREQUENCY,
+       if (survey->channel &&
+           nla_put_u32(msg, NL80211_SURVEY_INFO_FREQUENCY,
                        survey->channel->center_freq))
                goto nla_put_failure;
 
@@ -6509,25 +6647,25 @@ static int nl80211_send_survey(struct sk_buff *msg, u32 portid, u32 seq,
        if ((survey->filled & SURVEY_INFO_IN_USE) &&
            nla_put_flag(msg, NL80211_SURVEY_INFO_IN_USE))
                goto nla_put_failure;
-       if ((survey->filled & SURVEY_INFO_CHANNEL_TIME) &&
-           nla_put_u64(msg, NL80211_SURVEY_INFO_CHANNEL_TIME,
-                       survey->channel_time))
+       if ((survey->filled & SURVEY_INFO_TIME) &&
+           nla_put_u64(msg, NL80211_SURVEY_INFO_TIME,
+                       survey->time))
                goto nla_put_failure;
-       if ((survey->filled & SURVEY_INFO_CHANNEL_TIME_BUSY) &&
-           nla_put_u64(msg, NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY,
-                       survey->channel_time_busy))
+       if ((survey->filled & SURVEY_INFO_TIME_BUSY) &&
+           nla_put_u64(msg, NL80211_SURVEY_INFO_TIME_BUSY,
+                       survey->time_busy))
                goto nla_put_failure;
-       if ((survey->filled & SURVEY_INFO_CHANNEL_TIME_EXT_BUSY) &&
-           nla_put_u64(msg, NL80211_SURVEY_INFO_CHANNEL_TIME_EXT_BUSY,
-                       survey->channel_time_ext_busy))
+       if ((survey->filled & SURVEY_INFO_TIME_EXT_BUSY) &&
+           nla_put_u64(msg, NL80211_SURVEY_INFO_TIME_EXT_BUSY,
+                       survey->time_ext_busy))
                goto nla_put_failure;
-       if ((survey->filled & SURVEY_INFO_CHANNEL_TIME_RX) &&
-           nla_put_u64(msg, NL80211_SURVEY_INFO_CHANNEL_TIME_RX,
-                       survey->channel_time_rx))
+       if ((survey->filled & SURVEY_INFO_TIME_RX) &&
+           nla_put_u64(msg, NL80211_SURVEY_INFO_TIME_RX,
+                       survey->time_rx))
                goto nla_put_failure;
-       if ((survey->filled & SURVEY_INFO_CHANNEL_TIME_TX) &&
-           nla_put_u64(msg, NL80211_SURVEY_INFO_CHANNEL_TIME_TX,
-                       survey->channel_time_tx))
+       if ((survey->filled & SURVEY_INFO_TIME_TX) &&
+           nla_put_u64(msg, NL80211_SURVEY_INFO_TIME_TX,
+                       survey->time_tx))
                goto nla_put_failure;
 
        nla_nest_end(msg, infoattr);
@@ -6539,19 +6677,22 @@ static int nl80211_send_survey(struct sk_buff *msg, u32 portid, u32 seq,
        return -EMSGSIZE;
 }
 
-static int nl80211_dump_survey(struct sk_buff *skb,
-                       struct netlink_callback *cb)
+static int nl80211_dump_survey(struct sk_buff *skb, struct netlink_callback *cb)
 {
        struct survey_info survey;
        struct cfg80211_registered_device *rdev;
        struct wireless_dev *wdev;
        int survey_idx = cb->args[2];
        int res;
+       bool radio_stats;
 
        res = nl80211_prepare_wdev_dump(skb, cb, &rdev, &wdev);
        if (res)
                return res;
 
+       /* prepare_wdev_dump parsed the attributes */
+       radio_stats = nl80211_fam.attrbuf[NL80211_ATTR_SURVEY_RADIO_STATS];
+
        if (!wdev->netdev) {
                res = -EINVAL;
                goto out_err;
@@ -6569,13 +6710,9 @@ static int nl80211_dump_survey(struct sk_buff *skb,
                if (res)
                        goto out_err;
 
-               /* Survey without a channel doesn't make sense */
-               if (!survey.channel) {
-                       res = -EINVAL;
-                       goto out;
-               }
-
-               if (survey.channel->flags & IEEE80211_CHAN_DISABLED) {
+               /* don't send disabled channels, but do send non-channel data */
+               if (survey.channel &&
+                   survey.channel->flags & IEEE80211_CHAN_DISABLED) {
                        survey_idx++;
                        continue;
                }
@@ -6583,7 +6720,7 @@ static int nl80211_dump_survey(struct sk_buff *skb,
                if (nl80211_send_survey(skb,
                                NETLINK_CB(cb->skb).portid,
                                cb->nlh->nlmsg_seq, NLM_F_MULTI,
-                               wdev->netdev, &survey) < 0)
+                               wdev->netdev, radio_stats, &survey) < 0)
                        goto out;
                survey_idx++;
        }
@@ -10225,7 +10362,8 @@ static const struct genl_ops nl80211_ops[] = {
        },
        {
                .cmd = NL80211_CMD_GET_REG,
-               .doit = nl80211_get_reg,
+               .doit = nl80211_get_reg_do,
+               .dumpit = nl80211_get_reg_dump,
                .policy = nl80211_policy,
                .internal_flags = NL80211_FLAG_NEED_RTNL,
                /* can be retrieved by unprivileged users */
@@ -10939,25 +11077,9 @@ void nl80211_send_sched_scan(struct cfg80211_registered_device *rdev,
                                NL80211_MCGRP_SCAN, GFP_KERNEL);
 }
 
-/*
- * This can happen on global regulatory changes or device specific settings
- * based on custom world regulatory domains.
- */
-void nl80211_send_reg_change_event(struct regulatory_request *request)
+static bool nl80211_reg_change_event_fill(struct sk_buff *msg,
+                                         struct regulatory_request *request)
 {
-       struct sk_buff *msg;
-       void *hdr;
-
-       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
-       if (!msg)
-               return;
-
-       hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_REG_CHANGE);
-       if (!hdr) {
-               nlmsg_free(msg);
-               return;
-       }
-
        /* Userspace can always count this one always being set */
        if (nla_put_u8(msg, NL80211_ATTR_REG_INITIATOR, request->initiator))
                goto nla_put_failure;
@@ -10983,8 +11105,46 @@ void nl80211_send_reg_change_event(struct regulatory_request *request)
                        goto nla_put_failure;
        }
 
-       if (request->wiphy_idx != WIPHY_IDX_INVALID &&
-           nla_put_u32(msg, NL80211_ATTR_WIPHY, request->wiphy_idx))
+       if (request->wiphy_idx != WIPHY_IDX_INVALID) {
+               struct wiphy *wiphy = wiphy_idx_to_wiphy(request->wiphy_idx);
+
+               if (wiphy &&
+                   nla_put_u32(msg, NL80211_ATTR_WIPHY, request->wiphy_idx))
+                       goto nla_put_failure;
+
+               if (wiphy &&
+                   wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED &&
+                   nla_put_flag(msg, NL80211_ATTR_WIPHY_SELF_MANAGED_REG))
+                       goto nla_put_failure;
+       }
+
+       return true;
+
+nla_put_failure:
+       return false;
+}
+
+/*
+ * This can happen on global regulatory changes or device specific settings
+ * based on custom regulatory domains.
+ */
+void nl80211_common_reg_change_event(enum nl80211_commands cmd_id,
+                                    struct regulatory_request *request)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg)
+               return;
+
+       hdr = nl80211hdr_put(msg, 0, 0, 0, cmd_id);
+       if (!hdr) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       if (nl80211_reg_change_event_fill(msg, request) == false)
                goto nla_put_failure;
 
        genlmsg_end(msg, hdr);
@@ -12471,6 +12631,13 @@ static int nl80211_netlink_notify(struct notifier_block * nb,
 
        list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) {
                bool schedule_destroy_work = false;
+               bool schedule_scan_stop = false;
+               struct cfg80211_sched_scan_request *sched_scan_req =
+                       rcu_dereference(rdev->sched_scan_req);
+
+               if (sched_scan_req && notify->portid &&
+                   sched_scan_req->owner_nlportid == notify->portid)
+                       schedule_scan_stop = true;
 
                list_for_each_entry_rcu(wdev, &rdev->wdev_list, list) {
                        cfg80211_mlme_unregister_socket(wdev, notify->portid);
@@ -12501,6 +12668,12 @@ static int nl80211_netlink_notify(struct notifier_block * nb,
                                spin_unlock(&rdev->destroy_list_lock);
                                schedule_work(&rdev->destroy_work);
                        }
+               } else if (schedule_scan_stop) {
+                       sched_scan_req->owner_nlportid = 0;
+
+                       if (rdev->ops->sched_scan_stop &&
+                           rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN)
+                               schedule_work(&rdev->sched_scan_stop_wk);
                }
        }
 
This page took 0.03929 seconds and 5 git commands to generate.