where the flamingcow roams

WiFi bridging redux

I previously wrote about building WiFi client routers instead of bridges; they get you broadcast domain isolation and a degree of conceptual simplicity (no L2 tricks). I finally ran into a requirement on a different project to build an actual bridge; here’s how I did it.

You can copy the hardware from the router post, or use what you’ve got; I don’t believe this is driver-specific.

Your access point, however, does require support for this to work. It needs to:

Bridging

Linux supports bridging. There’s a bridge-utils package in Ubuntu with the tools you need:

sudo apt-get install bridge-utils

To see current bridges, run:

brcrl show

However, you can’t normally add a WiFi interface to a bridge:

$ sudo brctl addif br0 eth0 wlan0
can't add wlan0 to bridge br0: Operation not supported

Googling this error produces a wide range of well-meaning yet completely unhelpful results.

Enable 4 address mode

To be able to add a WiFi interface to a bridge, you have to put it into 4-address mode first:

# If necessary:
# sudo apt-get install iw
sudo iw dev wlan0 set 4addr on

From that point forward, the interface won’t be able to talk normally, and wpa_supplicant is likely to time out in the association phase (run “sudo wpa_cli” to watch its logs).

Now add the interface to a bridge:

sudo brctl addif br0 wlan0

You should now be able to fetch an IP on br0 via DHCP. Unless, of course, you need wpa_supplicant to work…

wpa_supplicant

wpa_supplicant needs to be bridge-aware to work with 4-address mode. Fortunately, it has a flag (-b) to set the bridge interface. Unfortunately, this flag is broken in 2.1, the version in Ubuntu Trusty. I verified that it works in wpa_supplicant 2.5 built from source; I haven’t verified 2.4 from Xenial.

A wpa_supplicant commandline looks something like:

wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf -C /var/run/wpa_supplicant -P /var/run/wpa_supplicant.wlan0.pid -b br0

With that working, the interface should get to wpa_state=COMPLETED, and br0 should work normally. Remember that wlan0 will still be unusable directly.

Ordering

Bringing up these interfaces is tricky; the ordering is annoying.

Putting it together

Because of the ordering issues, it’s easier to treat this all as one interface that has to come up together. Here’s an example interface stanza that does this:

auto br0
iface br0 inet dhcp
  pre-up /sbin/iw dev wlan0 set 4addr on
  pre-up /sbin/brctl addbr $IFACE || true
  pre-up /sbin/start-stop-daemon --start --pidfile=/var/run/wpa_supplicant.wlan0.pid --exec=/usr/local/sbin/wpa_supplicant --user=root -- -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf -C /var/run/wpa_supplicant -P /var/run/wpa_supplicant.wlan0.pid -b $IFACE
  bridge_ports eth0 eth1 wlan0
  post-down /sbin/start-stop-daemon --stop --pidfile=/var/run/wpa_supplicant.wlan0.pid --exec=/usr/local/sbin/wpa_supplicant --user=root
  post-down /sbin/iw dev wlan0 set 4addr off

We use start-stop-daemon because it provides idempotence and safety from stale PID files.