Skip to content

Commit 704c6cd

Browse files
committed
Add simulated band steering post
1 parent 367e257 commit 704c6cd

File tree

3 files changed

+276
-0
lines changed

3 files changed

+276
-0
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
+++
2+
title = "Simulated WiFi Band Steering"
3+
date = "2025-11-01"
4+
description = ""
5+
path = "blog/simulated-band-steering"
6+
7+
draft = true
8+
9+
#[taxonomies]
10+
#tags = ["linux", "wifi"]
11+
+++
12+
13+
## Overview
14+
15+
In this post, I'll provide some background on and demonstrate how to simulate WiFi band steering. We'll use open source
16+
Linux tools that are used in commercial WiFi products. _No physical WiFi radios are required!_
17+
18+
If you'd like to replicate this yourself, you'll need a Linux system with `sudo` access, kernel support for the `mac80211_hwsim` driver
19+
(which is generally the case for most Linux distributions), and `hostapd` and `wpa_supplicant` installed. These tools will permit
20+
complete WiFi simulation in software. However, the same setup will also work with real WiFi radios. This can also be run within
21+
a container or virtual machine.
22+
23+
This setup will require some experience using Linux, Wireshark, and WiFi. There are plenty of great resources out there.
24+
I maintain a reference of Linux commands I use often [here](@/blog/2023-12-linux-cmds.md). Additionally, if you'd like to (responsibly) do real
25+
WiFi packet capture with Wireshark, I wrote a guide which is available [here](@/blog/2023-10-wifi-packet-capture.md).
26+
27+
## Background
28+
29+
Before we dive in, I'd like to first cover some background WiFi information, especially as it relates to band steering. If you're familiar already, skip to the next section.
30+
31+
In WiFi, clients select and connect to the WiFi access point (AP) BSSID they want. Each client operates slightly differently, but metrics like channel utilization and estimated throughput strongly influence this decision. As a client operates and possibly moves around in its environment, conditions will change. The client may decide to shift to another channel on the same AP (different BSSID), it may roam to another AP, or it may do something else entirely! These decisions are ultimately left up to the client, not the AP.
32+
33+
Given this, an AP is left with limited options to influence the client decisions. An AP can reject the client's authentication/association request or send a deauthentication/disassociation. However, these methods are heavy handed and may result in poor user experience.
34+
35+
Several amendments to the WiFi 802.11 standard serve to address this and to improve user experience generally. Amendments 802.11k (neighbor reports), 802.11r (fast transition), and 802.11v (BSS transition management) are a couple widely known examples. For the purposes of this post, we'll focus on 802.11k and 802.11v, which introduce neighbor reports and BSS transition management (BTM), respectively. Both are essential for band steering, whether it be simulated or real-world.
36+
37+
If you'd like to brush up on band steering or the amendments I mentioned, check out my colleague Isaac Konikoff's talk on band steering fails [here](https://www.youtube.com/watch?v=X5ffNbd5Duw) and this Cisco 802.11v explainer available [here](https://web.archive.org/web/20230815035221/https://www.cisco.com/c/en/us/td/docs/wireless/controller/9800/config-guide/b_wl_16_10_cg/802-11v.pdf). For more in-depth coverage, check out my colleague Sitarama Penumetsa's lecture [here](https://youtu.be/BiktVCtMGnk?si=c6LDYNyg1qJ_DqQ8&t=2002) from his WiFi fundamentals course.
38+
39+
With this all in mind, we're ready to setup for testing!
40+
41+
## Test Setup
42+
43+
In order to simulate band steering, we must first configure our test environment. While this same configuration is possible with
44+
real radios, we'll instead use the `mac80211_hwsim` Linux driver to simulate this testing entirely in software.
45+
46+
### Recommendations
47+
48+
To make your life easier, I recommend the following:
49+
50+
- **Use three terminals**
51+
52+
- One terminal each for AP, client, and configuration CLI
53+
54+
- This will permit reading logs while we setup and run the test
55+
56+
- Now may be a good time to learn how to use `tmux` or `screen`
57+
58+
- It's possible to setup this test with `wpa_supplicant` and `hostapd` daemonized (`-B` argument), but I would only recommend it for advanced users
59+
60+
- **Disconnect from your WiFi (if present) while running this**
61+
62+
- When running with verbose logging, `wpa_supplicant` and `hostapd` will output information on any changes to any interface
63+
on the system. Verbose logs can especially become busy on a normal system, as they will log periodic scans run by
64+
your networking daemon for any existing WiFi interface.
65+
66+
- Make sure **not** to run `nmcli radio wifi off`, as this will enable rfkill which soft blocks WiFi on all interfaces
67+
(including the `mac80211_hwsim` ones)
68+
69+
### Instructions
70+
71+
**NOTE:** Many commands in this post will require root permissions (e.g. run with `sudo`), including `hostapd`, `wpa_supplicant`, `modprobe`.
72+
73+
1. **Load the `mac80211_hwsim` kernel module (driver)**
74+
75+
```Bash
76+
# Three simulated radios are needed
77+
# 1. Client (STA)
78+
# 2. AP 2.4 GHz BSSID
79+
# 3. AP 5 GHz BSSID
80+
modprobe mac80211_hwsim radios=3 support_p2p_device=0
81+
```
82+
83+
1. **Identify simulated WiFi interfaces and their MAC addresses**
84+
85+
**Most likely the `mac80211_hwsim` interfaces will be named `wlan0`, `wlan1`, and `wlan2`**. The driver will additionally
86+
create another interface named `hwsim0`, which permits packet capture of simulated WiFi traffic.
87+
88+
To identify the interfaces and corresponding MAC addresses, use the `ip -br addr show` and `ip -br link show` commands.
89+
First check the MAC address. The driver creates the interfaces using the format `02:00:00:00:XX:00`, where the `XX`
90+
matches the simulated radio number, which is easy to identify.
91+
92+
If you're encountering issues or want to know more, check out [this section](#identifying-interfaces-and-macs-cont).
93+
94+
1. **Configure your network daemon to ignore the interfaces**
95+
96+
Practically all modern workstation Linux distributions ship with NetworkManager. If you're using something else, I assume you know
97+
what you're doing or can figure it out...
98+
99+
When using NetworkManager, we must configure it to ignore the new `mac80211_hwsim` interfaces, which you can do with the
100+
following command.
101+
102+
```Bash
103+
# Run this for each simulated WiFi interface, substituting in
104+
# your system's interface names for this and subsequent commands
105+
nmcli device set wlan0 managed false
106+
```
107+
108+
When complete, the interfaces should show as `unmanaged` in the output of `nmcli device status` (or `nmcli device` for short),
109+
as shown below. You may still see some unexpected traffic (e.g. ARP, ICMPv6, DHCP) and the interfaces may even autoconfigure
110+
with a link-local IPv4 address. However, we configured NetworkManager to not control the WiFi interfaces, which it should do.
111+
112+
1. **Download example `hostapd` and `wpa_supplicant` configs from [here](https://codeberg.org/a-gavin/hostap-confs)**
113+
114+
I recommend using the open (no security) configuration, as this will make packet capture much more simple.
115+
With configurations supporting encryption, packet decryption is possible, but security improvements like 802.11w
116+
and WPA3 make this increasingly difficult (which is good for security!).
117+
118+
```Bash
119+
git clone https://codeberg.org/a-gavin/hostap-confs.git
120+
```
121+
122+
1. **Start the AP interfaces**
123+
124+
```Bash
125+
# Options used are:
126+
# -t: Print timestamps
127+
# -i: Specify interface to run AP with. Can be specified multiple times.
128+
#
129+
# For verbose or very verbose logging, add the '-d' or '-dd' options
130+
hostapd -t -i wlan0 -i wlan1 hostapd_2.4GHz-open.conf hostapd_5GHz-open.conf
131+
```
132+
133+
Assuming no errors appear in the logs, you can verify the AP interface channel/frequency, SSID, and MAC
134+
with the `iw wlan0 info` command.
135+
136+
1. **Enable `mac80211_hwsim` packet capture interface**
137+
138+
This interface is likely named `hwsim0`.
139+
140+
```Bash
141+
ip link set up dev hwsim0
142+
```
143+
144+
1. **Run Wireshark using the `mac80211_hwsim` packet capture interface**
145+
146+
Packet capture requires root permissions. See the Wireshark documentation [here](https://wiki.wireshark.org/capturesetup/captureprivileges#gnulinux-distributions-wireshark-is-installed-using-a-package-manager) for more information on how to enable
147+
this.
148+
149+
1. **Start the client interface**
150+
151+
```Bash
152+
# Similar options and requirements as 'hostapd'
153+
# The '-c' option required to specify the config file
154+
wpa_supplicant -t -i wlan2 -c supplicant_open.conf
155+
```
156+
157+
1. **Wait for client to connect**
158+
159+
As the client connects, you should see start to see the simulated WiFi traffic in Wireshark. The simulated client will
160+
connect through the normal WiFi client connection process (use the `wlan.fc.type_subtype != 8` filter to filter out beacons).
161+
Connection is complete once the AP responds with a successful association response frame.
162+
163+
Once the client connects, you should see the message `CTRL-EVENT-CONNECTED` in the station logs and `AP-STA-CONNECTED` in
164+
the AP logs. Take note of the AP interface that the client connected to. As with the AP interfaces, you can run the
165+
`iw wlan2 info` command to verify connection, channel, SSID, BSSID, etc.
166+
167+
1. **Connect to the `hostapd` CLI interface**
168+
169+
In another terminal, run the following command. This will enter a the `hostapd` CLI interface which may
170+
or may not permit line editing and/or command history, depending on your system.
171+
172+
```Bash
173+
# The '-p' option isn't necessary if using the example configs, as they use
174+
# the default control interface path
175+
hostapd_cli -p /var/run/hostapd/
176+
```
177+
178+
1. **Select the AP interface to which the client connected**
179+
180+
Using the `interface` command, select the desired interface. Then, check the list of connected clients
181+
with the `list_sta` command, as shown below.
182+
183+
```Bash
184+
$ hostapd_cli -p /var/run/hostapd/
185+
...
186+
> interface wlan0
187+
> list_sta <---- No clients associated, check the other AP interface
188+
> interface wlan1
189+
> list_sta
190+
02:00:00:00:02:00
191+
```
192+
193+
With the simulated client connected, Wireshark running, and the `hostapd` CLI up, we're ready to run some tests!
194+
195+
## Tests
196+
197+
At this point, with a client connected and two APs running, there's quite a few tests we can run. In the following
198+
sections, though, we'll keep it straightforward and run a couple of band steering tests triggered by the AP as well
199+
as demonstrate a couple things a client can do as well.
200+
201+
### Simulated Band Steering
202+
203+
To start, we'll first attempt to steer the client from its currently associated AP BSSID to the other AP BSSID
204+
using the `hostapd` CLI `bss_tm_req` command. This aims to simulate a real AP requesting a client transition.
205+
206+
This type of interaction is often seen as client signal reduces, for example when the client moves away from the AP.
207+
The AP monitors this and suggests the client transition to a BSSID with better signal (e.g. from 6 GHz to 5 GHz).
208+
Metrics like signal to noise ratio (SNR) are often used for this.
209+
210+
Using the `hostapd` CLI interface we configured earlier, we'll craft a `bss_tm_req` command which instructs the AP to transmit
211+
a BTM request frame to the client. With the example configs used here, the client should respond and steer successfully,
212+
assuming the command was configured correctly. We can also simulate a client which doesn't support BTM requests by
213+
setting the `disable_btm=1` config option (outside the network block) and reconnecting, but that's left as an
214+
exercise to the reader.
215+
216+
For this example, the following commands will generate BTM requests to steer the client from either AP to the other.
217+
Adjust as needed. More information on these commands can be found in [this section](#cli-commands).
218+
219+
```Bash
220+
# From 5 GHz to 2.4 GHz
221+
# First MAC is client MAC. Second MAC (neighbor) is 2.4 GHz BSSID.
222+
bss_tm_req 02:00:00:00:02:00 pref=1 neighbor=02:00:00:00:00:00,0x0000008f,81,1,14
223+
224+
# From 2.4 GHz to 5 GHz
225+
# First MAC is client MAC. Second MAC (neighbor) is 5 GHz BSSID.
226+
bss_tm_req 02:00:00:00:02:00 pref=1 neighbor=02:00:00:00:01:00,0x0000008f,81,36,14
227+
```
228+
229+
Assuming this ran okay, you should see 'OK' in the CLI and a successful BTM request and response exchange in Wireshark as shown below.
230+
231+
{{ image(src="/blog/2025-11-simulated-band-steering/btm_request_success_5ghz_to_2.4ghz.png", caption="BTM Request to downsteer from 5 GHz to 2.4 GHz BSSID", alt="BTM Request to downsteer from 5 GHz to 2.4 GHz BSSID") }}
232+
233+
In the case that the command failed to parse, you'll see a 'FAIL' in the CLI. However, if the command succeeded
234+
but the client did not steer, there are other issues present. Here's another BTM request exchange, this time
235+
where the client rejects the AP's request with status code 7, no suitable candidates. If you see similar,
236+
possibly you misconfigured the neighbor BSSID, or the client was unable to scan the neighbor.
237+
238+
{{ image(src="/blog/2025-11-simulated-band-steering/btm_request_reject_no_suitable_candidates.png", caption="BTM Request rejected, no suitable candidates", alt="BTM Request rejected, no suitable candidates") }}
239+
240+
## Identifying Interfaces and MACs Cont
241+
242+
In the case that interface names and MAC addresses are non-default or difficult to identify, there may be several
243+
things getting in the way. This section covers a couple reasons why this may be.
244+
245+
When any WiFi kernel module loads, the driver creates a virtual interface (vif) per radio. A vif is a general term which refers
246+
to a WiFi interface, be it a station, AP, monitor, or others and applies regardless of WiFi driver (simulated or not).
247+
It's possible to load the `mac80211_hwsim` kernel module without creating a vif for each radio, but that's beyond the
248+
scope of this post (see `modinfo mac80211_hwsim`).
249+
250+
When created, virtual interfaces will generally use a unique name on most modern Linux distributions, thanks to systemd's
251+
predictable network interface naming (see [here](https://systemd.io/PREDICTABLE_INTERFACE_NAMES/) for more info). The
252+
`mac80211_hwsim` interfaces generally dodge this and are reliably named `wlanX` (unsure if they skip udev), assuming the
253+
configured name doesn't already exist.
254+
255+
If the `mac80211_hwsim` interfaces are difficult to identify by name, check the MAC address which is configured to something matching
256+
the format `02:00:00:00:XX:00` (output by `ip -br link show`), assuming your network daemon doesn't get in the way (NetworkManager
257+
may randomize the MACs, have fun).
258+
259+
<!-- TODO: How to disable MAC changing on NetworkManager -->
260+
261+
## CLI Commands
262+
263+
In general, `hostapd` and `wpa_supplicant` CLI commands are straightforward but not well documented. Running `help` in
264+
each CLI will display all commands and running `help CMD` will display info on the command itself. However, often
265+
times the output is terse. I find myself referencing the source doe often. If you also need to do that, start [here](https://git.w1.fi/cgit/hostap/tree/wpa_supplicant/ctrl_iface.c)
266+
for `wpa_supplicant` CLI and [here](https://git.w1.fi/cgit/hostap/tree/src/ap/ctrl_iface_ap.c) for `hostapd` CLI.
267+
268+
1. `bss_tm_req` in `hostapd` CLI
269+
270+
Only the client MAC address is required, but you'll need more than that to successfully steer the client.
271+
272+
```Bash
273+
# Send BTM request with preferred neighbor report
274+
# The BSSID info, operating class, and PHY type may require some digging to configure
275+
bss_tm_req CLIENT_MAC pref=1 neighbor=BSSID,BSSID_INFO,OP_CLASS,CHANNEL,PHY_TYPE
276+
```
137 KB
Loading
226 KB
Loading

0 commit comments

Comments
 (0)