Thursday, April 27, 2017

(finally) investigating how to get dynamic WDS (DWDS) working in FreeBSD!

I sat down recently to figure out how to get dynamic WDS working on FreeBSD-HEAD. It's been in FreeBSD since forever, and it in theory should actually have just worked, but it's extremely not documented in any useful way. It's basically the same technology in earlier Apple Airports (before it grew into what the wireless tech world calls "Proxy-STA") and is what the "extender" technology on Qualcomm Atheros chipsets implement.

A common question I get from people is "why can't I bridge multiple virtual machines on my laptop and have them show up over wifi? It works on ethernet!" And my response is "when I make dynamic WDS work, you can just make this work on FreeBSD devices - but for now, use NAT." That always makes people sad.

So what is it?

With normal station / access point setups, wireless frames have up to three addresses. In the header it's "address 1", "address 2", and "address 3".

Depending upon the packet type, these can be a variety of addresses:

  • SA - source address - source of the packet (eg the STA address)
  • TA - transmitter address - STA/AP that transmitted the frame
  • RA - receiver address - immediate destination of the packet 
  • DA - finally recipient of the data
  • BSSID - BSS ID (ie, AP mac address)
There are a lot of addresses. There are, in fact, more than the number of address fields in a normal 802.11 frame.

Now, if you want to understand when each of these are used in which frames, you can totally find blog posts from people which describe things (eg will fill you in. But the TL;DR for normal AP/STA traffic is:

  • From an AP, the frame has BSSID, SA of the MAC (eg ethernet behind the AP bridge) sending data, and DA is the STA MAC address
  • From a STA, the frame has BSSID, TA is the STA that transmits, and DA is the final destination of the frame (eg ethernet behind the AP bridge.)
The big note here is that there's not enough MAC addresses to say "please send this frame to a station MAC address, but then have them forward it to another MAC address attached behind it in a bridge." That would require 4 mac addresses in the 802.11 header, which we don't get.

.. except we do. There's a separate address format where from-DS and to-DS bits in the header set to 1, which means "this frame is coming from distribution system to a distribution system", and it has four mac addresses. The RA is then the AP you're sending to, and then a fourth field indicates the eventual destination address that may be an ethernet device connected behind said STA.

If you don't configure up WDS, then when you send frames from a station from a MAC address that isn't actually your 802.11 interface MAC address, the system would be confused. The STA wouldn't be able to transmit it easily, and the AP wouldn't know how to get back to your bridged ethernet addresses.

Ok, so how does this work with WDS? The above from/to-DS mode is actually the technical hilarity behind "Wireless Distribution System",  which is a fancy way of saying "an AP connects to another AP and can relay frames for you." It's what was used for extending wireless networks before true meshing solutions came into existence.

The original WDS was a statically configured thing. You'd configure up a particular device as a WDS extender, and both sides would need configuring:

  • The central AP would need to know the MAC address of a WDS master, so it would know that frames from/to that particular AP needed the four-address frame format, and
  • The extender AP would need to be configured to talk to the central AP to act as a WDS master - it would then associate as a station to that central AP, and would use 4-address frames to relay traffic to it.
 So for static configurations, this works great. You'd associate your extender AP as a station of the central AP, it'd use wpa_supplicant to setup encryption, then anything between that central AP and that extender AP (as a station) would be encrypted as normal station traffic (but, 4-address frame format.)

But that's not very convenient. You have to statically configure everything, including telling your central AP about all of your satellite extender APs. If you want to replace your central AP, you have to reprogram all of your extenders to use the new MAC addresses.

So, Sam Leffler came up with "dynamic WDS" - where you don't have to explicitly state the list of central/satellite APs. Instead, you configure a central AP as "dynamic WDS", and when a 4-address frame shows up from an associated station, it "promotes" it to a WDS peer for you. On the satellite AP, it will just find an AP to communicate to, and then assume it'll do WDS and start using 4-address frames. It's still a bit clunky (there's no beacon, probe request, etc IEs that say "I do dynamic WDS!" so you'd better make ALL your central APs a different SSID!) but it certainly is better than what we had.

(Yes, one of the things I'll be doing to FreeBSD now that this works is adding that concept of "I'm a DWDS primary!" node concept so satellites can just "find" a DWDS primary enabled AP to associate to. Baby steps..)

But, I tried it - and ... let's just say, the documentation didn't say very much. So I couldn't really get it to work.

Then a friend pointed out he figured it out. (Thankyou Edward!)

Firstly, there are scripts in src/tools/tools/net80211/ - setup.wdsmain and setup.wdsrelay. These scripts are .. well, the almost complete documentation on a dynamic WDS setup. The manpage doesn't go into anywhere near enough information.

So I dug into it. It turns out that dynamic WDS uses a helper daemon - 'wlanwds' -  which listens for dynamic WDS configuration changes and will do things for you. This is what runs on the central AP side. Then it started making sense!
  • For dynamic WDS, there are no WDS interface types created by default
  • net80211 will post a routing socket message if a 4-address frame shows up on a "dwds" enabled interface, which is the signal to userland to plumb up a DWDS interface for that particular peer
  • wlanwds is then responsible for creating that virtual interface with the right configuration
  • Then it runs a shell script that you provide which lets you do things like assign it to a bridge group so it can bridge traffic
  • Finally, if the station goes away, wlanwds will get another notification from net80211 saying the station has gone, and wlanwds will destroy the virtual interface for that peer.
So far, so good. I followed that script, modified it a bit to use encryption, and .. well, it half worked. Association worked fine, but no traffic was passing.

A little tcpdump'ing later showed what was going on!
  • 4-address frames from the extender side was successfully being encrypted and transmitted to the central AP
  • 4-address frames from the central AP were being send, but unencrypted
  • .. so the station dropped them as well, unencrypted when they should've been encrypted.
A little more digging showed the actual problem - the dynamic WDS example scripts are for an open/unencrypted network. If you are using an encrypted network, the central AP side needs to enable privacy on the virtual interfaces so traffic gets encrypted with the parent interface encryption keys. So adding this:

ifconfig $DEV wepmode mixed

.. to the shell script for when an interface was created made everything work.

Now, I've only done enough testing to show that indeed it is working. I haven't done anything like pass lots of traffic via iperf, or have a mix of DWDS and normal STA peers, nor actually run it for longer than 5 minutes. I'm sure there will be issues to fix. However - I do need it at home, as I can't see the home AP from the upstairs room (and now you see why I care about DWDS!) and so when I start using it daily I'll fix whatever hilarity ensues.


  1. Hello.
    Very interesting post.
    Can you give us more detailed how-to?
    Maybe some useful links?
    I just want to make my home router wireless and wired networks to be bridged, not routed.
    Where can I find this: "helper daemon - 'wlanwds'"?

    Thank You for clearing things.

    1. it's in tools/net80211/net80211!

    2. Thank You. I found it: /usr/src/tools/tools/net80211/wlanwds