Skip to content

fix(ci): reload hwsim module between WiFi bundle test suites #250

fix(ci): reload hwsim module between WiFi bundle test suites

fix(ci): reload hwsim module between WiFi bundle test suites #250

Workflow file for this run

name: RXNM CI (Native Nspawn)
on:
push:
branches: [ "main", "master", "v1.1.0", "1.1.x-CI", "agentic-review" ]
pull_request:
branches: [ "main", "master", "v1.1.0" ]
jobs:
unit-and-posix:
name: "Unit Tests & POSIX Compat"
runs-on: ubuntu-24.04
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y dash shellcheck valgrind jq
- name: Build & Unit Tests
run: |
echo "=== PHASE 1: Build & Unit Tests ==="
make tiny
make lint
export GITHUB_ACTIONS=true
make test-all
- name: "Path B: POSIX/dash compatibility"
run: |
echo "=== Step 1: dash -n syntax check on lib/*.sh ==="
FAIL=0
for f in lib/*.sh; do
# rxnm-config-schema.sh uses Bash-only syntax below its POSIX guard.
# It cannot pass dash -n. We skip it here and explicitly lint it as bash.
case "$f" in lib/rxnm-config-schema.sh) continue ;; esac
if ! dash -n "$f"; then
echo "POSIX FAIL [dash -n]: $f"
FAIL=1
fi
done
for f in bin/rxnm; do
[ -f "$f" ] || continue
if ! dash -n "$f"; then
echo "POSIX FAIL [dash -n]: $f"
FAIL=1
fi
done
[ "$FAIL" -eq 0 ] || { echo "dash -n check failed"; exit 1; }
echo "=== Step 2: shellcheck strict POSIX on key lib files ==="
shellcheck --shell=sh --exclude=SC3043 \
lib/rxnm-utils.sh lib/rxnm-nullify.sh lib/rxnm-routes.sh \
lib/rxnm-wifi.sh lib/rxnm-system.sh lib/rxnm-roaming.sh || exit 1
# Explicitly lint the schema file as bash to ensure safety
shellcheck --shell=bash lib/rxnm-config-schema.sh || exit 1
echo "=== Step 3b: Full POSIX library source test ==="
RXNM_SHELL_IS_BASH=false RXNM_AGENT_BIN="/nonexistent/rxnm-agent" \
dash -c '
RXNM_LIB_DIR=./lib
for f in ./lib/rxnm-constants.sh ./lib/rxnm-utils.sh \
./lib/rxnm-system.sh ./lib/rxnm-wifi.sh \
./lib/rxnm-bluetooth.sh ./lib/rxnm-interfaces.sh \
./lib/rxnm-routes.sh ./lib/rxnm-profiles.sh \
./lib/rxnm-templates.sh ./lib/rxnm-nullify.sh \
./lib/rxnm-roaming.sh ./lib/rxnm-virt.sh \
./lib/rxnm-vpn.sh ./lib/rxnm-pppoe.sh \
./lib/rxnm-tunnel.sh ./lib/rxnm-mpls.sh \
./lib/rxnm-ha.sh ./lib/rxnm-plugins.sh \
./lib/rxnm-diagnostics.sh ./lib/rxnm-help.sh; do
[ -f "$f" ] || continue
. "$f" || { echo "FAIL: source $f"; exit 1; }
done
json_success "{\"test\":\"posix_source_all\"}" | grep -q "success.*true" || exit 1
echo "POSIX source all: OK"
' || exit 1
integration-wired:
name: "Integration: Wired & Bridge"
runs-on: ubuntu-24.04
needs: unit-and-posix
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Prepare Host Environment
run: |
sudo apt-get update
sudo apt-get install -y systemd-container bridge-utils dbus-user-session jq kmod
if systemctl is-active --quiet apparmor; then
sudo systemctl stop apparmor
sudo systemctl disable apparmor
fi
sudo sysctl -w net.ipv4.ip_forward=1
sudo sysctl -w net.ipv6.conf.all.forwarding=1
- name: Build RXNM Agent
run: make tiny
- name: Run Integration Tests
run: |
sudo chmod +x tests/integration/run_interop.sh
sudo ./tests/integration/run_interop.sh --skip-wifi
- name: Cleanup before bundle tests
if: always()
run: |
for m in $(sudo machinectl list --no-legend --no-pager 2>/dev/null | awk '{print $1}'); do
sudo machinectl terminate "$m" 2>/dev/null || true
done
sleep 2
sudo rm -rf /var/lib/machines/fedora-rxnm-bundle 2>/dev/null || true
- name: Run ROCKNIX Bundle Integration Tests
run: |
make rocknix-release
sudo chmod +x tests/integration/run_rocknix_interop.sh
sudo ./tests/integration/run_rocknix_interop.sh --skip-wifi
- name: Cleanup between bundle test suites
if: always()
run: |
for m in $(sudo machinectl list --no-legend --no-pager 2>/dev/null | awk '{print $1}'); do
sudo machinectl terminate "$m" 2>/dev/null || true
done
sleep 3
sudo rm -rf /var/lib/machines/fedora-rxnm-bundle 2>/dev/null || true
- name: Run Full Combined Bundle Integration Tests
run: |
make combined-full
sudo chmod +x tests/integration/run_rocknix_interop.sh
sudo BUNDLE_BIN=build/rxnm-full ./tests/integration/run_rocknix_interop.sh --skip-wifi
integration-wifi:
name: "Integration: Virtual WiFi (hwsim)"
runs-on: ubuntu-24.04
needs: unit-and-posix
continue-on-error: true
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Restore cached hwsim modules
id: hwsim-cache
uses: actions/cache@v4
with:
path: /tmp/hwsim-cache
key: hwsim-modules-${{ runner.os }}-${{ hashFiles('/proc/version') }}
restore-keys: |
hwsim-modules-${{ runner.os }}-
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y linux-headers-$(uname -r) build-essential iw rfkill jq systemd-container bridge-utils dbus-user-session kmod curl bc libssl-dev libelf-dev libdw-dev flex bison dpkg-dev
sudo mkdir -p /etc/NetworkManager/conf.d
printf '[keyfile]\nunmanaged-devices=driver:mac80211_hwsim\n' | sudo tee /etc/NetworkManager/conf.d/99-hwsim-ignore.conf >/dev/null
sudo systemctl reload-or-restart NetworkManager || true
sudo systemctl stop wpa_supplicant 2>/dev/null || true
sudo systemctl mask wpa_supplicant || true
if systemctl is-active --quiet apparmor; then
sudo systemctl stop apparmor
sudo systemctl disable apparmor
fi
sudo sysctl -w net.ipv4.ip_forward=1
sudo sysctl -w net.ipv6.conf.all.forwarding=1
- name: "Load mac80211_hwsim (Stage 1: prebuilt, Stage 2: cache/compile)"
run: |
HWSIM_LOADED=false
#---------------------------------------------------------------
# Stage 1: Try prebuilt modules from the running kernel
#---------------------------------------------------------------
echo "=== Stage 1: Attempting prebuilt modprobe ==="
REQUIRED_MODULES="pkcs8_key_parser arc4 md4 cmac ecb des3_ede algif_skcipher algif_hash af_alg cfg80211 mac80211 rfkill ccm cbc sha256_generic sha512_generic mac80211_hwsim"
MISSING_MODULES=""
for mod in $REQUIRED_MODULES; do
if ! sudo modprobe "$mod" 2>/dev/null; then
MISSING_MODULES="$MISSING_MODULES $mod"
fi
done
if [ -n "$MISSING_MODULES" ]; then
echo "Missing: $MISSING_MODULES"
echo "Installing linux-modules-extra packages..."
sudo apt-get install -y "linux-modules-extra-$(uname -r)" 2>/dev/null || true
# Try azure-specific package as fallback
sudo apt-get install -y linux-modules-extra-azure 2>/dev/null || true
MISSING_MODULES=""
for mod in $REQUIRED_MODULES; do
if ! sudo modprobe "$mod" 2>/dev/null; then
MISSING_MODULES="$MISSING_MODULES $mod"
fi
done
fi
if lsmod | grep -q mac80211_hwsim; then
echo "Stage 1 SUCCESS: mac80211_hwsim loaded from prebuilt modules"
HWSIM_LOADED=true
fi
#---------------------------------------------------------------
# Stage 2: Restore from cache or compile from kernel.org source
#---------------------------------------------------------------
if [ "$HWSIM_LOADED" = "false" ]; then
echo "=== Stage 2: Cache/Compile path ==="
CACHE_DIR="/tmp/hwsim-cache"
# 2a: Try restoring from actions/cache
if [ -d "$CACHE_DIR" ] && [ -n "$(ls -A "$CACHE_DIR"/*.ko 2>/dev/null)" ]; then
echo "Cache HIT: Restoring compiled modules from cache..."
sudo mkdir -p "/lib/modules/$(uname -r)/updates"
sudo cp "$CACHE_DIR"/*.ko "/lib/modules/$(uname -r)/updates/"
sudo depmod -a
# Verify vermagic before loading
for ko in "$CACHE_DIR"/*.ko; do
echo " $(basename "$ko"): $(modinfo -F vermagic "$ko" 2>/dev/null || echo 'unknown')"
done
for mod in pkcs8_key_parser arc4 md4 cmac ecb des_generic algif_skcipher algif_hash af_alg cfg80211 mac80211 rfkill ccm cbc sha256_generic sha512_generic; do
sudo modprobe "$mod" 2>/dev/null || true
done
sudo modprobe mac80211_hwsim radios=2 2>/dev/null || true
if lsmod | grep -q mac80211_hwsim; then
echo "Stage 2a SUCCESS: mac80211_hwsim loaded from cached modules"
HWSIM_LOADED=true
else
echo "Cache STALE: vermagic mismatch, clearing cache and recompiling..."
rm -rf "$CACHE_DIR"
fi
fi
# 2b: Out-of-tree build using kernel.org source + installed headers
#
# Strategy: Download mainline source for the .c files, but build
# against the INSTALLED kernel headers (which have Module.symvers
# matching the running Azure kernel). This is the standard
# out-of-tree module build approach:
# make -C /lib/modules/$(uname -r)/build M=/path/to/source modules
#
if [ "$HWSIM_LOADED" = "false" ]; then
echo "Cache MISS: Out-of-tree build from kernel.org source..."
echo "Still missing: $MISSING_MODULES"
KBUILD_DIR="/lib/modules/$(uname -r)/build"
if [ ! -d "$KBUILD_DIR" ]; then
echo "::warning::Kernel build dir not found at $KBUILD_DIR"
echo "Available in /lib/modules/$(uname -r)/:"
ls -la "/lib/modules/$(uname -r)/" 2>/dev/null || true
echo "Available in /usr/src/:"
ls -la /usr/src/ 2>/dev/null || true
echo "HWSIM_FAILED=true" >> "$GITHUB_ENV"
exit 0
fi
echo "Kernel build tree: $KBUILD_DIR"
if [ -f "$KBUILD_DIR/Module.symvers" ]; then
echo "Module.symvers: $(wc -l < "$KBUILD_DIR/Module.symvers") symbols"
else
echo "::warning::Module.symvers missing from build tree!"
fi
# Download mainline source for the module .c files
mkdir -p /tmp/linux-src && cd /tmp/linux-src
KVER=$(uname -r | cut -d- -f1)
KVER_MAJ_MIN=$(echo "$KVER" | sed 's/\.0$//')
MAJOR=$(echo "$KVER" | cut -d. -f1)
URL="https://cdn.kernel.org/pub/linux/kernel/v${MAJOR}.x/linux-${KVER_MAJ_MIN}.tar.xz"
echo "Downloading $URL..."
if ! curl -fSsl -O "$URL"; then
echo "::warning::Failed to download kernel source"
echo "HWSIM_FAILED=true" >> "$GITHUB_ENV"
exit 0
fi
tar -xf "linux-${KVER_MAJ_MIN}.tar.xz"
SRC_DIR="/tmp/linux-src/linux-${KVER_MAJ_MIN}"
# Locate hwsim source (moved to virtual/ in kernel 6.x)
HWSIM_SRC=$(find "$SRC_DIR/drivers/net/wireless" -name "mac80211_hwsim.c" | head -n1)
echo "hwsim source: $HWSIM_SRC"
# Out-of-tree builds need explicit obj-m in a standalone Kbuild.
# The upstream Makefiles use obj-$(CONFIG_*) which isn't set in M= context.
# Build mac80211_hwsim (copy all files — .c needs .h from same dir)
echo "=== Building mac80211_hwsim ==="
HWSIM_BUILD=/tmp/build-hwsim
mkdir -p "$HWSIM_BUILD"
cp "$(dirname "$HWSIM_SRC")"/* "$HWSIM_BUILD/"
echo "obj-m := mac80211_hwsim.o" > "$HWSIM_BUILD/Makefile"
make -C "$KBUILD_DIR" M="$HWSIM_BUILD" modules \
&& echo "hwsim build SUCCESS" \
|| echo "::warning::hwsim build failed"
# Build arc4 (IWD WPA dependency)
echo "=== Building arc4 ==="
ARC4_BUILD=/tmp/build-arc4
mkdir -p "$ARC4_BUILD"
cp "$SRC_DIR/crypto/arc4.c" "$ARC4_BUILD/"
echo "obj-m := arc4.o" > "$ARC4_BUILD/Makefile"
make -C "$KBUILD_DIR" M="$ARC4_BUILD" modules \
&& echo "arc4 build SUCCESS" \
|| echo "::warning::arc4 build failed"
# Install any successfully compiled .ko files
sudo mkdir -p "/lib/modules/$(uname -r)/updates"
mkdir -p "$CACHE_DIR"
BUILT_COUNT=0
for ko in $(find /tmp/build-* -name "*.ko" -type f 2>/dev/null); do
echo " Built: $(basename "$ko") vermagic=$(modinfo -F vermagic "$ko" 2>/dev/null || echo N/A)"
sudo cp "$ko" "/lib/modules/$(uname -r)/updates/"
cp "$ko" "$CACHE_DIR/"
BUILT_COUNT=$((BUILT_COUNT + 1))
done
if [ "$BUILT_COUNT" -eq 0 ]; then
echo "::warning::No .ko files were produced"
echo "HWSIM_FAILED=true" >> "$GITHUB_ENV"
exit 0
fi
sudo depmod -a
# Load modules in dependency order
for mod in pkcs8_key_parser arc4 md4 cmac ecb des_generic algif_skcipher algif_hash af_alg cfg80211 mac80211 rfkill ccm cbc sha256_generic sha512_generic; do
sudo modprobe "$mod" 2>/dev/null || true
done
sudo modprobe mac80211_hwsim radios=2 2>/dev/null || true
if lsmod | grep -q mac80211_hwsim; then
echo "Stage 2b SUCCESS: mac80211_hwsim loaded"
HWSIM_LOADED=true
else
echo "::warning::Modules built but mac80211_hwsim failed to load"
echo "dmesg tail:"
sudo dmesg | tail -n 10
fi
fi
fi
#---------------------------------------------------------------
# Final status
#---------------------------------------------------------------
if [ "$HWSIM_LOADED" = "true" ]; then
echo "HWSIM_FAILED=false" >> "$GITHUB_ENV"
echo "::notice::mac80211_hwsim loaded successfully"
lsmod | grep -E "mac80211|cfg80211|hwsim"
else
echo "HWSIM_FAILED=true" >> "$GITHUB_ENV"
echo "::warning::mac80211_hwsim failed to load on this runner"
echo "--- dmesg (last 40 lines) ---"
sudo dmesg | tail -n 40
echo "--- modinfo for cached .ko files ---"
if ls /tmp/hwsim-cache/*.ko >/dev/null 2>&1; then
for ko in /tmp/hwsim-cache/*.ko; do
[ -f "$ko" ] && echo " $(basename "$ko"): vermagic=$(modinfo -F vermagic "$ko" 2>/dev/null || echo 'N/A')"
done
fi
echo "Running kernel: $(uname -r)"
fi
- name: Build RXNM Agent
run: make tiny
- name: Run WiFi Integration Tests
if: env.HWSIM_FAILED != 'true'
run: |
sudo chmod +x tests/integration/run_interop.sh
sudo ./tests/integration/run_interop.sh --wifi-only
- name: Cleanup and reset hwsim between test suites
if: always() && env.HWSIM_FAILED != 'true'
run: |
for m in $(sudo machinectl list --no-legend --no-pager 2>/dev/null | awk '{print $1}'); do
sudo machinectl terminate "$m" 2>/dev/null || true
done
sleep 3
sudo rm -rf /var/lib/machines/fedora-rxnm-bundle 2>/dev/null || true
# Reload hwsim to get fresh virtual radios for the next test suite.
# After PHY injection + container termination, radios can be wedged.
sudo modprobe -r mac80211_hwsim 2>/dev/null || true
sleep 1
sudo modprobe mac80211_hwsim radios=2 2>/dev/null || true
sleep 2
- name: Run ROCKNIX Bundle WiFi Tests
if: env.HWSIM_FAILED != 'true'
run: |
make rocknix-release
sudo chmod +x tests/integration/run_rocknix_interop.sh
sudo ./tests/integration/run_rocknix_interop.sh --wifi-only
- name: Cleanup and reset hwsim between test suites
if: always() && env.HWSIM_FAILED != 'true'
run: |
for m in $(sudo machinectl list --no-legend --no-pager 2>/dev/null | awk '{print $1}'); do
sudo machinectl terminate "$m" 2>/dev/null || true
done
sleep 3
sudo rm -rf /var/lib/machines/fedora-rxnm-bundle 2>/dev/null || true
sudo modprobe -r mac80211_hwsim 2>/dev/null || true
sleep 1
sudo modprobe mac80211_hwsim radios=2 2>/dev/null || true
sleep 2
sleep 3
sudo rm -rf /var/lib/machines/fedora-rxnm-bundle 2>/dev/null || true
- name: Run Full Combined Bundle WiFi Tests
if: env.HWSIM_FAILED != 'true'
run: |
make combined-full
sudo chmod +x tests/integration/run_rocknix_interop.sh
sudo BUNDLE_BIN=build/rxnm-full ./tests/integration/run_rocknix_interop.sh --wifi-only
- name: WiFi tests skipped (hwsim unavailable)
if: env.HWSIM_FAILED == 'true'
run: |
echo "::error::mac80211_hwsim could not be loaded. WiFi integration tests cannot run."
exit 1