mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-25 18:23:01 +02:00
Compare commits
73
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
980b9dac7c | ||
|
|
e32b72d1ee | ||
|
|
62b890f7b6 | ||
|
|
ce4184e048 | ||
|
|
013932ed06 | ||
|
|
8c6f9cec49 | ||
|
|
27dc42622e | ||
|
|
f4de58a54c | ||
|
|
4899279ed2 | ||
|
|
42c8dc0346 | ||
|
|
a2937e13eb | ||
|
|
b35f4df8a5 | ||
|
|
8890274e90 | ||
|
|
127bbdfc4f | ||
|
|
8fb9e9b68e | ||
|
|
7d9319b8e4 | ||
|
|
7f4c4b63ee | ||
|
|
beb2439922 | ||
|
|
7dc1811389 | ||
|
|
01b9c04f95 | ||
|
|
8b60e447f3 | ||
|
|
d90d0d2a9c | ||
|
|
6a669e6d92 | ||
|
|
155f74e968 | ||
|
|
7f0b88be63 | ||
|
|
05d4598ac9 | ||
|
|
79ec51860b | ||
|
|
51aa4d26b7 | ||
|
|
a33214d67f | ||
|
|
cff66cd74c | ||
|
|
b662984f09 | ||
|
|
39114f89dd | ||
|
|
0b193cb5bd | ||
|
|
98022e2e5f | ||
|
|
52c7e4e90e | ||
|
|
f50f6add9c | ||
|
|
5071a1f177 | ||
|
|
ee5499501c | ||
|
|
e9ff748df9 | ||
|
|
bf850f59d7 | ||
|
|
76de3427ff | ||
|
|
6a78def525 | ||
|
|
59fbb26dca | ||
|
|
56b7904d1a | ||
|
|
3e23997289 | ||
|
|
c7ab211e51 | ||
|
|
32bed66267 | ||
|
|
ca7447dfc9 | ||
|
|
dc7a86c604 | ||
|
|
f305bd3527 | ||
|
|
73f6b52a70 | ||
|
|
9e403831b3 | ||
|
|
b40f64492f | ||
|
|
2dffad67b7 | ||
|
|
0cd598d67c | ||
|
|
6ce28a7e40 | ||
|
|
e938c2dce2 | ||
|
|
61a7c85d96 | ||
|
|
72d3777d3f | ||
|
|
02db44bb82 | ||
|
|
4cc9751c7b | ||
|
|
7b45f2589c | ||
|
|
ad67a5aacf | ||
|
|
fdbf00ca76 | ||
|
|
4131167ede | ||
|
|
281adb45b6 | ||
|
|
35f8d6e3d5 | ||
|
|
1e932f09c1 | ||
|
|
6d27f67b37 | ||
|
|
ce4dd2d3e3 | ||
|
|
0deff2515c | ||
|
|
30907b0343 | ||
|
|
89fee60663 |
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.'
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
Submodule buildroot updated: dd816b9233...707ade3a89
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -0,0 +1,3 @@
|
||||
service <> name:yangerd log:prio:daemon.notice,tag:yangerd \
|
||||
env:-/etc/default/yangerd \
|
||||
[2345] yangerd -- Operational data daemon
|
||||
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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,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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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})")
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
# Build artifacts (root-level binaries only; do not match cmd/ source dirs)
|
||||
/yangerd
|
||||
/yangerctl
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user