home WiFi setup

last modified on

There are two Access Points (APs) in my house: one in the living room, and one in the kitchen. They are connected via LAN-over-powerline, and have WiFi Roaming set up for a seamless connection throughout.

internetinternetrouterrouterinternet--routerkitchenkitchenlaptoplaptopkitchen--laptopWiFilightbulblightbulbkitchen--lightbulbWiFirouter--kitchenpowerlinerouter--laptopWiFi
contents…

Roaming

WiFi Roaming allows a client device (e.g. a phone) to transparently switch from one AP to another. Unlike “repeaters” or “boosters”, which create a second WiFi network that the client needs to disconnect from and reconnect to, Roaming is the same network across APs and allows for a seamless experience.

This is implemented with two extensions of the WiFi spec, 802.11i and 802.11r:

How does 802.11r actually work?

In a given roaming connection, there are 3 keys:

There are 3 players:

In “pull mode”:

  1. The client connects to the first AP.
  2. The client is given a key and an R0KH-ID (the NAS Identifier, named for RADIUS’ Network Access Server).
  3. The client connects to the second AP.
  4. The client gives R0KH-ID to the second AP.
  5. The second AP, now R1KH, contacts R0KH using the ID and its own R1KH-ID.
  6. R0KH sends R1KH a key R1 derived from R0 and R1KH-ID.
  7. R1KH, gives the client R1.

In “push mode”:

  1. R0KH knows all its APs.
  2. R0KH sends each AP a unique R1 derived from R0 and the AP’s R1KH-ID.
  3. the client connects normally.
For PSK mode, the MSK is the PSK, so any AP can generate R0 and R1 for any NAS Identifier.

Router configuration

My router is a PC Engines APU 1d4, which has:

One of the ethernet ports is for WAN, and the other two and wifi card are LAN. The LAN interfaces are joined with a bridge, br0.

WAN

This is a fairly normal systemd-networkd client config, with the addition of IPForward=yes:

$ cat /etc/systemd/network/wan.network
[Match]
# enp1s0 is the name of my router's WAN interface.
Name=enp1s0

[Network]
# take a IP from my ISP's modem over DHCP.
DHCP=yes

# use Google's public DNS servers.
DNS=8.8.8.8
DNS=8.8.4.4

# prefer DNS-over-TLS if available.
DNSOverTLS=opportunistic

# forward packets from this interface to other interfaces.
IPForward=yes

[DHCP]
# some ISPs' DNS invent A records, so don't use them.
UseDNS=no

LAN

The LAN is controlled with systemd-networkd where possible, and the wifi is managed by hostapd. There are three parts to the networkd configuration:

First, create a bridge br0:

$ cat /etc/systemd/network/bridge.netdev
[NetDev]
Name=br0
Kind=bridge

Connect the LAN ethernet ports to the bridge:

$ cat /etc/systemd/network/lan.network
[Match]
Name=enp2s0 enp3s0

[Network]
Bridge=br0

Finally, configure the local network on the bridge itself.

$ cat /etc/systemd/network/bridge.network
[Match]
Name=br0

[Network]
# the router's LAN IP.
Address=192.168.16.1/24

# run a DHCP server.
DHCPServer=yes

# forward packets from this interface to other interfaces.
IPForward=yes

# make forwarded packets appear to come from this device,
# a.k.a. enable NAT.
IPMasquerade=yes

[DHCPServer]
DefaultLeaseTimeSec=600

# LAN DNS & Stubby DNS-over-TLS bridge.
DNS=192.168.16.1

# fallback DNS.
DNS=8.8.8.8
DNS=8.8.4.4

WiFi

Because hostapd has its own bridge management, it isn’t included in the systemd-networkd configuration.

This configuration:

$ cat /etc/hostapd/hostapd.conf
bridge=br0
interface=wlan0
driver=nl80211
ssid=something-witty

# select a channel automatically
# using the ACS survey-based algorithm,
# instead of setting a channel manually.
channel=0
country_code=GB
ieee80211d=1

# hw_mode=g for 802.11n, hw_mode=a for 802.11ac.
hw_mode=g

# 802.11n support.
ieee80211n=1
wmm_enabled=1

# WPA2 encryption settings.
# note that wpa_key_mgmt also has FT-PSK for 802.11r.
wpa=2
wpa_passphrase=something-secret
wpa_key_mgmt=WPA-PSK FT-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP

# 802.11i support.
rsn_preauth=1
rsn_preauth_interfaces=br0

# 802.11r support.
# mobility_domain must be shared across APs.
# nas_identifier must be different between APs.
mobility_domain=19fc
nas_identifier=8e71540f0467
ft_psk_generate_local=1

Kitchen

The WiFi in the kitchen is provided by a Raspberry Pi 2B and a USB WiFi adapter that supports host mode. I chose this hardware because I already owned it.

LAN

This is a simpler version of the Router’s LAN configuration, but with only two steps:

As before, create a bridge br0:

$ cat /etc/systemd/network/bridge.netdev
[NetDev]
Name=br0
Kind=bridge

Connect the ethernet port to the bridge:

$ cat /etc/systemd/network/wired.network
[Match]
Name=eth0

[Network]
Bridge=br0

Configure the local network on the bridge itself. Because this is not the primary router, it can be just another DHCP client:

$ cat /etc/systemd/network/bridge.network
[Match]
Name=br0

[Network]
DHCP=yes
DNSOverTLS=opportunistic

WiFi

This is basically the same as the Router’s WiFi configuration, although remember to change the nas_identifier!