|
| 1 | +--- |
| 2 | +title: Seamless Wi-Fi with Mesh Backhaul and Roaming |
| 3 | +author: mattiaswal |
| 4 | +date: 2026-07-02 08:00:00 +0200 |
| 5 | +categories: [howto] |
| 6 | +tags: [wifi, mesh, roaming, networking] |
| 7 | +image: |
| 8 | + path: /assets/img/wifi-mesh-roaming-hero.svg |
| 9 | + alt: Seamless Wi-Fi with 802.11s mesh backhaul and 802.11r/k/v fast roaming |
| 10 | + show_in_post: false |
| 11 | +--- |
| 12 | + |
| 13 | +Covering a building or a yard with Wi-Fi usually means several access |
| 14 | +points sharing one network name, so a laptop or a phone keeps working as |
| 15 | +it moves between them. Two problems show up quickly: |
| 16 | + |
| 17 | +1. **Getting the network to every AP.** Pulling Ethernet to each AP is the |
| 18 | + reliable way, but it is not always possible. Where you cannot run a |
| 19 | + cable, a wireless backhaul has to carry the traffic instead. |
| 20 | +2. **Moving clients between APs without dropping them.** A client that has |
| 21 | + to fully re-authenticate every time it changes AP will stutter on voice |
| 22 | + calls and long downloads. Fast roaming closes that gap. |
| 23 | + |
| 24 | +Infix [**v26.06**][release] adds both pieces: 802.11s mesh for the |
| 25 | +wireless backhaul, and 802.11r/k/v for fast roaming. Basic access point |
| 26 | +support arrived in v26.01; this release is what ties several APs into |
| 27 | +one network. The rest of this post builds a three-node network with |
| 28 | +them and explains why each part is configured the way it is. At the |
| 29 | +end we look at the automated test that verifies the setup. |
| 30 | + |
| 31 | +The [Wi-Fi documentation][docs] has the reference material behind each |
| 32 | +setting: bands, channels, security modes, and the full list of roaming |
| 33 | +knobs. This post is the worked example. |
| 34 | + |
| 35 | +## The design |
| 36 | + |
| 37 | +Three nodes cover the area. Each node does two jobs at once, and that is |
| 38 | +why each one needs two radios: |
| 39 | + |
| 40 | +- **radio0 (mesh backhaul).** All three nodes join one 802.11s mesh on |
| 41 | + 5 GHz. The mesh carries traffic between the nodes, so only one node needs |
| 42 | + a wire back to the rest of the LAN. The others reach it over the air. |
| 43 | +- **radio1 (client access).** Each node runs an access point on 2.4 GHz |
| 44 | + with the same SSID and the same passphrase. This is the network clients |
| 45 | + actually see and roam across. |
| 46 | + |
| 47 | +{: width="800" } |
| 48 | +_Three nodes bridge a 5 GHz mesh backhaul and a 2.4 GHz roaming AP; only gw1 has a wire._ |
| 49 | + |
| 50 | +Keeping backhaul and access on different bands matters. If the mesh and |
| 51 | +the AP shared one radio they would also share one channel and take turns on |
| 52 | +the air, and every client frame would compete with backhaul traffic. |
| 53 | +Splitting them, 5 GHz for the mesh and 2.4 GHz for clients, lets both run |
| 54 | +at full tilt, and the wider 5 GHz channels suit the backhaul where the |
| 55 | +throughput adds up. |
| 56 | + |
| 57 | +A bridge (`br0`) on each node ties the mesh interface, the AP interface, |
| 58 | +and (on the wired node) the uplink into one layer-2 segment. With mesh |
| 59 | +forwarding on, the mesh acts as a transparent backhaul: a client attached |
| 60 | +to gw3's AP sits on the same LAN as something plugged into gw1, and the |
| 61 | +frames cross the mesh without anyone configuring a route. |
| 62 | + |
| 63 | +> A mesh point and an access point cannot share a radio; Infix rejects |
| 64 | +> that combination. Plan on two radios per node. |
| 65 | +{: .prompt-info } |
| 66 | + |
| 67 | +## Building it |
| 68 | + |
| 69 | +The three nodes are configured almost identically. Only gw1 carries the |
| 70 | +wired uplink; gw2 and gw3 are reachable purely over the mesh. The examples |
| 71 | +below show gw1 in full, then the one difference for the others. |
| 72 | + |
| 73 | +### 1. The shared secrets |
| 74 | + |
| 75 | +Every node needs two keystore entries: one passphrase for the mesh (all |
| 76 | +mesh links use WPA3-SAE) and one for the client AP. Use the same two |
| 77 | +secrets on all three nodes. |
| 78 | + |
| 79 | +```console |
| 80 | +admin@gw1:/> configure |
| 81 | +admin@gw1:/config/> edit keystore symmetric-key mesh-secret |
| 82 | +admin@gw1:/config/keystore/.../mesh-secret/> set key-format passphrase-key-format |
| 83 | +admin@gw1:/config/keystore/.../mesh-secret/> change cleartext-symmetric-key |
| 84 | +Passphrase: ************ |
| 85 | +Retype passphrase: ************ |
| 86 | +admin@gw1:/config/keystore/.../mesh-secret/> end |
| 87 | +admin@gw1:/config/> edit keystore symmetric-key wifi-secret |
| 88 | +admin@gw1:/config/keystore/.../wifi-secret/> set key-format passphrase-key-format |
| 89 | +admin@gw1:/config/keystore/.../wifi-secret/> change cleartext-symmetric-key |
| 90 | +Passphrase: ************ |
| 91 | +Retype passphrase: ************ |
| 92 | +admin@gw1:/config/keystore/.../wifi-secret/> leave |
| 93 | +``` |
| 94 | + |
| 95 | +### 2. The radios |
| 96 | + |
| 97 | +radio0 carries the mesh, so it needs a band, a channel, and a real country |
| 98 | +code. The mesh refuses to start without them. radio1 carries the AP on |
| 99 | +2.4 GHz. |
| 100 | + |
| 101 | +```console |
| 102 | +admin@gw1:/> configure |
| 103 | +admin@gw1:/config/> edit hardware component radio0 wifi-radio |
| 104 | +admin@gw1:/config/hardware/component/radio0/wifi-radio/> set country-code SE |
| 105 | +admin@gw1:/config/hardware/component/radio0/wifi-radio/> set band 5GHz |
| 106 | +admin@gw1:/config/hardware/component/radio0/wifi-radio/> set channel 36 |
| 107 | +admin@gw1:/config/hardware/component/radio0/wifi-radio/> end |
| 108 | +admin@gw1:/config/> edit hardware component radio1 wifi-radio |
| 109 | +admin@gw1:/config/hardware/component/radio1/wifi-radio/> set country-code SE |
| 110 | +admin@gw1:/config/hardware/component/radio1/wifi-radio/> set band 2.4GHz |
| 111 | +admin@gw1:/config/hardware/component/radio1/wifi-radio/> set channel 1 |
| 112 | +admin@gw1:/config/hardware/component/radio1/wifi-radio/> leave |
| 113 | +``` |
| 114 | + |
| 115 | +All three nodes use the same mesh channel (36), since mesh peers must meet |
| 116 | +on one channel, and the same AP channel (1), so a roaming client does not |
| 117 | +have to change channel as it moves. |
| 118 | + |
| 119 | +### 3. The mesh interface |
| 120 | + |
| 121 | +`wifi0` rides on radio0 as the 802.11s mesh point. The `mesh-id` is the |
| 122 | +mesh equivalent of an SSID: every node that should join uses the same one. |
| 123 | +Leaving `forwarding` at its default (`true`) is what lets the mesh be |
| 124 | +bridged. |
| 125 | + |
| 126 | +```console |
| 127 | +admin@gw1:/config/> edit interface wifi0 |
| 128 | +admin@gw1:/config/interface/wifi0/> set wifi radio radio0 |
| 129 | +admin@gw1:/config/interface/wifi0/> set wifi mesh-point mesh-id backhaul |
| 130 | +admin@gw1:/config/interface/wifi0/> set wifi mesh-point security secret mesh-secret |
| 131 | +admin@gw1:/config/interface/wifi0/> leave |
| 132 | +``` |
| 133 | + |
| 134 | +### 4. The access point, with roaming |
| 135 | + |
| 136 | +`wifi1` rides on radio1 as the client-facing AP. The roaming settings are |
| 137 | +the point of this step. All three APs must agree on the SSID, the |
| 138 | +passphrase, and the mobility domain. That trio is what tells a client the |
| 139 | +three APs are one roaming network rather than three unrelated ones. |
| 140 | + |
| 141 | +Using `mobility-domain hash` derives the domain from the SSID, so the three |
| 142 | +APs land on the same domain without anyone copying a hex value around. |
| 143 | + |
| 144 | +```console |
| 145 | +admin@gw1:/config/> edit interface wifi1 |
| 146 | +admin@gw1:/config/interface/wifi1/> set wifi radio radio1 |
| 147 | +admin@gw1:/config/interface/wifi1/> set wifi access-point ssid campus |
| 148 | +admin@gw1:/config/interface/wifi1/> set wifi access-point security mode wpa2-wpa3-personal |
| 149 | +admin@gw1:/config/interface/wifi1/> set wifi access-point security secret wifi-secret |
| 150 | +admin@gw1:/config/interface/wifi1/> set wifi access-point roaming dot11r |
| 151 | +admin@gw1:/config/interface/wifi1/> set wifi access-point roaming dot11r mobility-domain hash |
| 152 | +admin@gw1:/config/interface/wifi1/> set wifi access-point roaming dot11k |
| 153 | +admin@gw1:/config/interface/wifi1/> set wifi access-point roaming dot11v |
| 154 | +admin@gw1:/config/interface/wifi1/> leave |
| 155 | +``` |
| 156 | + |
| 157 | +The three roaming features do different jobs: |
| 158 | + |
| 159 | +- **dot11r** (Fast BSS Transition) is the one that makes the handoff quick. |
| 160 | + It pre-shares the keys so a roaming client skips the full WPA handshake, |
| 161 | + cutting the gap from roughly a second to under 50 ms. |
| 162 | +- **dot11k** (Radio Resource Management) hands the client a neighbour list, |
| 163 | + so it already knows which APs to look at instead of scanning blindly. |
| 164 | +- **dot11v** (BSS Transition Management) lets an AP nudge a client toward a |
| 165 | + better one. |
| 166 | + |
| 167 | +### 5. The bridge |
| 168 | + |
| 169 | +`br0` joins the mesh and the AP into one segment. On gw1 the wired uplink |
| 170 | +joins too, which is what gives the other nodes a path to the rest of the |
| 171 | +LAN. |
| 172 | + |
| 173 | +```console |
| 174 | +admin@gw1:/config/> edit interface br0 |
| 175 | +admin@gw1:/config/interface/br0/> set type bridge |
| 176 | +admin@gw1:/config/interface/br0/> end |
| 177 | +admin@gw1:/config/> set interface wifi0 bridge-port bridge br0 |
| 178 | +admin@gw1:/config/> set interface wifi1 bridge-port bridge br0 |
| 179 | +admin@gw1:/config/> set interface eth0 bridge-port bridge br0 |
| 180 | +admin@gw1:/config/> leave |
| 181 | +``` |
| 182 | + |
| 183 | +gw2 and gw3 are identical except they have no wired uplink, so drop the |
| 184 | +`eth0` line. Their br0 carries only `wifi0` (mesh) and `wifi1` (AP), and |
| 185 | +they reach the LAN over the mesh. |
| 186 | + |
| 187 | +## Checking it works |
| 188 | + |
| 189 | +**Did the mesh form?** Each node should list the other two as peers: |
| 190 | + |
| 191 | +```console |
| 192 | +admin@gw1:/> show interface wifi0 |
| 193 | +... |
| 194 | +mode : mesh-point |
| 195 | +mesh id : backhaul |
| 196 | +peers : 2 |
| 197 | +``` |
| 198 | + |
| 199 | +**Is a client associated, and where?** The AP lists the clients on it. As |
| 200 | +a client roams, it disappears from one node's list and shows up on another: |
| 201 | + |
| 202 | +```console |
| 203 | +admin@gw2:/> show interface wifi1 |
| 204 | +... |
| 205 | +mode : access-point |
| 206 | +ssid : campus |
| 207 | +stations : 1 |
| 208 | +``` |
| 209 | + |
| 210 | +**Does traffic actually cross the mesh?** Attach a client to gw3's AP (the |
| 211 | +node with no wire) and reach something on the wired LAN behind gw1. If |
| 212 | +that works, the mesh is carrying the traffic. |
| 213 | + |
| 214 | +## How we keep it working |
| 215 | + |
| 216 | +A worked example that only ever ran by hand tends to rot. This one has an |
| 217 | +automated counterpart in the Infix regression test suite: |
| 218 | + |
| 219 | + test/case/interfaces/wifi_mesh_roaming/ |
| 220 | + |
| 221 | +It brings up three nodes exactly as above (mesh backhaul on radio0, a |
| 222 | +roaming AP on radio1, all bridged) plus a fourth node as the client. It |
| 223 | +then checks the claims this post makes: |
| 224 | + |
| 225 | +- the three nodes form a mesh (each sees its peers); |
| 226 | +- a client associates to the `campus` SSID; |
| 227 | +- traffic reaches the client across the mesh backhaul; |
| 228 | +- when the AP the client is on goes away, the client roams to another node |
| 229 | + carrying the same SSID and mobility domain, and traffic recovers. |
| 230 | + |
| 231 | +In simulation every radio hears every other at the same strength, so there |
| 232 | +is no signal gradient to drift a client from one AP to the next. The test |
| 233 | +forces the move instead. It takes down the AP the client is on and |
| 234 | +confirms the client lands on another one and keeps talking. That is a |
| 235 | +harsher check than a gentle signal-based drift: it proves the second AP |
| 236 | +accepts the client and the backhaul re-converges, with no human in the |
| 237 | +loop. |
| 238 | + |
| 239 | +The virtual radios are `mac80211_hwsim`, and a small relay, `wifimedium`, |
| 240 | +carries frames between the separate guests so they can hear each other. |
| 241 | +On real hardware none of that exists. The radios use the air, and the |
| 242 | +same configuration applies unchanged. |
| 243 | + |
| 244 | +[docs]: https://kernelkit.org/infix/latest/wifi/ |
| 245 | +[release]: https://github.com/kernelkit/infix/releases/tag/v26.06.0 |
0 commit comments