From 0b9d88585d34d1d10a9b3807983f70f529a1e647 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 01/19] 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 c7da58da3..e3a7fe755 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 7ab270afc30832d045336e3f05f0a5ed86ff5b78 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 02/19] yang: wifi: Use correct type for counters --- src/confd/yang/confd/infix-if-wifi.yang | 8 ++++---- src/statd/python/yanger/ietf_interfaces/wifi.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index 843421f94..687fab567 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -401,23 +401,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."; } diff --git a/src/statd/python/yanger/ietf_interfaces/wifi.py b/src/statd/python/yanger/ietf_interfaces/wifi.py index c298e78e1..2d1afdb88 100644 --- a/src/statd/python/yanger/ietf_interfaces/wifi.py +++ b/src/statd/python/yanger/ietf_interfaces/wifi.py @@ -152,13 +152,13 @@ def parse_iw_stations(output): seconds = int(value.split()[0]) current_station["connected-time"] = seconds elif key == "rx packets": - current_station["rx-packets"] = int(value) + current_station["rx-packets"] = value elif key == "tx packets": - current_station["tx-packets"] = int(value) + current_station["tx-packets"] = value elif key == "rx bytes": - current_station["rx-bytes"] = int(value) + current_station["rx-bytes"] = value elif key == "tx bytes": - current_station["tx-bytes"] = int(value) + current_station["tx-bytes"] = value elif key == "tx bitrate": # Format: "866.7 MBit/s ..." - extract speed and convert to 100kbit/s units speed_mbps = float(value.split()[0]) From 0fc7a1ae1f12fca60e1f88b219827fb02a32626c 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 03/19] 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 d80d5a51ad9f7ccf1d05e490e67000296c833b91 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 04/19] 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 f3cf3d1225ab38efccaa141f6e9bd0e8dad9313d 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 05/19] 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 bfc7519e2e69419a30256394244b58b85586b2f5 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 06/19] 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 e3a7fe755..d7e7d8726 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 fc8b1abeb5062694cbbd1efefe306ea49e136225 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 07/19] 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 5f07cc6d751c5fba829af9bf3018166b52611b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 19 Jan 2026 16:38:37 +0100 Subject: [PATCH 08/19] workflow: Fix SD-card/eMMC image builds --- .github/workflows/build-image.yml | 101 +++++++----------------------- 1 file changed, 21 insertions(+), 80 deletions(-) diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index c7878bbab..b5a6ef3d8 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -12,10 +12,6 @@ on: - bananapi-bpi-r3 - friendlyarm-nanopi-r2s default: 'raspberrypi-rpi64' - use_latest_release: - description: 'Use latest release artifacts instead of workflow artifacts' - type: boolean - default: false jobs: create-image: @@ -54,19 +50,19 @@ jobs: run: | case "${{ inputs.board }}" in raspberrypi-rpi64) - echo "BOOTLOADER=rpi64_boot" >> $GITHUB_ENV - echo "TARGET=aarch64" >> $GITHUB_ENV + echo "BOOTLOADER=rpi64-boot" >> $GITHUB_ENV + echo "ARCH=aarch64" >> $GITHUB_ENV echo "BUILD_EMMC=false" >> $GITHUB_ENV ;; bananapi-bpi-r3) - echo "BOOTLOADER_SD=bpi_r3_sd_boot" >> $GITHUB_ENV - echo "BOOTLOADER_EMMC=bpi_r3_emmc_boot" >> $GITHUB_ENV - echo "TARGET=aarch64" >> $GITHUB_ENV + echo "BOOTLOADER_SD=bpi-r3-sd-boot" >> $GITHUB_ENV + echo "BOOTLOADER_EMMC=bpi-r3-emmc_boot" >> $GITHUB_ENV + echo "ARCH=aarch64" >> $GITHUB_ENV echo "BUILD_EMMC=true" >> $GITHUB_ENV ;; friendlyarm-nanopi-r2s) - echo "BOOTLOADER=nanopi_r2s_boot" >> $GITHUB_ENV - echo "TARGET=aarch64" >> $GITHUB_ENV + echo "BOOTLOADER=nanopi-r2s-boot" >> $GITHUB_ENV + echo "ARCH=aarch64" >> $GITHUB_ENV echo "BUILD_EMMC=false" >> $GITHUB_ENV ;; *) @@ -74,7 +70,7 @@ jobs: exit 1 ;; esac - echo "Target: $TARGET for board: ${{ inputs.board }}" + echo "Arch: $ARCH for board: ${{ inputs.board }}" if [ "$BUILD_EMMC" = "true" ]; then echo "Building both SD and eMMC images" echo "SD Bootloader: $BOOTLOADER_SD" @@ -85,16 +81,12 @@ jobs: fi - name: Download bootloader artifacts - if: ${{ !inputs.use_latest_release }} run: | - # Download from latest bootloader build workflow on main branch - gh run list --workflow=build-boot.yml --branch=main --limit=1 --status=success --json databaseId --jq '.[0].databaseId' > latest_boot_run_id - BOOT_RUN_ID=$(cat latest_boot_run_id) - + # Download bootloader from latest-boot release tag if [ "$BUILD_EMMC" = "true" ]; then # Download both SD and eMMC bootloaders for boards that support both echo "Downloading SD bootloader: ${BOOTLOADER_SD}" - gh run download ${BOOT_RUN_ID} --name artifact-${BOOTLOADER_SD} --dir temp_bootloader_sd/ + gh release download latest-boot --pattern "*${BOOTLOADER_SD}*" --dir temp_bootloader_sd/ mkdir -p output_sd/images cd temp_bootloader_sd/ tar -xzf *.tar.gz --strip-components=1 -C ../output_sd/images/ @@ -102,7 +94,7 @@ jobs: rm -rf temp_bootloader_sd/ echo "Downloading eMMC bootloader: ${BOOTLOADER_EMMC}" - gh run download ${BOOT_RUN_ID} --name artifact-${BOOTLOADER_EMMC} --dir temp_bootloader_emmc/ + gh release download latest-boot --pattern "*${BOOTLOADER_EMMC}*" --dir temp_bootloader_emmc/ mkdir -p output_emmc/images cd temp_bootloader_emmc/ tar -xzf *.tar.gz --strip-components=1 -C ../output_emmc/images/ @@ -115,7 +107,7 @@ jobs: ls -la output_emmc/images/ else # Single bootloader for boards that only support SD - gh run download ${BOOT_RUN_ID} --name artifact-${BOOTLOADER} --dir temp_bootloader/ + gh release download latest-boot --pattern "*${BOOTLOADER}*" --dir temp_bootloader/ # Extract bootloader directly to output/images cd temp_bootloader/ @@ -130,7 +122,6 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Download Infix artifacts - if: ${{ !inputs.use_latest_release }} run: | # Download from latest Kernelkit Trigger workflow for main branch gh run list --workflow=164295764 --branch=main --limit=1 --status=success --json databaseId --jq '.[0].databaseId' > latest_infix_run_id @@ -138,7 +129,7 @@ jobs: if [ "$BUILD_EMMC" = "true" ]; then # Copy Infix artifacts to both SD and eMMC output directories - gh run download ${INFIX_RUN_ID} --name artifact-${TARGET} --dir temp_infix/ + gh run download ${INFIX_RUN_ID} --name artifact-${ARCH} --dir temp_infix/ cd temp_infix/ tar -xzf *.tar.gz --strip-components=1 -C ../output_sd/images/ @@ -152,7 +143,7 @@ jobs: ls -la output_emmc/images/ else # Single output directory for SD-only boards - gh run download ${INFIX_RUN_ID} --name artifact-${TARGET} --dir temp_infix/ + gh run download ${INFIX_RUN_ID} --name artifact-${ARCH} --dir temp_infix/ # Extract Infix directly to output/images cd temp_infix/ @@ -166,58 +157,6 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Download from latest releases - if: ${{ inputs.use_latest_release }} - run: | - if [ "$BUILD_EMMC" = "true" ]; then - # Download both SD and eMMC bootloaders - gh release download latest-boot --pattern "*${BOOTLOADER_SD}*" --dir temp_bootloader_sd/ - mkdir -p output_sd/images - cd temp_bootloader_sd/ - tar -xzf *.tar.gz --strip-components=1 -C ../output_sd/images/ - cd ../ - rm -rf temp_bootloader_sd/ - - gh release download latest-boot --pattern "*${BOOTLOADER_EMMC}*" --dir temp_bootloader_emmc/ - mkdir -p output_emmc/images - cd temp_bootloader_emmc/ - tar -xzf *.tar.gz --strip-components=1 -C ../output_emmc/images/ - cd ../ - rm -rf temp_bootloader_emmc/ - - # Download Infix once and extract to both directories - gh release download latest --pattern "*${TARGET}*" --dir temp_infix/ - cd temp_infix/ - tar -xzf *.tar.gz --strip-components=1 -C ../output_sd/images/ - tar -xzf *.tar.gz --strip-components=1 -C ../output_emmc/images/ - cd ../ - rm -rf temp_infix/ - - echo "SD files extracted to output_sd/images:" - ls -la output_sd/images/ - echo "eMMC files extracted to output_emmc/images:" - ls -la output_emmc/images/ - else - # Download latest bootloader release - gh release download latest-boot --pattern "*${BOOTLOADER}*" --dir temp_bootloader/ - cd temp_bootloader/ - tar -xzf *.tar.gz --strip-components=1 -C ../output/images/ - cd ../ - rm -rf temp_bootloader/ - - # Download latest Infix release - gh release download latest --pattern "*${TARGET}*" --dir temp_infix/ - cd temp_infix/ - tar -xzf *.tar.gz --strip-components=1 -C ../output/images/ - cd ../ - rm -rf temp_infix/ - - echo "All files extracted to output/images:" - ls -la output/images/ - fi - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Verify extracted files run: | if [ "$BUILD_EMMC" = "true" ]; then @@ -249,7 +188,7 @@ jobs: export BR2_EXTERNAL_INFIX_PATH=$PWD export RELEASE="" export INFIX_ID="infix" - ./utils/mkimage.sh ${{ inputs.board }} + ./utils/mkimage.sh -t sdcard ${{ inputs.board }} fi - name: Create eMMC image @@ -347,10 +286,11 @@ jobs: # SD Card & eMMC Image Build Complete! 🚀 **Board:** ${{ inputs.board }} - **Target:** ${{ env.TARGET }} + **Arch:** ${{ env.ARCH }} **SD Bootloader:** ${{ env.BOOTLOADER_SD }} **eMMC Bootloader:** ${{ env.BOOTLOADER_EMMC }} - **Artifact Source:** ${{ inputs.use_latest_release && 'Latest Release' || 'Latest Workflow Run' }} + **Bootloader Source:** latest-boot release + **Infix Source:** Latest workflow run on main ## Created Images $(find output/images/ -name "*.img" -o -name "*.img.bmap" | xargs ls -lh 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' || echo "- No images found") @@ -363,9 +303,10 @@ jobs: # SD Card Image Build Complete! 🚀 **Board:** ${{ inputs.board }} - **Target:** ${{ env.TARGET }} + **Arch:** ${{ env.ARCH }} **Bootloader:** ${{ env.BOOTLOADER }} - **Artifact Source:** ${{ inputs.use_latest_release && 'Latest Release' || 'Latest Workflow Run' }} + **Bootloader Source:** latest-boot release + **Infix Source:** Latest workflow run on main ## Created Images $(find output/images/ -name "*.img" -o -name "*.img.bmap" | xargs ls -lh 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' || echo "- No images found") From de0f3dce0f52ddcd42cfc9b0648eb52f204e9601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 20 Jan 2026 20:27:40 +0100 Subject: [PATCH 09/19] wifi: rpi: Silence some bogus error message related to the firmaware The firmware seems to keep states when have been configured as an AP then change to be a station. --- ...ppress-log-spam-for-regulatory-restr.patch | 43 ++++++++++++ ...duce-log-noise-during-AP-to-station-.patch | 68 +++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 patches/linux/6.18.6/0031-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch create mode 100644 patches/linux/6.18.6/0032-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch diff --git a/patches/linux/6.18.6/0031-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch b/patches/linux/6.18.6/0031-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch new file mode 100644 index 000000000..2cb246337 --- /dev/null +++ b/patches/linux/6.18.6/0031-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch @@ -0,0 +1,43 @@ +From f4d3d21bbb0bf21d7ad12cbb419e6a4c464ea619 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Tue, 20 Jan 2026 20:12:10 +0100 +Subject: [PATCH 31/32] wifi: brcmfmac: suppress log spam for + regulatory-restricted channels +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +When scanning, the driver attempts to set each channel and logs an +error if the firmware rejects it. For regulatory-restricted channels, +the firmware returns -52 (EBADE), which is expected behavior. + +Suppress the error message for -52 errors to avoid spamming the kernel +log during normal scan operations. + +Signed-off-by: Mattias Walström +--- + .../net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +index 1d937e41b95f..f49e11160d02 100644 +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +@@ -8280,7 +8280,12 @@ brcmf_set_channel(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, + if (chspec != INVCHANSPEC) { + err = brcmf_fil_iovar_int_set(ifp, "chanspec", chspec); + if (err) { +- brcmf_err("set chanspec 0x%04x fail, reason %d\n", chspec, err); ++ /* -52 (EBADE) is expected for regulatory-restricted ++ * channels, don't spam the log for these. ++ */ ++ if (err != -EBADE) ++ brcmf_err("set chanspec 0x%04x fail, reason %d\n", ++ chspec, err); + err = -EINVAL; + } + } else { +-- +2.43.0 + diff --git a/patches/linux/6.18.6/0032-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch b/patches/linux/6.18.6/0032-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch new file mode 100644 index 000000000..295041af5 --- /dev/null +++ b/patches/linux/6.18.6/0032-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch @@ -0,0 +1,68 @@ +From 3592365d811531eaafd1973ba4954b8131c47bf8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Tue, 20 Jan 2026 20:18:45 +0100 +Subject: [PATCH 32/32] wifi: brcmfmac: reduce log noise during AP to station + transition +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +When transitioning from AP mode to station mode (e.g., hostapd stopping +and wpa_supplicant starting), several non-fatal errors can occur: + +1. SET SSID error (-512) in stop_ap: This happens when hostapd is killed + before the firmware operation completes. -512 is ERESTARTSYS which is + expected in this scenario. + +2. MPC (Minimum Power Consumption) setting failures: These can occur + during mode transitions when the firmware is in an intermediate state. + MPC is a power optimization and failures are not critical. + +Suppress the ERESTARTSYS error in stop_ap and downgrade the MPC error +to debug level to reduce log spam during normal operation. + +Signed-off-by: Mattias Walström +--- + .../wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 13 +++++++++---- + 1 file changed, 9 insertions(+), 4 deletions(-) + +diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +index f49e11160d02..101cbb4e87e1 100644 +--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c ++++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +@@ -1130,13 +1130,15 @@ static void brcmf_scan_config_mpc(struct brcmf_if *ifp, int mpc) + + void brcmf_set_mpc(struct brcmf_if *ifp, int mpc) + { +- struct brcmf_pub *drvr = ifp->drvr; + s32 err = 0; + + if (check_vif_up(ifp->vif)) { + err = brcmf_fil_iovar_int_set(ifp, "mpc", mpc); + if (err) { +- bphy_err(drvr, "fail to set mpc\n"); ++ /* MPC setting can fail during mode transitions, ++ * this is not critical. ++ */ ++ brcmf_dbg(INFO, "fail to set mpc to %d: %d\n", mpc, err); + return; + } + brcmf_dbg(INFO, "MPC : %d\n", mpc); +@@ -5558,8 +5560,11 @@ static int brcmf_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev, + memset(&join_params, 0, sizeof(join_params)); + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, + &join_params, sizeof(join_params)); +- if (err < 0) +- bphy_err(drvr, "SET SSID error (%d)\n", err); ++ if (err < 0) { ++ /* -ERESTARTSYS is expected if hostapd was killed */ ++ if (err != -ERESTARTSYS) ++ bphy_err(drvr, "SET SSID error (%d)\n", err); ++ } + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1); + if (err < 0) + bphy_err(drvr, "BRCMF_C_DOWN error %d\n", err); +-- +2.43.0 + From 64162036122d3531bc9cfe7dea7c6ad57bdc43c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 20 Jan 2026 20:30:54 +0100 Subject: [PATCH 10/19] confd: Fix errors in the dependency tracking regarding Wi-Fi --- src/confd/src/core.c | 217 ++++++++++++++++++++++++------------------- 1 file changed, 120 insertions(+), 97 deletions(-) diff --git a/src/confd/src/core.c b/src/confd/src/core.c index 0dbcf3d60..cac6876f1 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -66,7 +66,7 @@ static confd_dependency_t add_dependencies(struct lyd_node **diff, const char *x static confd_dependency_t handle_dependencies(struct lyd_node **diff, struct lyd_node *config) { - struct lyd_node *dkeys, *dkey, *hostname; + struct lyd_node *dkeys, *dkey, *hostname, *dcomponents, *dcomponent; confd_dependency_t result = CONFD_DEP_DONE; const char *key_name; @@ -140,143 +140,166 @@ static confd_dependency_t handle_dependencies(struct lyd_node **diff, struct lyd } } - /* When WiFi interfaces change, add their radios to the diff */ struct lyd_node *difs = lydx_get_descendant(*diff, "interfaces", "interface", NULL); if (difs) { - struct lyd_node *dif; + struct lyd_node *dif, *cif; LYX_LIST_FOR_EACH(difs, dif, "interface") { - struct lyd_node *dwifi, *dcustom_phys, *radio_node, *cwifi; + struct lyd_node *dwifi, *dcustom_phys, *radio_node, *cwifi, *ap; const char *ifname, *radio_name; - char xpath[256]; bool is_wifi_change = false; + enum iftype type; + char xpath[256]; + - /* 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; + cif = lydx_get_xpathf(config, "/ietf-interfaces:interfaces/interface[name='%s']", ifname); + + + type = iftype_from_iface(dif); + if (type == IFT_UNKNOWN) + type = iftype_from_iface(cif); + + if (type == IFT_WIFI) { + /* Check if this interface has a wifi container in the diff */ + dwifi = lydx_get_child(dif, "wifi"); + if (dwifi) + is_wifi_change = true; + + ap = lydx_get_child(dwifi, "access-point"); + if (ap) + 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", - ifname); - if (!radio_node) - continue; + /* Get radio reference from config tree using xpath */ + radio_node = lydx_get_xpathf(config, + "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/radio", + ifname); + if (!radio_node) + continue; - radio_name = lyd_get_value(radio_node); - if (!radio_name) - continue; + radio_name = lyd_get_value(radio_node); - /* Add the radio to the diff */ - snprintf(xpath, sizeof(xpath), "/ietf-hardware:hardware/component[name='%s']/infix-hardware:wifi-radio", radio_name); - result = add_dependencies(diff, xpath, radio_name); - if (result == CONFD_DEP_ERROR) { - ERROR("Failed to add radio %s to diff for WiFi interface %s", radio_name, ifname); - return result; - } + if (!radio_name) + continue; + + /* Add the radio to the diff */ + snprintf(xpath, sizeof(xpath), "/ietf-hardware:hardware/component[name='%s']/infix-hardware:wifi-radio", radio_name); + result = add_dependencies(diff, xpath, radio_name); + if (result == CONFD_DEP_ERROR) { + ERROR("Failed to add radio %s to diff for WiFi interface %s", radio_name, ifname); + return result; + } - DEBUG("Added radio %s to diff for WiFi interface %s", radio_name, ifname); + DEBUG("Added radio %s to diff for WiFi interface %s", radio_name, ifname); + } } } - - /* When WiFi radios change, add all interfaces using that radio to the diff */ - struct ly_set *radios = lydx_find_xpathf(*diff, - "/ietf-hardware:hardware/component[infix-hardware:wifi-radio]"); - if (radios && radios->count > 0) { - for (uint32_t i = 0; i < radios->count; i++) { - struct lyd_node *hradio = radios->dnodes[i]; - const char *radio_name = lydx_get_cattr(hradio, "name"); + dcomponents = lydx_get_descendant(*diff, "hardware", "component", NULL); + LYX_LIST_FOR_EACH(dcomponents, dcomponent, "component") { + const char *class, *name; + + name = lydx_get_cattr(dcomponent, "name"); + if (!name) + continue; + + class = lydx_get_cattr(dcomponent, "class"); + if (!class) { + struct lyd_node *class_node = lydx_get_xpathf(config, "/ietf-hardware:hardware/component[name='%s']/class", name); + if (class_node) + class = lyd_get_value(class_node); + } + if (!strcmp(class, "infix-hardware:wifi")) + { struct ly_set *ifaces; uint32_t j; char xpath[256]; - if (!radio_name) - continue; - /* Find all interfaces that reference this radio */ ifaces = lydx_find_xpathf(config, - "/ietf-interfaces:interfaces/interface[infix-interfaces:wifi/radio='%s']", - radio_name); - if (ifaces && ifaces->count > 0) { - for (j = 0; j < ifaces->count; j++) { - const char *ifname = lydx_get_cattr(ifaces->dnodes[j], "name"); + "/ietf-interfaces:interfaces/interface/infix-interfaces:wifi/radio[.='%s']", + name); + if (!ifaces || ifaces->count == 0) { + if (ifaces) + ly_set_free(ifaces, NULL); + continue; + } + DEBUG("Found %d interfaces using radio %s", ifaces->count, name); + + for (j = 0; j < ifaces->count; j++) { + /* ifaces->dnodes[j] is the radio leaf, navigate up: radio -> wifi -> interface */ + struct lyd_node *wifi_node = lyd_parent(ifaces->dnodes[j]); + struct lyd_node *iface_node = lyd_parent(wifi_node); + const char *ifname; + + ifname = lydx_get_cattr(iface_node, "name"); + if (!ifname) + continue; + + /* Add the wifi container */ + snprintf(xpath, sizeof(xpath), + "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi", + ifname); + result = add_dependencies(diff, xpath, ifname); + if (result == CONFD_DEP_ERROR) { + ERROR("Failed to add interface wifi for %s (radio %s)", ifname, name); + ly_set_free(ifaces, NULL); + return result; + } + + /* Add the radio leaf */ + snprintf(xpath, sizeof(xpath), + "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/radio", + ifname); + result = add_dependencies(diff, xpath, name); + if (result == CONFD_DEP_ERROR) { + ERROR("Failed to add radio leaf for interface %s (radio %s)", ifname, name); + ly_set_free(ifaces, NULL); + return result; + } - /* Add the wifi container */ + /* Add station or access-point container depending on mode */ + if (lydx_get_child(wifi_node, "station")) { snprintf(xpath, sizeof(xpath), - "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi", + "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/station", ifname); result = add_dependencies(diff, xpath, ifname); if (result == CONFD_DEP_ERROR) { - ERROR("Failed to add interface wifi for %s (radio %s)", ifname, radio_name); + ERROR("Failed to add station for interface %s (radio %s)", ifname, name); ly_set_free(ifaces, NULL); - ly_set_free(radios, NULL); return result; } - - /* Add the radio leaf */ + } else if (lydx_get_child(wifi_node, "access-point")) { snprintf(xpath, sizeof(xpath), - "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/radio", + "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/access-point", ifname); - result = add_dependencies(diff, xpath, radio_name); + result = add_dependencies(diff, xpath, ifname); if (result == CONFD_DEP_ERROR) { - ERROR("Failed to add radio leaf for interface %s (radio %s)", ifname, radio_name); + ERROR("Failed to add access-point for interface %s (radio %s)", ifname, name); ly_set_free(ifaces, NULL); - ly_set_free(radios, NULL); return result; } - - /* Add station or access-point container depending on mode */ - if (lydx_get_descendant(ifaces->dnodes[j], "interface", "wifi", "station", NULL)) { - snprintf(xpath, sizeof(xpath), - "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/station", - ifname); - result = add_dependencies(diff, xpath, ifname); - if (result == CONFD_DEP_ERROR) { - ERROR("Failed to add station for interface %s (radio %s)", ifname, radio_name); - ly_set_free(ifaces, NULL); - ly_set_free(radios, NULL); - return result; - } - } else if (lydx_get_descendant(ifaces->dnodes[j], "interface", "wifi", "access-point", NULL)) { - snprintf(xpath, sizeof(xpath), - "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/access-point", - ifname); - result = add_dependencies(diff, xpath, ifname); - if (result == CONFD_DEP_ERROR) { - ERROR("Failed to add access-point for interface %s (radio %s)", ifname, radio_name); - ly_set_free(ifaces, NULL); - ly_set_free(radios, NULL); - return result; - } - } - - DEBUG("Added interface %s to diff for radio %s", ifname, radio_name); } - ly_set_free(ifaces, NULL); + + DEBUG("Added interface %s to diff for radio %s", ifname, name); } + ly_set_free(ifaces, NULL); } - ly_set_free(radios, NULL); } return result; From 46f0cdc50420eb53b28e54570d86a14b70ea95c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 20 Jan 2026 20:32:21 +0100 Subject: [PATCH 11/19] confd: wifi: Refactor station out from hardware.c This is run per interface, not per radio so its place in hardware.c was wrong. This also opens up for multiple station interfaces per radio, but not for now that it locked down in a must expression for now. --- src/confd/src/hardware.c | 176 ++------------------------------------- src/confd/src/if-wifi.c | 122 ++++++++++++++++++++++++--- 2 files changed, 120 insertions(+), 178 deletions(-) diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index d7e7d8726..87526f18e 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -16,8 +16,6 @@ #define XPATH_BASE_ "/ietf-hardware:hardware" #define HOSTAPD_CONF "/etc/hostapd-%s.conf" #define HOSTAPD_CONF_NEXT HOSTAPD_CONF"+" -#define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf" -#define WPA_SUPPLICANT_CONF_NEXT WPA_SUPPLICANT_CONF"+" static int dir_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) @@ -189,82 +187,6 @@ static int wifi_find_interfaces_on_radio(struct lyd_node *ifs, const char *radio return 0; } -static int wifi_gen_station(const char *ifname, struct lyd_node *station, - const char *radio, struct lyd_node *config) -{ - const char *ssid, *secret_name, *secret, *security_mode; - struct lyd_node *security, *secret_node, *radio_node; - FILE *wpa_supplicant = NULL; - char *security_str = NULL; - const char *country; - int rc = SR_ERR_OK; - - /* If station is NULL, we're in scan-only mode (no station container) */ - if (station) { - ssid = lydx_get_cattr(station, "ssid"); - security = lydx_get_child(station, "security"); - security_mode = lydx_get_cattr(security, "mode"); - secret_name = lydx_get_cattr(security, "secret"); - } else { - ssid = NULL; - security = NULL; - security_mode = "disabled"; - secret_name = NULL; - } - - radio_node = lydx_get_xpathf(config, - "/hardware/component[name='%s']/wifi-radio", radio); - country = radio_node ? lydx_get_cattr(radio_node, "country-code") : NULL; - - if (secret_name && strcmp(security_mode, "disabled") != 0) { - secret_node = lydx_get_xpathf(config, - "/keystore/symmetric-keys/symmetric-key[name='%s']/symmetric-key", - secret_name); - secret = secret_node ? lyd_get_value(secret_node) : NULL; - } else { - secret = NULL; - } - - wpa_supplicant = fopenf("w", WPA_SUPPLICANT_CONF_NEXT, ifname); - if (!wpa_supplicant) { - rc = SR_ERR_INTERNAL; - goto out; - } - - fprintf(wpa_supplicant, - "ctrl_interface=/run/wpa_supplicant\n" - "autoscan=periodic:10\n" - "ap_scan=1\n"); - - if (country) - fprintf(wpa_supplicant, "country=%s\n", country); - - /* If SSID is present, create network block. Otherwise, scan-only mode */ - if (ssid) { - /* Station mode with network configured */ - if (!strcmp(security_mode, "disabled")) { - asprintf(&security_str, "key_mgmt=NONE"); - } else if (secret) { - asprintf(&security_str, "key_mgmt=SAE WPA-PSK\npsk=\"%s\"", secret); - } - fprintf(wpa_supplicant, - "network={\n" - "bgscan=\"simple: 30:-45:300\"\n" - "ssid=\"%s\"\n" - "%s\n" - "}\n", ssid, security_str); - free(security_str); - } else { - /* Scan-only mode - no station container configured */ - fprintf(wpa_supplicant, "# Scan-only mode - no network configured\n"); - } - -out: - if (wpa_supplicant) - fclose(wpa_supplicant); - return rc; -} - /* Helper: Find all AP interfaces on a specific radio */ static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name, char ***ap_list, int *count) @@ -696,7 +618,7 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l } else if (!strcmp(class, "infix-hardware:wifi")) { struct lyd_node *interfaces_config, *interfaces_diff; struct lyd_node **wifi_iface_list = NULL; - struct lyd_node *station, *ap; + struct lyd_node *ap; struct lyd_node *cwifi_radio; int wifi_iface_count = 0; char src[40], dst[40]; @@ -710,58 +632,13 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l case SR_EV_DONE: interfaces_diff = lydx_get_descendant(diff, "interfaces", "interface", NULL); - /* Handle deleted WiFi interfaces - stop wpa_supplicant */ - if (interfaces_diff) { - struct lyd_node *iface, *wifi; - const char *radio; - - LYX_LIST_FOR_EACH(interfaces_diff, iface, "interface") { - const char *ifname; - if (lydx_get_op(iface) != LYDX_OP_DELETE) - continue; - wifi = lydx_get_child(iface, "wifi"); - if (!wifi) - continue; - radio = lydx_get_cattr(wifi, "radio"); - if (!radio || strcmp(radio, name)) - continue; - - ifname = lydx_get_cattr(iface, "name"); - erasef(WPA_SUPPLICANT_CONF, ifname); - erasef(WPA_SUPPLICANT_CONF_NEXT, ifname); - systemf("initctl -bfq disable wifi@%s", ifname); - } - } - wifi_find_interfaces_on_radio(interfaces_diff, name, &wifi_iface_list, &wifi_iface_count); if (wifi_iface_count > 0) { bool running, enabled; - station = lydx_get_descendant(wifi_iface_list[0], "interface", "wifi", "station", NULL); - ap = lydx_get_descendant(wifi_iface_list[0], "interface", "wifi", "access-point", NULL); - if (station || !ap || lydx_get_op(ap) == LYDX_OP_DELETE) { - const char *ifname = lydx_get_cattr(wifi_iface_list[0], "name"); - running = !systemf("initctl -bfq status wpa_supplicant:%s", ifname); - if (lydx_get_op(station) == LYDX_OP_DELETE) { - erasef(WPA_SUPPLICANT_CONF, ifname); - erasef(WPA_SUPPLICANT_CONF_NEXT, ifname); - systemf("initctl -bfq disable wifi@%s", ifname); - } else { - snprintf(src, sizeof(src), WPA_SUPPLICANT_CONF_NEXT, ifname); - snprintf(dst, sizeof(dst), WPA_SUPPLICANT_CONF, ifname); - running = !systemf("initctl -bfq status wpa_supplicant:%s", ifname); - enabled = fexistf(WPA_SUPPLICANT_CONF_NEXT, ifname); - - if (enabled) { - (void)rename(src, dst); - if (running) - systemf("initctl -bfq touch wifi@%s", ifname); - else - systemf("initctl -bfq enable wifi@%s", ifname); - } - } - } else if (wifi_iface_count > 0) { + ap = lydx_get_descendant(wifi_iface_list[0], "interface", "wifi", "access-point", NULL); + if (ap && lydx_get_op(ap) != LYDX_OP_DELETE) { /* AP mode - activate hostapd for radio */ snprintf(src, sizeof(src), HOSTAPD_CONF_NEXT, name); snprintf(dst, sizeof(dst), HOSTAPD_CONF, name); @@ -802,48 +679,11 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l if (!wifi_iface_count) continue; - /* - * A radio operates in one of three modes: - * 1. Station mode: One station interface (client mode) - * 2. AP mode: One or more AP interfaces (hostapd multi-SSID) - * 3. Scan-only mode: WiFi interface with radio but no mode configured - * - * Check for station first - there can be at most one per radio. - * If no station or AP is configured, default to scan-only mode. - */ - station = lydx_get_descendant(wifi_iface_list[0], "interface", "wifi", "station", NULL); - ap = lydx_get_descendant(wifi_iface_list[0], "interface", "wifi", "access-point", NULL); - if (wifi_iface_count == 1 && station) { - /* Station mode (with or without SSID for scan-only) */ - struct lyd_node *iface = wifi_iface_list[0]; - if (lydx_is_enabled(iface, "enabled")) { - const char *ifname = lydx_get_cattr(iface, "name"); - rc = wifi_gen_station(ifname, station, name, config); - if (rc != SR_ERR_OK) { - ERROR("Failed to generate station config for %s", ifname); - goto next; - } - } - } else if (!station && !ap) { - /* No station/AP configured - default to scan-only mode */ - struct lyd_node *iface = wifi_iface_list[0]; - if (lydx_is_enabled(iface, "enabled")) { - const char *ifname = lydx_get_cattr(iface, "name"); - rc = wifi_gen_station(ifname, NULL, name, config); - if (rc != SR_ERR_OK) { - ERROR("Failed to generate scan-only config for %s", ifname); - goto next; - } - } - } else { - /* Multiple interfaces or APs */ - rc = wifi_gen_aps_on_radio(name, interfaces_config, cwifi_radio, config); - if (rc != SR_ERR_OK) { - ERROR("Failed to generate AP config for radio %s", name); - goto next; - } - } - next: + + /* Generate AP config (hostapd) for all APs on this radio */ + rc = wifi_gen_aps_on_radio(name, interfaces_config, cwifi_radio, config); + if (rc != SR_ERR_OK) + ERROR("Failed to generate AP config for radio %s", name); /* Free the interface list */ free(wifi_iface_list); wifi_iface_list = NULL; diff --git a/src/confd/src/if-wifi.c b/src/confd/src/if-wifi.c index 9aebc7178..101afa172 100644 --- a/src/confd/src/if-wifi.c +++ b/src/confd/src/if-wifi.c @@ -3,12 +3,14 @@ #include "interfaces.h" +#define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf" + /* * WiFi Interface Management * - * This file handles only virtual WiFi interface creation/deletion. - * WiFi daemon configuration (hostapd/wpa_supplicant) is handled by - * hardware.c when the WiFi radio (phy) is configured. + * This file handles virtual WiFi interface creation/deletion and + * station mode configuration (wpa_supplicant). Access point mode + * configuration (hostapd) is handled by hardware.c. */ /* @@ -19,9 +21,12 @@ typedef enum wifi_mode_t { wifi_ap, wifi_unknown } wifi_mode_t; + static wifi_mode_t wifi_get_mode(struct lyd_node *wifi) { - if (lydx_get_child(wifi, "access-point")) + struct lyd_node *ap = lydx_get_child(wifi, "access-point"); + + if (ap && (lydx_get_op(ap) != LYDX_OP_DELETE)) return wifi_ap; else return wifi_station; /* Need to return station even if "station" also is false, since that is the default scanning mode */ @@ -29,20 +34,106 @@ static wifi_mode_t wifi_get_mode(struct lyd_node *wifi) int wifi_mode_changed(struct lyd_node *wifi) { - struct lyd_node *station, *ap; - enum lydx_op station_op, ap_op; + struct lyd_node *ap; + enum lydx_op ap_op; if (!wifi) return 0; - station = lydx_get_child(wifi, "station"); + ap = lydx_get_child(wifi, "access-point"); - if (station) - station_op = lydx_get_op(station); + if (ap) ap_op = lydx_get_op(ap); - return ((station && station_op == LYDX_OP_DELETE) || (ap && ap_op == LYDX_OP_DELETE)); + ERROR("MODE CHANGED: %d", ap && (ap_op == LYDX_OP_CREATE || ap_op == LYDX_OP_DELETE)); + return (ap && (ap_op == LYDX_OP_CREATE || ap_op == LYDX_OP_DELETE)); } + +/* + * Generate wpa_supplicant config for station mode + */ +static int wifi_gen_station(struct lyd_node *cif) +{ + const char *ifname, *ssid, *secret_name, *secret, *security_mode, *radio; + struct lyd_node *security, *secret_node, *radio_node, *station, *wifi; + FILE *wpa_supplicant = NULL; + char *security_str = NULL; + const char *country; + int rc = SR_ERR_OK; + + ifname = lydx_get_cattr(cif, "name"); + wifi = lydx_get_child(cif, "wifi"); + if (!wifi) + return SR_ERR_OK; + + radio = lydx_get_cattr(wifi, "radio"); + station = lydx_get_child(wifi, "station"); + /* If station is NULL, we're in scan-only mode (no station container) */ + if (station) { + ssid = lydx_get_cattr(station, "ssid"); + security = lydx_get_child(station, "security"); + security_mode = lydx_get_cattr(security, "mode"); + secret_name = lydx_get_cattr(security, "secret"); + } else { + ssid = NULL; + security = NULL; + security_mode = "disabled"; + secret_name = NULL; + } + + radio_node = lydx_get_xpathf(cif, + "../../hardware/component[name='%s']/wifi-radio", radio); + country = lydx_get_cattr(radio_node, "country-code"); + + if (secret_name && strcmp(security_mode, "disabled") != 0) { + secret_node = lydx_get_xpathf(cif, + "../../keystore/symmetric-keys/symmetric-key[name='%s']", + secret_name); + secret = lydx_get_cattr(secret_node, "symmetric-key"); + } else { + secret = NULL; + } + + wpa_supplicant = fopenf("w", WPA_SUPPLICANT_CONF, ifname); + if (!wpa_supplicant) { + rc = SR_ERR_INTERNAL; + goto out; + } + + fprintf(wpa_supplicant, + "ctrl_interface=/run/wpa_supplicant\n" + "autoscan=periodic:10\n" + "ap_scan=1\n"); + + if (country) + fprintf(wpa_supplicant, "country=%s\n", country); + + /* If SSID is present, create network block. Otherwise, scan-only mode */ + if (ssid) { + /* Station mode with network configured */ + if (!strcmp(security_mode, "disabled")) { + asprintf(&security_str, "key_mgmt=NONE"); + } else if (secret) { + asprintf(&security_str, "key_mgmt=SAE WPA-PSK\npsk=\"%s\"", secret); + } + fprintf(wpa_supplicant, + "network={\n" + "bgscan=\"simple: 30:-45:300\"\n" + "ssid=\"%s\"\n" + "%s\n" + "}\n", ssid, security_str); + free(security_str); + } else { + /* Scan-only mode - no station container configured */ + fprintf(wpa_supplicant, "# Scan-only mode - no network configured\n"); + } + +out: + if (wpa_supplicant) + fclose(wpa_supplicant); + return rc; +} + /* * Add WiFi virtual interface using iw */ @@ -83,6 +174,9 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net) switch(mode) { case wifi_station: fprintf(iw, "iw phy %s interface add %s type managed\n", radio, ifname); + wifi_gen_station(cif); + fprintf(iw, "initctl -bfq enable wifi@%s\n", ifname); + fprintf(iw, "initctl -bfq touch wifi@%s\n", ifname); break; case wifi_ap: fprintf(iw, "iw phy %s interface add %s type __ap\n", radio, ifname); @@ -102,6 +196,7 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net) */ int wifi_del_iface(struct lyd_node *dif, struct dagger *net) { + struct lyd_node *wifi; const char *ifname; FILE *iw; @@ -117,6 +212,13 @@ int wifi_del_iface(struct lyd_node *dif, struct dagger *net) fprintf(iw, "ip link set %s down\n", ifname); /* Required to change modes. */ fprintf(iw, "iw dev %s disconnect\n", ifname); fprintf(iw, "iw dev %s del\n", ifname); + + wifi = lydx_get_child(dif, "wifi"); + if (wifi && wifi_get_mode(wifi) == wifi_ap) { /* if not station or scanning mode */ + erasef(WPA_SUPPLICANT_CONF, ifname); + fprintf(iw, "initctl -bfq disable wifi@%s\n", ifname); + } + fclose(iw); return SR_ERR_OK; From 45b14145e893254259b63fd8fd7b8c31fd94d0ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 20 Jan 2026 21:00:17 +0100 Subject: [PATCH 12/19] yang: Lockdown to only allow one station (or scanning) interface per radio This since this is 99% of the cases wrong config, and crashes on many Wi-Fi chipset. --- src/confd/yang/confd/infix-if-wifi.yang | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index 687fab567..7a18ef062 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -99,6 +99,9 @@ submodule infix-if-wifi { must "derived-from-or-self(/iehw:hardware/iehw:component[iehw:name=current()]/iehw:class, 'ih:wifi')" { error-message "Referenced hardware component must be a WiFi radio (class 'ih:wifi')"; } + must "count(/if:interfaces/if:interface[wifi/radio = current()][not(wifi/access-point)]) <= 1" { + error-message "Only one station or scan interface is allowed per radio"; + } description "Reference to parent WiFi radio (PHY). @@ -134,10 +137,6 @@ submodule infix-if-wifi { container station { presence "Configure WiFi station (client) mode"; - must "count(/if:interfaces/if:interface[wifi/radio = current()/../radio][wifi/station]) <= 1" { - error-message "Only one station interface is allowed per radio"; - } - description "WiFi Station mode configuration. From 3625551be7ca7bff9b0a74c5e0b114d6c4ee2dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 20 Jan 2026 21:02:32 +0100 Subject: [PATCH 13/19] kernel: Fix kernel panic in brcmfmac Happened when adding a second station interface (only supports one), was lacking a check for a nullpointer. --- ...pport-deletion-and-recreation-of-pri.patch | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/patches/linux/6.18.6/0029-wifi-brcmfmac-support-deletion-and-recreation-of-pri.patch b/patches/linux/6.18.6/0029-wifi-brcmfmac-support-deletion-and-recreation-of-pri.patch index 393637241..24efb6b5e 100644 --- a/patches/linux/6.18.6/0029-wifi-brcmfmac-support-deletion-and-recreation-of-pri.patch +++ b/patches/linux/6.18.6/0029-wifi-brcmfmac-support-deletion-and-recreation-of-pri.patch @@ -1,11 +1,12 @@ -From 6d6e9958d1066f53e1359c09ff3e92386244c89e Mon Sep 17 00:00:00 2001 -From: Mattias Walström +From d571b8417741b2dbed814bc38c133dcf37055c66 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Thu, 15 Jan 2026 22:47:37 +0100 -Subject: [PATCH 29/30] wifi: brcmfmac: support deletion and recreation of +Subject: [PATCH 29/32] wifi: brcmfmac: support deletion and recreation of primary interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit +Organization: Wires The Broadcom FullMAC firmware does not allow the primary interface (bsscfgidx 0) to be deleted - it always exists in firmware. However, @@ -37,14 +38,14 @@ Add support for this by: Signed-off-by: Mattias Walström --- - .../broadcom/brcm80211/brcmfmac/cfg80211.c | 177 ++++++++++++++++-- + .../broadcom/brcm80211/brcmfmac/cfg80211.c | 179 ++++++++++++++++-- .../broadcom/brcm80211/brcmfmac/cfg80211.h | 4 +- .../broadcom/brcm80211/brcmfmac/core.c | 6 +- .../broadcom/brcm80211/brcmfmac/p2p.c | 16 +- - 4 files changed, 183 insertions(+), 20 deletions(-) + 4 files changed, 185 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c -index bb96b87b2a6e..9c8128551072 100644 +index bb96b87b2a6e..41b75ebc9d4f 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -124,6 +124,9 @@ struct cca_msrmnt_query { @@ -269,7 +270,16 @@ index bb96b87b2a6e..9c8128551072 100644 return brcmf_cfg80211_del_apsta_iface(wiphy, wdev); case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_P2P_GO: -@@ -8114,11 +8261,11 @@ brcmf_dump_obss(struct brcmf_if *ifp, struct cca_msrmnt_query req, +@@ -7790,6 +7937,8 @@ static s32 brcmf_config_dongle(struct brcmf_cfg80211_info *cfg) + return err; + + ndev = cfg_to_ndev(cfg); ++ if (!ndev) ++ return -ENODEV; + wdev = ndev->ieee80211_ptr; + ifp = netdev_priv(ndev); + +@@ -8114,11 +8263,11 @@ brcmf_dump_obss(struct brcmf_if *ifp, struct cca_msrmnt_query req, } static s32 @@ -283,7 +293,7 @@ index bb96b87b2a6e..9c8128551072 100644 if (chan->flags & IEEE80211_CHAN_DISABLED) return -EINVAL; -@@ -8144,7 +8291,7 @@ brcmf_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *ndev, +@@ -8144,7 +8293,7 @@ brcmf_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *ndev, int idx, struct survey_info *info) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); @@ -292,7 +302,7 @@ index bb96b87b2a6e..9c8128551072 100644 struct brcmf_dump_survey survey = {}; struct ieee80211_supported_band *band; enum nl80211_band band_id; -@@ -8175,21 +8322,21 @@ brcmf_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *ndev, +@@ -8175,21 +8324,21 @@ brcmf_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *ndev, if (band_id == NUM_NL80211_BANDS) return -ENOENT; @@ -396,3 +406,6 @@ index e1752a513c73..2dc0fbba271d 100644 if (brcmf_cfg80211_vif_event_armed(cfg)) return ERR_PTR(-EBUSY); +-- +2.43.0 + From a1ef052a6060da7a85f61167dc7aa25a22d3ff05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 20 Jan 2026 21:05:40 +0100 Subject: [PATCH 14/19] YANG: Remove precence of wifi-container This is obsolete. By remove this we expose radio as mandatory. This since a Wi-Fi interface is not possible to create without a radio to connect it to. --- src/confd/yang/confd/infix-if-wifi.yang | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index 7a18ef062..c5d1615b1 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -77,10 +77,8 @@ submodule infix-if-wifi { description "Applies only to interfaces of type 'wifi'."; } - container wifi { if-feature wifi; - presence "Configure Wi-Fi virtual interface"; description "WiFi virtual interface configuration. From 14c22b05ad8e67757b060dad4c510de45d49991e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 20 Jan 2026 22:19:46 +0100 Subject: [PATCH 15/19] Revert "udev: Always enumerate wifi phys from 0" This reverts commit bf1eca05679ed288d3b99e5a9814874f8618fc7a. This mess up order if having more than 1 phy, have not been reproduce the error that got me to introduce this fix from the beginning. --- .../rootfs/etc/udev/rules.d/60-rename-wifi-phy.rules | 2 +- board/common/rootfs/usr/libexec/infix/rename-wifi-phy | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100755 board/common/rootfs/usr/libexec/infix/rename-wifi-phy diff --git a/board/common/rootfs/etc/udev/rules.d/60-rename-wifi-phy.rules b/board/common/rootfs/etc/udev/rules.d/60-rename-wifi-phy.rules index a74495064..89c641545 100644 --- a/board/common/rootfs/etc/udev/rules.d/60-rename-wifi-phy.rules +++ b/board/common/rootfs/etc/udev/rules.d/60-rename-wifi-phy.rules @@ -1,3 +1,3 @@ # Rename WiFi PHY devices from phy0 to radio0 to avoid name clashes SUBSYSTEM=="ieee80211", ACTION=="add", KERNEL=="phy*", \ - RUN+="/usr/libexec/infix/rename-wifi-phy %k" + RUN+="/bin/sh -c '/usr/sbin/iw phy %k set name radio%n'" diff --git a/board/common/rootfs/usr/libexec/infix/rename-wifi-phy b/board/common/rootfs/usr/libexec/infix/rename-wifi-phy deleted file mode 100755 index b50dcda80..000000000 --- a/board/common/rootfs/usr/libexec/infix/rename-wifi-phy +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# Rename WiFi PHY to radio{N}, enumerating from 0 -phy="$1" -n=0 -while [ -d "/sys/class/ieee80211/radio$n" ]; do - n=$((n + 1)) -done -/usr/sbin/iw phy "$phy" set name "radio$n" From dd67e60952849169753aab2dd959a0271cbf5e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 20 Jan 2026 22:33:07 +0100 Subject: [PATCH 16/19] confd: wireguard: Add check for null pointer --- src/confd/src/if-wireguard.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/confd/src/if-wireguard.c b/src/confd/src/if-wireguard.c index 7552885aa..f4ca46403 100644 --- a/src/confd/src/if-wireguard.c +++ b/src/confd/src/if-wireguard.c @@ -150,6 +150,8 @@ int wireguard_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, struct d /* Create activation script */ wg_sh = dagger_fopen_net_init(net, ifname, NETDAG_INIT_POST, "enable-wireguard.sh"); + if (!wg_sh) + return SR_ERR_INTERNAL; fprintf(wg_sh, "wg setconf %s ", ifname); fprintf(wg_sh, WIREGUARD_CONFIG, ifname); From 82ae720775b82db4cc539e84bd10f2a8c6548dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 21 Jan 2026 17:07:10 +0100 Subject: [PATCH 17/19] confd: Fix migration script When migrate to 1.7 the keystore was not correctly migrated. --- .../1.7/20-keystore-cleartext-key-rename.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/confd/share/migrate/1.7/20-keystore-cleartext-key-rename.sh b/src/confd/share/migrate/1.7/20-keystore-cleartext-key-rename.sh index fec54bc5e..b55b42d79 100755 --- a/src/confd/share/migrate/1.7/20-keystore-cleartext-key-rename.sh +++ b/src/confd/share/migrate/1.7/20-keystore-cleartext-key-rename.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Rename cleartext-symmetric-key to symmetric-key +# Rename cleartext-key to symmetric-key file=$1 temp=${file}.tmp @@ -7,12 +7,12 @@ temp=${file}.tmp jq ' if .["ietf-keystore:keystore"]?."symmetric-keys"?."symmetric-key" then .["ietf-keystore:keystore"]."symmetric-keys"."symmetric-key" |= map( - if ."key-type"?."cleartext-key" then - # Rename cleartext-key to cleartext-symmetric-key - ."key-type"."cleartext-key" as $key_value | - ."key-type" |= (del(."cleartext-key") | . + { - "symmetric-key": $key_value - }) + if ."infix-keystore:cleartext-key" then + # Rename cleartext-key to symmetric-key + ."infix-keystore:cleartext-key" as $key_value | + del(."infix-keystore:cleartext-key") | . + { + "infix-keystore:symmetric-key": $key_value + } else . end From c4926f2a796286ea32e3c9d03a5164aa4a82cd87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 21 Jan 2026 20:35:16 +0100 Subject: [PATCH 18/19] confd: wifi: Disable legacy rates (802.11b) This may improve range of Wi-Fi, with low/none impact. This could make configurable later on. --- src/confd/src/hardware.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index 87526f18e..e801e393a 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -436,6 +436,10 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, /* Set hardware mode based on band */ if (!strcmp(band, "2.4GHz")) { fprintf(hostapd, "hw_mode=g\n"); + + /* Disable 802.11b rates, ancient devices. This will improve range. */ + fprintf(hostapd, "supported_rates=60 90 120 180 240 360 480 540\n"); + fprintf(hostapd, "basic_rates=60 120 240\n"); } else if (!strcmp(band, "5GHz") || !strcmp(band, "6GHz")) { fprintf(hostapd, "hw_mode=a\n"); } From 433fd77549dd421b2b76248404ae900edb5f903e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 21 Jan 2026 21:52:00 +0100 Subject: [PATCH 19/19] confd: wifi: Enable wmm (Wi-Fi Multimedia) - QoS for WiFi. This should always be enabled. It is a requirement for higher speeds n/ac/ax. --- src/confd/src/hardware.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index e801e393a..8bbff3fa7 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -269,6 +269,7 @@ static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char if (hidden && !strcmp(hidden, "true")) fprintf(hostapd, "ignore_broadcast_ssid=1\n"); + fprintf(hostapd, "wmm_enabled=1\n"); /* Security configuration */ security = lydx_get_child(ap, "security"); security_mode = lydx_get_cattr(security, "mode"); @@ -430,6 +431,7 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, /* Enable 802.11d (regulatory domain) and 802.11h (spectrum management/DFS) */ fprintf(hostapd, "ieee80211d=1\n"); fprintf(hostapd, "ieee80211h=1\n"); + fprintf(hostapd, "wmm_enabled=1\n"); /* Band and channel configuration */ if (band) {