mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-29 04:03:01 +02:00
Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b04b978d40 | ||
|
|
1d1afecdc3 | ||
|
|
2cfddb448c | ||
|
|
f25a3189ed | ||
|
|
1422a0ebdd | ||
|
|
ce1892a60b | ||
|
|
4b340a38b3 | ||
|
|
2a766d7886 | ||
|
|
1bb9f778ff | ||
|
|
d2abdf0f64 | ||
|
|
5df26b217a | ||
|
|
b3ae276a14 | ||
|
|
5bbb83a3e6 | ||
|
|
e233dbc6da | ||
|
|
d83c708cd1 | ||
|
|
d333c66f9c | ||
|
|
1b11bae8eb | ||
|
|
2744e6ee85 | ||
|
|
9b41139066 | ||
|
|
d0d95f4098 | ||
|
|
84dfaf7c03 | ||
|
|
afcc40589a | ||
|
|
208f7c9f08 | ||
|
|
f8c68a38af | ||
|
|
8994879e26 | ||
|
|
b5a0ea0b1b | ||
|
|
435406af68 | ||
|
|
79107d2548 | ||
|
|
7a9cece84f | ||
|
|
b8ba1a1042 | ||
|
|
60676e21be | ||
|
|
763750b6ed | ||
|
|
23c5f16a4f | ||
|
|
5d3fce35c8 |
@@ -110,9 +110,10 @@ config INFIX_OEM_PATH
|
||||
directory (absolute path) and the Infix post-build.sh will call `git
|
||||
describe -C $INFIX_OEM_PATH`.
|
||||
|
||||
Note: for release builds the global variable INFIX_RELEASE overrides
|
||||
the version information derived from `git describe`. However, the
|
||||
GIT version is always saved as the BUILD_ID in /etc/os-releases.
|
||||
Note: the OS version (VERSION, VERSION_ID, BUILD_ID in
|
||||
/etc/os-release) is always derived from `git describe`. The global
|
||||
variable INFIX_RELEASE does not change it; it only labels the release
|
||||
channel (IMAGE_VERSION) and names the published artifacts.
|
||||
|
||||
endmenu
|
||||
|
||||
|
||||
@@ -253,3 +253,19 @@ config QEMU_NET_PORTS
|
||||
int "Number of Rocker switch ports"
|
||||
depends on QEMU_NET_ROCKER
|
||||
default 10
|
||||
|
||||
comment "Wireless"
|
||||
|
||||
config QEMU_WIFI_RADIOS
|
||||
int "Number of virtual WiFi radios (mac80211_hwsim)"
|
||||
default 0
|
||||
help
|
||||
Number of simulated 802.11 radios to create with mac80211_hwsim.
|
||||
The count is passed to the guest via the opt/wifi fw_cfg and loaded
|
||||
by the 00-hwsim boot script.
|
||||
|
||||
0 (default) means no radios, so nothing wireless clutters the guest.
|
||||
Set a small number (e.g. 2) for local AP/station experiments.
|
||||
|
||||
Note: 'make run' is a single guest with no inter-guest wireless
|
||||
medium, so these radios only reach each other within this one guest.
|
||||
|
||||
@@ -310,6 +310,16 @@ EOF
|
||||
echo -n "-fw_cfg name=opt/vpd,file=$vpd_file"
|
||||
}
|
||||
|
||||
wifi_args()
|
||||
{
|
||||
# Number of mac80211_hwsim radios for the guest, read at boot by 00-hwsim
|
||||
# (fw_cfg opt/wifi). Default 0 -> no radios, no wifi clutter.
|
||||
radios=${CONFIG_QEMU_WIFI_RADIOS:-0}
|
||||
wifi="${qdir}/wifi"
|
||||
echo "$radios" > "$wifi"
|
||||
echo -n "-fw_cfg name=opt/wifi,file=$wifi "
|
||||
}
|
||||
|
||||
wdt_args()
|
||||
{
|
||||
echo -n "-device i6300esb "
|
||||
@@ -387,6 +397,7 @@ run_qemu()
|
||||
$(usb_args) \
|
||||
$(host_args) \
|
||||
$(net_args) \
|
||||
$(wifi_args) \
|
||||
$(wdt_args) \
|
||||
$(rtc_args) \
|
||||
$(vpd_args) \
|
||||
|
||||
@@ -654,10 +654,17 @@ def main():
|
||||
err = 1
|
||||
|
||||
os.umask(0o337)
|
||||
# Write atomically: confd reads system.json as soon as ixinit signals
|
||||
# done, so a truncated in-place write could be read half-written. Write
|
||||
# to a temp file in the same dir, then rename (atomic on the same fs).
|
||||
# pylint: disable=invalid-name
|
||||
with open(SYSTEM_JSON, "w", encoding="ascii") as f:
|
||||
tmp = SYSTEM_JSON + ".tmp"
|
||||
with open(tmp, "w", encoding="ascii") as f:
|
||||
json.dump(out, f)
|
||||
shutil.chown(SYSTEM_JSON, user="root", group="wheel")
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
shutil.chown(tmp, user="root", group="wheel")
|
||||
os.replace(tmp, SYSTEM_JSON)
|
||||
return err
|
||||
|
||||
|
||||
|
||||
@@ -43,5 +43,12 @@ for iface in $ports; do
|
||||
ip link set "$iface" group port
|
||||
done
|
||||
|
||||
# The mac80211_hwsim monitor tap (hwsim test images only) is a debug
|
||||
# observer, not a user-facing interface. It cannot be deleted (no rtnl
|
||||
# link ops), so classify it as internal like the DSA CPU interfaces.
|
||||
if [ -d /sys/class/net/hwsim0 ]; then
|
||||
ip link set dev hwsim0 group internal
|
||||
fi
|
||||
|
||||
# At least loopback in iface group
|
||||
ip link set lo group iface
|
||||
|
||||
@@ -176,19 +176,25 @@ def parse_phy_info(phy_name):
|
||||
if current_band and current_band.get('frequencies'):
|
||||
result['bands'].append(current_band)
|
||||
|
||||
# Determine band names and assign band numbers
|
||||
# Keep only the bands Infix supports (2.4/5/6 GHz), naming them by their
|
||||
# first frequency. Hardware may expose others (e.g. S1G on hwsim) that we
|
||||
# neither configure nor report.
|
||||
bands = []
|
||||
for band in result['bands']:
|
||||
if band['frequencies']:
|
||||
freq = band['frequencies'][0]
|
||||
if 2400 <= freq <= 2500:
|
||||
band['name'] = '2.4GHz'
|
||||
band['band'] = 1
|
||||
elif 5150 <= freq <= 5900:
|
||||
band['name'] = '5GHz'
|
||||
band['band'] = 2
|
||||
elif 5955 <= freq <= 7115:
|
||||
band['name'] = '6GHz'
|
||||
band['band'] = 3
|
||||
freq = band['frequencies'][0]
|
||||
if 2400 <= freq <= 2500:
|
||||
band['name'] = '2.4GHz'
|
||||
band['band'] = 1
|
||||
elif 5150 <= freq <= 5900:
|
||||
band['name'] = '5GHz'
|
||||
band['band'] = 2
|
||||
elif 5955 <= freq <= 7115:
|
||||
band['name'] = '6GHz'
|
||||
band['band'] = 3
|
||||
else:
|
||||
continue
|
||||
bands.append(band)
|
||||
result['bands'] = bands
|
||||
|
||||
# Set max TX power
|
||||
if max_power is not None:
|
||||
@@ -207,7 +213,9 @@ def parse_phy_info(phy_name):
|
||||
|
||||
# Map driver to manufacturer
|
||||
driver_lower = driver_name.lower()
|
||||
if 'mt' in driver_lower or 'mediatek' in driver_lower:
|
||||
if 'hwsim' in driver_lower:
|
||||
result['manufacturer'] = 'Virtual (hwsim)'
|
||||
elif 'mt' in driver_lower or 'mediatek' in driver_lower:
|
||||
result['manufacturer'] = 'MediaTek Inc.'
|
||||
elif 'rtw' in driver_lower or 'realtek' in driver_lower:
|
||||
result['manufacturer'] = 'Realtek Semiconductor Corp.'
|
||||
|
||||
+1
-1
Submodule buildroot updated: 707ade3a89...dd816b9233
@@ -27,7 +27,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
|
||||
@@ -27,7 +27,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
|
||||
@@ -28,7 +28,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/arm/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
|
||||
@@ -28,7 +28,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/arm/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
|
||||
@@ -26,7 +26,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
@@ -146,6 +146,7 @@ INFIX_DOC="https://www.kernelkit.org/infix/"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_FEATURE_GPS=y
|
||||
BR2_PACKAGE_FEATURE_WIFI=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_HWSIM=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_QUALCOMM=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_REALTEK=y
|
||||
|
||||
@@ -26,7 +26,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
|
||||
+34
-3
@@ -3,12 +3,28 @@ Change Log
|
||||
|
||||
All notable changes to the project are documented in this file.
|
||||
|
||||
[v26.06.0][UNRELEASED]
|
||||
[v26.06.0][] - 2026-07-01
|
||||
-------------------------
|
||||
|
||||
> [!NOTE]
|
||||
> Noteworthy changes and additions in this release:
|
||||
>
|
||||
> **🌐 Web Interface:** Infix gets its first-ever web interface! Browse live
|
||||
> status and a full operational tree, handle common tasks from curated
|
||||
> configuration pages, and drop into a YANG tree editor for everything else.
|
||||
> A maintenance section covers firmware upgrade, backup & restore, and more.
|
||||
>
|
||||
> **📶 Wi-Fi Roaming & Mesh:** Access points sharing an SSID can hand clients
|
||||
> off seamlessly with 802.11k/v/r, form a cable-free 802.11s mesh backhaul,
|
||||
> and steer dual-band clients onto the faster 5/6 GHz band.
|
||||
>
|
||||
> **🗓️ System Scheduling:** Reusable time schedules based on ietf-schedule
|
||||
> (RFC 9922) let features like scheduled reboot and software update checks
|
||||
> run on a recurring, cron-style calendar.
|
||||
|
||||
### Changes
|
||||
|
||||
- Upgrade Linux kernel to 6.18.36 (LTS)
|
||||
- Upgrade Linux kernel to 6.18.37 (LTS)
|
||||
- Upgrade Buildroot to 2025.02.15 (LTS)
|
||||
- Add basic web interface: static status pages and a tree view of operational
|
||||
status. Curated configuration pages for some common tasks and a YANG tree
|
||||
@@ -27,6 +43,13 @@ All notable changes to the project are documented in this file.
|
||||
iCalendar recurrence grouping pruned to cron-expressible rules. Schedules
|
||||
are reusable time-specs; features (`scheduled-reboot`,
|
||||
`software/check-update`) trigger off them via a schedule reference
|
||||
- Configuring multiple BSS (more than one SSID) on a single Wi-Fi radio now
|
||||
requires an explicitly configured MAC address per BSS
|
||||
- New operational `advertised-pmd-types` leaf-list on each Ethernet interface,
|
||||
exposing the link modes currently advertised, to compare against the
|
||||
`supported-pmd-types` introduced in v26.05.0
|
||||
- Release assets no longer ship separate `.sha256` checksum files; the
|
||||
download page now publishes a SHA-256 checksum for each asset directly
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -39,6 +62,14 @@ All notable changes to the project are documented in this file.
|
||||
default `admin` user no permission to read or write the file from shell
|
||||
- Fix admin url shown for HTTP/HTTPS links in <https://network.local> browser,
|
||||
used pre-conflict resolution hostname.local, instead of hostname-2.local
|
||||
- Fix unreadable per-port temperature sensor names in `show hardware` on
|
||||
Marvell based switches: each sensor is now named after the front-panel port
|
||||
it serves (e.g. `e1`, `e2`) instead of a raw device-tree path. `show
|
||||
system` also reports a representative SoC temperature on CN913x platforms
|
||||
- Fix missing `contact` and `location` settings in operational status; the
|
||||
values were configurable but never returned on RESTCONF/NETCONF reads
|
||||
- Fix spurious YANG validation warnings, for NTP and WireGuard configuration,
|
||||
emitted on every NETCONF session and schema load
|
||||
|
||||
[wifi]: https://www.kernelkit.org/infix/latest/wifi/
|
||||
|
||||
@@ -2174,7 +2205,7 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
|
||||
- N/A
|
||||
|
||||
[buildroot]: https://buildroot.org/
|
||||
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v26.05.0...HEAD
|
||||
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v26.06.0...HEAD
|
||||
[v26.06.0]: https://github.com/kernelkit/infix/compare/v26.05.0...v26.06.0
|
||||
[v26.05.0]: https://github.com/kernelkit/infix/compare/v26.04.0...v26.05.0
|
||||
[v26.04.0]: https://github.com/kernelkit/infix/compare/v26.03.0...v26.04.0
|
||||
|
||||
@@ -297,6 +297,36 @@ image. The device will boot into test mode on first power-on.
|
||||
> unless `startup_override()` is called first (or the marker is removed
|
||||
> and a normal startup config is saved).
|
||||
|
||||
Simulated Wi-Fi
|
||||
---------------
|
||||
|
||||
The virtual topology has no real radios, so Wi-Fi tests run against
|
||||
`mac80211_hwsim` devices and the "air" between guests is faked. A small
|
||||
relay, `wifimedium` (`package/feature-wifi/wifimedium`), takes every frame
|
||||
a radio transmits, forwards it to the other guests, and injects what
|
||||
arrives back into the local radios.
|
||||
|
||||
Each radio (`radio0`, `radio1`, ...) is paired with a carrier NIC of the
|
||||
same name, and that carrier joins the multicast group (a *cell*) the
|
||||
topology wires it to. Two radios hear each other only when their carriers
|
||||
share a cell. Radios on the same cell that tune to different channels are
|
||||
still kept apart, as on real hardware.
|
||||
|
||||
By convention the two general cells carry `radio0` and `radio1` across all
|
||||
DUTs, so most tests put communicating radios on the same index: a station
|
||||
joins an AP on the same `radioN`. The mesh-roaming test, for instance,
|
||||
uses `radio1` for both the gateway APs and the client. That is only a
|
||||
convention, though. A cell can carry any set of radios, including two of
|
||||
the *same* DUT. The band-steering test relies on that: it wires one DUT's
|
||||
`radio2` (2.4GHz) and `radio3` (5GHz) onto a dedicated cell so a single
|
||||
client radio hears the same SSID on both bands.
|
||||
|
||||
How many radios each DUT gets depends on the topology. The number of
|
||||
`radioN` ports wired to a node is passed in via the `opt/wifi` fw_cfg and
|
||||
loaded at boot by the `00-hwsim` script, so a node with no wifi links has
|
||||
no radios at all. The wiring for the standard suite lives in
|
||||
`test/virt/quad`.
|
||||
|
||||
[9PM]: https://github.com/rical/9pm
|
||||
[Qeneth]: https://github.com/wkz/qeneth
|
||||
[TAP]: https://testanything.org/
|
||||
|
||||
@@ -304,6 +304,7 @@ in-octets : 148388
|
||||
out-octets : 24555
|
||||
mode : station
|
||||
ssid : MyNetwork
|
||||
bssid : b4:fb:e4:17:b6:a7
|
||||
signal : -45 dBm (good)
|
||||
rx bitrate : 72.2 Mbps
|
||||
tx bitrate : 86.6 Mbps
|
||||
@@ -319,6 +320,11 @@ In the CLI, signal strength is reported as: excellent, good, fair or bad.
|
||||
For precise signal strength values in dBm, use NETCONF or RESTCONF to access
|
||||
the `signal-strength` leaf in the operational datastore.
|
||||
|
||||
When connected, `bssid` shows the MAC address of the access point the
|
||||
station is associated to. It appears only while connected. When several
|
||||
access points share one SSID (a roaming network), the `bssid` is what
|
||||
tells them apart, and it changes as the station roams between them.
|
||||
|
||||
## Passphrase Requirements
|
||||
|
||||
To ensure your connection is secure and compatible with all network
|
||||
@@ -378,6 +384,7 @@ operational status : up
|
||||
physical address : f0:09:0d:36:5f:86
|
||||
mode : station
|
||||
ssid : MyHomeNetwork
|
||||
bssid : b4:fb:e4:17:b6:a7
|
||||
signal : -52 dBm (good)
|
||||
</code></pre>
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@ oem-dir := $(call qstrip,$(INFIX_OEM_PATH))
|
||||
INFIX_TOPDIR = $(if $(oem-dir),$(oem-dir),$(BR2_EXTERNAL_INFIX_PATH))
|
||||
|
||||
# Unless the user specifies an explicit build id, source it from git.
|
||||
# The build id also becomes the image version, unless an official
|
||||
# release is being built.
|
||||
export INFIX_BUILD_ID ?= $(shell git -C $(INFIX_TOPDIR) describe --dirty --always --tags)
|
||||
export INFIX_VERSION = $(if $(INFIX_RELEASE),$(INFIX_RELEASE),$(INFIX_BUILD_ID))
|
||||
# Exclude the moving 'latest*' tags so the version always resolves to a
|
||||
# real release tag, see issue #1524. The build id is also the version
|
||||
# shown to users; INFIX_RELEASE only labels the release channel and names
|
||||
# artifacts (see INFIX_ARTIFACT below).
|
||||
export INFIX_BUILD_ID ?= $(shell git -C $(INFIX_TOPDIR) describe --dirty --always --tags --exclude 'latest*')
|
||||
export INFIX_VERSION = $(INFIX_BUILD_ID)
|
||||
export INFIX_ARTIFACT = $(call qstrip,$(INFIX_IMAGE_ID)$(if $(INFIX_RELEASE),-$(INFIX_RELEASE)))
|
||||
|
||||
INFIX_CFLAGS:=-Wall -Werror -Wextra -Wno-unused-parameter -Wformat=2 -Wformat-overflow=2 -Winit-self -Wstrict-overflow=4 -Wno-format-truncation -Wno-format-nonliteral
|
||||
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
# Load mac80211_hwsim virtual radios sized to this DUT's topology, then wait
|
||||
# for udev to rename the phys (phyN -> radioN) before returning.
|
||||
#
|
||||
# The radio count comes from a dedicated fw_cfg file (opt/wifi) that qeneth
|
||||
# writes per node from the number of radioN carrier ports the topology wired
|
||||
# to it. A node with no wifi links gets 0 (or no file), so hwsim is not loaded
|
||||
# at all -- no phys, nothing clutters its interface view.
|
||||
#
|
||||
# Runs early in ixinit, before 00-probe, so the system probe records the radios
|
||||
# by their final radioN names. ixinit is past the <pid/syslogd> / udev-settle
|
||||
# barrier, so 60-rename-wifi-phy.rules (phyN -> radioN) and
|
||||
# 70-remove-virtual-wifi-interfaces.rules (drop the stray wlanN) are loaded and
|
||||
# fire on the phy add events. On success it sets the <usr/hwsim> condition so
|
||||
# wifimedium starts only where radios exist. Installed only with
|
||||
# BR2_PACKAGE_FEATURE_WIFI_HWSIM.
|
||||
|
||||
wifi=/sys/firmware/qemu_fw_cfg/by_name/opt/wifi/raw
|
||||
ieee=/sys/class/ieee80211
|
||||
|
||||
# Number of radios to create, from the topology (via fw_cfg).
|
||||
radios=0
|
||||
[ -r "$wifi" ] && radios=$(cat "$wifi" 2>/dev/null)
|
||||
|
||||
# No radios wanted -> don't load hwsim, keep the view wifi-free.
|
||||
[ "$radios" -gt 0 ] 2>/dev/null || exit 0
|
||||
|
||||
modprobe mac80211_hwsim radios="$radios" || exit 0
|
||||
|
||||
# Wait (bounded ~10s) for the phyN -> radioN rename to finish: the class has
|
||||
# entries and none are still named phyN. The rename is a udev RUN, so it lags
|
||||
# modprobe's return. On timeout the radios never settled -- fail (and log) so
|
||||
# it is visible why 00-probe may record stale phyN names.
|
||||
TIMEOUT=100
|
||||
i=0
|
||||
while [ "$i" -lt "$TIMEOUT" ]; do
|
||||
names=$(ls "$ieee" 2>/dev/null)
|
||||
if [ -n "$names" ] && ! echo "$names" | grep -q '^phy'; then
|
||||
initctl -bq cond set hwsim
|
||||
exit 0
|
||||
fi
|
||||
i=$((i + 1))
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
logger -p user.err -t 00-hwsim "timed out waiting for hwsim phys to be renamed radioN"
|
||||
exit 1
|
||||
@@ -18,6 +18,18 @@ config BR2_PACKAGE_FEATURE_WIFI
|
||||
help
|
||||
Enables WiFi in Infix. Enables all requried applications.
|
||||
|
||||
config BR2_PACKAGE_FEATURE_WIFI_HWSIM
|
||||
bool "Virtual Wi-Fi radios (mac80211_hwsim)"
|
||||
depends on BR2_PACKAGE_FEATURE_WIFI
|
||||
help
|
||||
Build the mac80211_hwsim virtual radio driver (as a module) for the
|
||||
QEMU test target, to exercise AP, station, and mesh modes without
|
||||
physical WiFi adapters. The module is autoloaded at boot (see the
|
||||
x86_64 /etc/modules-load.d) and frames are bridged between guests by
|
||||
the wifimedium relay.
|
||||
|
||||
Leave disabled on real hardware; it only provides simulated radios.
|
||||
|
||||
config BR2_PACKAGE_FEATURE_WIFI_MEDIATEK
|
||||
bool "Mediatek WiFi Devices"
|
||||
depends on BR2_PACKAGE_FEATURE_WIFI
|
||||
|
||||
@@ -14,6 +14,15 @@ define FEATURE_WIFI_LINUX_CONFIG_FIXUPS
|
||||
$(call KCONFIG_SET_OPT,CONFIG_CFG80211,m)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MAC80211_MESH)
|
||||
|
||||
# Virtual radio for automated testing (mac80211_hwsim), built as a
|
||||
# module and gated behind BR2_PACKAGE_FEATURE_WIFI_HWSIM so it is not
|
||||
# built on real boards. When selected, the module is loaded early in
|
||||
# ixinit by 00-hwsim -- sized to the DUT's topology, so radio-less nodes
|
||||
# load nothing -- and bridged between QEMU guests by the wifimedium relay
|
||||
# (both installed by FEATURE_WIFI_INSTALL_HWSIM below).
|
||||
$(if $(BR2_PACKAGE_FEATURE_WIFI_HWSIM),
|
||||
$(call KCONFIG_SET_OPT,CONFIG_MAC80211_HWSIM,m))
|
||||
|
||||
$(if $(filter y,$(BR2_PACKAGE_FEATURE_WIFI_MEDIATEK)),
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MT7601U)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MT76x0U)
|
||||
@@ -76,5 +85,20 @@ define FEATURE_WIFI_INSTALL_IN_ROMFS
|
||||
endef
|
||||
FEATURE_WIFI_POST_INSTALL_TARGET_HOOKS += FEATURE_WIFI_INSTALL_IN_ROMFS
|
||||
|
||||
# Virtual-radio (mac80211_hwsim) runtime, installed only when the option is
|
||||
# selected: the 00-hwsim ixinit script loads the module sized to the opt/wifi
|
||||
# fw_cfg qeneth writes per node (no radios -> not loaded), and the wifimedium
|
||||
# relay + its Finit service bridge hwsim frames between QEMU guests so they can
|
||||
# associate "wirelessly".
|
||||
ifeq ($(BR2_PACKAGE_FEATURE_WIFI_HWSIM),y)
|
||||
define FEATURE_WIFI_INSTALL_HWSIM
|
||||
$(INSTALL) -D -m 0755 $(FEATURE_WIFI_PKGDIR)/00-hwsim $(TARGET_DIR)/usr/libexec/infix/init.d/00-hwsim
|
||||
$(INSTALL) -D -m 0755 $(FEATURE_WIFI_PKGDIR)/wifimedium $(TARGET_DIR)/usr/libexec/infix/wifimedium
|
||||
$(INSTALL) -D -m 0644 $(FEATURE_WIFI_PKGDIR)/wifimedium.conf $(TARGET_DIR)/etc/finit.d/available/wifimedium.conf
|
||||
ln -sf ../available/wifimedium.conf $(TARGET_DIR)/etc/finit.d/enabled/wifimedium.conf
|
||||
endef
|
||||
FEATURE_WIFI_POST_INSTALL_TARGET_HOOKS += FEATURE_WIFI_INSTALL_HWSIM
|
||||
endif
|
||||
|
||||
|
||||
$(eval $(generic-package))
|
||||
|
||||
Executable
+401
@@ -0,0 +1,401 @@
|
||||
#!/usr/bin/env python3
|
||||
"""wifimedium - a virtual WiFi medium for mac80211_hwsim, extended over Ethernet.
|
||||
|
||||
mac80211_hwsim simulates 802.11 radios in software. By default all radios in
|
||||
one kernel share an in-kernel medium; radios in *different* kernels (i.e.
|
||||
different QEMU guests) cannot hear each other. The moment a userspace process
|
||||
registers on the hwsim generic-netlink family, the kernel stops delivering
|
||||
frames itself and hands every transmitted frame to that process, which becomes
|
||||
responsible for delivery.
|
||||
|
||||
This daemon is that process. On each DUT it:
|
||||
|
||||
* registers on the "mac80211_hwsim" genl family and receives every frame the
|
||||
local radios transmit (HWSIM_CMD_FRAME),
|
||||
* acknowledges each transmit back to the kernel (HWSIM_CMD_TX_INFO_FRAME) so
|
||||
mac80211's TX path completes, and
|
||||
* relays the frame per radio: each radio (phy radioN) is paired by name with a
|
||||
carrier NIC (netdev radioN) that joins one multicast "cell" -- a QEMU socket
|
||||
multicast group shared by every DUT's radioN (see test/virt/quad and
|
||||
test/qeneth). A radio's frame is sent only onto its own carrier, and a
|
||||
carrier's frames are injected only into its own radio
|
||||
(HWSIM_CMD_FRAME with HWSIM_ATTR_ADDR_RECEIVER).
|
||||
|
||||
Each cell is a shared medium: every DUT's radioN is "in range" of every other
|
||||
radioN. Radios on different indices are on different cells, so two radios
|
||||
communicate only if they share an index -- the convention the test topologies
|
||||
follow (a station uses the same radio index as the AP it joins). Channel
|
||||
filtering in mac80211 still separates radios that share a cell but tune to
|
||||
different frequencies.
|
||||
|
||||
This is intended for the QEMU/qeneth test topology only. On real hardware the
|
||||
radios use the air, there are no radioN carrier netdevs, and this daemon idles.
|
||||
See doc/wifi.md.
|
||||
"""
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
import subprocess
|
||||
import sys
|
||||
import select
|
||||
|
||||
# ---- generic netlink / hwsim ABI (drivers/net/wireless/virtual/mac80211_hwsim.h)
|
||||
NETLINK_GENERIC = 16
|
||||
GENL_ID_CTRL = 16
|
||||
CTRL_CMD_GETFAMILY = 3
|
||||
CTRL_ATTR_FAMILY_ID = 1
|
||||
CTRL_ATTR_FAMILY_NAME = 2
|
||||
CTRL_ATTR_MCAST_GROUPS = 7
|
||||
CTRL_ATTR_MCAST_GRP_NAME = 1
|
||||
CTRL_ATTR_MCAST_GRP_ID = 2
|
||||
|
||||
SOL_NETLINK = 270
|
||||
NETLINK_ADD_MEMBERSHIP = 1
|
||||
|
||||
HWSIM_CMD_REGISTER = 1
|
||||
HWSIM_CMD_FRAME = 2
|
||||
HWSIM_CMD_TX_INFO_FRAME = 3
|
||||
|
||||
HWSIM_ATTR_ADDR_RECEIVER = 1
|
||||
HWSIM_ATTR_ADDR_TRANSMITTER = 2
|
||||
HWSIM_ATTR_FRAME = 3
|
||||
HWSIM_ATTR_FLAGS = 4
|
||||
HWSIM_ATTR_RX_RATE = 5
|
||||
HWSIM_ATTR_SIGNAL = 6
|
||||
HWSIM_ATTR_TX_INFO = 7
|
||||
HWSIM_ATTR_COOKIE = 8
|
||||
HWSIM_ATTR_FREQ = 19
|
||||
HWSIM_ATTR_TX_INFO_FLAGS = 21
|
||||
|
||||
HWSIM_TX_STAT_ACK = 1 << 2
|
||||
|
||||
# Signal/rate reported for injected (received) frames. A strong, fixed signal
|
||||
# keeps mac80211 happy; the exact value is not significant for a virtual cell.
|
||||
RX_SIGNAL = -30
|
||||
RX_RATE = 0
|
||||
|
||||
# The medium is carried as raw Ethernet frames with a custom EtherType on the
|
||||
# configured interface -- no IP addressing needed (the carrier link has none).
|
||||
# A broadcast destination means every DUT on the segment is "in range".
|
||||
ETH_P_WIFIMEDIUM = 0x88B5 # local-experimental EtherType
|
||||
ETH_BROADCAST = b"\xff\xff\xff\xff\xff\xff"
|
||||
ETYPE = struct.pack("!H", ETH_P_WIFIMEDIUM)
|
||||
SOL_PACKET = 263
|
||||
PACKET_IGNORE_OUTGOING = 23 # don't deliver our own TX back to the socket
|
||||
|
||||
NLMSG_HDR = struct.Struct("=IHHII") # len, type, flags, seq, pid
|
||||
GENL_HDR = struct.Struct("=BBH") # cmd, version, reserved
|
||||
|
||||
|
||||
DEBUG = bool(os.environ.get("WIFIMEDIUM_DEBUG"))
|
||||
|
||||
|
||||
def log(msg):
|
||||
sys.stderr.write(f"wifimedium: {msg}\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
def dbg(msg):
|
||||
if DEBUG:
|
||||
log(msg)
|
||||
|
||||
|
||||
def nla_align(n):
|
||||
return (n + 3) & ~3
|
||||
|
||||
|
||||
def put_attr(atype, payload):
|
||||
hdr = struct.pack("=HH", len(payload) + 4, atype)
|
||||
return hdr + payload + b"\0" * (nla_align(len(payload) + 4) - (len(payload) + 4))
|
||||
|
||||
|
||||
def parse_attrs(buf):
|
||||
attrs = {}
|
||||
off = 0
|
||||
while off + 4 <= len(buf):
|
||||
alen, atype = struct.unpack_from("=HH", buf, off)
|
||||
if alen < 4:
|
||||
break
|
||||
attrs[atype & 0x7fff] = buf[off + 4:off + alen]
|
||||
off += nla_align(alen)
|
||||
return attrs
|
||||
|
||||
|
||||
class Netlink:
|
||||
"""Minimal generic-netlink client for the hwsim family."""
|
||||
|
||||
def __init__(self):
|
||||
self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_GENERIC)
|
||||
self.sock.bind((0, 0))
|
||||
self.seq = 0
|
||||
# NB: the genl family is "MAC80211_HWSIM" (uppercase); the lowercase
|
||||
# "mac80211_hwsim" is the platform driver name, not the family.
|
||||
self.family_id, self.mcast_id = self._resolve("MAC80211_HWSIM", "config")
|
||||
|
||||
def fileno(self):
|
||||
return self.sock.fileno()
|
||||
|
||||
def _send(self, family_id, cmd, attrs=b"", flags=0):
|
||||
self.seq += 1
|
||||
payload = GENL_HDR.pack(cmd, 1, 0) + attrs
|
||||
msg = NLMSG_HDR.pack(NLMSG_HDR.size + len(payload), family_id,
|
||||
flags | 1, self.seq, 0) + payload # NLM_F_REQUEST
|
||||
self.sock.send(msg)
|
||||
|
||||
def _resolve(self, name, grpname):
|
||||
self._send(GENL_ID_CTRL, CTRL_CMD_GETFAMILY,
|
||||
put_attr(CTRL_ATTR_FAMILY_NAME, name.encode() + b"\0"))
|
||||
data = self.sock.recv(65536)
|
||||
_, mtype, _, _, _ = NLMSG_HDR.unpack_from(data, 0)
|
||||
if mtype == 2: # NLMSG_ERROR (also used for ACKs; err<0 is a failure)
|
||||
err = struct.unpack_from("=i", data, NLMSG_HDR.size)[0]
|
||||
raise RuntimeError(f"genl GETFAMILY({name}) failed: "
|
||||
f"{os.strerror(-err) if err else 'empty reply'}")
|
||||
attrs = parse_attrs(data[NLMSG_HDR.size + GENL_HDR.size:])
|
||||
family_id = struct.unpack("=H", attrs[CTRL_ATTR_FAMILY_ID][:2])[0]
|
||||
|
||||
mcast_id = None
|
||||
for _, grp in _parse_nested(attrs.get(CTRL_ATTR_MCAST_GROUPS, b"")):
|
||||
gattrs = parse_attrs(grp)
|
||||
gname = gattrs.get(CTRL_ATTR_MCAST_GRP_NAME, b"").rstrip(b"\0").decode()
|
||||
if gname == grpname:
|
||||
mcast_id = struct.unpack("=I", gattrs[CTRL_ATTR_MCAST_GRP_ID])[0]
|
||||
if mcast_id is None:
|
||||
raise RuntimeError(f"hwsim multicast group '{grpname}' not found")
|
||||
return family_id, mcast_id
|
||||
|
||||
def register(self):
|
||||
self.sock.setsockopt(SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, self.mcast_id)
|
||||
self._send(self.family_id, HWSIM_CMD_REGISTER)
|
||||
|
||||
def recv(self):
|
||||
return self.sock.recv(65536)
|
||||
|
||||
def inject(self, receiver, frame, freq):
|
||||
attrs = put_attr(HWSIM_ATTR_ADDR_RECEIVER, receiver)
|
||||
attrs += put_attr(HWSIM_ATTR_FRAME, frame)
|
||||
attrs += put_attr(HWSIM_ATTR_RX_RATE, struct.pack("=I", RX_RATE))
|
||||
attrs += put_attr(HWSIM_ATTR_SIGNAL, struct.pack("=i", RX_SIGNAL))
|
||||
if freq:
|
||||
attrs += put_attr(HWSIM_ATTR_FREQ, struct.pack("=I", freq))
|
||||
self._send(self.family_id, HWSIM_CMD_FRAME, attrs)
|
||||
|
||||
def tx_ack(self, transmitter, flags, tx_info, tx_info_flags, cookie):
|
||||
attrs = put_attr(HWSIM_ATTR_ADDR_TRANSMITTER, transmitter)
|
||||
attrs += put_attr(HWSIM_ATTR_FLAGS, struct.pack("=I", flags | HWSIM_TX_STAT_ACK))
|
||||
attrs += put_attr(HWSIM_ATTR_SIGNAL, struct.pack("=i", RX_SIGNAL))
|
||||
if tx_info:
|
||||
attrs += put_attr(HWSIM_ATTR_TX_INFO, tx_info)
|
||||
if tx_info_flags:
|
||||
attrs += put_attr(HWSIM_ATTR_TX_INFO_FLAGS, tx_info_flags)
|
||||
if cookie:
|
||||
attrs += put_attr(HWSIM_ATTR_COOKIE, cookie)
|
||||
self._send(self.family_id, HWSIM_CMD_TX_INFO_FRAME, attrs)
|
||||
|
||||
|
||||
def _parse_nested(buf):
|
||||
off = 0
|
||||
while off + 4 <= len(buf):
|
||||
alen, atype = struct.unpack_from("=HH", buf, off)
|
||||
if alen < 4:
|
||||
break
|
||||
yield atype & 0x7fff, buf[off + 4:off + alen]
|
||||
off += nla_align(alen)
|
||||
|
||||
|
||||
def radio_addr1(phy):
|
||||
"""The address mac80211_hwsim identifies a radio by on the medium.
|
||||
|
||||
A hwsim radio has three addresses (kernel new_radio()). sysfs 'macaddress'
|
||||
is addresses[0]; the address the kernel stamps as HWSIM_ATTR_ADDR_TRANSMITTER
|
||||
and matches HWSIM_ATTR_ADDR_RECEIVER against (its rhashtable key) is
|
||||
addresses[1] -- addresses[0] with bit 0x40 set in the first octet, for the
|
||||
default no-perm_addr radios we use. So both the transmitter we observe and
|
||||
the receiver we must inject to are this 0x40 variant, not the sysfs value.
|
||||
(Note custom-phys-address changes the vif/netdev MAC, not these.)
|
||||
"""
|
||||
with open(f"/sys/class/ieee80211/{phy}/macaddress") as fh:
|
||||
addr = bytearray.fromhex(fh.read().strip().replace(":", ""))
|
||||
addr[0] |= 0x40
|
||||
return bytes(addr)
|
||||
|
||||
|
||||
def open_medium(ifname):
|
||||
"""Raw-Ethernet socket on the medium interface, or (None, None).
|
||||
|
||||
Returns (sock, mac); mac is the interface's own (unique) address, used as
|
||||
the Ethernet source and to drop frames we transmitted ourselves -- the
|
||||
multicast cell loops our own frames back to us."""
|
||||
if not ifname:
|
||||
return None, None
|
||||
# The carrier NIC is unconfigured (it is not a real DUT port), so Infix
|
||||
# leaves it administratively down -- a raw packet socket on a down link
|
||||
# carries no frames. Bring it up; wifimedium owns the medium, like hwsim0.
|
||||
ifup(ifname)
|
||||
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
|
||||
socket.htons(ETH_P_WIFIMEDIUM))
|
||||
sock.bind((ifname, ETH_P_WIFIMEDIUM))
|
||||
# Don't receive our own transmitted frames (avoids a self-feedback path
|
||||
# and any reliance on the source MAC, which can collide between guests).
|
||||
try:
|
||||
sock.setsockopt(SOL_PACKET, PACKET_IGNORE_OUTGOING, 1)
|
||||
except OSError:
|
||||
pass # pre-4.20 kernels; the length check below still applies
|
||||
with open(f"/sys/class/net/{ifname}/address") as fh:
|
||||
mac = bytes.fromhex(fh.read().strip().replace(":", ""))
|
||||
return sock, mac
|
||||
|
||||
|
||||
# Wire format on the medium: 6-byte transmitter MAC, 4-byte LE freq, then the
|
||||
# raw 802.11 frame.
|
||||
WIRE = struct.Struct("=6sI")
|
||||
|
||||
|
||||
def wait_radios_renamed(timeout=20):
|
||||
"""Wait until the hwsim phys have been renamed phyN -> radioN.
|
||||
|
||||
60-rename-wifi-phy.rules renames each phy *asynchronously* (a udev RUN),
|
||||
and the carrier NICs are already named radioN (mactab/nameif, early boot).
|
||||
We pair phy and carrier by name, so discovering while a phy is still 'phyN'
|
||||
would miss it -- and we discover only once. This raced our start (gated
|
||||
merely on the module load) and left some boots relaying nothing until a
|
||||
restart. Block until every phy is renamed (no 'phyN' left) and the set has
|
||||
settled, so discovery sees radioN phys that match the radioN carriers.
|
||||
"""
|
||||
base = "/sys/class/ieee80211"
|
||||
prev, stable = None, 0
|
||||
for _ in range(timeout * 4):
|
||||
phys = sorted(os.listdir(base)) if os.path.isdir(base) else []
|
||||
if phys and not any(p.startswith("phy") for p in phys):
|
||||
if phys == prev:
|
||||
stable += 1
|
||||
if stable >= 2: # unchanged for ~0.5s
|
||||
return
|
||||
else:
|
||||
stable = 0
|
||||
prev = phys
|
||||
time.sleep(0.25)
|
||||
|
||||
|
||||
def discover_radios():
|
||||
"""Pair each local hwsim radio with its multicast carrier interface.
|
||||
|
||||
A radio's phy is named radioN (60-rename-wifi-phy.rules) under
|
||||
/sys/class/ieee80211/. In the qeneth test topology each radio also has a
|
||||
carrier NIC of the SAME name under /sys/class/net/ -- the topology names the
|
||||
port radioN, so the mactab/nameif rename the carrier to match -- joined to
|
||||
one multicast cell shared by every DUT's radioN. Pairing them by name gives
|
||||
a per-radio relay: a radio's frames go onto its own carrier (its cell) and a
|
||||
carrier's frames are injected only into its own radio.
|
||||
|
||||
Returns a list of {name, addr1, sock, mac}. A phy with no matching carrier
|
||||
netdev (e.g. real hardware) is skipped, so the daemon simply idles there.
|
||||
"""
|
||||
wait_radios_renamed()
|
||||
radios = []
|
||||
base = "/sys/class/ieee80211"
|
||||
if not os.path.isdir(base):
|
||||
return radios
|
||||
for phy in sorted(os.listdir(base)):
|
||||
if not os.path.isdir(f"/sys/class/net/{phy}"):
|
||||
continue # no carrier of this name -> not a relayed radio
|
||||
sock, mac = open_medium(phy)
|
||||
if sock:
|
||||
radios.append({"name": phy, "addr1": radio_addr1(phy),
|
||||
"sock": sock, "mac": mac})
|
||||
return radios
|
||||
|
||||
|
||||
def ifup(ifname):
|
||||
"""Bring an interface up (best effort)."""
|
||||
subprocess.run(["ip", "link", "set", ifname, "up"], check=False)
|
||||
|
||||
|
||||
def main():
|
||||
# The hwsim monitor interface is down after boot; bring it up so the
|
||||
# simulated medium is observable (tcpdump -i hwsim0) and active.
|
||||
ifup("hwsim0")
|
||||
|
||||
# Discover carriers first. With none -- e.g. 'make run', which creates
|
||||
# radios but no inter-guest medium -- registering on the hwsim family would
|
||||
# make the kernel hand us every frame with nowhere to relay it, breaking
|
||||
# delivery between this guest's own radios. So leave the medium to the
|
||||
# kernel and idle instead of registering.
|
||||
radios = discover_radios()
|
||||
if not radios:
|
||||
log("no radio carriers found; leaving local hwsim medium to the kernel")
|
||||
while True:
|
||||
time.sleep(3600)
|
||||
|
||||
nl = Netlink()
|
||||
nl.register()
|
||||
log(f"registered on hwsim family {nl.family_id}; "
|
||||
f"radios={' '.join(r['name'] for r in radios)}")
|
||||
|
||||
# TX routing: a frame's transmitter (addresses[1]) -> that radio's carrier.
|
||||
by_addr1 = {r["addr1"]: r for r in radios}
|
||||
dbg(f"netlink portid={nl.sock.getsockname()[0]}; "
|
||||
+ "; ".join(f"{r['name']} addr1={r['addr1'].hex()} "
|
||||
f"carrier={r['mac'].hex()}" for r in radios))
|
||||
|
||||
socks = [nl] + [r["sock"] for r in radios]
|
||||
while True:
|
||||
ready, _, _ = select.select(socks, [], [])
|
||||
|
||||
if nl in ready:
|
||||
data = nl.recv()
|
||||
off = 0
|
||||
while off + NLMSG_HDR.size <= len(data):
|
||||
mlen, mtype, _, _, _ = NLMSG_HDR.unpack_from(data, off)
|
||||
if mlen < NLMSG_HDR.size:
|
||||
break
|
||||
if mtype == nl.family_id and \
|
||||
data[off + NLMSG_HDR.size] == HWSIM_CMD_FRAME:
|
||||
a = parse_attrs(data[off + NLMSG_HDR.size + GENL_HDR.size:
|
||||
off + mlen])
|
||||
tx = a.get(HWSIM_ATTR_ADDR_TRANSMITTER)
|
||||
frame = a.get(HWSIM_ATTR_FRAME)
|
||||
if tx and frame:
|
||||
freq = struct.unpack("=I", a[HWSIM_ATTR_FREQ])[0] \
|
||||
if HWSIM_ATTR_FREQ in a else 0
|
||||
flags = struct.unpack("=I", a[HWSIM_ATTR_FLAGS])[0] \
|
||||
if HWSIM_ATTR_FLAGS in a else 0
|
||||
# Complete the kernel TX path regardless of delivery.
|
||||
nl.tx_ack(tx, flags, a.get(HWSIM_ATTR_TX_INFO),
|
||||
a.get(HWSIM_ATTR_TX_INFO_FLAGS),
|
||||
a.get(HWSIM_ATTR_COOKIE))
|
||||
# Send only onto the transmitting radio's own carrier.
|
||||
r = by_addr1.get(tx)
|
||||
if r:
|
||||
r["sock"].send(ETH_BROADCAST + r["mac"] + ETYPE +
|
||||
WIRE.pack(tx, freq) + frame)
|
||||
dbg(f"tx {r['name']} freq={freq} len={len(frame)}")
|
||||
else:
|
||||
dbg(f"tx from unknown radio {tx.hex()} -- dropped")
|
||||
off += nla_align(mlen)
|
||||
|
||||
for r in radios:
|
||||
if r["sock"] not in ready:
|
||||
continue
|
||||
pkt = r["sock"].recv(65536)
|
||||
if len(pkt) < 14 + WIRE.size:
|
||||
continue
|
||||
# The cell multicasts our own frames back to us; PACKET_IGNORE_-
|
||||
# OUTGOING does not catch that looped copy, so drop anything whose
|
||||
# Ethernet source is our own carrier (carrier MACs are unique per
|
||||
# DUT; transmitter addrs are not, so we must key on the carrier).
|
||||
if pkt[6:12] == r["mac"]:
|
||||
continue
|
||||
tx, freq = WIRE.unpack_from(pkt, 14)
|
||||
frame = pkt[14 + WIRE.size:]
|
||||
# Inject into THIS radio only -- its carrier is its cell.
|
||||
nl.inject(r["addr1"], frame, freq)
|
||||
dbg(f"rx {r['name']} tx={tx.hex()} freq={freq} len={len(frame)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
@@ -0,0 +1,14 @@
|
||||
# Virtual WiFi medium for mac80211_hwsim (QEMU test target).
|
||||
#
|
||||
# Installed only when BR2_PACKAGE_FEATURE_WIFI_HWSIM is set. Bridges hwsim
|
||||
# frames between the local radios and -- when a medium interface is configured
|
||||
# (WIFIMEDIUM_IFACE env or 'wifimedium.iface=' on the kernel cmdline) -- to
|
||||
# peer DUTs over that Ethernet segment, so two QEMU guests can associate
|
||||
# "wirelessly". hwsim is loaded by the 00-hwsim ixinit script, which sets the
|
||||
# <usr/hwsim> condition once radios exist and are renamed -- so this service
|
||||
# starts only on nodes that actually have radios (registering on the hwsim genl
|
||||
# family would fail otherwise). On real hardware neither is shipped and the
|
||||
# radios use the air.
|
||||
service env:-/etc/default/wifimedium name:wifimedium \
|
||||
[2345] <usr/hwsim> \
|
||||
/usr/libexec/infix/wifimedium -- Virtual WiFi medium (hwsim)
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From a3165eb664d65da173e91e8b159b8daf9a1285c7 Mon Sep 17 00:00:00 2001
|
||||
From f614327b6eca20319f5eee20ac954ea8d92276e4 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 19 Sep 2023 18:38:10 +0200
|
||||
Subject: [PATCH 01/50] net: phy: marvell10g: Support firmware loading on
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 0ee3021b04ec64d96b90d9246f37d3d7a1f8f187 Mon Sep 17 00:00:00 2001
|
||||
From 7d453cb838611f44d7566b6174ae95a5d2265fa1 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 21 Nov 2023 20:15:24 +0100
|
||||
Subject: [PATCH 02/50] net: phy: marvell10g: Fix power-up when strapped to
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 7e5a199bca36c5d8b1fd3875bbb8343c86db73ea Mon Sep 17 00:00:00 2001
|
||||
From 4b08a36e3107c4018ddf3a19760cc87d02281bd7 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 15 Nov 2023 20:58:42 +0100
|
||||
Subject: [PATCH 03/50] net: phy: marvell10g: Add LED support for 88X3310
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 62a1468f5c6fdaba5074fa7555e3b7888762e4e7 Mon Sep 17 00:00:00 2001
|
||||
From e5e81eb1172db22be7c83efc8071108d0d42565e Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 12 Dec 2023 09:51:05 +0100
|
||||
Subject: [PATCH 04/50] net: phy: marvell10g: Support LEDs tied to a single
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 38519daa6941dc275e3efaaba699b6441f1c19e2 Mon Sep 17 00:00:00 2001
|
||||
From 1f735096f1b8920ee6f21bf60a4c2d3f6376adeb Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 27 Mar 2024 10:10:19 +0100
|
||||
Subject: [PATCH 05/50] net: phy: Do not resume PHY when attaching
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From e11c3a1cf1979f558a1a1c06ed68a99b89273aea Mon Sep 17 00:00:00 2001
|
||||
From e38d2d2f07f55819d2191d4282dc2d4688075d7d Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Mon, 4 Mar 2024 16:47:28 +0100
|
||||
Subject: [PATCH 06/50] net: bridge: avoid classifying unknown multicast as
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 9186aa368a4b580090d14ee1d9218ac33bc0aa5f Mon Sep 17 00:00:00 2001
|
||||
From a9b80b26c96e5492ab32a72ee36e3321a2b90ee9 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Tue, 5 Mar 2024 06:44:41 +0100
|
||||
Subject: [PATCH 07/50] net: bridge: Ignore router ports when forwarding L2
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 9641f50de1c780b0e45be9095ab28cbbc4c70011 Mon Sep 17 00:00:00 2001
|
||||
From 01e41d87efa1b63d292f0acfe4d1fb0e39baab20 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Thu, 4 Apr 2024 16:36:30 +0200
|
||||
Subject: [PATCH 08/50] net: bridge: drop delay for applying strict multicast
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From d14c3ad03563ac803180c18e6917eb506bea7079 Mon Sep 17 00:00:00 2001
|
||||
From 944c77d075c8e0655bd3a28e96892261465a5cf4 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 16 May 2024 14:51:54 +0200
|
||||
Subject: [PATCH 09/50] net: bridge: Differentiate MDB additions from
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 4d2335aac65d03c004d27cd27f077362588851e2 Mon Sep 17 00:00:00 2001
|
||||
From 3f58061ab8f869a6432a1256ee7422bb157b624e Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Fri, 24 Nov 2023 23:29:55 +0100
|
||||
Subject: [PATCH 10/50] nvmem: layouts: onie-tlv: Let device probe even when
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 1f727779b31e9dec09b669d07655371c45fbc64b Mon Sep 17 00:00:00 2001
|
||||
From f492d4efac3a65bca4d53e383336c36113e28a8b Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Sun, 11 Aug 2024 11:27:35 +0200
|
||||
Subject: [PATCH 11/50] net: usb: r8152: add r8153b support for link/activity
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 8053206624b54fb84715342e7b921e0939d594a7 Mon Sep 17 00:00:00 2001
|
||||
From a2448b84e5483b5b91b46ac31b27609352fd9ed6 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Sun, 10 Aug 2025 18:52:54 +0200
|
||||
Subject: [PATCH 12/50] arm64: dts: mediatek: mt7986a: rename BPi R3 ports to
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 5b4047d082c93431f77819b894e29a13acebd787 Mon Sep 17 00:00:00 2001
|
||||
From c4ac90dcc5aefbb704c70ebbfe041dee683b882b Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Wed, 20 Aug 2025 21:38:24 +0200
|
||||
Subject: [PATCH 13/50] drm/panel-simple: Add a timing for the Raspberry Pi 7"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 3efdf41a3464704c4ffb99ea94266dc900f7b556 Mon Sep 17 00:00:00 2001
|
||||
From 8da101ec2429ba0bf1410346260f9a7ddfb0bdbe Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Thu, 21 Aug 2025 11:20:23 +0200
|
||||
Subject: [PATCH 14/50] input:touchscreen:edt-ft5x06: Add polled mode
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From ce445d44a5e6ae84214986cbccef985a240e831d Mon Sep 17 00:00:00 2001
|
||||
From 93646f890903157bb78ef369ddf9d99a9161de0d Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 12 Mar 2024 10:27:24 +0100
|
||||
Subject: [PATCH 15/50] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 57aa43c667a99e08c11a6141f80c068021585829 Mon Sep 17 00:00:00 2001
|
||||
From 29cfa1b5ffba41728dc5e0991d1ee27b903d569c Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 27 Mar 2024 15:52:43 +0100
|
||||
Subject: [PATCH 16/50] net: dsa: mv88e6xxx: Improve indirect register access
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 70c247b71e2f2ede0cae581b0558bd09cd85275e Mon Sep 17 00:00:00 2001
|
||||
From a7fa9a45329781519702d46ba05815433fff4acc Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Mon, 22 Apr 2024 23:18:01 +0200
|
||||
Subject: [PATCH 17/50] net: dsa: mv88e6xxx: Honor ports being managed via
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From b9b9cc025114c8bb8546012d575cbb9759ce6975 Mon Sep 17 00:00:00 2001
|
||||
From cda7100b0a29e6a9bd29a67ff9db3bcccb72065c Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 24 Apr 2024 22:41:04 +0200
|
||||
Subject: [PATCH 18/50] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 071b831a6ba7c5de2cc05738b595cea10e2eb068 Mon Sep 17 00:00:00 2001
|
||||
From ccf9f9262e8570cfd522b841465f3edf76b3447a Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 28 May 2024 10:38:42 +0200
|
||||
Subject: [PATCH 19/50] net: dsa: tag_dsa: Use tag priority as initial
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 625493833fcdc4268a46ce4b2c59eefb1a0a0be0 Mon Sep 17 00:00:00 2001
|
||||
From 3200169701ef4d9afdea35355b3fe41d6f363134 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 16 Jan 2024 16:00:55 +0100
|
||||
Subject: [PATCH 20/50] net: dsa: Support MDB memberships whose L2 addresses
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From c59543ced113c4a66ccf07cbfac5dc7dd6cb253e Mon Sep 17 00:00:00 2001
|
||||
From dea9ff6c0bdf7c3e4abc2fb507d2ab4b94dabb63 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Thu, 21 Mar 2024 19:12:15 +0100
|
||||
Subject: [PATCH 21/50] net: dsa: Support EtherType based priority overrides
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From fb04b75bc53f6b1b27f91f4ed21013b293e8577c Mon Sep 17 00:00:00 2001
|
||||
From 1dc01d7420e3d511babfbdeda742d93527d72abb Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Fri, 22 Mar 2024 16:15:43 +0100
|
||||
Subject: [PATCH 22/50] net: dsa: mv88e6xxx: Support EtherType based priority
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 32e460b9fbc7b25817c05b9ec2c01f4827f7cca7 Mon Sep 17 00:00:00 2001
|
||||
From caed0d150badbb040e403975645bf09ba3f44e76 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 28 May 2024 11:04:22 +0200
|
||||
Subject: [PATCH 23/50] net: dsa: mv88e6xxx: Add mqprio qdisc support
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 30f92e6d97e8d6d8fe36e872e627b28fcf24fdac Mon Sep 17 00:00:00 2001
|
||||
From 1bd5b0e8a5574209b386d6d8bc1cf3bcf2e4fd5e Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 29 May 2024 13:20:41 +0200
|
||||
Subject: [PATCH 24/50] net: dsa: mv88e6xxx: Use VLAN prio over IP when both
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From a9055d21d1237a5449d5f410d3a0c3d8953a1965 Mon Sep 17 00:00:00 2001
|
||||
From 7894882d70e8223939a6d9a0e15cc6bbc08ae33c Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Tue, 26 Nov 2024 19:45:59 +0100
|
||||
Subject: [PATCH 25/50] [FIX] net: dsa: mv88e6xxx: Trap locally terminated
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 7fb7bc76b39078049d7965bef1bc976f9e1142c5 Mon Sep 17 00:00:00 2001
|
||||
From 09cb11eaf5cc754274620ff6eaedf768c9605e44 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Thu, 16 Jan 2025 12:35:12 +0100
|
||||
Subject: [PATCH 26/50] net: dsa: mv88e6xxx: collapse disabled state into
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 2a8ff34ccf08f85f985ebea74b6eb47a64e2f247 Mon Sep 17 00:00:00 2001
|
||||
From 7127fd4194cde754cbc9ea869c9e65e335701d95 Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Wed, 12 Feb 2025 22:03:14 +0100
|
||||
Subject: [PATCH 27/50] net: dsa: mv88e6xxx: Only activate LAG offloading when
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 348c281450bf30875877045d628f8a56ef7896a1 Mon Sep 17 00:00:00 2001
|
||||
From c2f3d9277e12e7622651b521af8b43b1fe036cf0 Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Wed, 14 Jan 2026 18:22:41 +0100
|
||||
Subject: [PATCH 28/50] net: dsa: mv88e6xxx: Add LED support for 6393X
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 653db5307cd9d69d6b14e163f02226c83e26bd05 Mon Sep 17 00:00:00 2001
|
||||
From ac98be8b6fd1b6afa264c68685d5dc82d73da1f8 Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Thu, 15 Jan 2026 22:47:37 +0100
|
||||
Subject: [PATCH 29/50] wifi: brcmfmac: support deletion and recreation of
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 6445e629e19b9380dc96c9e5bfd15bbf706e1311 Mon Sep 17 00:00:00 2001
|
||||
From 9f1110fe13c878c49d28859f27bd90bc1b77f7bc Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Mon, 19 Jan 2026 13:06:53 +0100
|
||||
Subject: [PATCH 30/50] wifi: brcmfmac: check connection state before querying
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 2b970f903b571a3f3ca6e6043b2608c30825463f Mon Sep 17 00:00:00 2001
|
||||
From 188203d0d4eeeb607b29efa344280189c2566f30 Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Tue, 20 Jan 2026 20:12:10 +0100
|
||||
Subject: [PATCH 31/50] wifi: brcmfmac: suppress log spam for
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From eab236832c10d0a0c05fcab145abfe4267857957 Mon Sep 17 00:00:00 2001
|
||||
From c02051fdfbcedf99bac490bcb0358774774e2bcf Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Tue, 20 Jan 2026 20:18:45 +0100
|
||||
Subject: [PATCH 32/50] wifi: brcmfmac: reduce log noise during AP to station
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 8bad16ffcea19fde114ac772399140ded40c6b91 Mon Sep 17 00:00:00 2001
|
||||
From 613dec2af90a208b2bb453150cb1851de07d51fb Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Tue, 17 Feb 2026 21:59:59 +0100
|
||||
Subject: [PATCH 33/50] net: phy: air_en8811h: add OF device table for
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 105d92fb189196e292f1ec60bc6f4abf883e1d3d Mon Sep 17 00:00:00 2001
|
||||
From 5df424aa25bcfd2f0309fbd05670f0b613219456 Mon Sep 17 00:00:00 2001
|
||||
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
|
||||
Date: Fri, 20 Sep 2024 12:05:18 +0100
|
||||
Subject: [PATCH 34/50] drm: vc4: dsi: enable video and then retry failed
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 85515b63fa683de8025f439c0ba290b535cada59 Mon Sep 17 00:00:00 2001
|
||||
From 88e744997f5c6ff9c9383fbe568c925c11715896 Mon Sep 17 00:00:00 2001
|
||||
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
|
||||
Date: Wed, 8 Jun 2022 17:23:47 +0100
|
||||
Subject: [PATCH 35/50] drm: vc4: dsi: Clocks should be running before reset
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 0bbed592738a780abea49cd71806daa40d9ce602 Mon Sep 17 00:00:00 2001
|
||||
From 9cd8a5347a9aa06f60512c4ca4fe1c7591c7e1d9 Mon Sep 17 00:00:00 2001
|
||||
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
|
||||
Date: Fri, 5 Apr 2024 17:51:55 +0100
|
||||
Subject: [PATCH 36/50] drm/vc4: Ensure DSI is enabled for FIFO resets
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From db28d17b76e911d9e592b07b38dcaa2990efd942 Mon Sep 17 00:00:00 2001
|
||||
From 7c91806a1637d2fc3c83b44cbd758741527b93bb Mon Sep 17 00:00:00 2001
|
||||
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
|
||||
Date: Thu, 26 May 2022 18:56:19 +0100
|
||||
Subject: [PATCH 37/50] drm: vc4: Reset DSI AFE on disable
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From a3da11f394aaa52ee4a3be1ea63b2439066b126a Mon Sep 17 00:00:00 2001
|
||||
From 772bd1971a7c1fb2554cadfb111e8aabb974dd1a Mon Sep 17 00:00:00 2001
|
||||
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
|
||||
Date: Wed, 20 Nov 2024 13:58:08 +0000
|
||||
Subject: [PATCH 38/50] drm: vc4: dsi: Handle the different command FIFO widths
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 4f9b6e7917fb525ce1c8a1247602e67e0a90be39 Mon Sep 17 00:00:00 2001
|
||||
From 419f4339059576e35a8e4ebebd3677a8e3f6143f Mon Sep 17 00:00:00 2001
|
||||
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
|
||||
Date: Tue, 9 Jan 2024 17:37:00 +0000
|
||||
Subject: [PATCH 39/50] drm/bridge: tc358762: Program the DPI mode into the
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 24575bd2a0efeeae983c605786095fb2d49978cf Mon Sep 17 00:00:00 2001
|
||||
From 66d0d523c84e59a08a1aa1849aa0363bfa3a7641 Mon Sep 17 00:00:00 2001
|
||||
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
|
||||
Date: Tue, 9 Jan 2024 18:44:49 +0000
|
||||
Subject: [PATCH 40/50] drm/bridge: tc358762: revert move ops to enable
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From bac7eb3cfe56c161944b77f9729f5f3fe5fd0d70 Mon Sep 17 00:00:00 2001
|
||||
From e55233b40d6ed7e6643bb439780c96613c0b1a0c Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Sat, 4 Apr 2026 18:04:19 +0200
|
||||
Subject: [PATCH 41/50] drm/bridge: tc358762: Set pre_enabled on pre_enable to
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 6dcd3256ab1fcf224fc7bb41fb47b37f9f7cda16 Mon Sep 17 00:00:00 2001
|
||||
From 6683a6a60276d5c39451eb7049247e043937bc2f Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Sun, 5 Apr 2026 11:33:00 +0200
|
||||
Subject: [PATCH 42/50] net/pcs: add standalone PCS registration infrastructure
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From ab53ad238214316a1c6feab8f6d85c06832e7a47 Mon Sep 17 00:00:00 2001
|
||||
From 07da654fcd16b2ae3a7a767ff89f87a3da94f22d Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Mon, 6 Apr 2026 14:14:23 +0200
|
||||
Subject: [PATCH 43/50] net/pcs: add MediaTek MT7988 USXGMII PCS driver
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 5c7ae0ba30f846779eca1be5a9f36e1239ff74d8 Mon Sep 17 00:00:00 2001
|
||||
From f9cb160046a757abf5b081d6030011b54f3bc9b0 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Mon, 6 Apr 2026 14:15:43 +0200
|
||||
Subject: [PATCH 44/50] net: ethernet: mediatek: add USXGMII support for MT7988
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 6f05fd435c115a8bee19c93c22cf31ca1ecc89be Mon Sep 17 00:00:00 2001
|
||||
From 5cb31be412825eb8b95a79b89117587a8034d924 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Mon, 6 Apr 2026 14:15:56 +0200
|
||||
Subject: [PATCH 45/50] arm64: dts: mediatek: mt7988a: add USXGMII PCS nodes
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 413263b811c631ae2ef984583590fd2297ef648a Mon Sep 17 00:00:00 2001
|
||||
From 58e5a335513c90e40ef4d6b0e0dff018d77dce84 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Mon, 6 Apr 2026 14:16:11 +0200
|
||||
Subject: [PATCH 46/50] arm64: dts: mediatek: bananapi-bpi-r4: enable SFP+
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From d23cac53e2dbce4ce7f37aa8dd98dc5528bc45e0 Mon Sep 17 00:00:00 2001
|
||||
From 7528752150ebe1c29135d29d3a69b5c692771c18 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Tue, 7 Apr 2026 07:34:52 +0200
|
||||
Subject: [PATCH 47/50] net: phy: sfp: add OEM SFP-10G-T-I quirk
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 98bd974f7aedfa4a8181daa3f39867e480e097fa Mon Sep 17 00:00:00 2001
|
||||
From 51077597c4f3e458d4feb0ea36cc4ecbef3d635a Mon Sep 17 00:00:00 2001
|
||||
From: Tobias Waldekranz <tobias@waldekranz.com>
|
||||
Date: Fri, 17 Apr 2026 09:13:04 +0000
|
||||
Subject: [PATCH 48/50] net: dsa: mv88e6xxx: Trap PTP frames on timestamping
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From a212af73720bb070c7b9a18e70985ea659ab0039 Mon Sep 17 00:00:00 2001
|
||||
From e5e0572d67cc4be9db0a4d4f7d5c1b973b12da8e Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Tue, 28 Apr 2026 15:30:01 +0200
|
||||
Subject: [PATCH 49/50] wifi: mt76: mt7615: add MODULE_DEVICE_TABLE for mt7622
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
From 6c7aca4d3d77be389ff4d533a9f7eed095442aad Mon Sep 17 00:00:00 2001
|
||||
From 29cd680b8b3b2089bbcb67ee36117459bc11919d Mon Sep 17 00:00:00 2001
|
||||
From: Mattias Walström <lazzer@gmail.com>
|
||||
Date: Wed, 22 Apr 2026 10:24:43 +0200
|
||||
Subject: [PATCH 50/50] PCI: mediatek-gen3: Fix PERST# control timing during
|
||||
@@ -1,2 +1,2 @@
|
||||
# Calculated with utils/kernel-refresh.sh
|
||||
sha256 fbab86c9f471c81075b280cca30bd85d790c060063a1245859b6344b07c9c44e linux-6.18.36.tar.xz
|
||||
sha256 a83cd200e6646db52866b8309e9137b9e9048b613cbda10ced2b811aae125255 linux-6.18.37.tar.xz
|
||||
|
||||
@@ -2,26 +2,20 @@
|
||||
# Generate ietf-system.yang hostname based on:
|
||||
# 1. Command line argument + MAC suffix
|
||||
# 2. Specific hostname from qemu_fw_cfg, from Qeneth
|
||||
# 3. /etc/hostname from squashfs + MAC suffix
|
||||
# 3. Default hostname + MAC suffix
|
||||
#
|
||||
# The MAC suffix is emitted as the %m format specifier, expanded by confd
|
||||
# at apply time, so the actual MAC never ends up baked into a saved
|
||||
# configuration (issue #1554).
|
||||
#
|
||||
set -e
|
||||
|
||||
gen()
|
||||
{
|
||||
base_mac=$(jq -r '.["mac-address"]' /run/system.json)
|
||||
if [ -z "$base_mac" ] || [ "$base_mac" = "null" ]; then
|
||||
base_mac=00:00:00:00:00:00
|
||||
fi
|
||||
|
||||
echo "$1-$(echo $base_mac | tail -c 9 | tr ':' '-')"
|
||||
}
|
||||
|
||||
if [ "$1" ]; then
|
||||
sysname=$(gen "$1")
|
||||
sysname="$1-%m"
|
||||
elif [ -f "/sys/firmware/qemu_fw_cfg/by_name/opt/hostname/raw" ]; then
|
||||
sysname="$(cat /sys/firmware/qemu_fw_cfg/by_name/opt/hostname/raw)"
|
||||
else
|
||||
sysname=$(gen "$(cat /etc/hostname)")
|
||||
sysname="%h-%m"
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
+5
-10
@@ -439,7 +439,7 @@ static confd_dependency_t dep_radio_components(struct lyd_node **diff, struct ly
|
||||
class = lyd_get_value(class_node);
|
||||
}
|
||||
|
||||
if (strcmp(class, "infix-hardware:wifi"))
|
||||
if (!class || strcmp(class, "infix-hardware:wifi"))
|
||||
continue;
|
||||
|
||||
/* Find all interfaces that reference this radio */
|
||||
@@ -571,14 +571,6 @@ static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *mod
|
||||
}
|
||||
max_dep--;
|
||||
}
|
||||
#if 0
|
||||
/* Debug: print diff to file */
|
||||
FILE *f = fopen("/tmp/confd-diff.json", "w");
|
||||
if (f) {
|
||||
lyd_print_file(f, diff, LYD_JSON, LYD_PRINT_SIBLINGS);
|
||||
fclose(f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ietf-hardware */
|
||||
@@ -728,8 +720,11 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
|
||||
goto err;
|
||||
|
||||
confd.root = json_load_file("/run/system.json", 0, NULL);
|
||||
if (!confd.root)
|
||||
if (!confd.root) {
|
||||
ERROR("failed loading /run/system.json, system probe incomplete");
|
||||
rc = SR_ERR_SYS;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* An optional file that contains hardware specific quirks for
|
||||
* the network interfaces on running board.
|
||||
|
||||
@@ -237,7 +237,7 @@ static const char *wifi_find_higher_band_twin(struct lyd_node *config,
|
||||
{
|
||||
struct lyd_node *cifs, *cif;
|
||||
|
||||
if (strcmp(current_band, "2.4GHz"))
|
||||
if (!current_band || !current_ssid || strcmp(current_band, "2.4GHz"))
|
||||
return NULL;
|
||||
|
||||
cifs = lydx_get_descendant(config, "interfaces", "interface", NULL);
|
||||
@@ -252,13 +252,13 @@ static const char *wifi_find_higher_band_twin(struct lyd_node *config,
|
||||
if (!ap)
|
||||
continue;
|
||||
ssid = lydx_get_cattr(ap, "ssid");
|
||||
if (strcmp(ssid, current_ssid))
|
||||
if (!ssid || strcmp(ssid, current_ssid))
|
||||
continue;
|
||||
radio = lydx_get_cattr(wifi, "radio");
|
||||
radio_node = lydx_get_xpathf(config,
|
||||
"/hardware/component[name='%s']/wifi-radio", radio);
|
||||
band = lydx_get_cattr(radio_node, "band");
|
||||
if (!strcmp(band, "5GHz") || !strcmp(band, "6GHz"))
|
||||
if (band && (!strcmp(band, "5GHz") || !strcmp(band, "6GHz")))
|
||||
return lydx_get_cattr(cif, "name");
|
||||
}
|
||||
|
||||
@@ -1379,8 +1379,32 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
|
||||
* all radio configs, so band edits and radio add/remove both apply.
|
||||
*/
|
||||
if (wifi_changed && event == SR_EV_DONE) {
|
||||
struct lyd_node *cifs;
|
||||
glob_t gl = { 0 };
|
||||
size_t i;
|
||||
|
||||
/* Need to remove old hostapd config files, for radios not used anymore */
|
||||
cifs = lydx_get_descendant(config, "interfaces", "interface", NULL);
|
||||
if (glob("/etc/hostapd-*.conf", 0, NULL, &gl) == 0) {
|
||||
for (i = 0; i < gl.gl_pathc; i++) {
|
||||
char radio[64], **aps = NULL;
|
||||
int n = 0, j;
|
||||
|
||||
if (sscanf(gl.gl_pathv[i], "/etc/hostapd-%63[^.].conf", radio) != 1)
|
||||
continue;
|
||||
wifi_find_radio_aps(cifs, radio, &aps, &n);
|
||||
for (j = 0; j < n; j++)
|
||||
free(aps[j]);
|
||||
free(aps);
|
||||
if (!n) {
|
||||
unlink(gl.gl_pathv[i]);
|
||||
erasef(HOSTAPD_CONF_NEXT, radio);
|
||||
}
|
||||
}
|
||||
globfree(&gl);
|
||||
}
|
||||
|
||||
gl = (glob_t){ 0 };
|
||||
if (glob("/etc/hostapd-*.conf", 0, NULL, &gl) == 0 && gl.gl_pathc > 0) {
|
||||
FILE *fp;
|
||||
|
||||
@@ -1389,8 +1413,6 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
|
||||
ERRNO("Could not open " HOSTAPD_SERVICE);
|
||||
rc = SR_ERR_INTERNAL;
|
||||
} else {
|
||||
size_t i;
|
||||
|
||||
fprintf(fp, "# Generated by confd, do not edit.\n");
|
||||
fprintf(fp, "service <!> name:hostapd \\\n");
|
||||
fprintf(fp, "\t[2345] hostapd -P /run/hostapd.pid");
|
||||
|
||||
@@ -237,7 +237,7 @@ static int netdag_gen_ethtool_autoneg(struct dagger *net, struct lyd_node *cif)
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (n != 1) {
|
||||
if (n != 1 || !forced) {
|
||||
sr_session_set_error_message(net->session,
|
||||
"%s: auto-negotiation/enable=false requires exactly one "
|
||||
"advertised-pmd-types entry (have %d)", ifname, n);
|
||||
|
||||
+8
-17
@@ -283,7 +283,7 @@ static int wifi_center_chan_160(int ch)
|
||||
*/
|
||||
static int wifi_chan_to_freq(int channel, const char *band)
|
||||
{
|
||||
if (!strcmp(band, "6GHz"))
|
||||
if (band && !strcmp(band, "6GHz"))
|
||||
return 5950 + channel * 5;
|
||||
|
||||
if (channel >= 1 && channel <= 13)
|
||||
@@ -392,7 +392,7 @@ int wifi_gen_mesh(struct lyd_node *cif)
|
||||
int center = wifi_center_chan_80(channel);
|
||||
|
||||
fprintf(wpa_supplicant, " ht40=1\n");
|
||||
if (!strcmp(band, "6GHz"))
|
||||
if (band && !strcmp(band, "6GHz"))
|
||||
fprintf(wpa_supplicant, " he=1\n");
|
||||
else
|
||||
fprintf(wpa_supplicant, " vht=1\n");
|
||||
@@ -403,7 +403,7 @@ int wifi_gen_mesh(struct lyd_node *cif)
|
||||
int center = wifi_center_chan_160(channel);
|
||||
|
||||
fprintf(wpa_supplicant, " ht40=1\n");
|
||||
if (!strcmp(band, "6GHz"))
|
||||
if (band && !strcmp(band, "6GHz"))
|
||||
fprintf(wpa_supplicant, " he=1\n");
|
||||
else
|
||||
fprintf(wpa_supplicant, " vht=1\n");
|
||||
@@ -540,7 +540,6 @@ out:
|
||||
*/
|
||||
int wifi_del_iface(struct lyd_node *dif, struct dagger *net)
|
||||
{
|
||||
struct lyd_node *wifi;
|
||||
const char *ifname;
|
||||
FILE *iw;
|
||||
|
||||
@@ -553,22 +552,14 @@ int wifi_del_iface(struct lyd_node *dif, struct dagger *net)
|
||||
}
|
||||
|
||||
fprintf(iw, "# Generated by Infix confd - WiFi Interface Deletion\n");
|
||||
|
||||
erasef(WPA_SUPPLICANT_CONF, ifname);
|
||||
fprintf(iw, "initctl -bfq disable mesh@%s\n", ifname);
|
||||
fprintf(iw, "initctl -bfq disable wifi@%s\n", ifname);
|
||||
|
||||
fprintf(iw, "ip link set %s down\n", ifname);
|
||||
fprintf(iw, "iw dev %s disconnect 2>/dev/null\n", ifname);
|
||||
fprintf(iw, "iw dev %s del 2>/dev/null || ip link del %s 2>/dev/null || true\n", ifname, ifname);
|
||||
|
||||
wifi = lydx_get_child(dif, "wifi");
|
||||
if (wifi) {
|
||||
int mode = wifi_get_mode(wifi);
|
||||
|
||||
if (mode == wifi_mesh) {
|
||||
erasef(WPA_SUPPLICANT_CONF, ifname);
|
||||
fprintf(iw, "initctl -bfq disable mesh@%s\n", ifname);
|
||||
} else if (mode != wifi_ap) {
|
||||
erasef(WPA_SUPPLICANT_CONF, ifname);
|
||||
fprintf(iw, "initctl -bfq disable wifi@%s\n", ifname);
|
||||
}
|
||||
}
|
||||
fclose(iw);
|
||||
|
||||
return SR_ERR_OK;
|
||||
|
||||
@@ -27,7 +27,7 @@ MODULES=(
|
||||
"infix-syslog@2025-11-17.yang"
|
||||
"iana-hardware@2018-03-13.yang"
|
||||
"ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor"
|
||||
"infix-hardware@2026-03-01.yang"
|
||||
"infix-hardware@2026-07-02.yang"
|
||||
"ieee802-dot1q-types@2022-10-29.yang"
|
||||
"infix-ip@2026-04-28.yang"
|
||||
"infix-if-type@2026-01-07.yang"
|
||||
|
||||
@@ -21,6 +21,12 @@ module infix-hardware {
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Vital Product Data augmentation of ieee-hardware and deviations.";
|
||||
|
||||
revision 2026-07-02 {
|
||||
description "Widen wifi max-interfaces ap/station to uint16, virtual
|
||||
radios (mac80211_hwsim) report combinations up to 2048.";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
revision 2026-03-01 {
|
||||
description "Add channel-width leaf for WiFi radio container.";
|
||||
reference "internal";
|
||||
@@ -457,13 +463,13 @@ module infix-hardware {
|
||||
"Maximum number of virtual interfaces supported by this radio.";
|
||||
|
||||
leaf ap {
|
||||
type uint8;
|
||||
type uint16;
|
||||
description
|
||||
"Maximum number of AP interfaces.";
|
||||
}
|
||||
|
||||
leaf station {
|
||||
type uint8;
|
||||
type uint16;
|
||||
description
|
||||
"Maximum number of station interfaces.";
|
||||
}
|
||||
|
||||
@@ -48,6 +48,13 @@ submodule infix-if-wifi {
|
||||
- Security: WPA2/WPA3 with keystore integration
|
||||
- Operational state: Connection status, RSSI, client lists";
|
||||
|
||||
revision 2026-07-01 {
|
||||
description
|
||||
"Add station 'bssid' operational leaf: the BSSID the station is
|
||||
currently associated to, to tell roaming-ESS access points apart.";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
revision 2026-03-06 {
|
||||
description
|
||||
"Adding mesh support and roaming.
|
||||
@@ -239,6 +246,18 @@ submodule infix-if-wifi {
|
||||
|
||||
/* Operational state */
|
||||
|
||||
leaf bssid {
|
||||
config false;
|
||||
type yang:mac-address;
|
||||
description
|
||||
"BSSID (MAC address) of the access point the station is
|
||||
currently associated to.
|
||||
|
||||
Only present while connected. With several access points
|
||||
sharing one SSID (a roaming ESS), this identifies which one
|
||||
the station is on.";
|
||||
}
|
||||
|
||||
leaf signal-strength {
|
||||
config false;
|
||||
type int16;
|
||||
|
||||
@@ -1762,8 +1762,11 @@ class Iface:
|
||||
station = self.wifi.get('station', {})
|
||||
signal = station.get('signal-strength')
|
||||
ssid = station.get('ssid', "----")
|
||||
bssid = station.get('bssid')
|
||||
print(f"{'mode':<{19}}: {mode}")
|
||||
print(f"{'ssid':<{19}}: {ssid}")
|
||||
if bssid is not None:
|
||||
print(f"{'bssid':<{19}}: {bssid}")
|
||||
if signal is not None:
|
||||
signal_status = signal_to_status(signal)
|
||||
print(f"{'signal':<{19}}: {signal} dBm ({signal_status})")
|
||||
|
||||
@@ -591,8 +591,9 @@ def convert_iw_phy_info_for_yanger(phy_info):
|
||||
for band in phy_info.get("bands", []):
|
||||
band_data = {
|
||||
"band": str(band.get("band", 0)),
|
||||
"name": band.get("name", "Unknown")
|
||||
}
|
||||
if band.get("name"):
|
||||
band_data["name"] = band["name"]
|
||||
|
||||
# Add capability flags (iw.py uses snake_case: ht_capable, vht_capable, he_capable)
|
||||
if band.get("ht_capable"):
|
||||
|
||||
@@ -83,6 +83,8 @@ def wifi_station(ifname):
|
||||
if link.get('connected'):
|
||||
if link.get('ssid'):
|
||||
station_data['ssid'] = link['ssid']
|
||||
if link.get('bssid'):
|
||||
station_data['bssid'] = link['bssid']
|
||||
if link.get('signal-strength') is not None:
|
||||
station_data['signal-strength'] = link['signal-strength']
|
||||
if link.get('rx-speed') is not None:
|
||||
|
||||
+4
-3
@@ -17,9 +17,6 @@
|
||||
infamy:
|
||||
specification: False
|
||||
|
||||
- name: "Miscellaneous"
|
||||
suite: misc/all.yaml
|
||||
|
||||
|
||||
- name: "System"
|
||||
suite: system/all.yaml
|
||||
@@ -62,3 +59,7 @@
|
||||
|
||||
- name: "Use Case Tests"
|
||||
suite: use_case/all.yaml
|
||||
|
||||
- name: "Miscellaneous"
|
||||
suite: misc/all.yaml
|
||||
|
||||
|
||||
@@ -52,3 +52,6 @@
|
||||
|
||||
- name: Layer-2/3 Tunnel Tests
|
||||
suite: tunnels.yaml
|
||||
|
||||
- name: WiFi Tests
|
||||
suite: wifi.yaml
|
||||
|
||||
@@ -133,7 +133,7 @@ with infamy.Test() as test:
|
||||
|
||||
print("Starting IPv4 multicast sender")
|
||||
with mcast.MCastSender(send_ns, ipv4_multicast_group):
|
||||
|
||||
|
||||
with test.step("Verify that 224.1.1.1 is flooded to host:data2 and host:data3"):
|
||||
infamy.parallel(
|
||||
lambda: receive_ns.must_receive(f"ip dst {ipv4_multicast_group}"),
|
||||
@@ -143,30 +143,30 @@ with infamy.Test() as test:
|
||||
set_static_multicast_filter(target, ipv4_multicast_group, mreceive)
|
||||
until(lambda: iface.exist_bridge_multicast_filter(target, ipv4_multicast_group, mreceive, "br0"))
|
||||
|
||||
with test.step("Verify that the group is still forwarded to host:data2"):
|
||||
receive_ns.must_receive(f"ip dst {ipv4_multicast_group}")
|
||||
with test.step("Verify that the group is still forwarded to host:data2"):
|
||||
receive_ns.must_receive(f"ip dst {ipv4_multicast_group}")
|
||||
|
||||
with test.step("Verify that the group is no longer forwarded to host:data3"):
|
||||
nojoin_ns.must_not_receive(f"ip dst {ipv4_multicast_group}")
|
||||
with test.step("Verify that the group is no longer forwarded to host:data3"):
|
||||
nojoin_ns.must_not_receive(f"ip dst {ipv4_multicast_group}")
|
||||
|
||||
with test.step("Start MAC multicast sender on host:data1, group 01:00:00:01:02:03"):
|
||||
|
||||
|
||||
print("Starting MAC multicast sender")
|
||||
with mcast.MacMCastSender(send_ns, mac_multicast_group):
|
||||
|
||||
|
||||
with test.step("Verify MAC multicast 01:00:00:01:02:03 is flooded to host:data2 and host:data3"):
|
||||
infamy.parallel(
|
||||
lambda: receive_ns.must_receive(f"ether dst {mac_multicast_group}"),
|
||||
lambda: nojoin_ns.must_receive(f"ether dst {mac_multicast_group}"))
|
||||
|
||||
|
||||
with test.step("Enable MAC multicast filter on host:data2, group 01:00:00:01:02:03"):
|
||||
set_static_multicast_filter(target, mac_multicast_group, mreceive)
|
||||
until(lambda: iface.exist_bridge_multicast_filter(target, mac_multicast_group, mreceive, "br0"))
|
||||
|
||||
with test.step("Verify that the MAC group is still forwarded to host:data2"):
|
||||
receive_ns.must_receive(f"ether dst {mac_multicast_group}"),
|
||||
|
||||
with test.step("Verify that the MAC group is no longer forwarded to host:data3"):
|
||||
nojoin_ns.must_not_receive(f"ether dst {mac_multicast_group}")
|
||||
|
||||
with test.step("Verify that the MAC group is still forwarded to host:data2"):
|
||||
receive_ns.must_receive(f"ether dst {mac_multicast_group}")
|
||||
|
||||
with test.step("Verify that the MAC group is no longer forwarded to host:data3"):
|
||||
nojoin_ns.must_not_receive(f"ether dst {mac_multicast_group}")
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: WiFi AP and Station Association across two DUTs
|
||||
case: wifi_ap_station_2dut/test.py
|
||||
|
||||
- name: WiFi Access Point serving three Stations
|
||||
case: wifi_ap_multi_station/test.py
|
||||
|
||||
- name: WiFi Mesh backhaul with roaming Access Points
|
||||
case: wifi_mesh_roaming/test.py
|
||||
|
||||
- name: WiFi Band Steering across a dual-band Access Point
|
||||
case: wifi_band_steering/test.py
|
||||
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
@@ -0,0 +1,46 @@
|
||||
=== WiFi Access Point serving three Stations
|
||||
|
||||
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/wifi_ap_multi_station]
|
||||
|
||||
==== Description
|
||||
|
||||
One AP and three Stations, each on its own DUT: the ap runs a
|
||||
WPA2/WPA3-personal Access Point on radio0, and station1, station2 and
|
||||
station3 each associate to it. The radios are in separate kernels, so the
|
||||
"air" between them is realised differently per environment:
|
||||
|
||||
* On real hardware the links are real RF -- the stations just associate.
|
||||
* On QEMU the radios are mac80211_hwsim, and the `wifimedium` relay on each
|
||||
DUT bridges hwsim frames over the Ethernet segments between the guests
|
||||
(the topology's wifi-links). See doc/wifi.md.
|
||||
|
||||
This is the multi-client case: a single AP cell must serve more than one
|
||||
station at once. Each station associating with WPA2/WPA3-personal is a
|
||||
strong end-to-end check -- authentication, association and the WPA 4-way
|
||||
handshake all require frames to cross the medium in *both* directions -- so
|
||||
three successful associations prove the AP keeps several stations
|
||||
associated simultaneously over the (relayed) radio links. It then confirms
|
||||
data-plane reach: the ap serves DHCP and each station leases an address
|
||||
from the pool over the radio link.
|
||||
|
||||
Topology:
|
||||
....
|
||||
((( station1 ==(mgmt)== host
|
||||
host ==(mgmt)== ap ((( station2 ==(mgmt)== host
|
||||
((( station3 ==(mgmt)== host
|
||||
....
|
||||
|
||||
==== Topology
|
||||
|
||||
image::topology.svg[WiFi Access Point serving three Stations topology, align=center, scaledwidth=75%]
|
||||
|
||||
==== Sequence
|
||||
|
||||
. Set up topology and attach to the ap and three stations
|
||||
. Configure the ap as an Access Point on radio0
|
||||
. Configure the station on radio0
|
||||
. Verify the station associates to the ap over the wifi link
|
||||
. Verify the station's wifi0 operational status is up
|
||||
. Verify the station leases an address from the ap over wifi
|
||||
|
||||
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
r"""
|
||||
WiFi Access Point serving multiple Stations
|
||||
|
||||
One AP and three Stations, each on its own DUT: the ap runs a
|
||||
WPA2/WPA3-personal Access Point on radio0, and station1, station2 and
|
||||
station3 each associate to it. The radios are in separate kernels, so the
|
||||
"air" between them is realised differently per environment:
|
||||
|
||||
* On real hardware the links are real RF -- the stations just associate.
|
||||
* On QEMU the radios are mac80211_hwsim, and the `wifimedium` relay on each
|
||||
DUT bridges hwsim frames over the Ethernet segments between the guests
|
||||
(the topology's wifi-links). See doc/wifi.md.
|
||||
|
||||
This is the multi-client case: a single AP cell must serve more than one
|
||||
station at once. Each station associating with WPA2/WPA3-personal is a
|
||||
strong end-to-end check -- authentication, association and the WPA 4-way
|
||||
handshake all require frames to cross the medium in *both* directions -- so
|
||||
three successful associations prove the AP keeps several stations
|
||||
associated simultaneously over the (relayed) radio links. It then confirms
|
||||
data-plane reach: the ap serves DHCP and each station leases an address
|
||||
from the pool over the radio link.
|
||||
|
||||
Topology:
|
||||
....
|
||||
((( station1 ==(mgmt)== host
|
||||
host ==(mgmt)== ap ((( station2 ==(mgmt)== host
|
||||
((( station3 ==(mgmt)== host
|
||||
....
|
||||
"""
|
||||
import base64
|
||||
|
||||
import infamy
|
||||
import infamy.iface as iface
|
||||
from infamy.util import until, parallel
|
||||
|
||||
SSID = "infix-test"
|
||||
PSK = "infixinfix" # 8-63 printable ASCII, see doc/wifi.md
|
||||
PSK_B64 = base64.b64encode(PSK.encode()).decode()
|
||||
COUNTRY = "SE"
|
||||
|
||||
# hwsim defaults every radio0 to 02:00:00:00:00:00, so the AP and all the
|
||||
# stations would otherwise share one MAC -- give each DUT a unique address.
|
||||
STATIONS = [
|
||||
("station1", "02:00:00:00:00:02"),
|
||||
("station2", "02:00:00:00:00:03"),
|
||||
("station3", "02:00:00:00:00:04"),
|
||||
]
|
||||
AP_MAC = "02:00:00:00:00:01"
|
||||
|
||||
# The ap runs a DHCP server on wifi0; every station leases an address from
|
||||
# the pool. A completed lease is real bidirectional IP traffic over the
|
||||
# radio link, so a lease per station proves all three reach the ap at L3.
|
||||
SUBNET = "192.168.20.0/24"
|
||||
AP_IP = "192.168.20.1"
|
||||
POOL_START = "192.168.20.100"
|
||||
POOL_END = "192.168.20.102" # three addresses, one per station
|
||||
|
||||
|
||||
def radio(name, band=None, channel=None):
|
||||
"""ietf-hardware component for a radio."""
|
||||
wifi_radio = {"country-code": COUNTRY}
|
||||
if band:
|
||||
wifi_radio["band"] = band
|
||||
if channel is not None:
|
||||
wifi_radio["channel"] = channel
|
||||
return {
|
||||
"name": name,
|
||||
"class": "infix-hardware:wifi",
|
||||
"infix-hardware:wifi-radio": wifi_radio,
|
||||
}
|
||||
|
||||
|
||||
def keystore():
|
||||
return {
|
||||
"keystore": {
|
||||
"symmetric-keys": {
|
||||
"symmetric-key": [{
|
||||
"name": "wifi",
|
||||
"key-format": "infix-crypto-types:passphrase-key-format",
|
||||
"cleartext-symmetric-key": PSK_B64,
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def wifi_iface(mac, wifi, ipv4=None):
|
||||
ifc = {
|
||||
"name": "wifi0",
|
||||
"type": "infix-if-type:wifi",
|
||||
"infix-interfaces:custom-phys-address": {"static": mac},
|
||||
"infix-interfaces:wifi": wifi,
|
||||
}
|
||||
if ipv4:
|
||||
ifc["ietf-ip:ipv4"] = ipv4
|
||||
return {"interfaces": {"interface": [ifc]}}
|
||||
|
||||
|
||||
def leased(dut):
|
||||
"""True once wifi0 holds an address handed out by the ap's DHCP server."""
|
||||
for addr in iface.get_ipv4_address(dut, "wifi0") or []:
|
||||
if addr.get("origin") == "dhcp" and \
|
||||
POOL_START <= addr.get("ip", "") <= POOL_END:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Set up topology and attach to the ap and three stations"):
|
||||
env = infamy.Env()
|
||||
# Connect to the AP and all stations concurrently to cut setup time.
|
||||
ap, *station_duts = parallel(
|
||||
lambda: env.attach("ap", "mgmt"),
|
||||
*((lambda n=name: env.attach(n, "mgmt")) for name, _ in STATIONS),
|
||||
)
|
||||
stations = [(name, mac, dut)
|
||||
for (name, mac), dut in zip(STATIONS, station_duts)]
|
||||
|
||||
for dut in [ap] + [s for _, _, s in stations]:
|
||||
if not dut.has_feature("infix-interfaces", "wifi"):
|
||||
print("DUT does not advertise the 'wifi' feature -- skipping")
|
||||
test.skip()
|
||||
|
||||
with test.step("Configure the ap as an Access Point on radio0"):
|
||||
ap.put_config_dicts({
|
||||
"ietf-hardware": {"hardware": {"component": [
|
||||
radio("radio0", band="2.4GHz", channel=1)]}},
|
||||
"ietf-keystore": keystore(),
|
||||
"ietf-interfaces": wifi_iface(AP_MAC, {
|
||||
"radio": "radio0",
|
||||
"access-point": {
|
||||
"ssid": SSID,
|
||||
"security": {"mode": "wpa2-wpa3-personal", "secret": "wifi"},
|
||||
},
|
||||
}, ipv4={"address": [{"ip": AP_IP, "prefix-length": 24}]}),
|
||||
"infix-dhcp-server": {"dhcp-server": {"subnet": [{
|
||||
"subnet": SUBNET,
|
||||
"pool": {"start-address": POOL_START, "end-address": POOL_END},
|
||||
}]}},
|
||||
})
|
||||
|
||||
for name, mac, dut in stations:
|
||||
with test.step("Configure the station on radio0"):
|
||||
print(f"Configuring {name}")
|
||||
dut.put_config_dicts({
|
||||
"ietf-hardware": {"hardware": {"component": [radio("radio0")]}},
|
||||
"ietf-keystore": keystore(),
|
||||
"ietf-interfaces": wifi_iface(mac, {
|
||||
"radio": "radio0",
|
||||
"station": {
|
||||
"ssid": SSID,
|
||||
"security": {"mode": "auto", "secret": "wifi"},
|
||||
},
|
||||
}, ipv4={"infix-dhcp-client:dhcp": {}}),
|
||||
})
|
||||
|
||||
for name, _mac, dut in stations:
|
||||
with test.step("Verify the station associates to the ap over the wifi link"):
|
||||
print(f"Verifying {name}")
|
||||
def associated(dut=dut):
|
||||
ifc = dut.get_iface("wifi0")
|
||||
if not ifc:
|
||||
return False
|
||||
wifi = ifc.get("infix-interfaces:wifi") or ifc.get("wifi") or {}
|
||||
station = wifi.get("station", {})
|
||||
return station.get("ssid") == SSID and \
|
||||
station.get("signal-strength") is not None
|
||||
until(associated, attempts=60, interval=2)
|
||||
|
||||
for name, _mac, dut in stations:
|
||||
with test.step("Verify the station's wifi0 operational status is up"):
|
||||
print(f"Verifying {name}")
|
||||
until(lambda dut=dut: iface.is_oper_up(dut, "wifi0"), attempts=30)
|
||||
|
||||
for name, _mac, dut in stations:
|
||||
with test.step("Verify the station leases an address from the ap over wifi"):
|
||||
print(f"Verifying {name}")
|
||||
until(lambda dut=dut: leased(dut), attempts=60, interval=2)
|
||||
|
||||
test.succeed()
|
||||
@@ -0,0 +1,61 @@
|
||||
graph "wifi-ap-multi-station" {
|
||||
layout="neato";
|
||||
overlap="false";
|
||||
esep="+40";
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <mgmt1> mgmt1 | <mgmt2> mgmt2 | <mgmt3> mgmt3 | <mgmt4> mgmt4 }",
|
||||
pos="0,0!",
|
||||
requires="controller",
|
||||
];
|
||||
|
||||
ap [
|
||||
label="{ <mgmt> mgmt | <wifi> wifi } | ap",
|
||||
pos="6,0!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
station1 [
|
||||
label="{ <mgmt> mgmt | <wifi> wifi } | station1",
|
||||
pos="11,3!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
station2 [
|
||||
label="{ <mgmt> mgmt | <wifi> wifi } | station2",
|
||||
pos="11,0!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
station3 [
|
||||
label="{ <mgmt> mgmt | <wifi> wifi } | station3",
|
||||
pos="11,-3!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
// One AP cell shared by the ap and all three stations: a single medium
|
||||
// node every radio joins (same index -> same cell). The ap's one radio
|
||||
// must be in range of every station, which the mapper guarantees by
|
||||
// placing all four on this shared medium:
|
||||
// * physical: maps onto one over-the-air RF cell;
|
||||
// * virtual (qeneth): maps onto one multicast cell the wifimedium
|
||||
// relay bridges mac80211_hwsim frames over.
|
||||
cell [
|
||||
label="cell",
|
||||
pos="8.5,0!",
|
||||
requires="wifi-radio",
|
||||
];
|
||||
|
||||
host:mgmt1 -- ap:mgmt [requires="mgmt", color="lightgray"]
|
||||
host:mgmt2 -- station1:mgmt [requires="mgmt", color="lightgray"]
|
||||
host:mgmt3 -- station2:mgmt [requires="mgmt", color="lightgray"]
|
||||
host:mgmt4 -- station3:mgmt [requires="mgmt", color="lightgray"]
|
||||
|
||||
ap:wifi -- cell [requires="wifi2.4GHz", style="dashed"]
|
||||
station1:wifi -- cell [requires="wifi2.4GHz", style="dashed"]
|
||||
station2:wifi -- cell [requires="wifi2.4GHz", style="dashed"]
|
||||
station3:wifi -- cell [requires="wifi2.4GHz", style="dashed"]
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?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: wifi-ap-multi-station Pages: 1 -->
|
||||
<svg width="980pt" height="519pt"
|
||||
viewBox="0.00 0.00 979.99 519.45" 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 515.45)">
|
||||
<title>wifi-ap-multi-station</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-515.45 975.99,-515.45 975.99,4 -4,4"/>
|
||||
<!-- host -->
|
||||
<g id="node1" class="node">
|
||||
<title>host</title>
|
||||
<polygon fill="none" stroke="black" points="0,-209.72 0,-301.72 108,-301.72 108,-209.72 0,-209.72"/>
|
||||
<text text-anchor="middle" x="25" y="-252.02" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
<polyline fill="none" stroke="black" points="50,-209.72 50,-301.72 "/>
|
||||
<text text-anchor="middle" x="79" y="-286.52" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
|
||||
<polyline fill="none" stroke="black" points="50,-278.72 108,-278.72 "/>
|
||||
<text text-anchor="middle" x="79" y="-263.52" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
|
||||
<polyline fill="none" stroke="black" points="50,-255.72 108,-255.72 "/>
|
||||
<text text-anchor="middle" x="79" y="-240.52" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt3</text>
|
||||
<polyline fill="none" stroke="black" points="50,-232.72 108,-232.72 "/>
|
||||
<text text-anchor="middle" x="79" y="-217.52" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt4</text>
|
||||
</g>
|
||||
<!-- ap -->
|
||||
<g id="node2" class="node">
|
||||
<title>ap</title>
|
||||
<polygon fill="none" stroke="black" points="476.95,-232.72 476.95,-278.72 559.95,-278.72 559.95,-232.72 476.95,-232.72"/>
|
||||
<text text-anchor="middle" x="501.95" y="-263.52" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="476.95,-255.72 526.95,-255.72 "/>
|
||||
<text text-anchor="middle" x="501.95" y="-240.52" font-family="DejaVu Sans Mono, Book" font-size="14.00">wifi</text>
|
||||
<polyline fill="none" stroke="black" points="526.95,-232.72 526.95,-278.72 "/>
|
||||
<text text-anchor="middle" x="543.45" y="-252.02" font-family="DejaVu Sans Mono, Book" font-size="14.00">ap</text>
|
||||
</g>
|
||||
<!-- host--ap -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt1--ap:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-290.72C108,-290.72 476.45,-267.72 476.45,-267.72"/>
|
||||
</g>
|
||||
<!-- station1 -->
|
||||
<g id="node3" class="node">
|
||||
<title>station1</title>
|
||||
<polygon fill="none" stroke="black" points="838.99,-464.95 838.99,-510.95 971.99,-510.95 971.99,-464.95 838.99,-464.95"/>
|
||||
<text text-anchor="middle" x="863.99" y="-495.75" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="838.99,-487.95 888.99,-487.95 "/>
|
||||
<text text-anchor="middle" x="863.99" y="-472.75" font-family="DejaVu Sans Mono, Book" font-size="14.00">wifi</text>
|
||||
<polyline fill="none" stroke="black" points="888.99,-464.95 888.99,-510.95 "/>
|
||||
<text text-anchor="middle" x="930.49" y="-484.25" font-family="DejaVu Sans Mono, Book" font-size="14.00">station1</text>
|
||||
</g>
|
||||
<!-- host--station1 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mgmt2--station1:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-267.72C108,-267.72 838.49,-499.95 838.49,-499.95"/>
|
||||
</g>
|
||||
<!-- station2 -->
|
||||
<g id="node4" class="node">
|
||||
<title>station2</title>
|
||||
<polygon fill="none" stroke="black" points="838.99,-232.72 838.99,-278.72 971.99,-278.72 971.99,-232.72 838.99,-232.72"/>
|
||||
<text text-anchor="middle" x="863.99" y="-263.52" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="838.99,-255.72 888.99,-255.72 "/>
|
||||
<text text-anchor="middle" x="863.99" y="-240.52" font-family="DejaVu Sans Mono, Book" font-size="14.00">wifi</text>
|
||||
<polyline fill="none" stroke="black" points="888.99,-232.72 888.99,-278.72 "/>
|
||||
<text text-anchor="middle" x="930.49" y="-252.02" font-family="DejaVu Sans Mono, Book" font-size="14.00">station2</text>
|
||||
</g>
|
||||
<!-- host--station2 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:mgmt3--station2:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-243.72C108,-243.72 838.49,-267.72 838.49,-267.72"/>
|
||||
</g>
|
||||
<!-- station3 -->
|
||||
<g id="node5" class="node">
|
||||
<title>station3</title>
|
||||
<polygon fill="none" stroke="black" points="838.99,-0.5 838.99,-46.5 971.99,-46.5 971.99,-0.5 838.99,-0.5"/>
|
||||
<text text-anchor="middle" x="863.99" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="838.99,-23.5 888.99,-23.5 "/>
|
||||
<text text-anchor="middle" x="863.99" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">wifi</text>
|
||||
<polyline fill="none" stroke="black" points="888.99,-0.5 888.99,-46.5 "/>
|
||||
<text text-anchor="middle" x="930.49" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">station3</text>
|
||||
</g>
|
||||
<!-- host--station3 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>host:mgmt4--station3:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-220.72C108,-220.72 838.49,-35.5 838.49,-35.5"/>
|
||||
</g>
|
||||
<!-- cell -->
|
||||
<g id="node6" class="node">
|
||||
<title>cell</title>
|
||||
<polygon fill="none" stroke="black" points="684.97,-237.72 684.97,-273.72 738.97,-273.72 738.97,-237.72 684.97,-237.72"/>
|
||||
<text text-anchor="middle" x="711.97" y="-252.02" font-family="DejaVu Sans Mono, Book" font-size="14.00">cell</text>
|
||||
</g>
|
||||
<!-- ap--cell -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>ap:wifi--cell</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" stroke-dasharray="5,2" d="M501.45,-232.72C501.45,-232.72 628.02,-246.55 684.88,-252.76"/>
|
||||
</g>
|
||||
<!-- station1--cell -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>station1:wifi--cell</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" stroke-dasharray="5,2" d="M838.49,-475.95C838.49,-475.95 752.54,-326.34 722.32,-273.75"/>
|
||||
</g>
|
||||
<!-- station2--cell -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>station2:wifi--cell</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" stroke-dasharray="5,2" d="M838.49,-243.72C838.49,-243.72 776.28,-249.62 739.11,-253.15"/>
|
||||
</g>
|
||||
<!-- station3--cell -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>station3:wifi--cell</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" stroke-dasharray="5,2" d="M838.49,-11.5C838.49,-11.5 750.9,-180.57 721.48,-237.37"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.5 KiB |
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
@@ -0,0 +1,43 @@
|
||||
=== WiFi AP and Station Association across two DUTs
|
||||
|
||||
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/wifi_ap_station_2dut]
|
||||
|
||||
==== Description
|
||||
|
||||
Two DUTs, each with its own radio: the ap runs a WPA2/WPA3-personal Access
|
||||
Point, the station associates to it. The radios are in
|
||||
separate kernels, so the "air" between them is realised differently per
|
||||
environment:
|
||||
|
||||
* On real hardware the link is real RF -- the radios just associate.
|
||||
* On QEMU the radios are mac80211_hwsim, and the `wifimedium` relay on
|
||||
each DUT bridges hwsim frames over the Ethernet segment between the
|
||||
guests (the topology's wifi-link). See doc/wifi.md.
|
||||
|
||||
The test asserts that the station associates with WPA2/WPA3-personal. That
|
||||
is a strong end-to-end check: authentication, association and the WPA
|
||||
4-way handshake all require frames to cross the medium in *both*
|
||||
directions, so a successful association proves real bidirectional
|
||||
communication over the (relayed) radio link -- not just that an interface
|
||||
came up. It then confirms data-plane reach: the ap serves DHCP and the
|
||||
station leases its address over the radio link.
|
||||
|
||||
Topology:
|
||||
....
|
||||
host ==(mgmt)== ap ))) ~ wifi-link ~ ((( station ==(mgmt)== host
|
||||
....
|
||||
|
||||
==== Topology
|
||||
|
||||
image::topology.svg[WiFi AP and Station Association across two DUTs topology, align=center, scaledwidth=75%]
|
||||
|
||||
==== Sequence
|
||||
|
||||
. Set up topology and attach to the ap and the station
|
||||
. Configure the ap as an Access Point on radio0
|
||||
. Configure the station on radio0
|
||||
. Verify the station associates to the ap over the wifi link
|
||||
. Verify the station's wifi0 operational status is up
|
||||
. Verify the station leases its address from the ap over wifi
|
||||
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python3
|
||||
r"""
|
||||
WiFi Access Point and Station association across two DUTs
|
||||
|
||||
Two DUTs, each with its own radio: the ap runs a WPA2/WPA3-personal Access
|
||||
Point, the station associates to it. The radios are in
|
||||
separate kernels, so the "air" between them is realised differently per
|
||||
environment:
|
||||
|
||||
* On real hardware the link is real RF -- the radios just associate.
|
||||
* On QEMU the radios are mac80211_hwsim, and the `wifimedium` relay on
|
||||
each DUT bridges hwsim frames over the Ethernet segment between the
|
||||
guests (the topology's wifi-link). See doc/wifi.md.
|
||||
|
||||
The test asserts that the station associates with WPA2/WPA3-personal. That
|
||||
is a strong end-to-end check: authentication, association and the WPA
|
||||
4-way handshake all require frames to cross the medium in *both*
|
||||
directions, so a successful association proves real bidirectional
|
||||
communication over the (relayed) radio link -- not just that an interface
|
||||
came up. It then confirms data-plane reach: the ap serves DHCP and the
|
||||
station leases its address over the radio link.
|
||||
|
||||
Topology:
|
||||
....
|
||||
host ==(mgmt)== ap ))) ~ wifi-link ~ ((( station ==(mgmt)== host
|
||||
....
|
||||
"""
|
||||
import base64
|
||||
|
||||
import infamy
|
||||
import infamy.iface as iface
|
||||
from infamy.util import until, parallel
|
||||
|
||||
SSID = "infix-test"
|
||||
PSK = "infixinfix" # 8-63 printable ASCII, see doc/wifi.md
|
||||
PSK_B64 = base64.b64encode(PSK.encode()).decode()
|
||||
COUNTRY = "SE"
|
||||
|
||||
# The ap runs a DHCP server on wifi0; the station leases its address. A
|
||||
# completed lease is real bidirectional IP traffic over the radio link.
|
||||
SUBNET = "192.168.20.0/24"
|
||||
AP_IP = "192.168.20.1"
|
||||
LEASE = "192.168.20.100" # single-address pool -> deterministic lease
|
||||
|
||||
|
||||
def radio(name, band=None, channel=None):
|
||||
"""ietf-hardware component for a radio."""
|
||||
wifi_radio = {"country-code": COUNTRY}
|
||||
if band:
|
||||
wifi_radio["band"] = band
|
||||
if channel is not None:
|
||||
wifi_radio["channel"] = channel
|
||||
return {
|
||||
"name": name,
|
||||
"class": "infix-hardware:wifi",
|
||||
"infix-hardware:wifi-radio": wifi_radio,
|
||||
}
|
||||
|
||||
|
||||
def keystore():
|
||||
return {
|
||||
"keystore": {
|
||||
"symmetric-keys": {
|
||||
"symmetric-key": [{
|
||||
"name": "wifi",
|
||||
"key-format": "infix-crypto-types:passphrase-key-format",
|
||||
"cleartext-symmetric-key": PSK_B64,
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Set up topology and attach to the ap and the station"):
|
||||
env = infamy.Env()
|
||||
# Connect to both nodes concurrently to cut setup time.
|
||||
ap, station = parallel(
|
||||
lambda: env.attach("ap", "mgmt"),
|
||||
lambda: env.attach("station", "mgmt"),
|
||||
)
|
||||
|
||||
for dut in (ap, station):
|
||||
if not dut.has_feature("infix-interfaces", "wifi"):
|
||||
print("DUT does not advertise the 'wifi' feature -- skipping")
|
||||
test.skip()
|
||||
|
||||
with test.step("Configure the ap as an Access Point on radio0"):
|
||||
ap.put_config_dicts({
|
||||
"ietf-hardware": {"hardware": {"component": [
|
||||
radio("radio0", band="2.4GHz", channel=1)]}},
|
||||
"ietf-keystore": keystore(),
|
||||
"ietf-interfaces": {"interfaces": {"interface": [{
|
||||
"name": "wifi0",
|
||||
"type": "infix-if-type:wifi",
|
||||
# hwsim defaults every radio0 to 02:00:00:00:00:00, so the AP
|
||||
# and the station would otherwise share a MAC -- give each a
|
||||
# unique address.
|
||||
"infix-interfaces:custom-phys-address": {"static": "02:00:00:00:00:01"},
|
||||
"infix-interfaces:wifi": {
|
||||
"radio": "radio0",
|
||||
"access-point": {
|
||||
"ssid": SSID,
|
||||
"security": {"mode": "wpa2-wpa3-personal", "secret": "wifi"},
|
||||
},
|
||||
},
|
||||
"ietf-ip:ipv4": {"address": [
|
||||
{"ip": AP_IP, "prefix-length": 24}]},
|
||||
}]}},
|
||||
"infix-dhcp-server": {"dhcp-server": {"subnet": [{
|
||||
"subnet": SUBNET,
|
||||
"pool": {"start-address": LEASE, "end-address": LEASE},
|
||||
}]}},
|
||||
})
|
||||
|
||||
with test.step("Configure the station on radio0"):
|
||||
station.put_config_dicts({
|
||||
"ietf-hardware": {"hardware": {"component": [radio("radio0")]}},
|
||||
"ietf-keystore": keystore(),
|
||||
"ietf-interfaces": {"interfaces": {"interface": [{
|
||||
"name": "wifi0",
|
||||
"type": "infix-if-type:wifi",
|
||||
"infix-interfaces:custom-phys-address": {"static": "02:00:00:00:00:02"},
|
||||
"infix-interfaces:wifi": {
|
||||
"radio": "radio0",
|
||||
"station": {
|
||||
"ssid": SSID,
|
||||
"security": {"mode": "auto", "secret": "wifi"},
|
||||
},
|
||||
},
|
||||
"ietf-ip:ipv4": {"infix-dhcp-client:dhcp": {}},
|
||||
}]}},
|
||||
})
|
||||
|
||||
with test.step("Verify the station associates to the ap over the wifi link"):
|
||||
def associated():
|
||||
ifc = station.get_iface("wifi0")
|
||||
if not ifc:
|
||||
return False
|
||||
wifi = ifc.get("infix-interfaces:wifi") or ifc.get("wifi") or {}
|
||||
sta = wifi.get("station", {})
|
||||
return sta.get("ssid") == SSID and \
|
||||
sta.get("signal-strength") is not None
|
||||
until(associated, attempts=60, interval=2)
|
||||
|
||||
with test.step("Verify the station's wifi0 operational status is up"):
|
||||
until(lambda: iface.is_oper_up(station, "wifi0"), attempts=30)
|
||||
|
||||
with test.step("Verify the station leases its address from the ap over wifi"):
|
||||
until(lambda: iface.address_exist(station, "wifi0", LEASE),
|
||||
attempts=60, interval=2)
|
||||
|
||||
test.succeed()
|
||||
@@ -0,0 +1,44 @@
|
||||
graph "wifi-ap-station-2dut" {
|
||||
layout="neato";
|
||||
overlap="false";
|
||||
esep="+40";
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <mgmt1> mgmt1 | <mgmt2> mgmt2 }",
|
||||
pos="0,0!",
|
||||
requires="controller",
|
||||
];
|
||||
|
||||
ap [
|
||||
label="{ <mgmt> mgmt | <wifi> wifi } | ap",
|
||||
pos="6,2!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
station [
|
||||
label="{ <mgmt> mgmt | <wifi> wifi } | station",
|
||||
pos="6,-2!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
// The wireless cell the ap and the station share, modelled as a medium
|
||||
// node both join (one radio each, same index -> same cell):
|
||||
// * physical: maps onto a real over-the-air RF cell;
|
||||
// * virtual (qeneth): maps onto one of the multicast cells over which
|
||||
// the wifimedium relay bridges mac80211_hwsim frames.
|
||||
// The mapper guarantees the two DUTs actually share this medium.
|
||||
cell [
|
||||
label="cell",
|
||||
pos="9,0!",
|
||||
requires="wifi-radio",
|
||||
];
|
||||
|
||||
host:mgmt1 -- ap:mgmt [requires="mgmt", color="lightgray"]
|
||||
host:mgmt2 -- station:mgmt [requires="mgmt", color="lightgray"]
|
||||
|
||||
ap:wifi -- cell [requires="wifi2.4GHz", style="dashed"]
|
||||
station:wifi -- cell [requires="wifi2.4GHz", style="dashed"]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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: wifi-ap-station-2dut Pages: 1 -->
|
||||
<svg width="656pt" height="307pt"
|
||||
viewBox="0.00 0.00 656.06 307.03" 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 303.03)">
|
||||
<title>wifi-ap-station-2dut</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-303.03 652.06,-303.03 652.06,4 -4,4"/>
|
||||
<!-- host -->
|
||||
<g id="node1" class="node">
|
||||
<title>host</title>
|
||||
<polygon fill="none" stroke="black" points="0,-126.51 0,-172.51 108,-172.51 108,-126.51 0,-126.51"/>
|
||||
<text text-anchor="middle" x="25" y="-145.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
<polyline fill="none" stroke="black" points="50,-126.51 50,-172.51 "/>
|
||||
<text text-anchor="middle" x="79" y="-157.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
|
||||
<polyline fill="none" stroke="black" points="50,-149.51 108,-149.51 "/>
|
||||
<text text-anchor="middle" x="79" y="-134.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
|
||||
</g>
|
||||
<!-- ap -->
|
||||
<g id="node2" class="node">
|
||||
<title>ap</title>
|
||||
<polygon fill="none" stroke="black" points="390.54,-252.53 390.54,-298.53 473.54,-298.53 473.54,-252.53 390.54,-252.53"/>
|
||||
<text text-anchor="middle" x="415.54" y="-283.33" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="390.54,-275.53 440.54,-275.53 "/>
|
||||
<text text-anchor="middle" x="415.54" y="-260.33" font-family="DejaVu Sans Mono, Book" font-size="14.00">wifi</text>
|
||||
<polyline fill="none" stroke="black" points="440.54,-252.53 440.54,-298.53 "/>
|
||||
<text text-anchor="middle" x="457.04" y="-271.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">ap</text>
|
||||
</g>
|
||||
<!-- host--ap -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt1--ap:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-161.51C108,-161.51 390.04,-287.53 390.04,-287.53"/>
|
||||
</g>
|
||||
<!-- station -->
|
||||
<g id="node3" class="node">
|
||||
<title>station</title>
|
||||
<polygon fill="none" stroke="black" points="370.04,-0.5 370.04,-46.5 494.04,-46.5 494.04,-0.5 370.04,-0.5"/>
|
||||
<text text-anchor="middle" x="395.04" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="370.04,-23.5 420.04,-23.5 "/>
|
||||
<text text-anchor="middle" x="395.04" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">wifi</text>
|
||||
<polyline fill="none" stroke="black" points="420.04,-0.5 420.04,-46.5 "/>
|
||||
<text text-anchor="middle" x="457.04" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">station</text>
|
||||
</g>
|
||||
<!-- host--station -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mgmt2--station:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-137.51C108,-137.51 370.04,-35.5 370.04,-35.5"/>
|
||||
</g>
|
||||
<!-- cell -->
|
||||
<g id="node4" class="node">
|
||||
<title>cell</title>
|
||||
<polygon fill="none" stroke="black" points="594.06,-131.51 594.06,-167.51 648.06,-167.51 648.06,-131.51 594.06,-131.51"/>
|
||||
<text text-anchor="middle" x="621.06" y="-145.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">cell</text>
|
||||
</g>
|
||||
<!-- ap--cell -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>ap:wifi--cell</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" stroke-dasharray="5,2" d="M415.04,-252.53C415.04,-252.53 537.66,-191.21 593.7,-163.19"/>
|
||||
</g>
|
||||
<!-- station--cell -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>station:wifi--cell</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" stroke-dasharray="5,2" d="M395.04,-0.5C395.04,-0.5 533.68,-91.91 593.79,-131.54"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user