Compare commits

..
Author SHA1 Message Date
Mattias Walström 980b9dac7c rauc: Enable streaming of upgrades 2026-06-27 08:41:22 +02:00
Mattias Walström e32b72d1ee yangerd: containers: widen event debounce to avoid podman contention 2026-06-27 08:41:22 +02:00
Mattias Walström 62b890f7b6 yangerd: Fix containers 2026-06-27 08:41:22 +02:00
Mattias Walström ce4184e048 yangerd: Change containers from polled to reactive 2026-06-27 08:41:22 +02:00
Mattias Walström 013932ed06 multicast: stream MAC sender continuously like the IPv4 sender 2026-06-27 08:41:22 +02:00
Mattias Walström 8c6f9cec49 test: operational_all: Poll for stuff to get removed from yangerd 2026-06-27 08:41:22 +02:00
Mattias Walström 27dc42622e test: operational_all: Try to adapt to yangerd 2026-06-27 08:41:22 +02:00
Mattias Walström f4de58a54c yangerd: Fix bugs with containers and boot order in CI 2026-06-27 08:41:22 +02:00
Mattias Walström 4899279ed2 test: dhcp: client_basic: Adapt to changes by yangerd 2026-06-27 08:41:22 +02:00
Mattias Walström 42c8dc0346 test: upgrade: Make more robust using yangerd 2026-06-27 08:41:22 +02:00
Mattias Walström a2937e13eb test: speed_duplex_coppar: Adapt to yangerd 2026-06-27 08:41:22 +02:00
Mattias Walström b35f4df8a5 yangerd: Add arm 32bit support
Int => int64
2026-06-27 08:41:22 +02:00
Mattias Walström 8890274e90 yangerd: add missing files 2026-06-27 08:41:22 +02:00
Mattias Walström 127bbdfc4f REMOVE THIS: TEMPORARY DISABLE PTP TESTS 2026-06-27 08:41:22 +02:00
Mattias Walström 8fb9e9b68e test: mdns_allow_deny: Adapt to yangerd behaviour changes 2026-06-27 08:41:22 +02:00
Mattias Walström 7d9319b8e4 test: lldp_admin_status: Adapt to yangerd changes 2026-06-27 08:41:22 +02:00
Mattias Walström 7f4c4b63ee dhcp: server_subnets: Adapt to yanger behaviour changes 2026-06-27 08:41:22 +02:00
Mattias Walström beb2439922 test: firewall: ipv6-zone-migration: Adapt to yanger behaviour changes 2026-06-27 08:41:22 +02:00
Mattias Walström 7dc1811389 test: rip_multihop: Adapt to yangerd changes 2026-06-27 08:41:22 +02:00
Mattias Walström 01b9c04f95 test: ntp: client_stratum_selection: Adapt to yangerd behaviour 2026-06-27 08:41:22 +02:00
Mattias Walström 8b60e447f3 yangerd: Fix ntp and lldp status 2026-06-27 08:41:22 +02:00
Mattias Walström d90d0d2a9c yangerd: Return {} instead of 404 if no match 2026-06-27 08:41:22 +02:00
Mattias Walström 6a669e6d92 statd: Adapt to yanger keys on module:container path
Instead of relying of the runtime xpath, which can be just /system-state
for example, resulting on no yangerd match.
2026-06-27 08:41:22 +02:00
Mattias Walström 155f74e968 test: ospf_default_route_advertise: Make test more robust
FRR can sometimes be pretty slow to actually install the route in
the kernel. Increase the timeout of must_reach.
2026-06-27 08:41:22 +02:00
Mattias Walström 7f0b88be63 test: route_pref_ospf: Update test spec 2026-06-27 08:41:22 +02:00
Mattias Walström 05d4598ac9 test: ntp_client: Update test spec 2026-06-27 08:41:22 +02:00
Mattias Walström 79ec51860b test: hostname: adapt to yanger 2026-06-27 08:41:22 +02:00
Mattias Walström 51aa4d26b7 test: ospf_point_to_multipoint: Adapt to yanger 2026-06-27 08:41:22 +02:00
Mattias Walström a33214d67f test: spec: Allow to generate test specification without logical topology 2026-06-27 08:41:22 +02:00
Mattias Walström cff66cd74c test: upgrade: add missing readme 2026-06-27 08:41:21 +02:00
Mattias Walström b662984f09 test: operational_all: Harden to catch more
- Test so the value in operational actually is correct
- Move test to last in suite to see that there are no stray
  data in operational (yangerd)
2026-06-27 08:41:21 +02:00
Mattias Walström 39114f89dd yangerd: Add missing files 2026-06-27 08:41:21 +02:00
Mattias Walström 0b193cb5bd test: upgrade: Adapt to yangerd 2026-06-27 08:41:21 +02:00
Mattias Walström 98022e2e5f test: route_pref_ospf: Adapt to yangerd 2026-06-27 08:41:21 +02:00
Mattias Walström 52c7e4e90e test: ospf_unnumbered_interface: Adapt to new yangerd 2026-06-27 08:41:21 +02:00
Mattias Walström f50f6add9c test: veth_delete: Adapt to new yangerd
Operational is now not instantly availible, is some lag.
2026-06-27 08:41:21 +02:00
Mattias Walström 5071a1f177 test: ntp_client: Adapt to new yangerd daemon 2026-06-27 08:41:21 +02:00
Mattias Walström ee5499501c test: stp_basic: Stabilize test
With the new yangerd it failed 100% of the times, but it could fail
before as well, but that whould be very rare. Wait until operational
has been updated.
2026-06-27 08:41:21 +02:00
Mattias Walström e9ff748df9 tests: Adapt to new yangerd 2026-06-27 08:41:20 +02:00
Mattias Walström bf850f59d7 yangerd: Add support for getting ARP neighbors 2026-06-27 08:39:48 +02:00
Mattias Walström 76de3427ff test: syslog: property_filter: Adapt to changes required by yangerd
The test got an unexpected input in one of its logs:

'May 22 11:18:32 test-00-03-00 yangerd[3677]: SIGHUP: triggering immediate re-poll'
limit the string grepping for to be more exact.
2026-06-27 08:39:48 +02:00
Mattias Walström 6a78def525 test: upgrade: Add adaptions needed for yangerd 2026-06-27 08:39:48 +02:00
Mattias Walström 59fbb26dca test: upgrade: Add adaptions needed for yangerd
Specially the change:
`
-            oper = target.get_dict("/system-state/software")
+            oper = target.get_dict("/ietf-system:system-state/software")
`
i feel is strange, since only the backend for sysrepo has changed, but the
new syntax is actually correct, unsure why it has worked before.
2026-06-27 08:39:48 +02:00
Mattias Walström 56b7904d1a statd: Remove old code 2026-06-27 08:39:48 +02:00
Mattias Walström 3e23997289 yanger: Remove code duplication and fix upgrade
Remove a lot of duplication and move to seperate libs in internal/

Also, instead of polling upgrade status using src/rauc-installation-status,
hook into the dbus monitor.
2026-06-27 08:39:48 +02:00
Mattias Walström c7ab211e51 yangerd: Add WiFi implementation 2026-06-27 08:39:48 +02:00
Mattias Walström 32bed66267 yangerd: Add backoff if ip batch work have crashed 2026-06-27 08:39:48 +02:00
Mattias Walström ca7447dfc9 yangerd: Do not start in runlevel S 2026-06-27 08:39:48 +02:00
Mattias Walström dc7a86c604 test: iface_enable_disable: Simplify test 2026-06-27 08:39:48 +02:00
Mattias Walström f305bd3527 test: wireguard_roadwarrior: Adapt to the new polled world in yangerd 2026-06-27 08:39:48 +02:00
Mattias Walström 73f6b52a70 yangerd: Add wireguard support 2026-06-27 08:39:48 +02:00
Mattias Walström 9e403831b3 yangerd: fix stp status 2026-06-27 08:39:48 +02:00
Mattias Walström b40f64492f yangerd: Send sighup to yangerd on config change
This is to force yangerd to repoll polled things
2026-06-27 08:39:48 +02:00
Mattias Walström 2dffad67b7 test: usb: adapt to new yangerd 2026-06-27 08:39:48 +02:00
Mattias Walström 0cd598d67c test: containers: host_commands/test.py: Adapt to new yangerd
Slightly slower to detect changes
2026-06-27 08:39:48 +02:00
Mattias Walström 6ce28a7e40 Fix containers, reactive now. 2026-06-27 08:39:48 +02:00
Mattias Walström e938c2dce2 test: case: ntp: Adapt tests for polled yangerd 2026-06-27 08:39:48 +02:00
Mattias Walström 61a7c85d96 test: dhcp: server_subnets: Add longer timeout for polling
This since NTP status is polled in yangerd.
2026-06-27 08:39:48 +02:00
Mattias Walström 72d3777d3f Fix ntp and dns status 2026-06-27 08:39:48 +02:00
Mattias Walström 02db44bb82 FRR does not send delete on OSPF route change
Handle this.
2026-06-27 08:39:48 +02:00
Mattias Walström 4cc9751c7b Fix interface and bridge race conditions 2026-06-27 08:39:48 +02:00
Mattias Walström 7b45f2589c zapi: Fix deletion of routes 2026-06-27 08:39:48 +02:00
Mattias Walström ad67a5aacf yangerd: Continues 2026-06-27 08:39:48 +02:00
Mattias Walström fdbf00ca76 Fix routing and ip addresses 2026-06-27 08:39:47 +02:00
Mattias Walström 4131167ede update doc 2026-06-27 08:39:47 +02:00
Mattias Walström 281adb45b6 statd: Include new yangerd backend for operational 2026-06-27 08:39:47 +02:00
Mattias Walström 35f8d6e3d5 move yanger 2026-06-27 08:39:47 +02:00
Mattias Walström 1e932f09c1 yangerd: Initial 2026-06-27 08:39:47 +02:00
Mattias Walström 6d27f67b37 test: firewall: Make tests more robust 2026-06-27 08:39:47 +02:00
Mattias Walström ce4dd2d3e3 test: system: Make tests more robust 2026-06-27 08:39:47 +02:00
Mattias Walström 0deff2515c firewall: simplify 2026-06-27 08:39:47 +02:00
Mattias Walström 30907b0343 TEMP: Update yanger spec 2026-06-27 08:39:47 +02:00
Mattias Walström 89fee60663 TEMP: WiP document 2026-06-27 08:39:47 +02:00
969 changed files with 313836 additions and 3262 deletions
+3 -4
View File
@@ -110,10 +110,9 @@ config INFIX_OEM_PATH
directory (absolute path) and the Infix post-build.sh will call `git
describe -C $INFIX_OEM_PATH`.
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.
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.
endmenu
-16
View File
@@ -253,19 +253,3 @@ 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.
-11
View File
@@ -310,16 +310,6 @@ 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 "
@@ -397,7 +387,6 @@ run_qemu()
$(usb_args) \
$(host_args) \
$(net_args) \
$(wifi_args) \
$(wdt_args) \
$(rtc_args) \
$(vpd_args) \
@@ -654,17 +654,10 @@ 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
tmp = SYSTEM_JSON + ".tmp"
with open(tmp, "w", encoding="ascii") as f:
with open(SYSTEM_JSON, "w", encoding="ascii") as f:
json.dump(out, f)
f.flush()
os.fsync(f.fileno())
shutil.chown(tmp, user="root", group="wheel")
os.replace(tmp, SYSTEM_JSON)
shutil.chown(SYSTEM_JSON, user="root", group="wheel")
return err
@@ -43,12 +43,5 @@ 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
+13 -21
View File
@@ -176,25 +176,19 @@ def parse_phy_info(phy_name):
if current_band and current_band.get('frequencies'):
result['bands'].append(current_band)
# 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 = []
# Determine band names and assign band numbers
for band in result['bands']:
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
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
# Set max TX power
if max_power is not None:
@@ -213,9 +207,7 @@ def parse_phy_info(phy_name):
# Map driver to manufacturer
driver_lower = driver_name.lower()
if 'hwsim' in driver_lower:
result['manufacturer'] = 'Virtual (hwsim)'
elif 'mt' in driver_lower or 'mediatek' in driver_lower:
if '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
View File
@@ -244,6 +244,7 @@ CONFIG_FW_CFG_SYSFS_CMDLINE=y
CONFIG_BLK_DEV_NULL_BLK=y
CONFIG_BLK_DEV_LOOP=y
CONFIG_BLK_DEV_RAM=y
CONFIG_BLK_DEV_NBD=y
CONFIG_VIRTIO_BLK=y
CONFIG_BLK_DEV_SD=y
CONFIG_BLK_DEV_SR=y
+2 -1
View File
@@ -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.37"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
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
@@ -162,6 +162,7 @@ BR2_PACKAGE_CURIOS_HTTPD=y
BR2_PACKAGE_CURIOS_NFTABLES=y
BR2_PACKAGE_GENCERT=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_YANGERD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
+2 -1
View File
@@ -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.37"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
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
@@ -131,6 +131,7 @@ BR2_PACKAGE_NETD=y
BR2_PACKAGE_CONFD_TEST_MODE=y
BR2_PACKAGE_GENCERT=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_YANGERD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
+2 -1
View File
@@ -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.37"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
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
@@ -149,6 +149,7 @@ BR2_PACKAGE_NETD=y
BR2_PACKAGE_CONFD_TEST_MODE=y
BR2_PACKAGE_GENCERT=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_YANGERD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
+2 -1
View File
@@ -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.37"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
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
@@ -129,6 +129,7 @@ BR2_PACKAGE_NETD=y
BR2_PACKAGE_CONFD_TEST_MODE=y
BR2_PACKAGE_GENCERT=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_YANGERD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
+1
View File
@@ -181,6 +181,7 @@ BR2_PACKAGE_CONFD=y
BR2_PACKAGE_NETD=y
BR2_PACKAGE_GENCERT=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_YANGERD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
+3 -2
View File
@@ -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.37"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
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
@@ -110,6 +110,7 @@ BR2_PACKAGE_RAUC=y
BR2_PACKAGE_RAUC_DBUS=y
BR2_PACKAGE_RAUC_GPT=y
BR2_PACKAGE_RAUC_NETWORK=y
BR2_PACKAGE_RAUC_STREAMING=y
BR2_PACKAGE_RAUC_JSON=y
BR2_PACKAGE_SYSKLOGD=y
BR2_PACKAGE_SYSKLOGD_LOGGER=y
@@ -146,7 +147,6 @@ 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
@@ -157,6 +157,7 @@ BR2_PACKAGE_CURIOS_HTTPD=y
BR2_PACKAGE_CURIOS_NFTABLES=y
BR2_PACKAGE_GENCERT=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_YANGERD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
+2 -1
View File
@@ -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.37"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.36"
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
@@ -128,6 +128,7 @@ BR2_PACKAGE_NETD=y
BR2_PACKAGE_CONFD_TEST_MODE=y
BR2_PACKAGE_GENCERT=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_YANGERD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
+3 -34
View File
@@ -3,28 +3,12 @@ Change Log
All notable changes to the project are documented in this file.
[v26.06.0][] - 2026-07-01
[v26.06.0][UNRELEASED]
-------------------------
> [!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.37 (LTS)
- Upgrade Linux kernel to 6.18.36 (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
@@ -43,13 +27,6 @@ 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
@@ -62,14 +39,6 @@ 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/
@@ -2205,7 +2174,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.06.0...HEAD
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v26.05.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
-30
View File
@@ -297,36 +297,6 @@ 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/
-7
View File
@@ -304,7 +304,6 @@ 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
@@ -320,11 +319,6 @@ 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
@@ -384,7 +378,6 @@ 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 -6
View File
@@ -4,12 +4,10 @@ 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.
# 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)
# 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))
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
+1
View File
@@ -13,6 +13,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/curios-httpd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/curios-nftables/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/gencert/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/statd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/yangerd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/factory/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/faux/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/finit/Config.in"
-47
View File
@@ -1,47 +0,0 @@
#!/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
-12
View File
@@ -18,18 +18,6 @@ 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
-24
View File
@@ -14,15 +14,6 @@ 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)
@@ -85,20 +76,5 @@ 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))
-401
View File
@@ -1,401 +0,0 @@
#!/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
-14
View File
@@ -1,14 +0,0 @@
# 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)
+7
View File
@@ -0,0 +1,7 @@
config BR2_PACKAGE_YANGERD
bool "yangerd"
depends on BR2_PACKAGE_HOST_GO_TARGET_ARCH_SUPPORTS
help
Operational data daemon for YANG/NETCONF/RESTCONF.
Replaces Python yanger scripts with a persistent Go daemon
serving operational data over a Unix socket IPC protocol.
+3
View File
@@ -0,0 +1,3 @@
service <> name:yangerd log:prio:daemon.notice,tag:yangerd \
env:-/etc/default/yangerd \
[2345] yangerd -- Operational data daemon
+35
View File
@@ -0,0 +1,35 @@
################################################################################
#
# yangerd
#
################################################################################
YANGERD_VERSION = 1.0.0
YANGERD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/yangerd
YANGERD_SITE_METHOD = local
YANGERD_GOMOD = github.com/kernelkit/infix/src/yangerd
YANGERD_LICENSE = BSD-2-Clause
YANGERD_LICENSE_FILES = LICENSE
YANGERD_REDISTRIBUTE = NO
YANGERD_BUILD_TARGETS = cmd/yangerd cmd/yangerctl
YANGERD_INSTALL_BINS = yangerd yangerctl
define YANGERD_INSTALL_EXTRA
$(INSTALL) -D -m 0644 $(YANGERD_PKGDIR)/yangerd.conf \
$(FINIT_D)/available/yangerd.conf
ln -sf ../available/yangerd.conf $(FINIT_D)/enabled/yangerd.conf
$(INSTALL) -d $(TARGET_DIR)/etc/default
echo '# yangerd build-time feature flags (generated by yangerd.mk)' \
> $(TARGET_DIR)/etc/default/yangerd
echo 'YANGERD_ENABLE_WIFI=$(if $(BR2_PACKAGE_IW),true,false)' \
>> $(TARGET_DIR)/etc/default/yangerd
echo 'YANGERD_ENABLE_CONTAINERS=$(if $(BR2_PACKAGE_PODMAN),true,false)' \
>> $(TARGET_DIR)/etc/default/yangerd
echo 'YANGERD_ENABLE_GPS=$(if $(BR2_PACKAGE_GPSD),true,false)' \
>> $(TARGET_DIR)/etc/default/yangerd
echo 'YANGERD_LOG_LEVEL=deug' >> $(TARGET_DIR)/etc/default/yangerd
endef
YANGERD_POST_INSTALL_TARGET_HOOKS += YANGERD_INSTALL_EXTRA
$(eval $(golang-package))
@@ -1,4 +1,4 @@
From f614327b6eca20319f5eee20ac954ea8d92276e4 Mon Sep 17 00:00:00 2001
From a3165eb664d65da173e91e8b159b8daf9a1285c7 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,4 +1,4 @@
From 7d453cb838611f44d7566b6174ae95a5d2265fa1 Mon Sep 17 00:00:00 2001
From 0ee3021b04ec64d96b90d9246f37d3d7a1f8f187 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,4 +1,4 @@
From 4b08a36e3107c4018ddf3a19760cc87d02281bd7 Mon Sep 17 00:00:00 2001
From 7e5a199bca36c5d8b1fd3875bbb8343c86db73ea 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,4 +1,4 @@
From e5e81eb1172db22be7c83efc8071108d0d42565e Mon Sep 17 00:00:00 2001
From 62a1468f5c6fdaba5074fa7555e3b7888762e4e7 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,4 +1,4 @@
From 1f735096f1b8920ee6f21bf60a4c2d3f6376adeb Mon Sep 17 00:00:00 2001
From 38519daa6941dc275e3efaaba699b6441f1c19e2 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,4 +1,4 @@
From e38d2d2f07f55819d2191d4282dc2d4688075d7d Mon Sep 17 00:00:00 2001
From e11c3a1cf1979f558a1a1c06ed68a99b89273aea 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,4 +1,4 @@
From a9b80b26c96e5492ab32a72ee36e3321a2b90ee9 Mon Sep 17 00:00:00 2001
From 9186aa368a4b580090d14ee1d9218ac33bc0aa5f 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,4 +1,4 @@
From 01e41d87efa1b63d292f0acfe4d1fb0e39baab20 Mon Sep 17 00:00:00 2001
From 9641f50de1c780b0e45be9095ab28cbbc4c70011 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,4 +1,4 @@
From 944c77d075c8e0655bd3a28e96892261465a5cf4 Mon Sep 17 00:00:00 2001
From d14c3ad03563ac803180c18e6917eb506bea7079 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,4 +1,4 @@
From 3f58061ab8f869a6432a1256ee7422bb157b624e Mon Sep 17 00:00:00 2001
From 4d2335aac65d03c004d27cd27f077362588851e2 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,4 +1,4 @@
From f492d4efac3a65bca4d53e383336c36113e28a8b Mon Sep 17 00:00:00 2001
From 1f727779b31e9dec09b669d07655371c45fbc64b 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,4 +1,4 @@
From a2448b84e5483b5b91b46ac31b27609352fd9ed6 Mon Sep 17 00:00:00 2001
From 8053206624b54fb84715342e7b921e0939d594a7 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,4 +1,4 @@
From c4ac90dcc5aefbb704c70ebbfe041dee683b882b Mon Sep 17 00:00:00 2001
From 5b4047d082c93431f77819b894e29a13acebd787 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,4 +1,4 @@
From 8da101ec2429ba0bf1410346260f9a7ddfb0bdbe Mon Sep 17 00:00:00 2001
From 3efdf41a3464704c4ffb99ea94266dc900f7b556 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,4 +1,4 @@
From 93646f890903157bb78ef369ddf9d99a9161de0d Mon Sep 17 00:00:00 2001
From ce445d44a5e6ae84214986cbccef985a240e831d 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,4 +1,4 @@
From 29cfa1b5ffba41728dc5e0991d1ee27b903d569c Mon Sep 17 00:00:00 2001
From 57aa43c667a99e08c11a6141f80c068021585829 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,4 +1,4 @@
From a7fa9a45329781519702d46ba05815433fff4acc Mon Sep 17 00:00:00 2001
From 70c247b71e2f2ede0cae581b0558bd09cd85275e 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,4 +1,4 @@
From cda7100b0a29e6a9bd29a67ff9db3bcccb72065c Mon Sep 17 00:00:00 2001
From b9b9cc025114c8bb8546012d575cbb9759ce6975 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,4 +1,4 @@
From ccf9f9262e8570cfd522b841465f3edf76b3447a Mon Sep 17 00:00:00 2001
From 071b831a6ba7c5de2cc05738b595cea10e2eb068 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,4 +1,4 @@
From 3200169701ef4d9afdea35355b3fe41d6f363134 Mon Sep 17 00:00:00 2001
From 625493833fcdc4268a46ce4b2c59eefb1a0a0be0 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,4 +1,4 @@
From dea9ff6c0bdf7c3e4abc2fb507d2ab4b94dabb63 Mon Sep 17 00:00:00 2001
From c59543ced113c4a66ccf07cbfac5dc7dd6cb253e 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,4 +1,4 @@
From 1dc01d7420e3d511babfbdeda742d93527d72abb Mon Sep 17 00:00:00 2001
From fb04b75bc53f6b1b27f91f4ed21013b293e8577c 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,4 +1,4 @@
From caed0d150badbb040e403975645bf09ba3f44e76 Mon Sep 17 00:00:00 2001
From 32e460b9fbc7b25817c05b9ec2c01f4827f7cca7 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,4 +1,4 @@
From 1bd5b0e8a5574209b386d6d8bc1cf3bcf2e4fd5e Mon Sep 17 00:00:00 2001
From 30f92e6d97e8d6d8fe36e872e627b28fcf24fdac 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,4 +1,4 @@
From 7894882d70e8223939a6d9a0e15cc6bbc08ae33c Mon Sep 17 00:00:00 2001
From a9055d21d1237a5449d5f410d3a0c3d8953a1965 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,4 +1,4 @@
From 09cb11eaf5cc754274620ff6eaedf768c9605e44 Mon Sep 17 00:00:00 2001
From 7fb7bc76b39078049d7965bef1bc976f9e1142c5 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,4 +1,4 @@
From 7127fd4194cde754cbc9ea869c9e65e335701d95 Mon Sep 17 00:00:00 2001
From 2a8ff34ccf08f85f985ebea74b6eb47a64e2f247 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,4 +1,4 @@
From c2f3d9277e12e7622651b521af8b43b1fe036cf0 Mon Sep 17 00:00:00 2001
From 348c281450bf30875877045d628f8a56ef7896a1 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,4 +1,4 @@
From ac98be8b6fd1b6afa264c68685d5dc82d73da1f8 Mon Sep 17 00:00:00 2001
From 653db5307cd9d69d6b14e163f02226c83e26bd05 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,4 +1,4 @@
From 9f1110fe13c878c49d28859f27bd90bc1b77f7bc Mon Sep 17 00:00:00 2001
From 6445e629e19b9380dc96c9e5bfd15bbf706e1311 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,4 +1,4 @@
From 188203d0d4eeeb607b29efa344280189c2566f30 Mon Sep 17 00:00:00 2001
From 2b970f903b571a3f3ca6e6043b2608c30825463f 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,4 +1,4 @@
From c02051fdfbcedf99bac490bcb0358774774e2bcf Mon Sep 17 00:00:00 2001
From eab236832c10d0a0c05fcab145abfe4267857957 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,4 +1,4 @@
From 613dec2af90a208b2bb453150cb1851de07d51fb Mon Sep 17 00:00:00 2001
From 8bad16ffcea19fde114ac772399140ded40c6b91 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,4 +1,4 @@
From 5df424aa25bcfd2f0309fbd05670f0b613219456 Mon Sep 17 00:00:00 2001
From 105d92fb189196e292f1ec60bc6f4abf883e1d3d 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,4 +1,4 @@
From 88e744997f5c6ff9c9383fbe568c925c11715896 Mon Sep 17 00:00:00 2001
From 85515b63fa683de8025f439c0ba290b535cada59 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,4 +1,4 @@
From 9cd8a5347a9aa06f60512c4ca4fe1c7591c7e1d9 Mon Sep 17 00:00:00 2001
From 0bbed592738a780abea49cd71806daa40d9ce602 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,4 +1,4 @@
From 7c91806a1637d2fc3c83b44cbd758741527b93bb Mon Sep 17 00:00:00 2001
From db28d17b76e911d9e592b07b38dcaa2990efd942 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,4 +1,4 @@
From 772bd1971a7c1fb2554cadfb111e8aabb974dd1a Mon Sep 17 00:00:00 2001
From a3da11f394aaa52ee4a3be1ea63b2439066b126a 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,4 +1,4 @@
From 419f4339059576e35a8e4ebebd3677a8e3f6143f Mon Sep 17 00:00:00 2001
From 4f9b6e7917fb525ce1c8a1247602e67e0a90be39 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,4 +1,4 @@
From 66d0d523c84e59a08a1aa1849aa0363bfa3a7641 Mon Sep 17 00:00:00 2001
From 24575bd2a0efeeae983c605786095fb2d49978cf 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,4 +1,4 @@
From e55233b40d6ed7e6643bb439780c96613c0b1a0c Mon Sep 17 00:00:00 2001
From bac7eb3cfe56c161944b77f9729f5f3fe5fd0d70 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,4 +1,4 @@
From 6683a6a60276d5c39451eb7049247e043937bc2f Mon Sep 17 00:00:00 2001
From 6dcd3256ab1fcf224fc7bb41fb47b37f9f7cda16 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,4 +1,4 @@
From 07da654fcd16b2ae3a7a767ff89f87a3da94f22d Mon Sep 17 00:00:00 2001
From ab53ad238214316a1c6feab8f6d85c06832e7a47 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,4 +1,4 @@
From f9cb160046a757abf5b081d6030011b54f3bc9b0 Mon Sep 17 00:00:00 2001
From 5c7ae0ba30f846779eca1be5a9f36e1239ff74d8 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,4 +1,4 @@
From 5cb31be412825eb8b95a79b89117587a8034d924 Mon Sep 17 00:00:00 2001
From 6f05fd435c115a8bee19c93c22cf31ca1ecc89be 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,4 +1,4 @@
From 58e5a335513c90e40ef4d6b0e0dff018d77dce84 Mon Sep 17 00:00:00 2001
From 413263b811c631ae2ef984583590fd2297ef648a 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,4 +1,4 @@
From 7528752150ebe1c29135d29d3a69b5c692771c18 Mon Sep 17 00:00:00 2001
From d23cac53e2dbce4ce7f37aa8dd98dc5528bc45e0 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,4 +1,4 @@
From 51077597c4f3e458d4feb0ea36cc4ecbef3d635a Mon Sep 17 00:00:00 2001
From 98bd974f7aedfa4a8181daa3f39867e480e097fa 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,4 +1,4 @@
From e5e0572d67cc4be9db0a4d4f7d5c1b973b12da8e Mon Sep 17 00:00:00 2001
From a212af73720bb070c7b9a18e70985ea659ab0039 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,4 +1,4 @@
From 29cd680b8b3b2089bbcb67ee36117459bc11919d Mon Sep 17 00:00:00 2001
From 6c7aca4d3d77be389ff4d533a9f7eed095442aad 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 -1
View File
@@ -1,2 +1,2 @@
# Calculated with utils/kernel-refresh.sh
sha256 a83cd200e6646db52866b8309e9137b9e9048b613cbda10ced2b811aae125255 linux-6.18.37.tar.xz
sha256 fbab86c9f471c81075b280cca30bd85d790c060063a1245859b6344b07c9c44e linux-6.18.36.tar.xz
+13 -7
View File
@@ -2,20 +2,26 @@
# Generate ietf-system.yang hostname based on:
# 1. Command line argument + MAC suffix
# 2. Specific hostname from qemu_fw_cfg, from Qeneth
# 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).
# 3. /etc/hostname from squashfs + MAC suffix
#
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="$1-%m"
sysname=$(gen "$1")
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="%h-%m"
sysname=$(gen "$(cat /etc/hostname)")
fi
cat <<EOF
+19 -5
View File
@@ -439,7 +439,7 @@ static confd_dependency_t dep_radio_components(struct lyd_node **diff, struct ly
class = lyd_get_value(class_node);
}
if (!class || strcmp(class, "infix-hardware:wifi"))
if (strcmp(class, "infix-hardware:wifi"))
continue;
/* Find all interfaces that reference this radio */
@@ -571,6 +571,14 @@ 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 */
@@ -666,6 +674,15 @@ static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *mod
return SR_ERR_SYS;
}
/*
Send sighup to yangerd to trigger a poll of polled values.
This will make sure that there is no stale data.
*/
if (systemf("initctl -b reload yangerd")) {
EMERG("Failed reloading yangerd");
return SR_ERR_SYS;
}
AUDIT("The new configuration has been applied.");
}
@@ -720,11 +737,8 @@ 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) {
ERROR("failed loading /run/system.json, system probe incomplete");
rc = SR_ERR_SYS;
if (!confd.root)
goto err;
}
/* An optional file that contains hardware specific quirks for
* the network interfaces on running board.
+5 -27
View File
@@ -237,7 +237,7 @@ static const char *wifi_find_higher_band_twin(struct lyd_node *config,
{
struct lyd_node *cifs, *cif;
if (!current_band || !current_ssid || strcmp(current_band, "2.4GHz"))
if (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 (!ssid || strcmp(ssid, current_ssid))
if (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 (band && (!strcmp(band, "5GHz") || !strcmp(band, "6GHz")))
if (!strcmp(band, "5GHz") || !strcmp(band, "6GHz"))
return lydx_get_cattr(cif, "name");
}
@@ -1379,32 +1379,8 @@ 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;
@@ -1413,6 +1389,8 @@ 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");
+1 -1
View File
@@ -237,7 +237,7 @@ static int netdag_gen_ethtool_autoneg(struct dagger *net, struct lyd_node *cif)
err = -EINVAL;
goto out;
}
if (n != 1 || !forced) {
if (n != 1) {
sr_session_set_error_message(net->session,
"%s: auto-negotiation/enable=false requires exactly one "
"advertised-pmd-types entry (have %d)", ifname, n);
+17 -8
View File
@@ -283,7 +283,7 @@ static int wifi_center_chan_160(int ch)
*/
static int wifi_chan_to_freq(int channel, const char *band)
{
if (band && !strcmp(band, "6GHz"))
if (!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 (band && !strcmp(band, "6GHz"))
if (!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 (band && !strcmp(band, "6GHz"))
if (!strcmp(band, "6GHz"))
fprintf(wpa_supplicant, " he=1\n");
else
fprintf(wpa_supplicant, " vht=1\n");
@@ -540,6 +540,7 @@ out:
*/
int wifi_del_iface(struct lyd_node *dif, struct dagger *net)
{
struct lyd_node *wifi;
const char *ifname;
FILE *iw;
@@ -552,14 +553,22 @@ 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;
+1 -1
View File
@@ -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-07-02.yang"
"infix-hardware@2026-03-01.yang"
"ieee802-dot1q-types@2022-10-29.yang"
"infix-ip@2026-04-28.yang"
"infix-if-type@2026-01-07.yang"
+2 -8
View File
@@ -21,12 +21,6 @@ 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";
@@ -463,13 +457,13 @@ module infix-hardware {
"Maximum number of virtual interfaces supported by this radio.";
leaf ap {
type uint16;
type uint8;
description
"Maximum number of AP interfaces.";
}
leaf station {
type uint16;
type uint8;
description
"Maximum number of station interfaces.";
}
-19
View File
@@ -48,13 +48,6 @@ 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.
@@ -246,18 +239,6 @@ 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;
+1 -1
View File
@@ -2,7 +2,7 @@ DISTCLEANFILES = *~ *.d
ACLOCAL_AMFLAGS = -I m4
sbin_PROGRAMS = statd
statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h avahi.c avahi.h
statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h avahi.c avahi.h yangerd.c yangerd.h
statd_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE
statd_CFLAGS = -W -Wall -Wextra
statd_CFLAGS += $(jansson_CFLAGS) $(libyang_CFLAGS) $(sysrepo_CFLAGS)
@@ -1762,11 +1762,8 @@ 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})")
+1 -2
View File
@@ -591,9 +591,8 @@ 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,8 +83,6 @@ 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:
+55 -114
View File
@@ -21,23 +21,16 @@
#include <ctype.h>
#include <linux/if.h>
#include <sys/queue.h>
#include <sys/mman.h>
#include <srx/common.h>
#include <srx/helpers.h>
#include <srx/lyx.h>
#include <srx/systemv.h>
#include "shared.h"
#include "journal.h"
#include "avahi.h"
#include "yangerd.h"
/* New kernel feature, not in sys/mman.h yet */
#ifndef MFD_NOEXEC_SEAL
#define MFD_NOEXEC_SEAL 0x0008U
#endif
#define YANGER_BINPATH YANGER_DIR"/yanger"
#define XPATH_MAX PATH_MAX
#define XPATH_IFACE_BASE "/ietf-interfaces:interfaces"
#define XPATH_ROUTING_BASE "/ietf-routing:routing/control-plane-protocols/control-plane-protocol"
@@ -59,6 +52,7 @@ TAILQ_HEAD(sub_head, sub);
struct sub {
struct ev_io watcher;
sr_subscription_ctx_t *sr_sub;
char key[XPATH_MAX]; /* yangerd key, derived from the subscription xpath */
TAILQ_ENTRY(sub)
entries;
@@ -74,98 +68,60 @@ struct statd {
struct mdns_ctx mdns; /* mDNS neighbor monitor */
};
static int ly_add_yanger_data(const struct ly_ctx *ctx, struct lyd_node **parent,
char *yanger_args[])
static int ly_add_yangerd_data(const struct ly_ctx *ctx, struct lyd_node **parent,
const char *path)
{
FILE *stream;
char *json = NULL;
size_t len = 0;
int err;
int fd;
fd = memfd_create("yanger_tmpfile", MFD_CLOEXEC | MFD_NOEXEC_SEAL);
if (fd == -1) {
ERROR("Error, unable to create memfd");
return SR_ERR_SYS;
}
/* Wrap the file descriptor in a FILE stream for fwrite */
stream = fdopen(fd, "w+");
if (stream == NULL) {
ERROR("Error, unable to fdopen memfd");
close(fd);
return SR_ERR_SYS;
}
err = fsystemv(yanger_args, NULL, stream, NULL);
err = yangerd_query(path, &json, &len);
if (err) {
ERROR("Error, running yanger");
fclose(stream);
free(json);
ERROR("yangerd: query failed for %s", path);
return SR_ERR_SYS;
}
fflush(stream);
NOTE("yangerd: got %zu bytes JSON for %s", len, path);
if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
ERROR("Error, unable reset stream (seek)");
fclose(stream);
return SR_ERR_SYS;
}
err = lyd_parse_data_fd(ctx, fd, LYD_JSON, LYD_PARSE_ONLY, 0, parent);
err = lyd_parse_data_mem(ctx, json, LYD_JSON, LYD_PARSE_ONLY, 0, parent);
if (err)
ERROR("Error, parsing yanger data (%d): %s", err, ly_errmsg(ctx));
fclose(stream);
/* Note: fclose() already closes the underlying fd from fdopen() */
free(json);
return err;
}
static char *xpath_extract(const char *xpath, const char *key)
static const char *xpath_to_yangerd_path(const char *xpath, char *buf, size_t bufsz)
{
char *res = NULL;
const char *ptr;
const char *end;
const char *start, *slash;
size_t len;
/* (also checks if key exist) */
ptr = strstr(xpath, key);
if (!ptr)
return NULL;
ptr += strlen(key);
end = strchr(ptr, '\'');
if (!end) {
ERROR("Can't find end quote for %s (sanity check)", key);
return NULL;
if (!xpath || !*xpath || !strcmp(xpath, "*") || !strcmp(xpath, "/*")) {
buf[0] = '\0';
return buf;
}
if ((end - ptr) >= XPATH_MAX) {
ERROR("Value for %s is to long (sanity check)", key);
return NULL;
}
start = xpath;
if (*start == '/')
start++;
res = calloc((end - ptr) + 1, sizeof(char));
if (!res)
return NULL;
slash = strchr(start, '/');
len = slash ? (size_t)(slash - start) : strlen(start);
strncpy(res, ptr, end - ptr);
res[end - ptr] = '\0';
if (len >= bufsz)
len = bufsz - 1;
return res;
memcpy(buf, start, len);
buf[len] = '\0';
return buf;
}
static int sr_iface_cb(sr_session_ctx_t *session, uint32_t, const char *model,
static int sr_iface_cb(sr_session_ctx_t *session, uint32_t, const char *,
const char *, const char *xpath, uint32_t,
struct lyd_node **parent, __attribute__((unused)) void *priv)
{
char *yanger_args[5] = {
YANGER_BINPATH,
(char *)model,
NULL,
NULL,
NULL
};
char *ifname = NULL;
const struct ly_ctx *ctx;
sr_conn_ctx_t *con;
int err;
@@ -184,34 +140,25 @@ static int sr_iface_cb(sr_session_ctx_t *session, uint32_t, const char *model,
return SR_ERR_INTERNAL;
}
ifname = xpath_extract(xpath, "[name='");
if (ifname) {
yanger_args[2] = "-p";
yanger_args[3] = ifname;
}
err = ly_add_yanger_data(ctx, parent, yanger_args);
err = ly_add_yangerd_data(ctx, parent, "ietf-interfaces:interfaces");
if (err)
ERROR("Error adding interface yanger data");
ERROR("Error adding interface data (err %d)", err);
sr_release_context(con);
return SR_ERR_OK;
return err ? SR_ERR_INTERNAL : SR_ERR_OK;
}
static int sr_generic_cb(sr_session_ctx_t *session, uint32_t, const char *model,
static int sr_generic_cb(sr_session_ctx_t *session, uint32_t, const char *,
const char *, const char *xpath, uint32_t,
struct lyd_node **parent, __attribute__((unused)) void *priv)
struct lyd_node **parent, void *priv)
{
char *yanger_args[5] = {
YANGER_BINPATH,
(char *)model,
NULL
};
struct sub *sub = priv;
const struct ly_ctx *ctx;
sr_conn_ctx_t *con;
sr_error_t err;
DEBUG("Incoming generic query for xpath: %s", xpath);
DEBUG("Incoming generic query for xpath: %s -> key %s", xpath, sub->key);
con = sr_session_get_connection(session);
if (!con) {
@@ -225,9 +172,9 @@ static int sr_generic_cb(sr_session_ctx_t *session, uint32_t, const char *model,
return SR_ERR_INTERNAL;
}
err = ly_add_yanger_data(ctx, parent, yanger_args);
err = ly_add_yangerd_data(ctx, parent, sub->key);
if (err)
ERROR("Error adding yanger data");
ERROR("Error adding data for %s", sub->key);
sr_release_context(con);
@@ -238,11 +185,6 @@ static int sr_ospf_cb(sr_session_ctx_t *session, uint32_t, const char *,
const char *, const char *xpath, uint32_t,
struct lyd_node **parent, __attribute__((unused)) void *priv)
{
char *yanger_args[5] = {
YANGER_BINPATH,
"ietf-ospf",
NULL
};
const struct ly_ctx *ctx;
sr_conn_ctx_t *con;
sr_error_t err;
@@ -261,9 +203,9 @@ static int sr_ospf_cb(sr_session_ctx_t *session, uint32_t, const char *,
return SR_ERR_INTERNAL;
}
err = ly_add_yanger_data(ctx, parent, yanger_args);
err = ly_add_yangerd_data(ctx, parent, "ietf-routing:routing");
if (err)
ERROR("Error adding yanger data");
ERROR("Error adding OSPF data");
sr_release_context(con);
@@ -274,11 +216,6 @@ static int sr_rip_cb(sr_session_ctx_t *session, uint32_t, const char *,
const char *, const char *xpath, uint32_t,
struct lyd_node **parent, __attribute__((unused)) void *priv)
{
char *yanger_args[5] = {
YANGER_BINPATH,
"ietf-rip",
NULL
};
const struct ly_ctx *ctx;
sr_conn_ctx_t *con;
sr_error_t err;
@@ -297,9 +234,9 @@ static int sr_rip_cb(sr_session_ctx_t *session, uint32_t, const char *,
return SR_ERR_INTERNAL;
}
err = ly_add_yanger_data(ctx, parent, yanger_args);
err = ly_add_yangerd_data(ctx, parent, "ietf-routing:routing");
if (err)
ERROR("Error adding yanger data");
ERROR("Error adding RIP data");
sr_release_context(con);
@@ -310,11 +247,6 @@ static int sr_bfd_cb(sr_session_ctx_t *session, uint32_t, const char *,
const char *, const char *xpath, uint32_t,
struct lyd_node **parent, __attribute__((unused)) void *priv)
{
char *yanger_args[5] = {
YANGER_BINPATH,
"ietf-bfd-ip-sh",
NULL
};
const struct ly_ctx *ctx;
sr_conn_ctx_t *con;
sr_error_t err;
@@ -333,9 +265,9 @@ static int sr_bfd_cb(sr_session_ctx_t *session, uint32_t, const char *,
return SR_ERR_INTERNAL;
}
err = ly_add_yanger_data(ctx, parent, yanger_args);
err = ly_add_yangerd_data(ctx, parent, "ietf-routing:routing");
if (err)
ERROR("Error adding yanger data");
ERROR("Error adding BFD data");
sr_release_context(con);
@@ -379,7 +311,16 @@ static int subscribe(struct statd *statd, char *model, char *xpath,
sub = malloc(sizeof(struct sub));
memset(sub, 0, sizeof(struct sub));
DEBUG("Subscribe to events for \"%s\"", xpath);
/*
* Derive the yangerd key from the (static) subscription xpath here,
* once. The generic callback must NOT derive it from the runtime
* request xpath sysrepo hands it -- that is unreliable and yields a
* bare "system-state" for /ietf-system:system-state, which yangerd
* (keyed "ietf-system:system-state") cannot match.
*/
xpath_to_yangerd_path(xpath, sub->key, sizeof(sub->key));
DEBUG("Subscribe to events for \"%s\" (key \"%s\")", xpath, sub->key);
err = sr_oper_get_subscribe(statd->sr_ses, model, xpath, cb, sub,
SR_SUBSCR_DEFAULT | SR_SUBSCR_NO_THREAD | SR_SUBSCR_DONE_ONLY,
&sub->sr_sub);
+246
View File
@@ -0,0 +1,246 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <jansson.h>
#include <srx/common.h>
#include "yangerd.h"
static const char *yangerd_socket_path(void)
{
const char *env;
env = getenv("YANGERD_SOCKET");
if (env && *env)
return env;
return YANGERD_SOCKET_DEFAULT;
}
static int yangerd_connect(void)
{
struct sockaddr_un addr = { .sun_family = AF_UNIX };
struct timeval tv = { .tv_sec = YANGERD_TIMEOUT_SEC };
const char *path;
int fd;
path = yangerd_socket_path();
if (strlen(path) >= sizeof(addr.sun_path)) {
ERROR("yangerd socket path too long: %s", path);
return -1;
}
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fd < 0) {
ERROR("yangerd: socket(): %s", strerror(errno));
return -1;
}
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
DEBUG("yangerd: connect(%s): %s", path, strerror(errno));
close(fd);
return -1;
}
return fd;
}
static int yangerd_write_all(int fd, const void *buf, size_t len)
{
const unsigned char *p = buf;
while (len > 0) {
ssize_t n = write(fd, p, len);
if (n < 0) {
if (errno == EINTR)
continue;
return -1;
}
p += n;
len -= n;
}
return 0;
}
static int yangerd_read_all(int fd, void *buf, size_t len)
{
unsigned char *p = buf;
while (len > 0) {
ssize_t n = read(fd, p, len);
if (n < 0) {
if (errno == EINTR)
continue;
return -1;
}
if (n == 0) {
errno = ECONNRESET;
return -1;
}
p += n;
len -= n;
}
return 0;
}
static int yangerd_send_request(int fd, const char *path)
{
json_t *req;
char *json_str;
size_t json_len;
unsigned char hdr[5];
int rc = -1;
req = json_pack("{s:s, s:s}", "method", "get", "path", path);
if (!req)
return -1;
json_str = json_dumps(req, JSON_COMPACT);
json_decref(req);
if (!json_str)
return -1;
json_len = strlen(json_str);
if (json_len > YANGERD_MAX_PAYLOAD) {
free(json_str);
return -1;
}
hdr[0] = YANGERD_PROTO_VERSION;
hdr[1] = (json_len >> 24) & 0xff;
hdr[2] = (json_len >> 16) & 0xff;
hdr[3] = (json_len >> 8) & 0xff;
hdr[4] = (json_len >> 0) & 0xff;
if (yangerd_write_all(fd, hdr, sizeof(hdr)) < 0)
goto out;
if (yangerd_write_all(fd, json_str, json_len) < 0)
goto out;
rc = 0;
out:
free(json_str);
return rc;
}
static int yangerd_recv_response(int fd, char **buf, size_t *len)
{
unsigned char hdr[5];
uint32_t payload_len;
json_error_t jerr;
json_t *resp;
json_t *status;
json_t *data;
char *body;
char *data_str;
*buf = NULL;
*len = 0;
if (yangerd_read_all(fd, hdr, sizeof(hdr)) < 0)
return -1;
if (hdr[0] != YANGERD_PROTO_VERSION) {
ERROR("yangerd: protocol version mismatch: got %u, want %u",
hdr[0], YANGERD_PROTO_VERSION);
return -1;
}
payload_len = ((uint32_t)hdr[1] << 24) |
((uint32_t)hdr[2] << 16) |
((uint32_t)hdr[3] << 8) |
((uint32_t)hdr[4]);
if (payload_len > YANGERD_MAX_PAYLOAD) {
ERROR("yangerd: payload too large: %u", payload_len);
return -1;
}
body = malloc(payload_len + 1);
if (!body)
return -1;
if (yangerd_read_all(fd, body, payload_len) < 0) {
free(body);
return -1;
}
body[payload_len] = '\0';
resp = json_loads(body, 0, &jerr);
free(body);
if (!resp) {
ERROR("yangerd: invalid response JSON: %s", jerr.text);
return -1;
}
status = json_object_get(resp, "status");
if (!json_is_string(status) || strcmp(json_string_value(status), "ok")) {
json_t *msg = json_object_get(resp, "message");
ERROR("yangerd: request failed: %s",
json_is_string(msg) ? json_string_value(msg) : "unknown");
json_decref(resp);
return -1;
}
data = json_object_get(resp, "data");
if (!data || json_is_null(data)) {
json_decref(resp);
*buf = strdup("{}");
*len = 2;
return 0;
}
data_str = json_dumps(data, JSON_COMPACT);
json_decref(resp);
if (!data_str)
return -1;
*buf = data_str;
*len = strlen(data_str);
return 0;
}
int yangerd_query(const char *path, char **buf, size_t *len)
{
int fd;
int rc;
*buf = NULL;
*len = 0;
fd = yangerd_connect();
if (fd < 0)
return -1;
if (yangerd_send_request(fd, path) < 0) {
ERROR("yangerd: failed sending request for %s", path);
close(fd);
return -1;
}
rc = yangerd_recv_response(fd, buf, len);
if (rc < 0)
ERROR("yangerd: failed reading response for %s", path);
close(fd);
return rc;
}
+28
View File
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef STATD_YANGERD_H_
#define STATD_YANGERD_H_
#include <stddef.h>
#define YANGERD_SOCKET_DEFAULT "/run/yangerd.sock"
#define YANGERD_TIMEOUT_SEC 5
#define YANGERD_MAX_PAYLOAD (4 << 20) /* 4 MiB, matches Go side */
#define YANGERD_PROTO_VERSION 0x01
/**
* yangerd_query() - Query yangerd daemon for operational YANG data
* @path: YANG model path, e.g. "ietf-interfaces:interfaces"
* @buf: Output pointer to malloc'd JSON string (caller must free)
* @len: Output length of JSON data
*
* Connects to the yangerd Unix socket, sends a "get" request for @path,
* reads the framed response, and extracts the "data" field as a JSON
* string. The socket path defaults to %YANGERD_SOCKET_DEFAULT but can
* be overridden with the YANGERD_SOCKET environment variable.
*
* Return: 0 on success, -1 on error (buf is set to NULL).
*/
int yangerd_query(const char *path, char **buf, size_t *len);
#endif
+3
View File
@@ -0,0 +1,3 @@
# Build artifacts (root-level binaries only; do not match cmd/ source dirs)
/yangerd
/yangerctl
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2025 The KernelKit Authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of copyright holders nor the names of
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+110
View File
@@ -0,0 +1,110 @@
package main
import (
"encoding/json"
"fmt"
"os"
"time"
"github.com/kernelkit/infix/src/yangerd/internal/ipc"
)
const defaultSocket = "/run/yangerd.sock"
const defaultTimeout = 5 * time.Second
func main() {
socket := defaultSocket
timeout := defaultTimeout
args := os.Args[1:]
for len(args) > 0 && len(args[0]) > 0 && args[0][0] == '-' {
switch args[0] {
case "--socket":
if len(args) < 2 {
die("--socket requires an argument")
}
socket = args[1]
args = args[2:]
case "--timeout":
if len(args) < 2 {
die("--timeout requires an argument")
}
d, err := time.ParseDuration(args[1])
if err != nil {
die("invalid duration: %v", err)
}
timeout = d
args = args[2:]
default:
die("unknown flag: %s", args[0])
}
}
if len(args) == 0 {
usage()
}
client := ipc.NewClient(socket, timeout)
switch args[0] {
case "get":
if len(args) < 2 {
die("get requires a path argument")
}
resp, err := client.Get(args[1])
if err != nil {
die("get: %v", err)
}
printResponse(resp)
case "dump":
resp, err := client.Get("/")
if err != nil {
die("dump: %v", err)
}
printResponse(resp)
case "health":
resp, err := client.Health()
if err != nil {
die("health: %v", err)
}
printResponse(resp)
default:
die("unknown command: %s", args[0])
}
}
func printResponse(resp *ipc.Response) {
if resp.Code == 503 {
fmt.Fprintf(os.Stderr, "yangerd is starting up\n")
os.Exit(3)
}
if resp.Status == "error" {
fmt.Fprintf(os.Stderr, "error %d: %s\n", resp.Code, resp.Message)
if resp.Code == 404 {
os.Exit(2)
}
os.Exit(1)
}
var out []byte
if resp.Data != nil {
out, _ = json.MarshalIndent(json.RawMessage(resp.Data), "", " ")
} else {
out, _ = json.MarshalIndent(resp, "", " ")
}
fmt.Println(string(out))
}
func usage() {
fmt.Fprintf(os.Stderr, "Usage: yangerctl [--socket path] [--timeout dur] <command> [args]\n\n")
fmt.Fprintf(os.Stderr, "Commands:\n")
fmt.Fprintf(os.Stderr, " get <path> Query a YANG subtree\n")
fmt.Fprintf(os.Stderr, " dump Dump entire tree\n")
fmt.Fprintf(os.Stderr, " health Show daemon health\n")
os.Exit(1)
}
func die(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, "yangerctl: "+format+"\n", args...)
os.Exit(1)
}
+434
View File
@@ -0,0 +1,434 @@
package main
import (
"context"
"encoding/json"
"log"
"log/slog"
"os"
"os/signal"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/kernelkit/infix/src/yangerd/internal/bridgebatch"
"github.com/kernelkit/infix/src/yangerd/internal/collector"
"github.com/kernelkit/infix/src/yangerd/internal/config"
"github.com/kernelkit/infix/src/yangerd/internal/containermonitor"
"github.com/kernelkit/infix/src/yangerd/internal/dbusmonitor"
"github.com/kernelkit/infix/src/yangerd/internal/ethmonitor"
"github.com/kernelkit/infix/src/yangerd/internal/frrvty"
"github.com/kernelkit/infix/src/yangerd/internal/fswatcher"
"github.com/kernelkit/infix/src/yangerd/internal/ipbatch"
"github.com/kernelkit/infix/src/yangerd/internal/ipc"
"github.com/kernelkit/infix/src/yangerd/internal/iwmonitor"
"github.com/kernelkit/infix/src/yangerd/internal/lldpmonitor"
"github.com/kernelkit/infix/src/yangerd/internal/monitor"
"github.com/kernelkit/infix/src/yangerd/internal/sysreaders"
"github.com/kernelkit/infix/src/yangerd/internal/tree"
"github.com/kernelkit/infix/src/yangerd/internal/wgquery"
"github.com/kernelkit/infix/src/yangerd/internal/zapiwatcher"
)
// osFileChecker implements iface.FileChecker using the real filesystem.
type osFileChecker struct{}
func (osFileChecker) Exists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func (osFileChecker) ReadFile(path string) (string, error) {
b, err := os.ReadFile(path)
if err != nil {
return "", err
}
return string(b), nil
}
func main() {
cfg := config.Load()
log.SetFlags(0)
t := tree.New()
ready := &atomic.Bool{}
srv := ipc.NewServer(t, ready)
if err := srv.Listen(cfg.Socket); err != nil {
log.Fatalf("listen %s: %v", cfg.Socket, err)
}
defer os.Remove(cfg.Socket)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var wg sync.WaitGroup
cmd := collector.ExecRunner{}
fs := collector.OSFileReader{}
collectors := []collector.Collector{
collector.NewSystemCollector(cmd, fs, cfg.PollSystem),
collector.NewRoutingCollector(cmd, cfg.PollRouting),
collector.NewNTPCollector(cmd, cfg.PollNTP),
collector.NewHardwareCollector(cmd, fs, cfg.PollHardware, cfg.EnableWifi, cfg.EnableGPS),
}
pokeCh := make(chan struct{}, len(collectors))
collector.RunAll(ctx, &wg, t, collectors, pokeCh)
inst := collector.DBusInstaller{}
t.RegisterProvider("ietf-system:system-state", func() json.RawMessage {
live := collector.LiveSystemState(fs)
installerOverlay := collector.MergeInstaller(t.GetCached("ietf-system:system-state"), inst)
if installerOverlay == nil {
return live
}
var base map[string]json.RawMessage
if json.Unmarshal(live, &base) != nil {
return live
}
var overlay map[string]json.RawMessage
if json.Unmarshal(installerOverlay, &overlay) != nil {
return live
}
for k, v := range overlay {
base[k] = v
}
merged, err := json.Marshal(base)
if err != nil {
return live
}
return merged
})
if data := collector.BootPlatform(fs); data != nil {
t.Merge("ietf-system:system-state", data)
}
if data := collector.BootSoftware(ctx, cmd); data != nil {
t.Merge("ietf-system:system-state", data)
}
slogLog := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slogLevel(cfg.LogLevel)}))
linkBatch, err := ipbatch.New(ctx, slogLog, ipbatch.WithStats(), ipbatch.WithDetails())
if err != nil {
log.Fatalf("start link batch: %v", err)
}
defer linkBatch.Close()
addrBatch, err := ipbatch.New(ctx, slogLog, ipbatch.WithDetails())
if err != nil {
log.Fatalf("start addr batch: %v", err)
}
defer addrBatch.Close()
neighBatch, err := ipbatch.New(ctx, slogLog)
if err != nil {
log.Fatalf("start neigh batch: %v", err)
}
defer neighBatch.Close()
brBatch, err := bridgebatch.New(ctx, slogLog)
if err != nil {
log.Fatalf("start bridge batch: %v", err)
}
defer brBatch.Close()
nlmon := monitor.New(linkBatch, addrBatch, neighBatch, brBatch, t, osFileChecker{}, slogLog)
ethMon, err := ethmonitor.New(slogLog, cmd)
if err != nil {
slogLog.Warn("ethmonitor unavailable, continuing without it", "err", err)
} else {
ethMon.SetOnUpdate(nlmon.SetEthernetData)
nlmon.SetEthRefresh(ethMon.RefreshInterface)
wg.Add(1)
go func() {
defer wg.Done()
if err := ethMon.Run(ctx); err != nil && ctx.Err() == nil {
slogLog.Error("ethmonitor exited", "err", err)
}
}()
}
wg.Add(1)
go func() {
defer wg.Done()
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
<-nlmon.WaitReady()
for {
links := nlmon.Links()
for ifname, data := range wgquery.Query(links) {
nlmon.SetWireguardData(ifname, data)
}
select {
case <-ctx.Done():
return
case <-ticker.C:
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
ticker := time.NewTicker(cfg.PollSTP)
defer ticker.Stop()
<-nlmon.WaitReady()
for {
nlmon.RefreshSTP()
select {
case <-ctx.Done():
return
case <-ticker.C:
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
for {
if err := nlmon.Run(ctx); err != nil {
if ctx.Err() != nil {
return
}
slogLog.Error("nlmonitor exited, restarting in 1s", "err", err)
select {
case <-ctx.Done():
return
case <-time.After(time.Second):
}
} else {
return
}
}
}()
if cfg.EnableWifi {
iwmon := iwmonitor.New(slogLog)
iwmon.SetOnUpdate(nlmon.SetWifiData)
iwmon.SetOnPhyChange(func() {
select {
case pokeCh <- struct{}{}:
default:
}
})
wg.Add(1)
go func() {
defer wg.Done()
if err := iwmon.Run(ctx); err != nil && ctx.Err() == nil {
slogLog.Error("iwmonitor exited", "err", err)
}
}()
}
if cfg.EnableLLDP {
lldpmon := lldpmonitor.New(t, slogLog)
wg.Add(1)
go func() {
defer wg.Done()
if err := lldpmon.Run(ctx); err != nil && ctx.Err() == nil {
slogLog.Error("lldpmonitor exited", "err", err)
}
}()
}
if cfg.EnableContainers {
ctrmon := containermonitor.New(t, cmd, fs, slogLog)
wg.Add(1)
go func() {
defer wg.Done()
if err := ctrmon.Run(ctx); err != nil && ctx.Err() == nil {
slogLog.Error("containermonitor exited", "err", err)
}
}()
}
zapi := zapiwatcher.New(t, frrvty.New(""), slogLog)
wg.Add(1)
go func() {
defer wg.Done()
if err := zapi.Run(ctx); err != nil && ctx.Err() == nil {
slogLog.Error("zapiwatcher exited", "err", err)
}
}()
if cfg.EnableDHCP || cfg.EnableFirewall {
dbusMon := dbusmonitor.New(t, slogLog)
wg.Add(1)
go func() {
defer wg.Done()
if err := dbusMon.Run(ctx); err != nil && ctx.Err() == nil {
slogLog.Error("dbusmonitor exited", "err", err)
}
}()
}
fsw, err := fswatcher.New(t, slogLog)
if err != nil {
log.Fatalf("start fswatcher: %v", err)
}
fwdAgg := sysreaders.NewForwardingAggregator()
forwardingPaths := []string{
"/proc/sys/net/ipv4/conf/*/forwarding",
"/proc/sys/net/ipv6/conf/*/forwarding",
}
for _, pattern := range forwardingPaths {
matches, globErr := filepath.Glob(pattern)
if globErr != nil {
slogLog.Warn("fswatcher glob failed", "pattern", pattern, "err", globErr)
continue
}
for _, path := range matches {
if err := fsw.Watch(path, fswatcher.WatchHandler{
TreeKey: routingTreeKey,
ReadFunc: fwdAgg.HandleForwardingChange,
Debounce: 100 * time.Millisecond,
UseMerge: true,
}); err != nil {
slogLog.Warn("fswatcher watch failed", "path", path, "err", err)
}
}
}
if err := fsw.Watch("/etc/hostname", fswatcher.WatchHandler{
TreeKey: "ietf-system:system",
ReadFunc: sysreaders.ReadHostname,
Debounce: 200 * time.Millisecond,
UseMerge: true,
}); err != nil {
slogLog.Warn("fswatcher watch failed", "path", "/etc/hostname", "err", err)
}
if err := fsw.WatchSymlink("/etc/localtime", fswatcher.WatchHandler{
TreeKey: "ietf-system:system",
ReadFunc: sysreaders.ReadTimezone,
Debounce: 200 * time.Millisecond,
UseMerge: true,
}); err != nil {
slogLog.Warn("fswatcher watch failed", "path", "/etc/localtime", "err", err)
}
usersHandler := fswatcher.WatchHandler{
TreeKey: "ietf-system:system",
ReadFunc: sysreaders.ReadUsers,
Debounce: 200 * time.Millisecond,
UseMerge: true,
}
if err := fsw.Watch("/etc/shadow", usersHandler); err != nil {
slogLog.Warn("fswatcher watch failed", "path", "/etc/shadow", "err", err)
}
if err := fsw.WatchDir(sysreaders.SSHDKeysDir, usersHandler); err != nil {
slogLog.Warn("fswatcher watch failed", "path", sysreaders.SSHDKeysDir, "err", err)
}
bootOrderHandler := fswatcher.WatchHandler{
TreeKey: "ietf-system:system-state",
ReadFunc: makeBootOrderReader(t, cmd),
Debounce: 200 * time.Millisecond,
UseMerge: true,
}
// Watch the parent directory, not the file: fw_setenv (U-Boot) and
// grub-editenv may rewrite the env via a temp file + rename, which
// gives it a new inode that a direct file watch never sees. Watching
// the directory catches the Create/Rename (and still catches in-place
// writes), so a boot-order change after a RAUC install is reflected
// without waiting for a reboot.
for _, path := range []string{"/mnt/aux/grub/grubenv", "/mnt/aux/uboot.env"} {
if err := fsw.WatchSymlink(path, bootOrderHandler); err != nil {
slogLog.Debug("fswatcher boot-order watch skipped", "path", path, "err", err)
}
}
dnsHandler := fswatcher.WatchHandler{
TreeKey: "ietf-system:system-state",
ReadFunc: sysreaders.ReadDNSResolver,
Debounce: 200 * time.Millisecond,
UseMerge: true,
}
for _, path := range []string{"/etc/resolv.conf.head", "/var/lib/misc/resolv.conf"} {
if err := fsw.WatchSymlink(path, dnsHandler); err != nil {
slogLog.Warn("fswatcher dns watch failed", "path", path, "err", err)
}
}
// Container operational data is handled by containermonitor (a
// `podman events` stream), not the fswatcher.
fsw.InitialRead()
wg.Add(1)
go func() {
defer wg.Done()
if err := fsw.Run(ctx); err != nil && ctx.Err() == nil {
slogLog.Error("fswatcher exited", "err", err)
}
}()
go func() {
<-nlmon.WaitReady()
ready.Store(true)
}()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)
go func() {
for sig := range sigCh {
if sig == syscall.SIGHUP {
log.Printf("SIGHUP: triggering immediate re-poll")
for range len(collectors) {
pokeCh <- struct{}{}
}
continue
}
cancel()
return
}
}()
if err := srv.Serve(ctx); err != nil {
log.Fatalf("serve: %v", err)
}
wg.Wait()
}
func slogLevel(s string) slog.Level {
switch strings.ToLower(s) {
case "debug":
return slog.LevelDebug
case "warn", "warning":
return slog.LevelWarn
case "error":
return slog.LevelError
default:
return slog.LevelInfo
}
}
const routingTreeKey = "ietf-routing:routing"
func makeBootOrderReader(t *tree.Tree, cmd collector.CommandRunner) func(string) (json.RawMessage, error) {
return func(_ string) (json.RawMessage, error) {
bootOrder := collector.ReadBootOrder(context.TODO(), cmd)
raw := t.Get("ietf-system:system-state")
var state map[string]interface{}
if raw != nil {
json.Unmarshal(raw, &state)
}
if state == nil {
state = make(map[string]interface{})
}
sw, _ := state["infix-system:software"].(map[string]interface{})
if sw == nil {
sw = make(map[string]interface{})
}
if bootOrder != nil {
sw["boot-order"] = bootOrder
} else {
delete(sw, "boot-order")
}
return json.Marshal(map[string]interface{}{"infix-system:software": sw})
}
}

Some files were not shown because too many files have changed in this diff Show More