nl80211: fix scheduled scan RSSI matchset attribute confusion
authorJohannes Berg <johannes.berg@intel.com>
Fri, 24 Jan 2014 09:53:53 +0000 (10:53 +0100)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 4 Feb 2014 20:58:12 +0000 (21:58 +0100)
The scheduled scan matchsets were intended to be a list of filters,
with the found BSS having to pass at least one of them to be passed
to the host. When the RSSI attribute was added, however, this was
broken and currently wpa_supplicant adds that attribute in its own
matchset; however, it doesn't intend that to mean that anything
that passes the RSSI filter should be passed to the host, instead
it wants it to mean that everything needs to also have higher RSSI.

This is semantically problematic because we have a list of filters
like [ SSID1, SSID2, SSID3, RSSI ] with no real indication which
one should be OR'ed and which one AND'ed.

To fix this, move the RSSI filter attribute into each matchset. As
we need to stay backward compatible, treat a matchset with only the
RSSI attribute as a "default RSSI filter" for all other matchsets,
but only if there are other matchsets (an RSSI-only matchset by
itself is still desirable.)

To make driver implementation easier, keep a global min_rssi_thold
for the entire request as well. The only affected driver is ath6kl.

I found this when I looked into the code after Raja Mani submitted
a patch fixing the n_match_sets calculation to disregard the RSSI,
but that patch didn't address the semantic issue.

Reported-by: Raja Mani <rmani@qti.qualcomm.com>
Acked-by: Luciano Coelho <luciano.coelho@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
drivers/net/wireless/ath/ath6kl/cfg80211.c
drivers/net/wireless/iwlwifi/mvm/scan.c
include/net/cfg80211.h
include/uapi/linux/nl80211.h
net/wireless/nl80211.c

index fd4c89df67e1658ae404686a911fc62607cee1cd..eba32f56850ab81efd3211afd528e1083f410ab8 100644 (file)
@@ -3256,6 +3256,15 @@ static int ath6kl_cfg80211_sscan_start(struct wiphy *wiphy,
        struct ath6kl_vif *vif = netdev_priv(dev);
        u16 interval;
        int ret, rssi_thold;
+       int n_match_sets = request->n_match_sets;
+
+       /*
+        * If there's a matchset w/o an SSID, then assume it's just for
+        * the RSSI (nothing else is currently supported) and ignore it.
+        * The device only supports a global RSSI filter that we set below.
+        */
+       if (n_match_sets == 1 && !request->match_sets[0].ssid.ssid_len)
+               n_match_sets = 0;
 
        if (ar->state != ATH6KL_STATE_ON)
                return -EIO;
@@ -3268,11 +3277,11 @@ static int ath6kl_cfg80211_sscan_start(struct wiphy *wiphy,
        ret = ath6kl_set_probed_ssids(ar, vif, request->ssids,
                                      request->n_ssids,
                                      request->match_sets,
-                                     request->n_match_sets);
+                                     n_match_sets);
        if (ret < 0)
                return ret;
 
-       if (!request->n_match_sets) {
+       if (!n_match_sets) {
                ret = ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx,
                                               ALL_BSS_FILTER, 0);
                if (ret < 0)
@@ -3286,12 +3295,12 @@ static int ath6kl_cfg80211_sscan_start(struct wiphy *wiphy,
 
        if (test_bit(ATH6KL_FW_CAPABILITY_RSSI_SCAN_THOLD,
                     ar->fw_capabilities)) {
-               if (request->rssi_thold <= NL80211_SCAN_RSSI_THOLD_OFF)
+               if (request->min_rssi_thold <= NL80211_SCAN_RSSI_THOLD_OFF)
                        rssi_thold = 0;
-               else if (request->rssi_thold < -127)
+               else if (request->min_rssi_thold < -127)
                        rssi_thold = -127;
                else
-                       rssi_thold = request->rssi_thold;
+                       rssi_thold = request->min_rssi_thold;
 
                ret = ath6kl_wmi_set_rssi_filter_cmd(ar->wmi, vif->fw_vif_idx,
                                                     rssi_thold);
index 0e0007960612e7866b332e27a1f19af9c5847afc..9674bfd978f136e1d0c642974f62c79fc0352fa1 100644 (file)
@@ -595,6 +595,9 @@ static void iwl_scan_offload_build_ssid(struct cfg80211_sched_scan_request *req,
         * config match list.
         */
        for (i = 0; i < req->n_match_sets && i < PROBE_OPTION_MAX; i++) {
+               /* skip empty SSID matchsets */
+               if (!req->match_sets[i].ssid.ssid_len)
+                       continue;
                scan->direct_scan[i].id = WLAN_EID_SSID;
                scan->direct_scan[i].len = req->match_sets[i].ssid.ssid_len;
                memcpy(scan->direct_scan[i].ssid, req->match_sets[i].ssid.ssid,
index d5e57bf678a643684d7fd3156ec90d495a4ac8e5..009290e36d157525fd3411af112c65e5b770da93 100644 (file)
@@ -1394,10 +1394,12 @@ struct cfg80211_scan_request {
 /**
  * struct cfg80211_match_set - sets of attributes to match
  *
- * @ssid: SSID to be matched
+ * @ssid: SSID to be matched; may be zero-length for no match (RSSI only)
+ * @rssi_thold: don't report scan results below this threshold (in s32 dBm)
  */
 struct cfg80211_match_set {
        struct cfg80211_ssid ssid;
+       s32 rssi_thold;
 };
 
 /**
@@ -1420,7 +1422,8 @@ struct cfg80211_match_set {
  * @dev: the interface
  * @scan_start: start time of the scheduled scan
  * @channels: channels to scan
- * @rssi_thold: don't report scan results below this threshold (in s32 dBm)
+ * @min_rssi_thold: for drivers only supporting a single threshold, this
+ *     contains the minimum over all matchsets
  */
 struct cfg80211_sched_scan_request {
        struct cfg80211_ssid *ssids;
@@ -1433,7 +1436,7 @@ struct cfg80211_sched_scan_request {
        u32 flags;
        struct cfg80211_match_set *match_sets;
        int n_match_sets;
-       s32 rssi_thold;
+       s32 min_rssi_thold;
 
        /* internal */
        struct wiphy *wiphy;
index 53e56cf7c0fec009408936b5b2fbbff0f66c317a..474ce32e07974e19f96adfa18adc42e81dae073c 100644 (file)
@@ -2467,9 +2467,15 @@ enum nl80211_reg_rule_attr {
  * enum nl80211_sched_scan_match_attr - scheduled scan match attributes
  * @__NL80211_SCHED_SCAN_MATCH_ATTR_INVALID: attribute number 0 is reserved
  * @NL80211_SCHED_SCAN_MATCH_ATTR_SSID: SSID to be used for matching,
- * only report BSS with matching SSID.
+ *     only report BSS with matching SSID.
  * @NL80211_SCHED_SCAN_MATCH_ATTR_RSSI: RSSI threshold (in dBm) for reporting a
- *     BSS in scan results. Filtering is turned off if not specified.
+ *     BSS in scan results. Filtering is turned off if not specified. Note that
+ *     if this attribute is in a match set of its own, then it is treated as
+ *     the default value for all matchsets with an SSID, rather than being a
+ *     matchset of its own without an RSSI filter. This is due to problems with
+ *     how this API was implemented in the past. Also, due to the same problem,
+ *     the only way to create a matchset with only an RSSI filter (with this
+ *     attribute) is if there's only a single matchset with the RSSI attribute.
  * @NL80211_SCHED_SCAN_MATCH_ATTR_MAX: highest scheduled scan filter
  *     attribute number currently defined
  * @__NL80211_SCHED_SCAN_MATCH_ATTR_AFTER_LAST: internal use
index 043bfbd58b56c08f5908f3d564ec8001ad0fd1a1..20be186f7f772df5a628d6b72971029d51971924 100644 (file)
@@ -5467,6 +5467,7 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
        enum ieee80211_band band;
        size_t ie_len;
        struct nlattr *tb[NL80211_SCHED_SCAN_MATCH_ATTR_MAX + 1];
+       s32 default_match_rssi = NL80211_SCAN_RSSI_THOLD_OFF;
 
        if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) ||
            !rdev->ops->sched_scan_start)
@@ -5501,11 +5502,40 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
        if (n_ssids > wiphy->max_sched_scan_ssids)
                return -EINVAL;
 
-       if (info->attrs[NL80211_ATTR_SCHED_SCAN_MATCH])
+       /*
+        * First, count the number of 'real' matchsets. Due to an issue with
+        * the old implementation, matchsets containing only the RSSI attribute
+        * (NL80211_SCHED_SCAN_MATCH_ATTR_RSSI) are considered as the 'default'
+        * RSSI for all matchsets, rather than their own matchset for reporting
+        * all APs with a strong RSSI. This is needed to be compatible with
+        * older userspace that treated a matchset with only the RSSI as the
+        * global RSSI for all other matchsets - if there are other matchsets.
+        */
+       if (info->attrs[NL80211_ATTR_SCHED_SCAN_MATCH]) {
                nla_for_each_nested(attr,
                                    info->attrs[NL80211_ATTR_SCHED_SCAN_MATCH],
-                                   tmp)
-                       n_match_sets++;
+                                   tmp) {
+                       struct nlattr *rssi;
+
+                       err = nla_parse(tb, NL80211_SCHED_SCAN_MATCH_ATTR_MAX,
+                                       nla_data(attr), nla_len(attr),
+                                       nl80211_match_policy);
+                       if (err)
+                               return err;
+                       /* add other standalone attributes here */
+                       if (tb[NL80211_SCHED_SCAN_MATCH_ATTR_SSID]) {
+                               n_match_sets++;
+                               continue;
+                       }
+                       rssi = tb[NL80211_SCHED_SCAN_MATCH_ATTR_RSSI];
+                       if (rssi)
+                               default_match_rssi = nla_get_s32(rssi);
+               }
+       }
+
+       /* However, if there's no other matchset, add the RSSI one */
+       if (!n_match_sets && default_match_rssi != NL80211_SCAN_RSSI_THOLD_OFF)
+               n_match_sets = 1;
 
        if (n_match_sets > wiphy->max_match_sets)
                return -EINVAL;
@@ -5633,6 +5663,15 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
                                goto out_free;
                        ssid = tb[NL80211_SCHED_SCAN_MATCH_ATTR_SSID];
                        if (ssid) {
+                               if (WARN_ON(i >= n_match_sets)) {
+                                       /* this indicates a programming error,
+                                        * the loop above should have verified
+                                        * things properly
+                                        */
+                                       err = -EINVAL;
+                                       goto out_free;
+                               }
+
                                if (nla_len(ssid) > IEEE80211_MAX_SSID_LEN) {
                                        err = -EINVAL;
                                        goto out_free;
@@ -5641,15 +5680,28 @@ static int nl80211_start_sched_scan(struct sk_buff *skb,
                                       nla_data(ssid), nla_len(ssid));
                                request->match_sets[i].ssid.ssid_len =
                                        nla_len(ssid);
+                               /* special attribute - old implemenation w/a */
+                               request->match_sets[i].rssi_thold =
+                                       default_match_rssi;
+                               rssi = tb[NL80211_SCHED_SCAN_MATCH_ATTR_RSSI];
+                               if (rssi)
+                                       request->match_sets[i].rssi_thold =
+                                               nla_get_s32(rssi);
                        }
-                       rssi = tb[NL80211_SCHED_SCAN_MATCH_ATTR_RSSI];
-                       if (rssi)
-                               request->rssi_thold = nla_get_u32(rssi);
-                       else
-                               request->rssi_thold =
-                                                  NL80211_SCAN_RSSI_THOLD_OFF;
                        i++;
                }
+
+               /* there was no other matchset, so the RSSI one is alone */
+               if (i == 0)
+                       request->match_sets[0].rssi_thold = default_match_rssi;
+
+               request->min_rssi_thold = INT_MAX;
+               for (i = 0; i < n_match_sets; i++)
+                       request->min_rssi_thold =
+                               min(request->match_sets[i].rssi_thold,
+                                   request->min_rssi_thold);
+       } else {
+               request->min_rssi_thold = NL80211_SCAN_RSSI_THOLD_OFF;
        }
 
        if (info->attrs[NL80211_ATTR_IE]) {