cfg80211: send extended capabilities IE in connect
authorJohannes Berg <johannes.berg@intel.com>
Tue, 31 Mar 2015 14:09:13 +0000 (16:09 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 7 Apr 2015 11:56:45 +0000 (13:56 +0200)
If the connect request from userspace didn't include an extended
capabilities IE, create one using the driver capabilities. This
fixes VHT associations, since those need to set the operating mode
notification capability.

Reviewed-by: Gregory Greenman <gregory.greenman@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
net/wireless/sme.c

index ea1da6621ff051028970f50154af56d6f2e01b46..d11454f87bacf9396241bd5bbc3ba643ef0b3302 100644 (file)
@@ -42,7 +42,7 @@ struct cfg80211_conn {
                CFG80211_CONN_CONNECTED,
        } state;
        u8 bssid[ETH_ALEN], prev_bssid[ETH_ALEN];
-       u8 *ie;
+       const u8 *ie;
        size_t ie_len;
        bool auto_auth, prev_bssid_valid;
 };
@@ -423,6 +423,62 @@ void cfg80211_sme_assoc_timeout(struct wireless_dev *wdev)
        schedule_work(&rdev->conn_work);
 }
 
+static int cfg80211_sme_get_conn_ies(struct wireless_dev *wdev,
+                                    const u8 *ies, size_t ies_len,
+                                    const u8 **out_ies, size_t *out_ies_len)
+{
+       struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
+       u8 *buf;
+       size_t offs;
+
+       if (!rdev->wiphy.extended_capabilities_len ||
+           (ies && cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, ies, ies_len))) {
+               *out_ies = kmemdup(ies, ies_len, GFP_KERNEL);
+               if (!*out_ies)
+                       return -ENOMEM;
+               *out_ies_len = ies_len;
+               return 0;
+       }
+
+       buf = kmalloc(ies_len + rdev->wiphy.extended_capabilities_len + 2,
+                     GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       if (ies_len) {
+               static const u8 before_extcapa[] = {
+                       /* not listing IEs expected to be created by driver */
+                       WLAN_EID_RSN,
+                       WLAN_EID_QOS_CAPA,
+                       WLAN_EID_RRM_ENABLED_CAPABILITIES,
+                       WLAN_EID_MOBILITY_DOMAIN,
+                       WLAN_EID_SUPPORTED_REGULATORY_CLASSES,
+                       WLAN_EID_BSS_COEX_2040,
+               };
+
+               offs = ieee80211_ie_split(ies, ies_len, before_extcapa,
+                                         ARRAY_SIZE(before_extcapa), 0);
+               memcpy(buf, ies, offs);
+               /* leave a whole for extended capabilities IE */
+               memcpy(buf + offs + rdev->wiphy.extended_capabilities_len + 2,
+                      ies + offs, ies_len - offs);
+       } else {
+               offs = 0;
+       }
+
+       /* place extended capabilities IE (with only driver capabilities) */
+       buf[offs] = WLAN_EID_EXT_CAPABILITY;
+       buf[offs + 1] = rdev->wiphy.extended_capabilities_len;
+       memcpy(buf + offs + 2,
+              rdev->wiphy.extended_capabilities,
+              rdev->wiphy.extended_capabilities_len);
+
+       *out_ies = buf;
+       *out_ies_len = ies_len + rdev->wiphy.extended_capabilities_len + 2;
+
+       return 0;
+}
+
 static int cfg80211_sme_connect(struct wireless_dev *wdev,
                                struct cfg80211_connect_params *connect,
                                const u8 *prev_bssid)
@@ -453,16 +509,14 @@ static int cfg80211_sme_connect(struct wireless_dev *wdev,
                memcpy(wdev->conn->bssid, connect->bssid, ETH_ALEN);
        }
 
-       if (connect->ie) {
-               wdev->conn->ie = kmemdup(connect->ie, connect->ie_len,
-                                       GFP_KERNEL);
-               wdev->conn->params.ie = wdev->conn->ie;
-               if (!wdev->conn->ie) {
-                       kfree(wdev->conn);
-                       wdev->conn = NULL;
-                       return -ENOMEM;
-               }
+       if (cfg80211_sme_get_conn_ies(wdev, connect->ie, connect->ie_len,
+                                     &wdev->conn->ie,
+                                     &wdev->conn->params.ie_len)) {
+               kfree(wdev->conn);
+               wdev->conn = NULL;
+               return -ENOMEM;
        }
+       wdev->conn->params.ie = wdev->conn->ie;
 
        if (connect->auth_type == NL80211_AUTHTYPE_AUTOMATIC) {
                wdev->conn->auto_auth = true;