cfg80211: Add option to specify previous BSSID for Connect command
[cascardo/linux.git] / net / wireless / nl80211.c
index 98c9242..4f89e2d 100644 (file)
@@ -402,6 +402,8 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
        [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] = { .type = NLA_U8 },
 };
 
 /* policy for the key attributes */
@@ -486,6 +488,15 @@ nl80211_plan_policy[NL80211_SCHED_SCAN_PLAN_MAX + 1] = {
        [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)
+       },
+};
+
 static int nl80211_prepare_wdev_dump(struct sk_buff *skb,
                                     struct netlink_callback *cb,
                                     struct cfg80211_registered_device **rdev,
@@ -1731,6 +1742,25 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev,
                            rdev->wiphy.ext_features))
                        goto nla_put_failure;
 
+               if (rdev->wiphy.bss_select_support) {
+                       struct nlattr *nested;
+                       u32 bss_select_support = rdev->wiphy.bss_select_support;
+
+                       nested = nla_nest_start(msg, NL80211_ATTR_BSS_SELECT);
+                       if (!nested)
+                               goto nla_put_failure;
+
+                       i = 0;
+                       while (bss_select_support) {
+                               if ((bss_select_support & 1) &&
+                                   nla_put_flag(msg, i))
+                                       goto nla_put_failure;
+                               i++;
+                               bss_select_support >>= 1;
+                       }
+                       nla_nest_end(msg, nested);
+               }
+
                /* done */
                state->split_start = 0;
                break;
@@ -3977,6 +4007,10 @@ int cfg80211_check_station_change(struct wiphy *wiphy,
            statype != CFG80211_STA_AP_CLIENT_UNASSOC)
                return -EINVAL;
 
+       if (params->support_p2p_ps != -1 &&
+           statype != CFG80211_STA_AP_CLIENT_UNASSOC)
+               return -EINVAL;
+
        if (params->aid &&
            !(params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) &&
            statype != CFG80211_STA_AP_CLIENT_UNASSOC)
@@ -4270,6 +4304,18 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
        else
                params.listen_interval = -1;
 
+       if (info->attrs[NL80211_ATTR_STA_SUPPORT_P2P_PS]) {
+               u8 tmp;
+
+               tmp = nla_get_u8(info->attrs[NL80211_ATTR_STA_SUPPORT_P2P_PS]);
+               if (tmp >= NUM_NL80211_P2P_PS_STATUS)
+                       return -EINVAL;
+
+               params.support_p2p_ps = tmp;
+       } else {
+               params.support_p2p_ps = -1;
+       }
+
        if (!info->attrs[NL80211_ATTR_MAC])
                return -EINVAL;
 
@@ -4393,6 +4439,23 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
        params.listen_interval =
                nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
 
+       if (info->attrs[NL80211_ATTR_STA_SUPPORT_P2P_PS]) {
+               u8 tmp;
+
+               tmp = nla_get_u8(info->attrs[NL80211_ATTR_STA_SUPPORT_P2P_PS]);
+               if (tmp >= NUM_NL80211_P2P_PS_STATUS)
+                       return -EINVAL;
+
+               params.support_p2p_ps = tmp;
+       } else {
+               /*
+                * if not specified, assume it's supported for P2P GO interface,
+                * and is NOT supported for AP interface
+                */
+               params.support_p2p_ps =
+                       dev->ieee80211_ptr->iftype == NL80211_IFTYPE_P2P_GO;
+       }
+
        if (info->attrs[NL80211_ATTR_PEER_AID])
                params.aid = nla_get_u16(info->attrs[NL80211_ATTR_PEER_AID]);
        else
@@ -5758,6 +5821,73 @@ static int validate_scan_freqs(struct nlattr *freqs)
        return n_channels;
 }
 
+static bool is_band_valid(struct wiphy *wiphy, enum ieee80211_band b)
+{
+       return b < IEEE80211_NUM_BANDS && wiphy->bands[b];
+}
+
+static int parse_bss_select(struct nlattr *nla, struct wiphy *wiphy,
+                           struct cfg80211_bss_selection *bss_select)
+{
+       struct nlattr *attr[NL80211_BSS_SELECT_ATTR_MAX + 1];
+       struct nlattr *nest;
+       int err;
+       bool found = false;
+       int i;
+
+       /* only process one nested attribute */
+       nest = nla_data(nla);
+       if (!nla_ok(nest, nla_len(nest)))
+               return -EINVAL;
+
+       err = nla_parse(attr, NL80211_BSS_SELECT_ATTR_MAX, nla_data(nest),
+                       nla_len(nest), nl80211_bss_select_policy);
+       if (err)
+               return err;
+
+       /* only one attribute may be given */
+       for (i = 0; i <= NL80211_BSS_SELECT_ATTR_MAX; i++) {
+               if (attr[i]) {
+                       if (found)
+                               return -EINVAL;
+                       found = true;
+               }
+       }
+
+       bss_select->behaviour = __NL80211_BSS_SELECT_ATTR_INVALID;
+
+       if (attr[NL80211_BSS_SELECT_ATTR_RSSI])
+               bss_select->behaviour = NL80211_BSS_SELECT_ATTR_RSSI;
+
+       if (attr[NL80211_BSS_SELECT_ATTR_BAND_PREF]) {
+               bss_select->behaviour = NL80211_BSS_SELECT_ATTR_BAND_PREF;
+               bss_select->param.band_pref =
+                       nla_get_u32(attr[NL80211_BSS_SELECT_ATTR_BAND_PREF]);
+               if (!is_band_valid(wiphy, bss_select->param.band_pref))
+                       return -EINVAL;
+       }
+
+       if (attr[NL80211_BSS_SELECT_ATTR_RSSI_ADJUST]) {
+               struct nl80211_bss_select_rssi_adjust *adj_param;
+
+               adj_param = nla_data(attr[NL80211_BSS_SELECT_ATTR_RSSI_ADJUST]);
+               bss_select->behaviour = NL80211_BSS_SELECT_ATTR_RSSI_ADJUST;
+               bss_select->param.adjust.band = adj_param->band;
+               bss_select->param.adjust.delta = adj_param->delta;
+               if (!is_band_valid(wiphy, bss_select->param.adjust.band))
+                       return -EINVAL;
+       }
+
+       /* user-space did not provide behaviour attribute */
+       if (bss_select->behaviour == __NL80211_BSS_SELECT_ATTR_INVALID)
+               return -EINVAL;
+
+       if (!(wiphy->bss_select_support & BIT(bss_select->behaviour)))
+               return -EINVAL;
+
+       return 0;
+}
+
 static int nl80211_parse_random_mac(struct nlattr **attrs,
                                    u8 *mac_addr, u8 *mac_addr_mask)
 {
@@ -5996,6 +6126,12 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
        request->no_cck =
                nla_get_flag(info->attrs[NL80211_ATTR_TX_NO_CCK_RATE]);
 
+       if (info->attrs[NL80211_ATTR_MAC])
+               memcpy(request->bssid, nla_data(info->attrs[NL80211_ATTR_MAC]),
+                      ETH_ALEN);
+       else
+               eth_broadcast_addr(request->bssid);
+
        request->wdev = wdev;
        request->wiphy = &rdev->wiphy;
        request->scan_start = jiffies;
@@ -7922,6 +8058,10 @@ static int nl80211_connect(struct sk_buff *skb, struct genl_info *info)
                connect.mfp = NL80211_MFP_NO;
        }
 
+       if (info->attrs[NL80211_ATTR_PREV_BSSID])
+               connect.prev_bssid =
+                       nla_data(info->attrs[NL80211_ATTR_PREV_BSSID]);
+
        if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
                connect.channel = nl80211_get_valid_chan(
                        wiphy, info->attrs[NL80211_ATTR_WIPHY_FREQ]);
@@ -7995,6 +8135,21 @@ static int nl80211_connect(struct sk_buff *skb, struct genl_info *info)
                return -EOPNOTSUPP;
        }
 
+       if (info->attrs[NL80211_ATTR_BSS_SELECT]) {
+               /* bss selection makes no sense if bssid is set */
+               if (connect.bssid) {
+                       kzfree(connkeys);
+                       return -EINVAL;
+               }
+
+               err = parse_bss_select(info->attrs[NL80211_ATTR_BSS_SELECT],
+                                      wiphy, &connect.bss_select);
+               if (err) {
+                       kzfree(connkeys);
+                       return err;
+               }
+       }
+
        wdev_lock(dev->ieee80211_ptr);
        err = cfg80211_connect(rdev, dev, &connect, connkeys, NULL);
        wdev_unlock(dev->ieee80211_ptr);