Merge pull request #1352 from kernelkit/misc2

Misc2
This commit is contained in:
Joachim Wiberg
2026-01-23 14:40:27 +01:00
committed by GitHub
26 changed files with 711 additions and 400 deletions
+21 -80
View File
@@ -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")
@@ -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'"
@@ -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"
+2
View File
@@ -72,10 +72,12 @@ Noteworthy changes and additions in this release are marked below in bold text.
- Fix default password hash in `do password encrypt` command. New hash is the
same as the more commonly used `change password` command, *yescrypt*
- Prevent MOTD from showing on non-shell user login attempts
- Fix mDNS reflector.
[wifi]: https://kernelkit.org/infix/latest/wifi/
[sd card image]: https://github.com/kernelkit/infix/releases/download/latest-boot/infix-rpi64-sdcard.img
[v25.11.0][] - 2025-12-02
-------------------------
+3 -3
View File
@@ -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)
@@ -1,11 +1,12 @@
From 6d6e9958d1066f53e1359c09ff3e92386244c89e Mon Sep 17 00:00:00 2001
From: Mattias Walström <lazzer@gmail.com>
From d571b8417741b2dbed814bc38c133dcf37055c66 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
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 <lazzer@gmail.com>
---
.../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
@@ -0,0 +1,43 @@
From f4d3d21bbb0bf21d7ad12cbb419e6a4c464ea619 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
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 <lazzer@gmail.com>
---
.../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
@@ -0,0 +1,68 @@
From 3592365d811531eaafd1973ba4954b8131c47bf8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
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 <lazzer@gmail.com>
---
.../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
@@ -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
+122 -80
View File
@@ -3,6 +3,8 @@
#include <srx/srx_val.h>
#include <srx/common.h>
#include <srx/lyx.h>
#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);
radio_name = lyd_get_value(radio_node);
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;
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;
radio_name = lyd_get_value(radio_node);
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);
}
}
dcomponents = lydx_get_descendant(*diff, "hardware", "component", NULL);
LYX_LIST_FOR_EACH(dcomponents, dcomponent, "component") {
const char *class, *name;
/* 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");
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);
/* Add the wifi container */
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 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;
+22 -176
View File
@@ -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) {
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;
+112 -10
View File
@@ -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 @@ out:
*/
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;
+2
View File
@@ -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);
+1 -1
View File
@@ -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=");
}
+6 -7
View File
@@ -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.";
}
/*
+7 -10
View File
@@ -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.";
}
+1 -1
View File
@@ -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).
+2 -2
View File
@@ -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"
@@ -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])
+3
View File
@@ -4,3 +4,6 @@
- name: mDNS allow/deny interfaces
case: mdns_allow_deny/test.py
- name: mDNS reflector
case: mdns_reflector/test.py
+1
View File
@@ -0,0 +1 @@
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
+134
View File
@@ -0,0 +1,134 @@
#!/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
from time import sleep
UNIQUE_MARKER = "infix-reflector-test"
def mdns_reflect_test():
"""Send mDNS from host:data1, check if it's reflected to host:data2/data3"""
snif2 = infamy.Sniffer(hdata2, "port 5353")
snif3 = infamy.Sniffer(hdata3, "port 5353")
with 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"
)
sleep(5)
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"):
hdata2.must_receive("port 5353")
hdata3.must_receive("port 5353")
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()
hdata2.must_receive("port 5353")
hdata3.must_receive("port 5353")
reflected = mdns_reflect_test()
if reflected != (False, False):
test.fail(f"Expected no reflection (False, False), got {reflected}")
test.succeed()
@@ -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> mgmt | <data1> data1 | <data2> data2 | <data3> data3 }",
pos="0,12!",
requires="controller",
];
dut [
label="{ <mgmt> mgmt | <data1> data1 | <data2> data2 | <data3> 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"]
}
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: 1x4 Pages: 1 -->
<svg width="481pt" height="101pt"
viewBox="0.00 0.00 481.03 101.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 97)">
<title>1x4</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-97 477.03,-97 477.03,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-0.5 0,-92.5 108,-92.5 108,-0.5 0,-0.5"/>
<text text-anchor="middle" x="25" y="-42.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-0.5 50,-92.5 "/>
<text text-anchor="middle" x="79" y="-77.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="50,-69.5 108,-69.5 "/>
<text text-anchor="middle" x="79" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
<polyline fill="none" stroke="black" points="50,-46.5 108,-46.5 "/>
<text text-anchor="middle" x="79" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
<polyline fill="none" stroke="black" points="50,-23.5 108,-23.5 "/>
<text text-anchor="middle" x="79" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data3</text>
</g>
<!-- dut -->
<g id="node2" class="node">
<title>dut</title>
<polygon fill="none" stroke="black" points="308.03,-0.5 308.03,-92.5 473.03,-92.5 473.03,-0.5 308.03,-0.5"/>
<text text-anchor="middle" x="337.03" y="-77.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="308.03,-69.5 366.03,-69.5 "/>
<text text-anchor="middle" x="337.03" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
<polyline fill="none" stroke="black" points="308.03,-46.5 366.03,-46.5 "/>
<text text-anchor="middle" x="337.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
<polyline fill="none" stroke="black" points="308.03,-23.5 366.03,-23.5 "/>
<text text-anchor="middle" x="337.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data3</text>
<polyline fill="none" stroke="black" points="366.03,-0.5 366.03,-92.5 "/>
<text text-anchor="middle" x="419.53" y="-58.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">dut </text>
<text text-anchor="middle" x="419.53" y="-27.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">10.0.X.1/24</text>
</g>
<!-- host&#45;&#45;dut -->
<g id="edge1" class="edge">
<title>host:mgmt&#45;&#45;dut:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-81.5C108,-81.5 307.53,-81.5 307.53,-81.5"/>
</g>
<!-- host&#45;&#45;dut -->
<g id="edge2" class="edge">
<title>host:data1&#45;&#45;dut:data1</title>
<path fill="none" stroke="black" stroke-width="2" d="M108,-58.5C108,-58.5 307.53,-58.5 307.53,-58.5"/>
<text text-anchor="middle" x="149" y="-62.3" font-family="DejaVu Serif, Book" font-size="14.00">10.0.1.2/24</text>
</g>
<!-- host&#45;&#45;dut -->
<g id="edge3" class="edge">
<title>host:data2&#45;&#45;dut:data2</title>
<path fill="none" stroke="black" stroke-width="2" d="M108,-34.5C108,-34.5 307.53,-34.5 307.53,-34.5"/>
<text text-anchor="middle" x="149" y="-38.3" font-family="DejaVu Serif, Book" font-size="14.00">10.0.2.2/24</text>
</g>
<!-- host&#45;&#45;dut -->
<g id="edge4" class="edge">
<title>host:data3&#45;&#45;dut:data3</title>
<path fill="none" stroke="black" stroke-width="2" d="M108,-11.5C108,-11.5 307.53,-11.5 307.53,-11.5"/>
<text text-anchor="middle" x="149" y="-15.3" font-family="DejaVu Serif, Book" font-size="14.00">10.0.3.2/24</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

+8 -1
View File
@@ -4,6 +4,7 @@ import signal
import subprocess
import tempfile
import time
import infamy.util as util
class Sniffer:
"""Helper class for tcpdump"""
@@ -20,7 +21,13 @@ class Sniffer:
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: