mac80211: support DTPC IE (from Cisco Client eXtensions)
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 3 Sep 2014 13:48:37 +0000 (06:48 -0700)
committerJohannes Berg <johannes@sipsolutions.net>
Mon, 8 Sep 2014 08:52:00 +0000 (10:52 +0200)
Linux already supports 802.11h, where the access point can tell the
client to reduce its transmission power. However, 802.11h is only
defined for 5 GHz, where the need for this is much smaller than on
2.4 GHz.

Cisco has their own solution, called DTPC (Dynamic Transmit Power
Control). Cisco APs on a controller sometimes but not always send
802.11h; they always send DTPC, even on 2.4 GHz. This patch adds support
for parsing and honoring the DTPC IE in addition to the 802.11h
element (they do not always contain the same limits, so both must
be honored); the format is not documented, but very simple.

Tested (on top of wireless.git and on 3.16.1) against a Cisco Aironet
1142 joined to a Cisco 2504 WLC, by setting various transmit power
levels for the given access points and observing the results.
The Wireshark 802.11 dissector agrees with the interpretation of the
element, except for negative numbers, which seem to never happen
anyway.

Signed-off-by: Steinar H. Gunderson <sgunderson@bigfoot.com>
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
include/linux/ieee80211.h
net/mac80211/ieee80211_i.h
net/mac80211/mlme.c
net/mac80211/util.c

index 77d4f26e02350c4abd5d9d78da614a5455de0f40..61a09f0644aaf2686239fcaa2fd0c43f34d2a4ca 100644 (file)
@@ -1824,7 +1824,8 @@ enum ieee80211_eid {
        WLAN_EID_DMG_TSPEC = 146,
        WLAN_EID_DMG_AT = 147,
        WLAN_EID_DMG_CAP = 148,
-       /* 149-150 reserved for Cisco */
+       /* 149 reserved for Cisco */
+       WLAN_EID_CISCO_VENDOR_SPECIFIC = 150,
        WLAN_EID_DMG_OPERATION = 151,
        WLAN_EID_DMG_BSS_PARAM_CHANGE = 152,
        WLAN_EID_DMG_BEAM_REFINEMENT = 153,
index 6b7bdd8fae2933964a71e211f312db9a6a268162..c2aaec4dfcf0ea38da3e45cfdc7b8ad0ecef5fc8 100644 (file)
@@ -1369,6 +1369,7 @@ struct ieee802_11_elems {
        const struct ieee80211_wide_bw_chansw_ie *wide_bw_chansw_ie;
        const u8 *country_elem;
        const u8 *pwr_constr_elem;
+       const u8 *cisco_dtpc_elem;
        const struct ieee80211_timeout_interval_ie *timeout_int;
        const u8 *opmode_notif;
        const struct ieee80211_sec_chan_offs_ie *sec_chan_offs;
index 0b812a88f95f7695c947dc6f8779c0a1c18dc487..a7b92f5f7161748c1f03fb8c73c26adeb5797220 100644 (file)
@@ -1230,14 +1230,30 @@ ieee80211_find_80211h_pwr_constr(struct ieee80211_sub_if_data *sdata,
        return have_chan_pwr;
 }
 
+static void ieee80211_find_cisco_dtpc(struct ieee80211_sub_if_data *sdata,
+                                     struct ieee80211_channel *channel,
+                                     const u8 *cisco_dtpc_ie,
+                                     int *pwr_level)
+{
+       /* From practical testing, the first data byte of the DTPC element
+        * seems to contain the requested dBm level, and the CLI on Cisco
+        * APs clearly state the range is -127 to 127 dBm, which indicates
+        * a signed byte, although it seemingly never actually goes negative.
+        * The other byte seems to always be zero.
+        */
+       *pwr_level = (__s8)cisco_dtpc_ie[4];
+}
+
 static u32 ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
                                       struct ieee80211_channel *channel,
                                       struct ieee80211_mgmt *mgmt,
                                       const u8 *country_ie, u8 country_ie_len,
-                                      const u8 *pwr_constr_ie)
+                                      const u8 *pwr_constr_ie,
+                                      const u8 *cisco_dtpc_ie)
 {
-       bool has_80211h_pwr = false;
-       int chan_pwr, pwr_reduction_80211h;
+       bool has_80211h_pwr = false, has_cisco_pwr = false;
+       int chan_pwr = 0, pwr_reduction_80211h = 0;
+       int pwr_level_cisco, pwr_level_80211h;
        int new_ap_level;
 
        if (country_ie && pwr_constr_ie &&
@@ -1246,16 +1262,35 @@ static u32 ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
                has_80211h_pwr = ieee80211_find_80211h_pwr_constr(
                        sdata, channel, country_ie, country_ie_len,
                        pwr_constr_ie, &chan_pwr, &pwr_reduction_80211h);
-               new_ap_level = max_t(int, 0, chan_pwr - pwr_reduction_80211h);
+               pwr_level_80211h =
+                       max_t(int, 0, chan_pwr - pwr_reduction_80211h);
+       }
+
+       if (cisco_dtpc_ie) {
+               ieee80211_find_cisco_dtpc(
+                       sdata, channel, cisco_dtpc_ie, &pwr_level_cisco);
+               has_cisco_pwr = true;
        }
 
-       if (!has_80211h_pwr)
+       if (!has_80211h_pwr && !has_cisco_pwr)
                return 0;
 
-       sdata_info(sdata,
-                  "Limiting TX power to %d (%d - %d) dBm as advertised by %pM\n",
-                  new_ap_level, chan_pwr, pwr_reduction_80211h,
-                  sdata->u.mgd.bssid);
+       /* If we have both 802.11h and Cisco DTPC, apply both limits
+        * by picking the smallest of the two power levels advertised.
+        */
+       if (has_80211h_pwr &&
+           (!has_cisco_pwr || pwr_level_80211h <= pwr_level_cisco)) {
+               sdata_info(sdata,
+                          "Limiting TX power to %d (%d - %d) dBm as advertised by %pM\n",
+                          pwr_level_80211h, chan_pwr, pwr_reduction_80211h,
+                          sdata->u.mgd.bssid);
+               new_ap_level = pwr_level_80211h;
+       } else {  /* has_cisco_pwr is always true here. */
+               sdata_info(sdata,
+                          "Limiting TX power to %d dBm as advertised by %pM\n",
+                          pwr_level_cisco, sdata->u.mgd.bssid);
+               new_ap_level = pwr_level_cisco;
+       }
 
        if (sdata->ap_power_level == new_ap_level)
                return 0;
@@ -2923,7 +2958,9 @@ static void ieee80211_rx_mgmt_probe_resp(struct ieee80211_sub_if_data *sdata,
 /*
  * This is the canonical list of information elements we care about,
  * the filter code also gives us all changes to the Microsoft OUI
- * (00:50:F2) vendor IE which is used for WMM which we need to track.
+ * (00:50:F2) vendor IE which is used for WMM which we need to track,
+ * as well as the DTPC IE (part of the Cisco OUI) used for signaling
+ * changes to requested client power.
  *
  * We implement beacon filtering in software since that means we can
  * avoid processing the frame here and in cfg80211, and userspace
@@ -3232,7 +3269,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
        changed |= ieee80211_handle_pwr_constr(sdata, chan, mgmt,
                                               elems.country_elem,
                                               elems.country_elem_len,
-                                              elems.pwr_constr_elem);
+                                              elems.pwr_constr_elem,
+                                              elems.cisco_dtpc_elem);
 
        ieee80211_bss_info_change_notify(sdata, changed);
 }
index b444f27a788edfb9e7de75aa8d0c8fb29a8052bd..3c61060a4d2b75bd89d4a792dde8b4721eb435dc 100644 (file)
@@ -1015,6 +1015,31 @@ u32 ieee802_11_parse_elems_crc(const u8 *start, size_t len, bool action,
                        }
                        elems->pwr_constr_elem = pos;
                        break;
+               case WLAN_EID_CISCO_VENDOR_SPECIFIC:
+                       /* Lots of different options exist, but we only care
+                        * about the Dynamic Transmit Power Control element.
+                        * First check for the Cisco OUI, then for the DTPC
+                        * tag (0x00).
+                        */
+                       if (elen < 4) {
+                               elem_parse_failed = true;
+                               break;
+                       }
+
+                       if (pos[0] != 0x00 || pos[1] != 0x40 ||
+                           pos[2] != 0x96 || pos[3] != 0x00)
+                               break;
+
+                       if (elen != 6) {
+                               elem_parse_failed = true;
+                               break;
+                       }
+
+                       if (calc_crc)
+                               crc = crc32_be(crc, pos - 2, elen + 2);
+
+                       elems->cisco_dtpc_elem = pos;
+                       break;
                case WLAN_EID_TIMEOUT_INTERVAL:
                        if (elen >= sizeof(struct ieee80211_timeout_interval_ie))
                                elems->timeout_int = (void *)pos;