Skip to content

Commit 7fcbc2f

Browse files
committed
Add simulated band steering post
1 parent a357408 commit 7fcbc2f

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
+++
2+
title = "Simulated WiFi Band Steering"
3+
date = "2025-11-01"
4+
description = ""
5+
path = "blog/simulated-band-steering"
6+
7+
#[taxonomies]
8+
#tags = ["linux", "wifi"]
9+
+++
10+
11+
## Overview
12+
13+
In this post, I'll provide some background on and demonstrate how to simulate WiFi band steering using open source Linux tools
14+
(which are used in commercial WiFi too!).
15+
16+
If you'd like to replicate this yourself, you'll need a Linux system with `sudo` access, kernel support for the `mac80211_hwsim` driver
17+
(which is generally the case for most Linux distributions), and `hostapd` and `wpa_supplicant` installed. This setup will require some
18+
experience using Linux, Wireshark, and WiFi. I have not tried it, but a container or virtual machine should work well for this.
19+
20+
## Background
21+
22+
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.
23+
24+
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.
25+
26+
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, forced these methods are heavy handed and may result in poor user experience.
27+
28+
Several amendements to the WiFi 802.11 standard serve to address this and to improve user experience generally. The 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 it simulated or real-world.
29+
30+
If you'd like to brush up on bout 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.
31+
32+
With this all in mind, we're ready to setup for testing!
33+
34+
## Recommendations
35+
36+
To make your life easier, I recommend the following:
37+
38+
- **Open three terminals**
39+
40+
- Now may be a good time to learn how to use `tmux` or `screen`
41+
42+
- This will permit reading logs while we setup and run the test
43+
44+
- It's possible to setup this test with `wpa_supplicant` and `hostapd` daemonized (`-B` argument), but I would only recommend it for advanced users
45+
46+
- **Disconnect from your WiFi (if present) while doing this**
47+
48+
- When running with verbose logging, `wpa_supplicant` and `hostapd` will output information on any interface changes
49+
and also run scans on the client interface. Verbose logs especially can become very busy on a normal system, as your
50+
networking daemon will likely run periodic background scanning.
51+
52+
- Make sure **not** to run `nmcli radio wifi off`, as this will enable rfkill which soft blocks WiFi on all interfaces
53+
(including the `mac80211_hwsim` ones)
54+
55+
## Test Setup
56+
57+
In order to simulate band steering, we must first configure our test environment.
58+
59+
**NOTE:** Many commands in this post will require root permissions (e.g. run with `sudo`), including `hostapd`, `wpa_supplicant`, `modprobe`.
60+
61+
1. **Load the `mac80211_hwsim` kernel module (driver)**
62+
63+
```Bash
64+
# Three simulated radios are needed
65+
# 1. Client (STA)
66+
# 2. AP 2.4 GHz BSSID
67+
# 3. AP 5 GHz BSSID
68+
modprobe mac80211_hwsim radios=3 support_p2p_device=0
69+
```
70+
71+
2. **Identify simulated WiFi interfaces and their MAC addresses**
72+
73+
**Most likely the `mac80211_hwsim` interfaces will be named `wlan0`, `wlan1`, and `wlan2`**. The driver will additionally
74+
create another interface named `hwsim0`, which permits packet capture of simulated WiFi traffic.
75+
76+
To identify the interfaces and corresponding MAC addresses, I suggest using the `ip -br addr show` and `ip -br link show` commands.
77+
First check the MAC address, as the driver creates the interfaces using the format `02:00:00:00:XX:00`, where the `XX`
78+
matches the simulated radio number.
79+
80+
If you're encountering issues or want to know more, check out [this section](#identifying-interfaces-and-macs-cont).
81+
82+
3. **Configure your network daemon to ignore the interfaces**
83+
84+
Practically all modern workstation Linux distributions ship with NetworkManager. If you're using something else, I assume you know
85+
what you're doing or can figure it out...
86+
87+
When using NetworkManager, we must configure it to ignore the new `mac80211_hwsim` interfaces, which you can do with the
88+
following command. We don't need any IP configuration for this testing anyway.
89+
90+
When complete, the interfaces should show as `unmanaged` in the output of `nmcli device status` (or `nmcli device` for short),
91+
as shown below.
92+
93+
```Bash
94+
# Run this for each simulated WiFi interface, substituting in
95+
# your system's interface names for this and subsequent commands
96+
nmcli device set wlan0 managed false
97+
```
98+
99+
4. **Download example `hostapd` and `wpa_supplicant` configs [here](https://codeberg.org/a-gavin/hostap-confs)**
100+
101+
I recommend using the open (no security) configuration, as this will make packet capture much more simple.
102+
With configurations supporting encryption, packet decryption is possible, but security improvements like 802.11w
103+
and WPA3 make this increasingly difficult (which is good for security!).
104+
105+
```Bash
106+
git clone https://codeberg.org/a-gavin/hostap-confs.git
107+
```
108+
109+
5. **Start the AP interfaces**
110+
111+
```Bash
112+
# Options used are:
113+
# -t: Print timestamps
114+
# -i: Specify interface to run AP with. Can be specified multiple times.
115+
#
116+
# For verbose or very verbose logging, add the '-d' or '-dd' options
117+
hostapd -t -i wlan0 -i wlan1 hostapd_2.4GHz-open.conf hostapd_5GHz-open.conf
118+
```
119+
120+
Assuming no errors appear in the logs, you can verify the AP interface channel/frequency, SSID, and MAC
121+
with the `iw wlan0 info` command.
122+
123+
6. **Enable `mac80211_hwsim` packet capture interface**
124+
125+
This interface is likely named `hwsim0`.
126+
127+
```Bash
128+
ip link set up dev hwsim0
129+
```
130+
131+
7. **Run Wireshark using the `mac80211_hwsim` packet capture interface**
132+
133+
Packet capture requires elevated permissions. See the Wireshark documentation [here](https://wiki.wireshark.org/capturesetup/captureprivileges#gnulinux-distributions-wireshark-is-installed-using-a-package-manager) for more information.
134+
135+
8. **Start the client interface**
136+
137+
```Bash
138+
# Similar options and requirements as 'hostapd'
139+
# The '-c' option required to specify the config file
140+
wpa_supplicant -t -i wlan2 -c supplicant_open.conf
141+
```
142+
143+
9. **Wait for client to connect**
144+
145+
As the client connects, you should see simulated WiFi traffic in Wireshark following the standard WiFi client
146+
connection process (use the `wlan.fc.type_subtype != 8` filter to filter out beacons).
147+
148+
Once the client connects, you should see the message `CTRL-EVENT-CONNECTED` appear in the station logs and
149+
`AP-STA-CONNECTED` in the AP logs. Take note of the AP interface that the client connected to. As with the
150+
AP interfaces, you can run the `iw wlan2 info` command to verify connection, channel, SSID, BSSID, etc.
151+
152+
With the simulated client connected and Wireshark running, we're ready to run some tests!
153+
154+
## Simulated Band Steering
155+
156+
To start, we'll first attempt to steer the client from its currently associated AP BSSID to the other AP BSSID.
157+
158+
1. **Connect to the `hostapd` CLI interface**
159+
160+
In another terminal, run the following command. This will enter a the `hostapd` CLI interface which may
161+
or may not permit line editing and/or command history, depending on your system.
162+
163+
```Bash
164+
# The '-p isn't necessary if using the example configs, as they configure
165+
# the default control interface path
166+
hostapd_cli -p /var/run/hostapd/
167+
```
168+
169+
2. **Select the AP interface to which the client connected**
170+
171+
Using the `interface` command, select the desired interface. Then, check the list of connected clients
172+
with the `list_sta` command, as shown below.
173+
174+
```Bash
175+
$ hostapd_cli -p /var/run/hostapd/
176+
...
177+
> interface wlan0
178+
> list_sta
179+
> interface wlan1
180+
> list_sta
181+
02:00:00:00:02:00
182+
```
183+
184+
3. **Send a BSS Transition Management (BTM) request to the client**
185+
186+
With the interface still selected in the `hostapd` CLI interface, run the following command to send a BTM request
187+
to the client.
188+
189+
This will generate a BTM request frame, to which the client should respond accordingly. If a client does not support BTM,
190+
then the client should not respond. You can simulate this by adding `disable_btm=1` to the client's `wpa_supplicant` config
191+
and resetting the client to enable this.
192+
193+
```Bash
194+
# From 5 GHz to 2.4 GHz
195+
# First MAC is client MAC. Second MAC (neighbor) is 2.4 GHz BSSID.
196+
bss_tm_req 02:00:00:00:02:00 pref=1 neighbor=02:00:00:00:00:00,0x0000008f,81,1,14
197+
198+
# From 2.4 GHz to 5 GHz
199+
# First MAC is client MAC. Second MAC (neighbor) is 5 GHz BSSID.
200+
bss_tm_req 02:00:00:00:02:00 pref=1 neighbor=02:00:00:00:01:00,0x0000008f,81,36,14
201+
```
202+
203+
Assuming this ran okay, you should see the BTM request and response frames in Wireshark as shown below.
204+
205+
{{ image(src="/blog/2025-11-simulated-band-steering/btm_request_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") }}
206+
207+
## Identifying Interfaces and MACs Cont
208+
209+
In the case that interface names and MAC addresses are non-default or difficult to identify, there may be several
210+
things getting in the way. This section covers a couple reasons why this may be.
211+
212+
When any WiFi kernel module loads, the driver creates a virtual interface (vif) per radio. A vif is a general term which refers
213+
to a WiFi interface, be it a station, AP, monitor, or others and applies regardless of WiFi driver (simulated or not).
214+
It's possible to load the `mac80211_hwsim` kernel module without creating a vif for each radio, but that's beyond the
215+
scope of this post (see `modinfo mac80211_hwsim`).
216+
217+
When created, virtual interfaces will generally use a unique name on most modern Linux distributions, thanks to systemd's
218+
predictable network interface naming (see [here](https://systemd.io/PREDICTABLE_INTERFACE_NAMES/) for more info). The
219+
`mac80211_hwsim` interfaces generally dodge this and are reliably named `wlanX` (unsure if they skip udev), assuming the
220+
configured name doesn't already exist.
221+
222+
If the `mac80211_hwsim` interfaces are difficult to identify by name, check the MAC address which is configured to something matching
223+
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
224+
may randomize the MACs, have fun).
225+
226+
<!-- TODO: How to disable MAC changing on NetworkManager -->
226 KB
Loading

0 commit comments

Comments
 (0)