Skip to content

Commit ee28435

Browse files
committed
Wi-Fi: Enable roaming
Enable roaming for Wi-Fi Access Point
1 parent ff5adb0 commit ee28435

File tree

3 files changed

+168
-11
lines changed

3 files changed

+168
-11
lines changed

doc/wifi.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,57 @@ admin@example:/config/interface/wifi0/> set bridge-port bridge br0
394394
admin@example:/config/interface/wifi0/> leave
395395
```
396396

397+
## Fast Roaming Between Access Points
398+
399+
Fast roaming enables seamless client handoff between access points without
400+
connection drops. This is essential for dual-band setups (same SSID on 2.4GHz
401+
and 5GHz) and multi-AP deployments.
402+
403+
When roaming is enabled, Infix activates 802.11k/r/v features that reduce
404+
handoff latency from ~1 second to <50ms and help clients make better roaming
405+
decisions.
406+
407+
### Requirements
408+
409+
For seamless roaming, all access points in the roaming group must have:
410+
411+
1. **Identical SSID** (network name)
412+
2. **Identical passphrase** (same keystore secret reference)
413+
3. **Identical mobility-domain** (if customized from default `4f57`)
414+
4. **Roaming enabled** on all AP interfaces
415+
416+
### Configuration
417+
418+
Enable roaming on each AP interface that should participate in the roaming group:
419+
420+
```
421+
admin@example:/config/interface/wifi0/> set wifi access-point roaming
422+
```
423+
424+
This uses the default mobility-domain `4f57`. To customize the mobility-domain:
425+
426+
```
427+
admin@example:/config/interface/wifi0/> set wifi access-point roaming mobility-domain abcd
428+
```
429+
430+
> [!IMPORTANT]
431+
> All APs in the same roaming group MUST use the same mobility-domain identifier.
432+
> The SSID and passphrase must also be identical across all APs.
433+
434+
### How It Works
435+
436+
With roaming enabled, clients seamlessly transition between APs:
437+
438+
1. Client connects to the AP with the strongest signal
439+
2. Client continuously monitors signal strength and discovers neighbor APs
440+
3. When signal degrades, client evaluates alternative APs
441+
4. Client performs fast handoff (<50ms) to the better AP
442+
5. Connection remains active without interruption
443+
444+
> [!NOTE]
445+
> Not all client devices support fast roaming (802.11r). Older devices will still
446+
> roam between APs but may experience brief interruptions during handoff.
447+
397448
## Troubleshooting Connection Issues
398449

399450
Use `show interface wifi0` to verify signal strength and connection status.
@@ -405,4 +456,10 @@ If issues arise, try the following troubleshooting steps:
405456
4. **Regulatory compliance**: Ensure the country-code on the radio matches your location
406457
5. **Hardware detection**: Confirm the WiFi radio appears in `show hardware`
407458

459+
**Roaming-specific issues:**
460+
461+
- **Clients not roaming**: Verify mobility-domain, SSID, and passphrase are identical on all APs
462+
- **Connection drops during roaming**: Ensure AP coverage areas overlap adequately
463+
- **Client compatibility**: Check if client device supports 802.11r (most modern devices do)
464+
408465
If issues persist, check the system log for specific error messages that can help identify the root cause.

src/confd/src/hardware.c

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -310,10 +310,12 @@ static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name,
310310
}
311311

312312
/* Generate BSS section for secondary AP (multi-SSID) */
313-
static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char *ifname, struct lyd_node *config)
313+
static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char *ifname,
314+
struct lyd_node *config)
314315
{
315316
const char *ssid, *hidden, *security_mode, *secret_name, *secret;
316-
struct lyd_node *cif, *wifi, *ap, *security, *secret_node;
317+
const char *mobility_domain = NULL;
318+
struct lyd_node *cif, *wifi, *ap, *security, *secret_node, *roaming;
317319
char bssid[18];
318320

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

372+
/* Check roaming configuration */
373+
roaming = lydx_get_child(ap, "roaming");
374+
if (roaming)
375+
mobility_domain = lydx_get_cattr(roaming, "mobility-domain");
376+
377+
370378
if (!strcmp(security_mode, "open")) {
371379
fprintf(hostapd, "# Open network\n");
372380
fprintf(hostapd, "auth_algs=1\n");
373381
} else if (!strcmp(security_mode, "wpa2-personal")) {
374382
fprintf(hostapd, "# WPA2-Personal\n");
375383
fprintf(hostapd, "wpa=2\n");
376-
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
384+
if (roaming)
385+
fprintf(hostapd, "wpa_key_mgmt=FT-PSK WPA-PSK\n");
386+
else
387+
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
377388
fprintf(hostapd, "wpa_pairwise=CCMP\n");
378389
if (secret)
379390
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
380391
} else if (!strcmp(security_mode, "wpa3-personal")) {
381392
fprintf(hostapd, "# WPA3-Personal\n");
382393
fprintf(hostapd, "wpa=2\n");
383-
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
394+
if (roaming)
395+
fprintf(hostapd, "wpa_key_mgmt=FT-SAE SAE\n");
396+
else
397+
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
384398
fprintf(hostapd, "rsn_pairwise=CCMP\n");
385399
if (secret)
386400
fprintf(hostapd, "sae_password=%s\n", secret);
387401
fprintf(hostapd, "ieee80211w=2\n");
388402
} else if (!strcmp(security_mode, "wpa2-wpa3-personal")) {
389403
fprintf(hostapd, "# WPA2/WPA3 Mixed\n");
390404
fprintf(hostapd, "wpa=2\n");
391-
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
405+
if (roaming)
406+
fprintf(hostapd, "wpa_key_mgmt=FT-PSK FT-SAE WPA-PSK SAE\n");
407+
else
408+
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
392409
fprintf(hostapd, "rsn_pairwise=CCMP\n");
393410
if (secret) {
394411
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
@@ -397,6 +414,15 @@ static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char
397414
fprintf(hostapd, "ieee80211w=1\n");
398415
}
399416

417+
/* Roaming configuration (only if enabled) */
418+
if (roaming) {
419+
fprintf(hostapd, "# Fast roaming (802.11r)\n");
420+
fprintf(hostapd, "mobility_domain=%s\n", mobility_domain);
421+
fprintf(hostapd, "ft_over_ds=1\n");
422+
fprintf(hostapd, "ft_psk_generate_local=1\n");
423+
fprintf(hostapd, "nas_identifier=%s.%s\n", ifname, mobility_domain);
424+
}
425+
400426
return 0;
401427
}
402428

@@ -405,11 +431,11 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
405431
struct lyd_node *radio_node, struct lyd_node *config)
406432
{
407433
const char *ssid, *hidden, *security_mode, *secret_name, *secret;
434+
const char *country, *channel, *band, *primary_ifname;
435+
const char *mobility_domain = NULL;
408436
struct lyd_node *primary_cif, *cif;
409437
struct lyd_node *primary_wifi, *primary_ap;
410-
struct lyd_node *security, *secret_node;
411-
const char *country, *channel, *band;
412-
const char *primary_ifname;
438+
struct lyd_node *security, *secret_node, *roaming;
413439
char hostapd_conf[256];
414440
char **ap_list = NULL;
415441
FILE *hostapd = NULL;
@@ -455,6 +481,11 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
455481
secret_name = lydx_get_cattr(security, "secret");
456482
secret = NULL;
457483

484+
/* Get roaming configuration */
485+
roaming = lydx_get_child(primary_ap, "roaming");
486+
if (roaming)
487+
mobility_domain = lydx_get_cattr(roaming, "mobility-domain");
488+
458489
/* Get radio configuration */
459490
country = lydx_get_cattr(radio_node, "country-code");
460491
band = lydx_get_cattr(radio_node, "band");
@@ -572,26 +603,51 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
572603
} else if (!strcmp(security_mode, "wpa2-personal")) {
573604
fprintf(hostapd, "# WPA2-Personal\n");
574605
fprintf(hostapd, "wpa=2\n");
575-
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
606+
if (roaming)
607+
fprintf(hostapd, "wpa_key_mgmt=FT-PSK WPA-PSK\n");
608+
else
609+
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
576610
fprintf(hostapd, "wpa_pairwise=CCMP\n");
577611
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
578612
} else if (!strcmp(security_mode, "wpa3-personal")) {
579613
fprintf(hostapd, "# WPA3-Personal\n");
580614
fprintf(hostapd, "wpa=2\n");
581-
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
615+
if (roaming)
616+
fprintf(hostapd, "wpa_key_mgmt=FT-SAE SAE\n");
617+
else
618+
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
582619
fprintf(hostapd, "rsn_pairwise=CCMP\n");
583620
fprintf(hostapd, "sae_password=%s\n", secret);
584621
fprintf(hostapd, "ieee80211w=2\n");
585622
} else if (!strcmp(security_mode, "wpa2-wpa3-personal")) {
586623
fprintf(hostapd, "# WPA2/WPA3 Mixed Mode\n");
587624
fprintf(hostapd, "wpa=2\n");
588-
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
625+
if (roaming)
626+
fprintf(hostapd, "wpa_key_mgmt=FT-PSK FT-SAE WPA-PSK SAE\n");
627+
else
628+
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
589629
fprintf(hostapd, "rsn_pairwise=CCMP\n");
590630
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
591631
fprintf(hostapd, "sae_password=%s\n", secret);
592632
fprintf(hostapd, "ieee80211w=1\n");
593633
}
594634

635+
/* Roaming configuration (802.11k/r/v) */
636+
if (roaming) {
637+
fprintf(hostapd, "\n# Fast roaming and seamless handoff (802.11k/r/v)\n");
638+
fprintf(hostapd, "# 802.11r: Fast BSS Transition\n");
639+
fprintf(hostapd, "mobility_domain=%s\n", mobility_domain);
640+
fprintf(hostapd, "ft_over_ds=1\n");
641+
fprintf(hostapd, "ft_psk_generate_local=1\n");
642+
fprintf(hostapd, "nas_identifier=%s.%s\n", primary_ifname, mobility_domain);
643+
fprintf(hostapd, "# 802.11k: Radio Resource Management\n");
644+
fprintf(hostapd, "rrm_neighbor_report=1\n");
645+
fprintf(hostapd, "rrm_beacon_report=1\n");
646+
fprintf(hostapd, "# 802.11v: BSS Transition Management\n");
647+
fprintf(hostapd, "bss_transition=1\n");
648+
fprintf(hostapd, "\n");
649+
}
650+
595651
/* Add BSS sections for secondary APs (multi-SSID) */
596652
for (i = 1; i < ap_count; i++) {
597653
DEBUG("Adding BSS section for secondary AP %s", ap_list[i]);

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

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

373+
container roaming {
374+
presence "Enable fast roaming (802.11k/r/v)";
375+
description
376+
"Fast roaming and seamless handoff configuration.
377+
378+
When present, enables 802.11k/r/v features for seamless
379+
transitions between APs with the same SSID (e.g., 2.4GHz
380+
and 5GHz band steering).
381+
382+
Activates:
383+
- 802.11r: Fast BSS Transition (FT)
384+
- 802.11k: Radio Resource Management (RRM)
385+
- 802.11v: BSS Transition Management
386+
387+
These features allow clients to roam seamlessly between
388+
APs without losing connectivity or experiencing delays.
389+
390+
Requirements for proper roaming:
391+
- Same SSID on all APs
392+
- Same passphrase on all APs
393+
- Same mobility-domain on all APs in the roaming group
394+
395+
Recommended for multi-AP deployments and dual-band setups.";
396+
397+
leaf mobility-domain {
398+
type string {
399+
pattern '[0-9a-fA-F]{4}';
400+
}
401+
default "4f57";
402+
description
403+
"802.11r Mobility Domain identifier (4 hex digits).
404+
405+
All APs that clients should roam between MUST use the
406+
same mobility domain ID.
407+
408+
Default: '4f57' (hex for 'OW')
409+
410+
Use the same value for:
411+
- 2.4GHz and 5GHz APs with the same SSID
412+
- Multiple APs in the same physical location
413+
- Any APs that should support fast roaming";
414+
}
415+
}
416+
373417
/* Operational state */
374418

375419
container stations {

0 commit comments

Comments
 (0)