diff mbox

nl80211: Allow configuring allowable legacy rate mask.

Message ID 1442361885-25829-1-git-send-email-greearb@candelatech.com
State Changes Requested
Headers show

Commit Message

Ben Greear Sept. 16, 2015, 12:04 a.m. UTC
From: Ben Greear <greearb@candelatech.com>

With this, we should be able to disable un-wanted rates
early in the association process, before 'iw' could
have a chance to make any settings.

Signed-off-by: Ben Greear <greearb@candelatech.com>
---
 src/drivers/driver.h                 |  10 ++
 src/drivers/driver_nl80211.c         | 216 +++++++++++++++++++++++++----------
 src/drivers/driver_nl80211.h         |   5 +-
 src/drivers/driver_nl80211_monitor.c |   4 +-
 wpa_supplicant/driver_i.h            |   7 ++
 wpa_supplicant/wpa_supplicant.c      |   8 ++
 wpa_supplicant/wpa_supplicant.conf   |   5 +
 7 files changed, 192 insertions(+), 63 deletions(-)

Comments

Jouni Malinen Oct. 6, 2015, 6:08 p.m. UTC | #1
On Tue, Sep 15, 2015 at 08:04:45PM -0400, greearb@candelatech.com wrote:
> With this, we should be able to disable un-wanted rates
> early in the association process, before 'iw' could
> have a chance to make any settings.

I cleaned up this patch and the following 3 patchset a bit and the
latest snapshot from my work branch is here:
http://w1.fi/p/nl80211-rates/

This needs some more testing. See patch 5/5 for mac80211_hwsim test
cases that would need more work to use tshark to check the TX rate and
supported rates elements; something similar is needed for P2P cases
(disable 11b) to check for regressions.

One unexpected part I saw was in how this works with association or to
be more exact, how this does not work at all with association.. Or well,
the actual TX rate of the frame does change, but supported rates
elements did not. Was that on purpose? It is certainly quite confusing
to the user, i.e., I would have expected this to be a global rate mask
that would apply for all operations on the interface.

I could not get ht_disable=1 vht_disable=1 to work. Was that supposed to
remove HT Capabilities from Probe Request frames? Please note that I had
to remove VHT rate set from the 2.4 GHz band to prevent cfg80211 from
rejecting the full command as invalid. Anyway, the HT capabilities show
up on 2.4 GHz band even when using ht_disable=1 driver parameter.
Ben Greear Oct. 6, 2015, 6:30 p.m. UTC | #2
On 10/06/2015 11:08 AM, Jouni Malinen wrote:
> On Tue, Sep 15, 2015 at 08:04:45PM -0400, greearb@candelatech.com wrote:
>> With this, we should be able to disable un-wanted rates
>> early in the association process, before 'iw' could
>> have a chance to make any settings.
>
> I cleaned up this patch and the following 3 patchset a bit and the
> latest snapshot from my work branch is here:
> http://w1.fi/p/nl80211-rates/
>
> This needs some more testing. See patch 5/5 for mac80211_hwsim test
> cases that would need more work to use tshark to check the TX rate and
> supported rates elements; something similar is needed for P2P cases
> (disable 11b) to check for regressions.
>
> One unexpected part I saw was in how this works with association or to
> be more exact, how this does not work at all with association.. Or well,
> the actual TX rate of the frame does change, but supported rates
> elements did not. Was that on purpose? It is certainly quite confusing
> to the user, i.e., I would have expected this to be a global rate mask
> that would apply for all operations on the interface.
>
> I could not get ht_disable=1 vht_disable=1 to work. Was that supposed to
> remove HT Capabilities from Probe Request frames? Please note that I had
> to remove VHT rate set from the 2.4 GHz band to prevent cfg80211 from
> rejecting the full command as invalid. Anyway, the HT capabilities show
> up on 2.4 GHz band even when using ht_disable=1 driver parameter.

I also had to make some changes to the linux kernel to make this work properly,
and for ath10k, down in the ath10k driver and firmware as well.  Upstream ath10k firmware probably
will never add the needed support, but my firmware & kernel does appear to work with this properly
now.

Kernel patches are here, I think everything is in the top 18 patches
...you could ignore the ath10k part, and notice I had
a few fixup patches..these would need cleanup before being acceptable upstream:

http://dmz2.candelatech.com/git/gitweb.cgi?p=linux-4.2.dev.y/.git;a=shortlog

I'd be perfectly fine with you submitting any of that upstream, and I'll
post them myself as soon as I find time to clean it up if no one beats me
to it.

In general, this concept may need some more improvement as well.  Maybe
a way to configure the rates in the probe/assoc requests independently of fixing
transmit rates.  I'm not too sure either way on that, but some APs will forbid
association if you don't support all of the basic rateset, so it would be impossible
to use my kernel patches at the hostap patches to fully set a fixed rate of 6Mbps
in that case.  Maybe that is a feature, maybe a bug...I guess it depends on the
use-case.

Thanks,
Ben
Jouni Malinen Oct. 12, 2015, 4:30 p.m. UTC | #3
On Tue, Oct 06, 2015 at 11:30:56AM -0700, Ben Greear wrote:
> On 10/06/2015 11:08 AM, Jouni Malinen wrote:
> >I cleaned up this patch and the following 3 patchset a bit and the
> >latest snapshot from my work branch is here:
> >http://w1.fi/p/nl80211-rates/

> I also had to make some changes to the linux kernel to make this work properly,
> and for ath10k, down in the ath10k driver and firmware as well.  Upstream ath10k firmware probably
> will never add the needed support, but my firmware & kernel does appear to work with this properly
> now.
> 
> Kernel patches are here, I think everything is in the top 18 patches
> ...you could ignore the ath10k part, and notice I had
> a few fixup patches..these would need cleanup before being acceptable upstream:
> 
> http://dmz2.candelatech.com/git/gitweb.cgi?p=linux-4.2.dev.y/.git;a=shortlog
> 
> I'd be perfectly fine with you submitting any of that upstream, and I'll
> post them myself as soon as I find time to clean it up if no one beats me
> to it.

Please let me know once the relevant cfg80211 and/or mac80211 changes
are in upstream. Whenever submitting hostapd or wpa_supplicant changes
that depends on pending kernel changes, it would be good to note that
clearly with the submission.

> In general, this concept may need some more improvement as well.  Maybe
> a way to configure the rates in the probe/assoc requests independently of fixing
> transmit rates.  I'm not too sure either way on that, but some APs will forbid
> association if you don't support all of the basic rateset, so it would be impossible
> to use my kernel patches at the hostap patches to fully set a fixed rate of 6Mbps
> in that case.  Maybe that is a feature, maybe a bug...I guess it depends on the
> use-case.

I'm not sure I understand your use case. I can see use for being able to
make a VHT or HT capable device looks like 802.11a/g-only, so that part
was clear. However, I was expecting the driver_param to do that for all
operations and not just TX rate or some of the Probe Request frame
contents.
  
If you want some independent configuration parameters, I'd suggest
discussing that with detailed design or at least describing the expected
behavior clearly in the commit message. I don't really want to make this
overly complex, though, so I would need to the use case better to be
able to support this..

All APs are supposed to reject association if the station does not
support all of the basic rates. That's a requirement in the standard..
Setting fixed TX rate is different from making the device drop its VHT
or HT capabilities. I'm myself more interested in disabling all signs of
VHT and HT capability, but if you have a use case for fixing TX rate
without affecting HT/VHT capabilities, I guess that could be doable. I'm
not convinced that driver_param is the way to go for that, though.
Ben Greear Oct. 12, 2015, 4:49 p.m. UTC | #4
On 10/12/2015 09:30 AM, Jouni Malinen wrote:
> On Tue, Oct 06, 2015 at 11:30:56AM -0700, Ben Greear wrote:
>> On 10/06/2015 11:08 AM, Jouni Malinen wrote:
>>> I cleaned up this patch and the following 3 patchset a bit and the
>>> latest snapshot from my work branch is here:
>>> http://w1.fi/p/nl80211-rates/
>
>> I also had to make some changes to the linux kernel to make this work properly,
>> and for ath10k, down in the ath10k driver and firmware as well.  Upstream ath10k firmware probably
>> will never add the needed support, but my firmware & kernel does appear to work with this properly
>> now.
>>
>> Kernel patches are here, I think everything is in the top 18 patches
>> ...you could ignore the ath10k part, and notice I had
>> a few fixup patches..these would need cleanup before being acceptable upstream:
>>
>> http://dmz2.candelatech.com/git/gitweb.cgi?p=linux-4.2.dev.y/.git;a=shortlog
>>
>> I'd be perfectly fine with you submitting any of that upstream, and I'll
>> post them myself as soon as I find time to clean it up if no one beats me
>> to it.
>
> Please let me know once the relevant cfg80211 and/or mac80211 changes
> are in upstream. Whenever submitting hostapd or wpa_supplicant changes
> that depends on pending kernel changes, it would be good to note that
> clearly with the submission.

I'll let you know, and try to do a better job of letting you know
the kernel dependencies in the future.

>> In general, this concept may need some more improvement as well.  Maybe
>> a way to configure the rates in the probe/assoc requests independently of fixing
>> transmit rates.  I'm not too sure either way on that, but some APs will forbid
>> association if you don't support all of the basic rateset, so it would be impossible
>> to use my kernel patches at the hostap patches to fully set a fixed rate of 6Mbps
>> in that case.  Maybe that is a feature, maybe a bug...I guess it depends on the
>> use-case.
>
> I'm not sure I understand your use case. I can see use for being able to
> make a VHT or HT capable device looks like 802.11a/g-only, so that part
> was clear. However, I was expecting the driver_param to do that for all
> operations and not just TX rate or some of the Probe Request frame
> contents.

That is my goal as well.  But, various kernel stack and driver limitations
were exposed in my testing.  I think my current code mostly gets the
rate IEs right, at least..but possibly I am still missing some things.

And, Johanne's feedback is that I need to do it a bit differently anyway.

I think basically I need to allow setting the ratemask that determines IEs
with a different API than normal rate-ctrl logic so that one can still set
rate-ctrl to '6Mbps' but not suddenly break probe requests because supported
rates are not included.

My current thinking is to duplicate the code I already added to
hostap so that I can set the tx-rate mask AND also set the advertised-tx-rates mask.
This will in turn require a new kernel API to allow setting advertised-tx-rates.
These advertised rates would affect IEs and such.  The tx-rate mask would just
affect the tx rate control logic, not IEs.

> If you want some independent configuration parameters, I'd suggest
> discussing that with detailed design or at least describing the expected
> behavior clearly in the commit message. I don't really want to make this
> overly complex, though, so I would need to the use case better to be
> able to support this..
>
> All APs are supposed to reject association if the station does not
> support all of the basic rates. That's a requirement in the standard..
> Setting fixed TX rate is different from making the device drop its VHT
> or HT capabilities. I'm myself more interested in disabling all signs of
> VHT and HT capability, but if you have a use case for fixing TX rate
> without affecting HT/VHT capabilities, I guess that could be doable. I'm
> not convinced that driver_param is the way to go for that, though.

I agree the driver_param was a bit of a hack.  I do need the logic there
very early, so if not driver_param, then I guess just adding a new
driver API call with better typed arguments is the way to go?

And, from what I remember, hostap APs and the 4.2 kernel will associate
stations that do not support all of the basic rates.  I will double-check
this when I re-do the patches to support tx-rate vs advertised-tx-rates.

Thanks,
Ben
diff mbox

Patch

diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 3cdab5a..d5fbf2c 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -1425,6 +1425,9 @@  struct wpa_init_params {
 	char **bridge;
 	size_t num_bridge;
 
+	/* legacy rate mask, 12 bits for legacy rates, (1<<31) to enable use of this mask */
+	unsigned int legacy_rates;
+
 	u8 *own_addr; /* buffer for writing own MAC address */
 };
 
@@ -3012,6 +3015,13 @@  struct wpa_driver_ops {
 	int (*stop_sched_scan)(void *priv);
 
 	/**
+	 * Set the legacy rates.  User would have previously configured the
+	 * ratemask through other means.  This just tells the kernel to apply
+	 * it.
+	 */
+	int (*set_legacy_rates)(void *priv);
+
+	/**
 	 * poll_client - Probe (null data or such) the given station
 	 * @priv: Private driver interface data
 	 * @own_addr: MAC address of sending interface
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 075a20e..3fbf0f4 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -186,8 +186,10 @@  static int have_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx);
 
 static int nl80211_set_channel(struct i802_bss *bss,
 			       struct hostapd_freq_params *freq, int set_chan);
-static int nl80211_disable_11b_rates(struct wpa_driver_nl80211_data *drv,
-				     int ifindex, int disabled);
+static int nl80211_set_legacy_rates(struct i802_bss *bss,
+				    struct wpa_driver_nl80211_data *drv,
+				    int ifindex, int b_disabled,
+				    unsigned int cfg_legacy_mask);
 
 static int nl80211_leave_ibss(struct wpa_driver_nl80211_data *drv,
 			      int reset_mode);
@@ -2295,8 +2297,8 @@  static void wpa_driver_nl80211_deinit(struct i802_bss *bss)
 {
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 
-	wpa_printf(MSG_INFO, "nl80211: deinit ifname=%s disabled_11b_rates=%d",
-		   bss->ifname, drv->disabled_11b_rates);
+	wpa_printf(MSG_INFO, "nl80211: deinit ifname=%s",
+		   bss->ifname);
 
 	bss->in_deinit = 1;
 	if (drv->data_tx_status)
@@ -2340,8 +2342,7 @@  static void wpa_driver_nl80211_deinit(struct i802_bss *bss)
 	if (drv->if_indices != drv->default_if_indices)
 		os_free(drv->if_indices);
 
-	if (drv->disabled_11b_rates)
-		nl80211_disable_11b_rates(drv, drv->ifindex, 0);
+	nl80211_set_legacy_rates(bss, drv, drv->ifindex, 0, 0xFFF);
 
 	netlink_send_oper_ifla(drv->global->netlink, drv->ifindex, 0,
 			       IF_OPER_UP);
@@ -2864,25 +2865,6 @@  static void nl80211_copy_auth_params(struct wpa_driver_nl80211_data *drv,
 }
 
 
-static void nl80211_unmask_11b_rates(struct i802_bss *bss)
-{
-	struct wpa_driver_nl80211_data *drv = bss->drv;
-
-	if (is_p2p_net_interface(drv->nlmode) || !drv->disabled_11b_rates)
-		return;
-
-	/*
-	 * Looks like we failed to unmask 11b rates previously. This could
-	 * happen, e.g., if the interface was down at the point in time when a
-	 * P2P group was terminated.
-	 */
-	wpa_printf(MSG_DEBUG,
-		   "nl80211: Interface %s mode is for non-P2P, but 11b rates were disabled - re-enable them",
-		   bss->ifname);
-	nl80211_disable_11b_rates(drv, drv->ifindex, 0);
-}
-
-
 static int wpa_driver_nl80211_authenticate(
 	struct i802_bss *bss, struct wpa_driver_auth_params *params)
 {
@@ -2894,7 +2876,7 @@  static int wpa_driver_nl80211_authenticate(
 	int count = 0;
 	int is_retry;
 
-	nl80211_unmask_11b_rates(bss);
+	nl80211_set_legacy_rates(bss, drv, drv->ifindex, 0, bss->legacy_rates);
 
 	is_retry = drv->retry_auth;
 	drv->retry_auth = 0;
@@ -4076,7 +4058,7 @@  static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv,
 }
 
 
-int nl80211_create_iface(struct wpa_driver_nl80211_data *drv,
+int nl80211_create_iface(struct i802_bss *bss, struct wpa_driver_nl80211_data *drv,
 			 const char *ifname, enum nl80211_iftype iftype,
 			 const u8 *addr, int wds,
 			 int (*handler)(struct nl_msg *, void *),
@@ -4118,7 +4100,7 @@  int nl80211_create_iface(struct wpa_driver_nl80211_data *drv,
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Interface %s created for P2P - disable 11b rates",
 			   ifname);
-		nl80211_disable_11b_rates(drv, ret, 1);
+		nl80211_set_legacy_rates(bss, drv, drv->ifindex, 1, bss->legacy_rates);
 	}
 
 	return ret;
@@ -4149,7 +4131,7 @@  static int nl80211_setup_ap(struct i802_bss *bss)
 			return -1;
 
 	if (!drv->device_ap_sme && drv->use_monitor &&
-	    nl80211_create_monitor_interface(drv) &&
+	    nl80211_create_monitor_interface(bss, drv) &&
 	    !drv->device_ap_sme)
 		return -1;
 
@@ -4810,7 +4792,7 @@  static int wpa_driver_nl80211_associate(
 	int ret = -1;
 	struct nl_msg *msg;
 
-	nl80211_unmask_11b_rates(bss);
+	nl80211_set_legacy_rates(bss, drv, drv->ifindex, 0, bss->legacy_rates);
 
 	if (params->mode == IEEE80211_MODE_AP)
 		return wpa_driver_nl80211_ap(drv, params);
@@ -4989,12 +4971,12 @@  done:
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Interface %s mode change to P2P - disable 11b rates",
 			   bss->ifname);
-		nl80211_disable_11b_rates(drv, drv->ifindex, 1);
-	} else if (drv->disabled_11b_rates) {
+		nl80211_set_legacy_rates(bss, drv, drv->ifindex, 1, bss->legacy_rates);
+	} else {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Interface %s mode changed to non-P2P - re-enable 11b rates",
 			   bss->ifname);
-		nl80211_disable_11b_rates(drv, drv->ifindex, 0);
+		nl80211_set_legacy_rates(bss, drv, drv->ifindex, 0, bss->legacy_rates);
 	}
 
 	if (is_ap_interface(nlmode)) {
@@ -5595,7 +5577,7 @@  static int i802_set_wds_sta(void *priv, const u8 *addr, int aid, int val,
 		   " aid=%d val=%d name=%s", MAC2STR(addr), aid, val, name);
 	if (val) {
 		if (!if_nametoindex(name)) {
-			if (nl80211_create_iface(drv, name,
+			if (nl80211_create_iface(bss, drv, name,
 						 NL80211_IFTYPE_AP_VLAN,
 						 bss->addr, 1, NULL, NULL, 0) <
 			    0)
@@ -5917,7 +5899,7 @@  static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type,
 		struct wdev_info p2pdev_info;
 
 		os_memset(&p2pdev_info, 0, sizeof(p2pdev_info));
-		ifidx = nl80211_create_iface(drv, ifname, nlmode, addr,
+		ifidx = nl80211_create_iface(bss, drv, ifname, nlmode, addr,
 					     0, nl80211_wdev_handler,
 					     &p2pdev_info, use_existing);
 		if (!p2pdev_info.wdev_id_set || ifidx != 0) {
@@ -5934,7 +5916,7 @@  static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type,
 			   ifname,
 			   (long long unsigned int) p2pdev_info.wdev_id);
 	} else {
-		ifidx = nl80211_create_iface(drv, ifname, nlmode, addr,
+		ifidx = nl80211_create_iface(bss, drv, ifname, nlmode, addr,
 					     0, NULL, NULL, use_existing);
 		if (use_existing && ifidx == -ENFILE) {
 			added = 0;
@@ -6373,17 +6355,19 @@  static int wpa_driver_nl80211_probe_req_report(struct i802_bss *bss, int report)
 }
 
 
-static int nl80211_disable_11b_rates(struct wpa_driver_nl80211_data *drv,
-				     int ifindex, int disabled)
+static int nl80211_set_legacy_rates(struct i802_bss *bss,
+				    struct wpa_driver_nl80211_data *drv,
+				    int ifindex, int b_disabled,
+				    unsigned int legacy_rates)
 {
 	struct nl_msg *msg;
 	struct nlattr *bands, *band;
 	int ret;
 
 	wpa_printf(MSG_DEBUG,
-		   "nl80211: NL80211_CMD_SET_TX_BITRATE_MASK (ifindex=%d %s)",
-		   ifindex, disabled ? "NL80211_TXRATE_LEGACY=OFDM-only" :
-		   "no NL80211_TXRATE_LEGACY constraint");
+		   "nl80211: NL80211_CMD_SET_TX_BITRATE_MASK (ifindex=%d %s cfg-legacy-mask: 0x%x)",
+		   ifindex, b_disabled ? "NL80211_TXRATE_LEGACY=OFDM-only" :
+		   "no NL80211_TXRATE_LEGACY constraint", legacy_rates);
 
 	msg = nl80211_ifindex_msg(drv, ifindex, 0,
 				  NL80211_CMD_SET_TX_BITRATE_MASK);
@@ -6400,20 +6384,130 @@  static int nl80211_disable_11b_rates(struct wpa_driver_nl80211_data *drv,
 	 * rates. All 5 GHz rates are left enabled.
 	 */
 	band = nla_nest_start(msg, NL80211_BAND_2GHZ);
-	if (!band ||
-	    (disabled && nla_put(msg, NL80211_TXRATE_LEGACY, 8,
-				 "\x0c\x12\x18\x24\x30\x48\x60\x6c")))
+	if (!band)
 		goto fail;
+
+	if (legacy_rates & (1<<31) || b_disabled) {
+		unsigned char rates[12];
+		int i = 0;
+		if (legacy_rates & (1<<31)) {
+			/* Pay attention to the legacy mask */
+			if (!(b_disabled || ((legacy_rates & 0x1) == 0))) {
+				rates[i] = 2;
+				i++;
+			}
+			if (!(b_disabled || ((legacy_rates & 0x2) == 0))) {
+				rates[i] = 4;
+				i++;
+			}
+			if (!(b_disabled || ((legacy_rates & 0x4) == 0))) {
+				rates[i] = 11;
+				i++;
+			}
+			if (!(b_disabled || ((legacy_rates & 0x8) == 0))) {
+				rates[i] = 22;
+				i++;
+			}
+			if (legacy_rates & 0x10) { // 6Mbps
+				rates[i] = 12;
+				i++;
+			}
+			if (legacy_rates & 0x20) { // 9Mbps
+				rates[i] = 18;
+				i++;
+			}
+			if (legacy_rates & 0x40) { // 12Mbps
+				rates[i] = 24;
+				i++;
+			}
+			if (legacy_rates & 0x80) { // 18Mbps
+				rates[i] = 36;
+				i++;
+			}
+			if (legacy_rates & 0x100) { // 24Mbps
+				rates[i] = 48;
+				i++;
+			}
+			if (legacy_rates & 0x200) { // 36Mbps
+				rates[i] = 72;
+				i++;
+			}
+			if (legacy_rates & 0x400) { // 48Mbps
+				rates[i] = 96;
+				i++;
+			}
+			if (legacy_rates & 0x800) { // 54Mbps
+				rates[i] = 108;
+				i++;
+			}
+		}/* if user configured a legacy mask */
+		else if (b_disabled) {
+			i = 8;
+			/* Enable all /g rates */
+			memcpy(rates, "\x0c\x12\x18\x24\x30\x48\x60\x6c", i);
+		}
+		if (legacy_rates & (1<<31) || i) {
+			if (nla_put(msg, NL80211_TXRATE_LEGACY, i, rates))
+				goto fail;
+		}
+	}/* if we might need to disable some rates */
+
 	nla_nest_end(msg, band);
 
+	if (legacy_rates & (1<<31)) {
+		unsigned char rates[8];
+		int i = 0;
+
+		band = nla_nest_start(msg, NL80211_BAND_5GHZ);
+		if (!band)
+			goto fail;
+
+		if (legacy_rates & 0x10) { // 6Mbps
+			rates[i] = 12;
+			i++;
+		}
+		if (legacy_rates & 0x20) { // 9Mbps
+			rates[i] = 18;
+			i++;
+		}
+		if (legacy_rates & 0x40) { // 12Mbps
+			rates[i] = 24;
+			i++;
+		}
+		if (legacy_rates & 0x80) { // 18Mbps
+			rates[i] = 36;
+			i++;
+		}
+		if (legacy_rates & 0x100) { // 24Mbps
+			rates[i] = 48;
+			i++;
+		}
+		if (legacy_rates & 0x200) { // 36Mbps
+			rates[i] = 72;
+			i++;
+		}
+		if (legacy_rates & 0x400) { // 48Mbps
+			rates[i] = 96;
+			i++;
+		}
+		if (legacy_rates & 0x800) { // 54Mbps
+			rates[i] = 108;
+			i++;
+		}
+
+		if (nla_put(msg, NL80211_TXRATE_LEGACY, i, rates))
+			goto fail;
+
+		nla_nest_end(msg, band);
+	}
+
 	nla_nest_end(msg, bands);
 
 	ret = send_and_recv_msgs(drv, msg, NULL, NULL);
 	if (ret) {
-		wpa_printf(MSG_DEBUG, "nl80211: Set TX rates failed: ret=%d "
+		wpa_printf(MSG_DEBUG, "nl80211: Set Legacy TX rates failed: ret=%d "
 			   "(%s)", ret, strerror(-ret));
-	} else
-		drv->disabled_11b_rates = disabled;
+	}
 
 	return ret;
 
@@ -6422,6 +6516,11 @@  fail:
 	return -1;
 }
 
+int wpa_driver_nl80211_set_legacy_rates(void *priv)
+{
+	struct i802_bss *bss = priv;
+	return nl80211_set_legacy_rates(bss, bss->drv, bss->drv->ifindex, 0, bss->legacy_rates);
+}
 
 static int wpa_driver_nl80211_deinit_ap(void *priv)
 {
@@ -6573,15 +6672,16 @@  static int nl80211_send_frame(void *priv, const u8 *data, size_t data_len,
 
 static int nl80211_set_param(void *priv, const char *param)
 {
+	char* tmp;
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+
 	wpa_printf(MSG_DEBUG, "nl80211: driver param='%s'", param);
 	if (param == NULL)
 		return 0;
 
 #ifdef CONFIG_P2P
 	if (os_strstr(param, "use_p2p_group_interface=1")) {
-		struct i802_bss *bss = priv;
-		struct wpa_driver_nl80211_data *drv = bss->drv;
-
 		wpa_printf(MSG_DEBUG, "nl80211: Use separate P2P group "
 			   "interface");
 		drv->capa.flags |= WPA_DRIVER_FLAGS_P2P_CONCURRENT;
@@ -6590,25 +6690,24 @@  static int nl80211_set_param(void *priv, const char *param)
 #endif /* CONFIG_P2P */
 
 	if (os_strstr(param, "use_monitor=1")) {
-		struct i802_bss *bss = priv;
-		struct wpa_driver_nl80211_data *drv = bss->drv;
 		drv->use_monitor = 1;
 	}
 
 	if (os_strstr(param, "force_connect_cmd=1")) {
-		struct i802_bss *bss = priv;
-		struct wpa_driver_nl80211_data *drv = bss->drv;
 		drv->capa.flags &= ~WPA_DRIVER_FLAGS_SME;
 		drv->force_connect_cmd = 1;
 	}
 
 	if (os_strstr(param, "no_offchannel_tx=1")) {
-		struct i802_bss *bss = priv;
-		struct wpa_driver_nl80211_data *drv = bss->drv;
 		drv->capa.flags &= ~WPA_DRIVER_FLAGS_OFFCHANNEL_TX;
 		drv->test_use_roc_tx = 1;
 	}
 
+	if ((tmp = os_strstr(param, "legacy_rates="))) {
+		bss->legacy_rates = atoi(tmp + strlen("legacy_rates="));
+		bss->legacy_rates |= (1<<31); /* so we know user actually configured something */
+	}
+
 	return 0;
 }
 
@@ -7456,7 +7555,7 @@  static int wpa_driver_nl80211_status(void *priv, char *buf, size_t buflen)
 			  "monitor_refcount=%d\n"
 			  "last_mgmt_freq=%u\n"
 			  "eapol_tx_sock=%d\n"
-			  "%s%s%s%s%s%s%s%s%s%s%s%s%s",
+			  "%s%s%s%s%s%s%s%s%s%s%s%s",
 			  drv->phyname,
 			  MAC2STR(drv->perm_addr),
 			  drv->ifindex,
@@ -7477,8 +7576,6 @@  static int wpa_driver_nl80211_status(void *priv, char *buf, size_t buflen)
 			  "ignore_if_down_event=1\n" : "",
 			  drv->scan_complete_events ?
 			  "scan_complete_events=1\n" : "",
-			  drv->disabled_11b_rates ?
-			  "disabled_11b_rates=1\n" : "",
 			  drv->pending_remain_on_chan ?
 			  "pending_remain_on_chan=1\n" : "",
 			  drv->in_interface_list ? "in_interface_list=1\n" : "",
@@ -8731,6 +8828,7 @@  const struct wpa_driver_ops wpa_driver_nl80211_ops = {
 	.scan2 = driver_nl80211_scan2,
 	.sched_scan = wpa_driver_nl80211_sched_scan,
 	.stop_sched_scan = wpa_driver_nl80211_stop_sched_scan,
+	.set_legacy_rates = wpa_driver_nl80211_set_legacy_rates,
 	.get_scan_results2 = wpa_driver_nl80211_get_scan_results,
 	.deauthenticate = driver_nl80211_deauthenticate,
 	.authenticate = driver_nl80211_authenticate,
diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
index 5c21e0f..bc64e5c 100644
--- a/src/drivers/driver_nl80211.h
+++ b/src/drivers/driver_nl80211.h
@@ -70,6 +70,7 @@  struct i802_bss {
 	int freq;
 	int bandwidth;
 	int if_dynamic;
+	unsigned int legacy_rates; /* 1, 2, 5.5, 11, 6, 9, 12, 18, 24, 36, 48, 54.  (1<<31) enables this mask */
 
 	void *ctx;
 	struct nl_handle *nl_preq, *nl_mgmt;
@@ -195,7 +196,7 @@  struct nl_msg * nl80211_bss_msg(struct i802_bss *bss, int flags, uint8_t cmd);
 int send_and_recv_msgs(struct wpa_driver_nl80211_data *drv, struct nl_msg *msg,
 		       int (*valid_handler)(struct nl_msg *, void *),
 		       void *valid_data);
-int nl80211_create_iface(struct wpa_driver_nl80211_data *drv,
+int nl80211_create_iface(struct i802_bss *bss, struct wpa_driver_nl80211_data *drv,
 			 const char *ifname, enum nl80211_iftype iftype,
 			 const u8 *addr, int wds,
 			 int (*handler)(struct nl_msg *, void *),
@@ -220,7 +221,7 @@  int wpa_driver_nl80211_mlme(struct wpa_driver_nl80211_data *drv,
 			    const u8 *addr, int cmd, u16 reason_code,
 			    int local_state_change);
 
-int nl80211_create_monitor_interface(struct wpa_driver_nl80211_data *drv);
+int nl80211_create_monitor_interface(struct i802_bss *bss, struct wpa_driver_nl80211_data *drv);
 void nl80211_remove_monitor_interface(struct wpa_driver_nl80211_data *drv);
 int nl80211_send_monitor(struct wpa_driver_nl80211_data *drv,
 			 const void *data, size_t len,
diff --git a/src/drivers/driver_nl80211_monitor.c b/src/drivers/driver_nl80211_monitor.c
index 45385da..8e0b482 100644
--- a/src/drivers/driver_nl80211_monitor.c
+++ b/src/drivers/driver_nl80211_monitor.c
@@ -338,7 +338,7 @@  void nl80211_remove_monitor_interface(struct wpa_driver_nl80211_data *drv)
 }
 
 
-int nl80211_create_monitor_interface(struct wpa_driver_nl80211_data *drv)
+int nl80211_create_monitor_interface(struct i802_bss *bss, struct wpa_driver_nl80211_data *drv)
 {
 	char buf[IFNAMSIZ];
 	struct sockaddr_ll ll;
@@ -368,7 +368,7 @@  int nl80211_create_monitor_interface(struct wpa_driver_nl80211_data *drv)
 	buf[IFNAMSIZ - 1] = '\0';
 
 	drv->monitor_ifidx =
-		nl80211_create_iface(drv, buf, NL80211_IFTYPE_MONITOR, NULL,
+		nl80211_create_iface(bss, drv, buf, NL80211_IFTYPE_MONITOR, NULL,
 				     0, NULL, NULL, 0);
 
 	if (drv->monitor_ifidx == -EOPNOTSUPP) {
diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h
index 73768c7..a1853e5 100644
--- a/wpa_supplicant/driver_i.h
+++ b/wpa_supplicant/driver_i.h
@@ -116,6 +116,13 @@  static inline int wpa_drv_stop_sched_scan(struct wpa_supplicant *wpa_s)
 	return -1;
 }
 
+static inline int wpa_drv_set_legacy_rates(struct wpa_supplicant *wpa_s)
+{
+	if (wpa_s->driver->set_legacy_rates)
+		return wpa_s->driver->set_legacy_rates(wpa_s->drv_priv);
+	return -1;
+}
+
 static inline struct wpa_scan_results * wpa_drv_get_scan_results2(
 	struct wpa_supplicant *wpa_s)
 {
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index 92b1137..09c9371 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -723,6 +723,12 @@  void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s,
 		wpa_s->normal_scans = 0;
 	}
 
+	/* If we are moving from DISCONNECTED to Scanning, then (re)set
+	 * the legacy ratemask, as it could have just been cleared as
+	 * the network device just bounced.
+	 */
+	wpa_drv_set_legacy_rates(wpa_s);
+
 	if (state == WPA_COMPLETED) {
 		wpas_connect_work_done(wpa_s);
 		/* Reinitialize normal_scan counter */
@@ -3375,6 +3381,8 @@  int wpa_supplicant_driver_init(struct wpa_supplicant *wpa_s)
 	wpa_dbg(wpa_s, MSG_DEBUG, "RSN: flushing PMKID list in the driver");
 	wpa_drv_flush_pmkid(wpa_s);
 
+	wpa_drv_set_legacy_rates(wpa_s);
+
 	wpa_s->prev_scan_ssid = WILDCARD_SSID_SCAN;
 	wpa_s->prev_scan_wildcard = 0;
 
diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf
index c738b3f..41c55ef 100644
--- a/wpa_supplicant/wpa_supplicant.conf
+++ b/wpa_supplicant/wpa_supplicant.conf
@@ -182,6 +182,11 @@  fast_reauth=1
 # format is specific to the selected driver interface. This field is not used
 # in most cases.
 #driver_param="field=value"
+#
+# In particular, nl80211 supports: use_monitor=1, force_connect_cmd=1,
+#        no_offchannel_tx=1, legacy_rates=[number]
+# For legacy_rates, the number is a bitfield where rate 1Mbps is first bit,
+# and 54Mbps is 12th bit.
 
 # Country code
 # The ISO/IEC alpha2 country code for the country in which this device is