Skip to content

Commit a580e3e

Browse files
committed
Wi-Fi: Add possibility to control roaming between APs
Useful if you have multiple APs on one SSID.
1 parent 1241824 commit a580e3e

File tree

3 files changed

+209
-8
lines changed

3 files changed

+209
-8
lines changed

doc/wifi.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,67 @@ radio settings, and `show interface` to see all active AP interfaces.
379379
> AP and Station modes cannot be mixed on the same radio. All virtual interfaces
380380
> on a radio must be the same mode (all APs or all Stations).
381381
382+
## Fast Roaming Between Access Points
383+
384+
Fast roaming enables seamless client handoff between access points through
385+
802.11k/r/v standards. These features can be enabled individually based on
386+
your requirements.
387+
388+
### 802.11r - Fast BSS Transition
389+
390+
Enable 802.11r for fast handoff (<50ms) between APs with the same SSID:
391+
392+
```
393+
admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211r true
394+
admin@example:/config/interface/wifi0/> set wifi access-point roaming mobility-domain 4f57
395+
```
396+
397+
**Requirements:**
398+
- All APs in roaming group must have **identical** SSID
399+
- All APs must have **identical** passphrase (same keystore secret)
400+
- All APs must use the **same mobility-domain** identifier
401+
402+
The mobility-domain defaults to `4f57` if not specified.
403+
404+
### 802.11k - Radio Resource Management
405+
406+
Enable 802.11k for client neighbor discovery and better roaming decisions:
407+
408+
```
409+
admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211k true
410+
```
411+
412+
Enables neighbor reports and beacon reports, allowing clients to discover
413+
nearby APs before roaming.
414+
415+
### 802.11v - BSS Transition Management
416+
417+
Enable 802.11v for network-assisted roaming:
418+
419+
```
420+
admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211v true
421+
```
422+
423+
Allows APs to suggest better APs to clients, improving roaming decisions.
424+
425+
### Recommended Configuration
426+
427+
For optimal roaming experience, enable all three features:
428+
429+
```
430+
admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211k true
431+
admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211r true
432+
admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211v true
433+
admin@example:/config/interface/wifi0/> set wifi access-point roaming mobility-domain 4f57
434+
```
435+
436+
Repeat for all APs that should participate in the roaming group.
437+
438+
> [!NOTE]
439+
> Not all client devices support all roaming features. Modern devices typically
440+
> support 802.11k/r/v, but older devices may only support basic roaming without
441+
> fast transition.
442+
382443
### AP as Bridge Port
383444

384445
WiFi AP interfaces can be added to bridges to integrate wireless devices

src/confd/src/hardware.c

Lines changed: 95 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,9 @@ static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name,
313313
static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char *ifname, struct lyd_node *config)
314314
{
315315
const char *ssid, *hidden, *security_mode, *secret_name, *secret;
316-
struct lyd_node *cif, *wifi, *ap, *security, *secret_node;
316+
const char *mobility_domain = NULL;
317+
struct lyd_node *cif, *wifi, *ap, *security, *secret_node, *roaming;
318+
bool enable_80211k, enable_80211r, enable_80211v;
317319
char bssid[18];
318320

319321
/* Find the interface node for this BSS */
@@ -367,28 +369,46 @@ static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char
367369
}
368370
}
369371

372+
/* Check 802.11k/r/v configuration */
373+
roaming = lydx_get_child(ap, "roaming");
374+
enable_80211k = roaming && lydx_get_bool(roaming, "enable-80211k");
375+
enable_80211r = roaming && lydx_get_bool(roaming, "enable-80211r");
376+
enable_80211v = roaming && lydx_get_bool(roaming, "enable-80211v");
377+
378+
if (enable_80211r)
379+
mobility_domain = lydx_get_cattr(roaming, "mobility-domain");
380+
370381
if (!strcmp(security_mode, "open")) {
371382
fprintf(hostapd, "# Open network\n");
372383
fprintf(hostapd, "auth_algs=1\n");
373384
} else if (!strcmp(security_mode, "wpa2-personal")) {
374385
fprintf(hostapd, "# WPA2-Personal\n");
375386
fprintf(hostapd, "wpa=2\n");
376-
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
387+
if (enable_80211r)
388+
fprintf(hostapd, "wpa_key_mgmt=FT-PSK WPA-PSK\n");
389+
else
390+
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
377391
fprintf(hostapd, "wpa_pairwise=CCMP\n");
378392
if (secret)
379393
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
380394
} else if (!strcmp(security_mode, "wpa3-personal")) {
381395
fprintf(hostapd, "# WPA3-Personal\n");
382396
fprintf(hostapd, "wpa=2\n");
383-
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
397+
if (enable_80211r)
398+
fprintf(hostapd, "wpa_key_mgmt=FT-SAE SAE\n");
399+
else
400+
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
384401
fprintf(hostapd, "rsn_pairwise=CCMP\n");
385402
if (secret)
386403
fprintf(hostapd, "sae_password=%s\n", secret);
387404
fprintf(hostapd, "ieee80211w=2\n");
388405
} else if (!strcmp(security_mode, "wpa2-wpa3-personal")) {
389406
fprintf(hostapd, "# WPA2/WPA3 Mixed\n");
390407
fprintf(hostapd, "wpa=2\n");
391-
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
408+
if (enable_80211r)
409+
fprintf(hostapd, "wpa_key_mgmt=FT-PSK FT-SAE WPA-PSK SAE\n");
410+
else
411+
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
392412
fprintf(hostapd, "rsn_pairwise=CCMP\n");
393413
if (secret) {
394414
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
@@ -397,6 +417,28 @@ static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char
397417
fprintf(hostapd, "ieee80211w=1\n");
398418
}
399419

420+
/* 802.11r: Fast BSS Transition */
421+
if (enable_80211r) {
422+
fprintf(hostapd, "# Fast BSS Transition (802.11r)\n");
423+
fprintf(hostapd, "mobility_domain=%s\n", mobility_domain);
424+
fprintf(hostapd, "ft_over_ds=1\n");
425+
fprintf(hostapd, "ft_psk_generate_local=1\n");
426+
fprintf(hostapd, "nas_identifier=%s.%s\n", ifname, mobility_domain);
427+
}
428+
429+
/* 802.11k: Radio Resource Management */
430+
if (enable_80211k) {
431+
fprintf(hostapd, "# Radio Resource Management (802.11k)\n");
432+
fprintf(hostapd, "rrm_neighbor_report=1\n");
433+
fprintf(hostapd, "rrm_beacon_report=1\n");
434+
}
435+
436+
/* 802.11v: BSS Transition Management */
437+
if (enable_80211v) {
438+
fprintf(hostapd, "# BSS Transition Management (802.11v)\n");
439+
fprintf(hostapd, "bss_transition=1\n");
440+
}
441+
400442
return 0;
401443
}
402444

@@ -405,11 +447,13 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
405447
struct lyd_node *radio_node, struct lyd_node *config)
406448
{
407449
const char *ssid, *hidden, *security_mode, *secret_name, *secret;
450+
const char *mobility_domain = NULL;
408451
struct lyd_node *primary_cif, *cif;
409452
struct lyd_node *primary_wifi, *primary_ap;
410-
struct lyd_node *security, *secret_node;
453+
struct lyd_node *security, *secret_node, *roaming;
411454
const char *country, *channel, *band;
412455
const char *primary_ifname;
456+
bool enable_80211k, enable_80211r, enable_80211v;
413457
char hostapd_conf[256];
414458
char **ap_list = NULL;
415459
FILE *hostapd = NULL;
@@ -472,6 +516,15 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
472516
}
473517
}
474518

519+
/* Check 802.11k/r/v configuration */
520+
roaming = lydx_get_child(primary_ap, "roaming");
521+
enable_80211k = roaming && lydx_get_bool(roaming, "enable-80211k");
522+
enable_80211r = roaming && lydx_get_bool(roaming, "enable-80211r");
523+
enable_80211v = roaming && lydx_get_bool(roaming, "enable-80211v");
524+
525+
if (enable_80211r)
526+
mobility_domain = lydx_get_cattr(roaming, "mobility-domain");
527+
475528
snprintf(hostapd_conf, sizeof(hostapd_conf), HOSTAPD_CONF_NEXT, radio_name);
476529

477530
hostapd = fopen(hostapd_conf, "w");
@@ -572,26 +625,60 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
572625
} else if (!strcmp(security_mode, "wpa2-personal")) {
573626
fprintf(hostapd, "# WPA2-Personal\n");
574627
fprintf(hostapd, "wpa=2\n");
575-
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
628+
if (enable_80211r)
629+
fprintf(hostapd, "wpa_key_mgmt=FT-PSK WPA-PSK\n");
630+
else
631+
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
576632
fprintf(hostapd, "wpa_pairwise=CCMP\n");
577633
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
578634
} else if (!strcmp(security_mode, "wpa3-personal")) {
579635
fprintf(hostapd, "# WPA3-Personal\n");
580636
fprintf(hostapd, "wpa=2\n");
581-
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
637+
if (enable_80211r)
638+
fprintf(hostapd, "wpa_key_mgmt=FT-SAE SAE\n");
639+
else
640+
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
582641
fprintf(hostapd, "rsn_pairwise=CCMP\n");
583642
fprintf(hostapd, "sae_password=%s\n", secret);
584643
fprintf(hostapd, "ieee80211w=2\n");
585644
} else if (!strcmp(security_mode, "wpa2-wpa3-personal")) {
586645
fprintf(hostapd, "# WPA2/WPA3 Mixed Mode\n");
587646
fprintf(hostapd, "wpa=2\n");
588-
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
647+
if (enable_80211r)
648+
fprintf(hostapd, "wpa_key_mgmt=FT-PSK FT-SAE WPA-PSK SAE\n");
649+
else
650+
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
589651
fprintf(hostapd, "rsn_pairwise=CCMP\n");
590652
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
591653
fprintf(hostapd, "sae_password=%s\n", secret);
592654
fprintf(hostapd, "ieee80211w=1\n");
593655
}
594656

657+
/* 802.11r: Fast BSS Transition */
658+
if (enable_80211r) {
659+
fprintf(hostapd, "\n# Fast BSS Transition (802.11r)\n");
660+
fprintf(hostapd, "mobility_domain=%s\n", mobility_domain);
661+
fprintf(hostapd, "ft_over_ds=1\n");
662+
fprintf(hostapd, "ft_psk_generate_local=1\n");
663+
fprintf(hostapd, "nas_identifier=%s.%s\n", primary_ifname, mobility_domain);
664+
}
665+
666+
/* 802.11k: Radio Resource Management */
667+
if (enable_80211k) {
668+
fprintf(hostapd, "\n# Radio Resource Management (802.11k)\n");
669+
fprintf(hostapd, "rrm_neighbor_report=1\n");
670+
fprintf(hostapd, "rrm_beacon_report=1\n");
671+
}
672+
673+
/* 802.11v: BSS Transition Management */
674+
if (enable_80211v) {
675+
fprintf(hostapd, "\n# BSS Transition Management (802.11v)\n");
676+
fprintf(hostapd, "bss_transition=1\n");
677+
}
678+
679+
if (enable_80211k || enable_80211r || enable_80211v)
680+
fprintf(hostapd, "\n");
681+
595682
/* Add BSS sections for secondary APs (multi-SSID) */
596683
for (i = 1; i < ap_count; i++) {
597684
DEBUG("Adding BSS section for secondary AP %s", ap_list[i]);

src/confd/yang/confd/infix-if-wifi.yang

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,59 @@ submodule infix-if-wifi {
370370
}
371371
}
372372

373+
container roaming {
374+
description
375+
"Fast roaming and seamless handoff configuration.
376+
377+
802.11k: Radio Resource Management (neighbor discovery)
378+
802.11r: Fast BSS Transition (fast handoff)
379+
802.11v: BSS Transition Management (network-assisted roaming)";
380+
381+
leaf enable-80211k {
382+
type boolean;
383+
description
384+
"Enable 802.11k Radio Resource Management.
385+
386+
Allows clients to discover neighboring APs for better
387+
roaming decisions. Enables:
388+
- Neighbor reports
389+
- Beacon reports";
390+
}
391+
392+
leaf enable-80211r {
393+
type boolean;
394+
description
395+
"Enable 802.11r Fast BSS Transition.
396+
397+
Reduces handoff latency from ~1s to <50ms through
398+
pre-authentication. Required for seamless roaming.";
399+
}
400+
401+
leaf enable-80211v {
402+
type boolean;
403+
description
404+
"Enable 802.11v BSS Transition Management.
405+
406+
Allows APs to suggest better APs to clients, improving
407+
network-assisted roaming decisions.";
408+
}
409+
410+
leaf mobility-domain {
411+
when "../enable-80211r = 'true'";
412+
type string {
413+
pattern '[0-9a-fA-F]{4}';
414+
}
415+
default "4f57";
416+
description
417+
"802.11r Mobility Domain identifier (4 hex digits).
418+
419+
All APs that clients should roam between MUST use the
420+
same mobility domain ID.
421+
422+
Only applicable when 802.11r is enabled.";
423+
}
424+
}
425+
373426
/* Operational state */
374427

375428
container stations {

0 commit comments

Comments
 (0)