From dabe9d1230f33c257969fedde6400301a0559e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Sun, 11 Jan 2026 14:48:52 +0100 Subject: [PATCH 1/8] confd: hardware: Coding style --- src/confd/src/hardware.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index 1af1f2e58..f4562491c 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -415,6 +415,7 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, FILE *hostapd = NULL; bool wifi6_enabled; int ap_count = 0; + char bssid[18]; int i; int rc = SR_ERR_OK; @@ -492,7 +493,6 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, fprintf(hostapd, "ctrl_interface=/run/hostapd\n"); /* Set BSSID if custom MAC is configured */ - char bssid[18]; if (!interface_get_phys_addr(primary_cif, bssid)) fprintf(hostapd, "bssid=%s\n", bssid); fprintf(hostapd, "\n"); From 09868789ffff727928dbf126b9cd9fde5e837252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Sun, 11 Jan 2026 19:22:37 +0100 Subject: [PATCH 2/8] yang: wifi: Use correct type for counters --- src/confd/yang/confd/infix-if-wifi.yang | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index a68d1f604..58081a58d 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -397,23 +397,23 @@ submodule infix-if-wifi { } leaf rx-packets { - type uint32; + type yang:counter64; description "Packets received from this client."; } leaf tx-packets { - type uint32; + type yang:counter64; description "Packets transmitted to this client."; } leaf rx-bytes { - type uint32; + type yang:counter64; units "octets"; description "Bytes received from this client."; } leaf tx-bytes { - type uint32; + type yang:counter64; units "octets"; description "Bytes transmitted to this client."; } From 2f0c31311ac0fa7021353066b9bd8257ebeb7bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Sun, 11 Jan 2026 22:10:11 +0100 Subject: [PATCH 3/8] confd: core: Handle when custom-phys-address is changed on a Wi-Fi interface Then hostapd needs to be restarted, handle this by adding the wifi container to the diff if a custom-phys-address has been changed on a Wi-Fi interface. --- src/confd/src/core.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/confd/src/core.c b/src/confd/src/core.c index fab04e787..0dbcf3d60 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -3,6 +3,8 @@ #include #include #include + +#include "interfaces.h" #include "core.h" struct confd confd; @@ -144,20 +146,37 @@ static confd_dependency_t handle_dependencies(struct lyd_node **diff, struct lyd struct lyd_node *dif; LYX_LIST_FOR_EACH(difs, dif, "interface") { - struct lyd_node *dwifi, *radio_node; + struct lyd_node *dwifi, *dcustom_phys, *radio_node, *cwifi; const char *ifname, *radio_name; char xpath[256]; - - /* Check if this interface has a wifi container in the diff */ - dwifi = lydx_get_child(dif, "wifi"); - if (!dwifi) - continue; + bool is_wifi_change = false; /* Get the interface name */ ifname = lydx_get_cattr(dif, "name"); if (!ifname) continue; + if (iftype_from_iface(dif) != IFT_WIFI) + continue; + + /* Check if this interface has a wifi container in the diff */ + dwifi = lydx_get_child(dif, "wifi"); + if (dwifi) + is_wifi_change = true; + + /* Check if custom-phys-address changed on a WiFi interface */ + if (!is_wifi_change) { + dcustom_phys = lydx_get_child(dif, "custom-phys-address"); + if (dcustom_phys) { + /* Check if this interface is a WiFi interface in the config */ + cwifi = lydx_get_xpathf(config, + "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi", + ifname); + if (cwifi) + is_wifi_change = true; + } + } + /* Get radio reference from config tree using xpath */ radio_node = lydx_get_xpathf(config, "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/radio", From 204b067bb4f9ec4cb6316a9a2369535b51a288d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 12 Jan 2026 08:23:16 +0100 Subject: [PATCH 4/8] yang: wireguard: Require IP address as endpoint hostname can work, but from startup it does not, wg setconf fails and with that dagger. --- src/confd/yang/confd/infix-if-wireguard.yang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/confd/yang/confd/infix-if-wireguard.yang b/src/confd/yang/confd/infix-if-wireguard.yang index 20ca77d93..ea5e2c2d0 100644 --- a/src/confd/yang/confd/infix-if-wireguard.yang +++ b/src/confd/yang/confd/infix-if-wireguard.yang @@ -81,7 +81,7 @@ submodule infix-if-wireguard { } leaf endpoint { - type inet:host; + type inet:ip-address; description "Peer endpoint address (IP address or DNS hostname). From 33eb4a2c8919a170433562f827bb4ecf96c7d8dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 12 Jan 2026 09:12:13 +0100 Subject: [PATCH 5/8] yanger: show proper phy name for Wi-Fi phys --- src/statd/python/yanger/ietf_hardware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/statd/python/yanger/ietf_hardware.py b/src/statd/python/yanger/ietf_hardware.py index 74bf46cca..4ee32970b 100644 --- a/src/statd/python/yanger/ietf_hardware.py +++ b/src/statd/python/yanger/ietf_hardware.py @@ -190,11 +190,11 @@ def get_wifi_phy_info(): # Build descriptions for phy, info in phy_info.items(): if info["iface"] and info["band"] != "Unknown": - info["description"] = f"WiFi Radio {info['iface']} ({info['band']})" + info["description"] = f"WiFi Radio {phy}" elif info["band"] != "Unknown": info["description"] = f"WiFi Radio ({info['band']})" elif info["iface"]: - info["description"] = f"WiFi Radio {info['iface']}" + info["description"] = f"WiFi Radio {phy}" else: info["description"] = "WiFi Radio" From 5fd2b3f971c0414ddff0e24e5a0898c9ea66d7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 13 Jan 2026 13:01:26 +0100 Subject: [PATCH 6/8] confd: wifi: rename setting enable-wifi6 Use the standard name instead of the marketing name, new syntax enable-80211ax, which is consistent with the nameing of the new roaming settings. --- doc/wifi.md | 6 +++--- src/confd/src/hardware.c | 14 +++++++------- src/confd/yang/confd/infix-hardware.yang | 13 ++++++------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/doc/wifi.md b/doc/wifi.md index ddb838025..9cf1e07aa 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -86,7 +86,7 @@ admin@example:/config/hardware/component/radio0/wifi-radio/> leave - `country-code`: Two-letter ISO 3166-1 code - determines allowed channels and maximum power. Examples: US, DE, GB, SE, FR, JP. **Must match your physical location for legal compliance.** - `band`: 2.4GHz, 5GHz, or 6GHz (required for AP mode). Band selection automatically enables appropriate WiFi standards (2.4GHz: 802.11n, 5GHz: 802.11n/ac, 6GHz: 802.11n/ac/ax) - `channel`: Channel number (1-196) or "auto" (required for AP mode). When set to "auto", defaults to channel 6 for 2.4GHz, channel 36 for 5GHz, or channel 109 for 6GHz -- `enable-wifi6`: Boolean (default: false). Opt-in to enable WiFi 6 (802.11ax) on 2.4GHz and 5GHz bands. The 6GHz band always uses WiFi 6 regardless of this setting +- `enable-80211ax`: Boolean (default: false). Opt-in to enable 802.11ax (WiFi 6) on 2.4GHz and 5GHz bands. The 6GHz band always uses 802.11ax regardless of this setting > [!NOTE] > TX power and channel width are automatically determined by the driver based on regulatory constraints, PHY mode, and hardware capabilities. @@ -105,7 +105,7 @@ admin@example:/config/> edit hardware component radio0 wifi-radio admin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE admin@example:/config/hardware/component/radio0/wifi-radio/> set band 5GHz admin@example:/config/hardware/component/radio0/wifi-radio/> set channel 36 -admin@example:/config/hardware/component/radio0/wifi-radio/> set enable-wifi6 true +admin@example:/config/hardware/component/radio0/wifi-radio/> set enable-80211ax true admin@example:/config/hardware/component/radio0/wifi-radio/> leave ``` @@ -121,7 +121,7 @@ admin@example:/config/hardware/component/radio0/wifi-radio/> leave - Older WiFi 5/4 clients can still connect but won't use WiFi 6 features > [!NOTE] -> The 6GHz band always uses WiFi 6 (802.11ax) regardless of the `enable-wifi6` +> The 6GHz band always uses WiFi 6 (802.11ax) regardless of the `enable-80211ax` > setting, as WiFi 6E requires 802.11ax support. ## Discovering Available Networks (Scanning) diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index f4562491c..f7c42c77e 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -413,7 +413,7 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, char hostapd_conf[256]; char **ap_list = NULL; FILE *hostapd = NULL; - bool wifi6_enabled; + bool ax_enabled; int ap_count = 0; char bssid[18]; int i; @@ -459,7 +459,7 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, country = lydx_get_cattr(radio_node, "country-code"); band = lydx_get_cattr(radio_node, "band"); channel = lydx_get_cattr(radio_node, "channel"); - wifi6_enabled = lydx_get_bool(radio_node, "enable_wifi6"); + ax_enabled = lydx_get_bool(radio_node, "enable-80211ax"); /* Get secret from keystore if not open network */ if (secret_name && strcmp(security_mode, "open") != 0) { @@ -544,20 +544,20 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, if (band) { if (!strcmp(band, "2.4GHz")) { - /* 2.4GHz: Enable 802.11n (HT), optionally WiFi 6 */ + /* 2.4GHz: Enable 802.11n (HT), optionally 802.11ax */ fprintf(hostapd, "ieee80211n=1\n"); - if (wifi6_enabled) { + if (ax_enabled) { fprintf(hostapd, "ieee80211ax=1\n"); } } else if (!strcmp(band, "5GHz")) { - /* 5GHz: Enable 802.11n and 802.11ac, optionally WiFi 6 */ + /* 5GHz: Enable 802.11n and 802.11ac, optionally 802.11ax */ fprintf(hostapd, "ieee80211n=1\n"); fprintf(hostapd, "ieee80211ac=1\n"); - if (wifi6_enabled) { + if (ax_enabled) { fprintf(hostapd, "ieee80211ax=1\n"); } } else if (!strcmp(band, "6GHz")) { - /* 6GHz: Enable 802.11ax (WiFi 6E required) */ + /* 6GHz: Enable 802.11ax (required for 6GHz) */ fprintf(hostapd, "ieee80211n=1\n"); fprintf(hostapd, "ieee80211ac=1\n"); fprintf(hostapd, "ieee80211ax=1\n"); diff --git a/src/confd/yang/confd/infix-hardware.yang b/src/confd/yang/confd/infix-hardware.yang index c4bce1ead..164f94e62 100644 --- a/src/confd/yang/confd/infix-hardware.yang +++ b/src/confd/yang/confd/infix-hardware.yang @@ -262,16 +262,15 @@ module infix-hardware { congestion in most environments."; } - leaf enable-wifi6 { + leaf enable-80211ax { type boolean; - default false; description - "Enable WiFi 6 (802.11ax) on 2.4GHz and 5GHz bands. + "Enable 802.11ax (WiFi 6) on 2.4GHz and 5GHz bands. - By default, WiFi 6 is enabled only on 6GHz (WiFi 6E). - Set to 'true' to enable WiFi 6 on 2.4GHz and 5GHz bands. + By default, 802.11ax is enabled only on 6GHz (WiFi 6E). + Set to 'true' to enable 802.11ax on 2.4GHz and 5GHz bands. - WiFi 6 provides: + 802.11ax provides: - OFDMA (better multi-user efficiency) - Target Wake Time (better battery life) - 1024-QAM (higher throughput) @@ -281,7 +280,7 @@ module infix-hardware { - Hardware support for 802.11ax - Compatible clients for full benefits - Note: 6GHz band always uses WiFi 6 regardless of this setting."; + Note: 6GHz band always uses 802.11ax regardless of this setting."; } /* From 12418247943d12ecaf221817ef55500418a84673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 13 Jan 2026 23:22:27 +0100 Subject: [PATCH 7/8] confd: mdns: Fix config syntax enable-reflector should be set to yes or no, not on off according to manpage. --- src/confd/src/services.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/confd/src/services.c b/src/confd/src/services.c index 79b0458e6..97e126997 100644 --- a/src/confd/src/services.c +++ b/src/confd/src/services.c @@ -238,7 +238,7 @@ static void mdns_conf(struct lyd_node *cfg) fprintf(fp, "\n[reflector]\n"); ctx = lydx_get_descendant(lyd_child(cfg), "reflector", NULL); if (ctx) { - fprintf(fp, "enable-reflector=%s\n", lydx_is_enabled(ctx, "enabled") ? "on" : "off"); + fprintf(fp, "enable-reflector=%s\n", lydx_is_enabled(ctx, "enabled") ? "yes" : "no"); fput_list(fp, ctx, "service-filter", "reflect-filters="); } From a580e3e983088d898e9bf0b59e99e165dfdf2fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 13 Jan 2026 23:32:17 +0100 Subject: [PATCH 8/8] Wi-Fi: Add possibility to control roaming between APs Useful if you have multiple APs on one SSID. --- doc/wifi.md | 61 ++++++++++++++ src/confd/src/hardware.c | 103 ++++++++++++++++++++++-- src/confd/yang/confd/infix-if-wifi.yang | 53 ++++++++++++ 3 files changed, 209 insertions(+), 8 deletions(-) diff --git a/doc/wifi.md b/doc/wifi.md index 9cf1e07aa..10a7cd267 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -379,6 +379,67 @@ radio settings, and `show interface` to see all active AP interfaces. > AP and Station modes cannot be mixed on the same radio. All virtual interfaces > on a radio must be the same mode (all APs or all Stations). +## Fast Roaming Between Access Points + +Fast roaming enables seamless client handoff between access points through +802.11k/r/v standards. These features can be enabled individually based on +your requirements. + +### 802.11r - Fast BSS Transition + +Enable 802.11r for fast handoff (<50ms) between APs with the same SSID: + +``` +admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211r true +admin@example:/config/interface/wifi0/> set wifi access-point roaming mobility-domain 4f57 +``` + +**Requirements:** +- All APs in roaming group must have **identical** SSID +- All APs must have **identical** passphrase (same keystore secret) +- All APs must use the **same mobility-domain** identifier + +The mobility-domain defaults to `4f57` if not specified. + +### 802.11k - Radio Resource Management + +Enable 802.11k for client neighbor discovery and better roaming decisions: + +``` +admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211k true +``` + +Enables neighbor reports and beacon reports, allowing clients to discover +nearby APs before roaming. + +### 802.11v - BSS Transition Management + +Enable 802.11v for network-assisted roaming: + +``` +admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211v true +``` + +Allows APs to suggest better APs to clients, improving roaming decisions. + +### Recommended Configuration + +For optimal roaming experience, enable all three features: + +``` +admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211k true +admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211r true +admin@example:/config/interface/wifi0/> set wifi access-point roaming enable-80211v true +admin@example:/config/interface/wifi0/> set wifi access-point roaming mobility-domain 4f57 +``` + +Repeat for all APs that should participate in the roaming group. + +> [!NOTE] +> Not all client devices support all roaming features. Modern devices typically +> support 802.11k/r/v, but older devices may only support basic roaming without +> fast transition. + ### AP as Bridge Port WiFi AP interfaces can be added to bridges to integrate wireless devices diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index f7c42c77e..ff6b48360 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -313,7 +313,9 @@ static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name, static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char *ifname, struct lyd_node *config) { const char *ssid, *hidden, *security_mode, *secret_name, *secret; - struct lyd_node *cif, *wifi, *ap, *security, *secret_node; + const char *mobility_domain = NULL; + struct lyd_node *cif, *wifi, *ap, *security, *secret_node, *roaming; + bool enable_80211k, enable_80211r, enable_80211v; char bssid[18]; /* Find the interface node for this BSS */ @@ -367,20 +369,35 @@ static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char } } + /* Check 802.11k/r/v configuration */ + roaming = lydx_get_child(ap, "roaming"); + enable_80211k = roaming && lydx_get_bool(roaming, "enable-80211k"); + enable_80211r = roaming && lydx_get_bool(roaming, "enable-80211r"); + enable_80211v = roaming && lydx_get_bool(roaming, "enable-80211v"); + + if (enable_80211r) + mobility_domain = lydx_get_cattr(roaming, "mobility-domain"); + if (!strcmp(security_mode, "open")) { fprintf(hostapd, "# Open network\n"); fprintf(hostapd, "auth_algs=1\n"); } else if (!strcmp(security_mode, "wpa2-personal")) { fprintf(hostapd, "# WPA2-Personal\n"); fprintf(hostapd, "wpa=2\n"); - fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n"); + if (enable_80211r) + fprintf(hostapd, "wpa_key_mgmt=FT-PSK WPA-PSK\n"); + else + fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n"); fprintf(hostapd, "wpa_pairwise=CCMP\n"); if (secret) fprintf(hostapd, "wpa_passphrase=%s\n", secret); } else if (!strcmp(security_mode, "wpa3-personal")) { fprintf(hostapd, "# WPA3-Personal\n"); fprintf(hostapd, "wpa=2\n"); - fprintf(hostapd, "wpa_key_mgmt=SAE\n"); + if (enable_80211r) + fprintf(hostapd, "wpa_key_mgmt=FT-SAE SAE\n"); + else + fprintf(hostapd, "wpa_key_mgmt=SAE\n"); fprintf(hostapd, "rsn_pairwise=CCMP\n"); if (secret) fprintf(hostapd, "sae_password=%s\n", secret); @@ -388,7 +405,10 @@ static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char } else if (!strcmp(security_mode, "wpa2-wpa3-personal")) { fprintf(hostapd, "# WPA2/WPA3 Mixed\n"); fprintf(hostapd, "wpa=2\n"); - fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n"); + if (enable_80211r) + fprintf(hostapd, "wpa_key_mgmt=FT-PSK FT-SAE WPA-PSK SAE\n"); + else + fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n"); fprintf(hostapd, "rsn_pairwise=CCMP\n"); if (secret) { 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 fprintf(hostapd, "ieee80211w=1\n"); } + /* 802.11r: Fast BSS Transition */ + if (enable_80211r) { + fprintf(hostapd, "# Fast BSS Transition (802.11r)\n"); + fprintf(hostapd, "mobility_domain=%s\n", mobility_domain); + fprintf(hostapd, "ft_over_ds=1\n"); + fprintf(hostapd, "ft_psk_generate_local=1\n"); + fprintf(hostapd, "nas_identifier=%s.%s\n", ifname, mobility_domain); + } + + /* 802.11k: Radio Resource Management */ + if (enable_80211k) { + fprintf(hostapd, "# Radio Resource Management (802.11k)\n"); + fprintf(hostapd, "rrm_neighbor_report=1\n"); + fprintf(hostapd, "rrm_beacon_report=1\n"); + } + + /* 802.11v: BSS Transition Management */ + if (enable_80211v) { + fprintf(hostapd, "# BSS Transition Management (802.11v)\n"); + fprintf(hostapd, "bss_transition=1\n"); + } + return 0; } @@ -405,11 +447,13 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, struct lyd_node *radio_node, struct lyd_node *config) { const char *ssid, *hidden, *security_mode, *secret_name, *secret; + const char *mobility_domain = NULL; struct lyd_node *primary_cif, *cif; struct lyd_node *primary_wifi, *primary_ap; - struct lyd_node *security, *secret_node; + struct lyd_node *security, *secret_node, *roaming; const char *country, *channel, *band; const char *primary_ifname; + bool enable_80211k, enable_80211r, enable_80211v; char hostapd_conf[256]; char **ap_list = NULL; FILE *hostapd = NULL; @@ -472,6 +516,15 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, } } + /* Check 802.11k/r/v configuration */ + roaming = lydx_get_child(primary_ap, "roaming"); + enable_80211k = roaming && lydx_get_bool(roaming, "enable-80211k"); + enable_80211r = roaming && lydx_get_bool(roaming, "enable-80211r"); + enable_80211v = roaming && lydx_get_bool(roaming, "enable-80211v"); + + if (enable_80211r) + mobility_domain = lydx_get_cattr(roaming, "mobility-domain"); + snprintf(hostapd_conf, sizeof(hostapd_conf), HOSTAPD_CONF_NEXT, radio_name); hostapd = fopen(hostapd_conf, "w"); @@ -572,26 +625,60 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, } else if (!strcmp(security_mode, "wpa2-personal")) { fprintf(hostapd, "# WPA2-Personal\n"); fprintf(hostapd, "wpa=2\n"); - fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n"); + if (enable_80211r) + fprintf(hostapd, "wpa_key_mgmt=FT-PSK WPA-PSK\n"); + else + fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n"); fprintf(hostapd, "wpa_pairwise=CCMP\n"); fprintf(hostapd, "wpa_passphrase=%s\n", secret); } else if (!strcmp(security_mode, "wpa3-personal")) { fprintf(hostapd, "# WPA3-Personal\n"); fprintf(hostapd, "wpa=2\n"); - fprintf(hostapd, "wpa_key_mgmt=SAE\n"); + if (enable_80211r) + fprintf(hostapd, "wpa_key_mgmt=FT-SAE SAE\n"); + else + fprintf(hostapd, "wpa_key_mgmt=SAE\n"); fprintf(hostapd, "rsn_pairwise=CCMP\n"); fprintf(hostapd, "sae_password=%s\n", secret); fprintf(hostapd, "ieee80211w=2\n"); } else if (!strcmp(security_mode, "wpa2-wpa3-personal")) { fprintf(hostapd, "# WPA2/WPA3 Mixed Mode\n"); fprintf(hostapd, "wpa=2\n"); - fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n"); + if (enable_80211r) + fprintf(hostapd, "wpa_key_mgmt=FT-PSK FT-SAE WPA-PSK SAE\n"); + else + fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n"); fprintf(hostapd, "rsn_pairwise=CCMP\n"); fprintf(hostapd, "wpa_passphrase=%s\n", secret); fprintf(hostapd, "sae_password=%s\n", secret); fprintf(hostapd, "ieee80211w=1\n"); } + /* 802.11r: Fast BSS Transition */ + if (enable_80211r) { + fprintf(hostapd, "\n# Fast BSS Transition (802.11r)\n"); + fprintf(hostapd, "mobility_domain=%s\n", mobility_domain); + fprintf(hostapd, "ft_over_ds=1\n"); + fprintf(hostapd, "ft_psk_generate_local=1\n"); + fprintf(hostapd, "nas_identifier=%s.%s\n", primary_ifname, mobility_domain); + } + + /* 802.11k: Radio Resource Management */ + if (enable_80211k) { + fprintf(hostapd, "\n# Radio Resource Management (802.11k)\n"); + fprintf(hostapd, "rrm_neighbor_report=1\n"); + fprintf(hostapd, "rrm_beacon_report=1\n"); + } + + /* 802.11v: BSS Transition Management */ + if (enable_80211v) { + fprintf(hostapd, "\n# BSS Transition Management (802.11v)\n"); + fprintf(hostapd, "bss_transition=1\n"); + } + + if (enable_80211k || enable_80211r || enable_80211v) + fprintf(hostapd, "\n"); + /* Add BSS sections for secondary APs (multi-SSID) */ for (i = 1; i < ap_count; i++) { DEBUG("Adding BSS section for secondary AP %s", ap_list[i]); diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index 58081a58d..808fa9198 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -370,6 +370,59 @@ submodule infix-if-wifi { } } + container roaming { + description + "Fast roaming and seamless handoff configuration. + + 802.11k: Radio Resource Management (neighbor discovery) + 802.11r: Fast BSS Transition (fast handoff) + 802.11v: BSS Transition Management (network-assisted roaming)"; + + leaf enable-80211k { + type boolean; + description + "Enable 802.11k Radio Resource Management. + + Allows clients to discover neighboring APs for better + roaming decisions. Enables: + - Neighbor reports + - Beacon reports"; + } + + leaf enable-80211r { + type boolean; + description + "Enable 802.11r Fast BSS Transition. + + Reduces handoff latency from ~1s to <50ms through + pre-authentication. Required for seamless roaming."; + } + + leaf enable-80211v { + type boolean; + description + "Enable 802.11v BSS Transition Management. + + Allows APs to suggest better APs to clients, improving + network-assisted roaming decisions."; + } + + leaf mobility-domain { + when "../enable-80211r = 'true'"; + type string { + pattern '[0-9a-fA-F]{4}'; + } + default "4f57"; + description + "802.11r Mobility Domain identifier (4 hex digits). + + All APs that clients should roam between MUST use the + same mobility domain ID. + + Only applicable when 802.11r is enabled."; + } + } + /* Operational state */ container stations {