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") 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" diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 878483d14..fd505e67a 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -65,7 +65,7 @@ All notable changes to the project are documented in this file. newer EEPROM firmware in newer boards require a newer rpi-firmware package - Fix #1082: Wi-Fi interfaces always scanned, introduce a `scan-mode` to the Wi-Fi concept in Infix. - +- Fix mDNS reflector. [v25.11.0][] - 2025-12-02 ------------------------- 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/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 + 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 + 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 diff --git a/src/confd/src/core.c b/src/confd/src/core.c index fab04e787..cac6876f1 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; @@ -64,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; @@ -138,126 +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, *radio_node; + struct lyd_node *dwifi, *dcustom_phys, *radio_node, *cwifi, *ap; const char *ifname, *radio_name; + bool is_wifi_change = false; + enum iftype type; char xpath[256]; - /* Check if this interface has a wifi container in the diff */ - dwifi = lydx_get_child(dif, "wifi"); - if (!dwifi) - continue; - /* Get the interface name */ ifname = lydx_get_cattr(dif, "name"); if (!ifname) 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; + 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; + } + } - radio_name = lyd_get_value(radio_node); - if (!radio_name) - 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; - /* 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; - } + radio_name = lyd_get_value(radio_node); - DEBUG("Added radio %s to diff for WiFi interface %s", radio_name, ifname); + 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); + } } } - - /* 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; - /* Add the wifi container */ + 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 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; diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index c7da58da3..8bbff3fa7 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) @@ -347,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"); @@ -413,8 +336,9 @@ 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; int rc = SR_ERR_OK; @@ -458,7 +382,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) { @@ -492,7 +416,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"); @@ -508,12 +431,17 @@ 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) { /* 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"); } @@ -544,20 +472,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"); @@ -696,7 +624,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 +638,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 +685,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; 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); 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="); } 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."; } /* diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index 843421f94..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. @@ -99,6 +97,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 +135,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. @@ -401,23 +398,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/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). 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" 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]) diff --git a/test/case/services/mdns/all.yaml b/test/case/services/mdns/all.yaml index 79c69f2cb..f1932d02b 100644 --- a/test/case/services/mdns/all.yaml +++ b/test/case/services/mdns/all.yaml @@ -4,3 +4,6 @@ - name: mDNS allow/deny interfaces case: mdns_allow_deny/test.py + +- name: mDNS reflector + case: mdns_reflector/test.py diff --git a/test/case/services/mdns/mdns_reflector/Readme.adoc b/test/case/services/mdns/mdns_reflector/Readme.adoc new file mode 120000 index 000000000..ae32c8412 --- /dev/null +++ b/test/case/services/mdns/mdns_reflector/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/services/mdns/mdns_reflector/test.adoc b/test/case/services/mdns/mdns_reflector/test.adoc new file mode 100644 index 000000000..4937cd742 --- /dev/null +++ b/test/case/services/mdns/mdns_reflector/test.adoc @@ -0,0 +1,29 @@ +=== mDNS reflector + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/services/mdns/mdns_reflector] + +==== Description + +Verify the mDNS reflector functionality. The reflector forwards mDNS +requests and responses between different network interfaces, allowing +service discovery across network segments. + +We verify operation with two scenarios: + + 1. Reflector enabled: mDNS traffic from host:data1 SHOULD be reflected + to host:data2 and host:data3 + 2. Reflector disabled: mDNS traffic from host:data1 should NOT be + reflected to host:data2 and host:data3 + +==== Topology + +image::topology.svg[mDNS reflector topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to target DUT +. Configure device interfaces and enable mDNS +. Verify mDNS from host:data1 is reflected to host:data2 and host:data3 +. Verify mDNS from host:data1 is not reflected after disabling reflector + + diff --git a/test/case/services/mdns/mdns_reflector/test.py b/test/case/services/mdns/mdns_reflector/test.py new file mode 100755 index 000000000..94e9b1a70 --- /dev/null +++ b/test/case/services/mdns/mdns_reflector/test.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""mDNS reflector + +Verify the mDNS reflector functionality. The reflector forwards mDNS +requests and responses between different network interfaces, allowing +service discovery across network segments. + +We verify operation with two scenarios: + + 1. Reflector enabled: mDNS traffic from host:data1 SHOULD be reflected + to host:data2 and host:data3 + 2. Reflector disabled: mDNS traffic from host:data1 should NOT be + reflected to host:data2 and host:data3 + +""" +import infamy + +UNIQUE_MARKER = "infix-reflector-test" + + +def mdns_reflect_test(): + """Send mDNS from host:data1, check if it's reflected to host:data2/data3""" + snif1 = infamy.Sniffer(hdata1, "port 5353") + snif2 = infamy.Sniffer(hdata2, "port 5353") + snif3 = infamy.Sniffer(hdata3, "port 5353") + + with snif1, snif2, snif3: + # Send mDNS query for "infix-reflector-test.local" (raw DNS packet) + hdata1.runsh( + "echo -ne '\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00" + "\\x14infix-reflector-test\\x05local\\x00\\x00\\xff\\x00\\x01' | " + "socat - UDP4-DATAGRAM:224.0.0.251:5353,bind=10.0.1.2" + ) + + out2 = snif2.packets() + out3 = snif3.packets() + + return (UNIQUE_MARKER in out2, UNIQUE_MARKER in out3) + + +def disable_reflector(): + """Disable mDNS reflector""" + dut.put_config_dict("infix-services", { + "mdns": { + "reflector": { + "enabled": False + } + } + }) + + +with infamy.Test() as test: + with test.step("Set up topology and attach to target DUT"): + env = infamy.Env() + dut = env.attach("dut", "mgmt") + _, ddata1 = env.ltop.xlate("dut", "data1") + _, ddata2 = env.ltop.xlate("dut", "data2") + _, ddata3 = env.ltop.xlate("dut", "data3") + _, hdata1 = env.ltop.xlate("host", "data1") + _, hdata2 = env.ltop.xlate("host", "data2") + _, hdata3 = env.ltop.xlate("host", "data3") + + with test.step("Configure device interfaces and enable mDNS"): + dut.put_config_dicts( + { + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": ddata1, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "10.0.1.1", + "prefix-length": 24 + }] + } + }, { + "name": ddata2, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "10.0.2.1", + "prefix-length": 24 + }] + } + }, { + "name": ddata3, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "10.0.3.1", + "prefix-length": 24 + }] + } + }] + } + }, + "infix-services": { + "mdns": { + "enabled": True, + "reflector": { + "enabled": True + } + } + } + } + ) + + with infamy.IsolatedMacVlan(hdata1) as hdata1, \ + infamy.IsolatedMacVlan(hdata2) as hdata2, \ + infamy.IsolatedMacVlan(hdata3) as hdata3: + hdata1.addip("10.0.1.2") + hdata2.addip("10.0.2.2") + hdata3.addip("10.0.3.2") + + with test.step("Verify mDNS from host:data1 is reflected to host:data2 and host:data3"): + reflected = mdns_reflect_test() + if reflected != (True, True): + test.fail(f"Expected reflection (True, True), got {reflected}") + + with test.step("Verify mDNS from host:data1 is not reflected after disabling reflector"): + disable_reflector() + reflected = mdns_reflect_test() + if reflected != (False, False): + test.fail(f"Expected no reflection (False, False), got {reflected}") + + test.succeed() diff --git a/test/case/services/mdns/mdns_reflector/topology.dot b/test/case/services/mdns/mdns_reflector/topology.dot new file mode 100644 index 000000000..4138b9b68 --- /dev/null +++ b/test/case/services/mdns/mdns_reflector/topology.dot @@ -0,0 +1,26 @@ +graph "1x4" { + layout="neato"; + overlap="false"; + esep="+80"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="host | { mgmt | data1 | data2 | data3 }", + pos="0,12!", + requires="controller", + ]; + + dut [ + label="{ mgmt | data1 | data2 | data3 } | dut \n\n10.0.X.1/24", + pos="10,12!", + + requires="infix", + ]; + + host:mgmt -- dut:mgmt [requires="mgmt", color="lightgray"] + host:data1 -- dut:data1 [color=black, fontcolor=black, taillabel="10.0.1.2/24"] + host:data2 -- dut:data2 [color=black, fontcolor=black, taillabel="10.0.2.2/24"] + host:data3 -- dut:data3 [color=black, fontcolor=black, taillabel="10.0.3.2/24"] +} diff --git a/test/case/services/mdns/mdns_reflector/topology.svg b/test/case/services/mdns/mdns_reflector/topology.svg new file mode 100644 index 000000000..0c0344b10 --- /dev/null +++ b/test/case/services/mdns/mdns_reflector/topology.svg @@ -0,0 +1,64 @@ + + + + + + +1x4 + + + +host + +host + +mgmt + +data1 + +data2 + +data3 + + + +dut + +mgmt + +data1 + +data2 + +data3 + +dut +10.0.X.1/24 + + + +host:mgmt--dut:mgmt + + + + +host:data1--dut:data1 + +10.0.1.2/24 + + + +host:data2--dut:data2 + +10.0.2.2/24 + + + +host:data3--dut:data3 + +10.0.3.2/24 + + + diff --git a/test/infamy/sniffer.py b/test/infamy/sniffer.py index 723e16507..58ff301da 100644 --- a/test/infamy/sniffer.py +++ b/test/infamy/sniffer.py @@ -4,6 +4,7 @@ import subprocess import tempfile import time +import infamy.util as util class Sniffer: """Helper class for tcpdump""" @@ -20,7 +21,13 @@ def __del__(self): def __enter__(self): cmd = f"tshark -lni iface -w {self.pcap.name} {self.expr}" arg = cmd.split(" ") - self.proc = self.netns.popen(arg, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + self.proc = self.netns.popen(arg, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + text=True) + + util.until(lambda: " -- Capture started." in self.proc.stderr.readline()) def __exit__(self, _, __, ___): if not self.proc: