Compare commits

...
Author SHA1 Message Date
Mattias WalströmandGitHub f47bf0e0b1 Merge pull request #1395 from kernelkit/run-fix
Fix extration of rootfs.squashfs in release tarball
2026-02-03 15:13:18 +01:00
Joachim Wiberg e7108f52cf board/common: fix extration of rootfs.squashfs in release tarball
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-03 15:05:20 +01:00
Joachim WibergandGitHub 26c7fe40c0 Merge pull request #1391 from kernelkit/update-bpi-recommendations
Doc: Update recommendations for bpi-r3
2026-02-03 09:58:13 +01:00
Mattias Walström 1e6286f1d3 Doc: Update recommendations for bpi-r3
[skip ci]
2026-02-03 09:55:05 +01:00
Joachim WibergandGitHub fe56591d8d Merge pull request #1379 from kernelkit/copilot/add-infix-keystore-documentation
Keystore refactor and documentation

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-03 08:09:05 +01:00
Joachim WibergandGitHub 18c812c1b4 Merge pull request #1390 from kernelkit/fix-boot-defconfigs
Add missing `fdt_add_pubkey` tool to bootloader defconfigs
2026-02-03 07:39:54 +01:00
Joachim Wiberg bb199590ce configs: add missing fdt_add_pubkey to bootloader defconfigs
Follow-up to c152c5f361

[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-03 07:35:25 +01:00
Joachim Wiberg 047d818cbd doc: update ChangeLog with new 'change' command feature
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:50:45 +01:00
Joachim Wiberg 2b6588eaf4 cli: add 'show keystore' command to admin-exec
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:50:13 +01:00
Joachim Wiberg 08742df166 doc: ux update with new 'change' and 'do wireguard' commands
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:50:12 +01:00
Joachim Wiberg 2a176d5888 cli: add 'wireguard <genkey | genpsk>' command to admin-exec
Helper command to improve the UX for setting up Wireguard tunnels.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:50:11 +01:00
Joachim Wiberg 93dfd00754 packge/klish-plugin-sysrepo: add 'change cleartext-symmetric-key'
This update brings new support for editing cleartext-symmetric-keys with
the 'change' command.  The cleartext-symmetric-key is of type binary and
its use differs between different key-formats.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:50:10 +01:00
Joachim Wiberg 94808331da doc: update docs with keystore format changes
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:50:09 +01:00
Joachim Wiberg 74a09d2c1b board/aarch64: update bpi factory-config and rpi readme example
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:50:08 +01:00
Joachim Wiberg 0c1aede731 confd: add wifi passphrase and key validation to SR_EV_CHANGE
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:50:08 +01:00
Joachim Wiberg 5ceda4569e confd: restore IETF standard keystore leaf types and NACM rules
Infix key format identities now derive from the IETF bases, so the standard
identityref accepts them without any need for deviations.  This also preserves
the nacm default rules, which were inadvertently dropped before.

Also rename format identities for generality:

 - wifi-preshared-key-format      -> renamed: 'passphrase-key-format'
 - wireguard-symmetric-key-format -> use IETF 'octet-string-key-format'

Since cleartext-symmetric-key is now type binary (base64-encoded), the
WiFi backends (station and AP) decode values before passing them to
wpa_supplicant and hostapd.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:50:07 +01:00
Joachim Wiberg 683f696e19 utils: add 'scan' feature to ixll
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:50:06 +01:00
Joachim Wiberg ad7c6573c8 doc: minor syntax fixes to match YANG model
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:50:05 +01:00
Joachim Wiberg 72860aa1b5 doc: add comprehensive keystore documentation
Fixes #914

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-02 17:49:41 +01:00
Joachim WibergandGitHub 69acd21f19 Merge pull request #1386 from kernelkit/container-restart-on-env
Container restart on env
2026-02-02 09:16:35 +01:00
Joachim Wiberg eeabb44bd5 doc: update ChangeLog
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-01 12:36:25 +01:00
Joachim Wiberg 76bfd6c57f test: update test to verify container restart on env change
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-01 10:40:45 +01:00
Joachim Wiberg 3d7fb595fb board/common: container not restarting on env variable changes
Environment variables are stored in a separate .env file from the main
container script.  The config SHA256 checksum only covered the script
file, so changes to env vars went undetected -- the container was not
recreated and kept running with stale values.

Include the env file contents in the config checksum calculation so that
any change to environment variables triggers a proper container restart.

Fixes #1313

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-01 10:40:45 +01:00
Joachim Wiberg 98a39d989c package/klish: add support for insert-comment
Type a command, regret you typed so much before checking something else,
comment it out for later with Meta-# (Alt-Shift-3).  The whole line is
now commented out and saved to history.  Recall it later with Ctrl-P or
Up arrow, delete the leading # with Ctrl-D, and run it.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-01 10:40:44 +01:00
Joachim WibergandGitHub 8e699736b8 Merge pull request #1380 from kernelkit/copilot/handle-ipv6-forwarding-netfilter 2026-01-30 23:40:36 +01:00
Joachim WibergandGitHub 80ab53ecbc Merge pull request #1383 from kernelkit/kernel-upgrade-3c441547-c319-40de-bb33-e2e50bf0b2c2
Upgrade to kernel 6.18.8
2026-01-30 14:06:35 +01:00
Joachim Wiberg 7ce668904a doc: update ChangeLog
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-30 13:09:20 +01:00
Joachim Wiberg 93fceb9a5f doc: update IPv6 forwarding documentation
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-30 13:09:19 +01:00
Joachim Wiberg 4f876bc58e test: update nacm basic test spec.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-30 13:09:18 +01:00
Joachim Wiberg db0c6417b2 test: add IPv6 forwarding verification to routing-basic
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-30 13:09:14 +01:00
ael-bot aeef240289 Upgrade Linux kernel to 6.18.8 2026-01-30 10:20:43 +00:00
Joachim Wiberg 129f92afdd statd: use IPv6 force_forwarding in operational state
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-30 09:11:41 +01:00
Joachim Wiberg 717c1c02f1 confd: use force_forwarding sysctl for IPv6
Introduced in Linux 6.17, the force_forwding flag corresponds to the
ipv4 forwarding flag, which maps perfectly to the ietf-ip.yang model.

Fixes #515

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-30 09:11:40 +01:00
88 changed files with 1196 additions and 382 deletions
+12 -2
View File
@@ -34,6 +34,10 @@ Infix comes preconfigured with:
The easiest way to get started is using an SD card:
> [!NOTE]
> SD card boot works but we have observed stability issues. For production
> use or long-term reliability, we recommend installing to eMMC (see below).
1. **Download the SD card image:** [infix-bpi-r3-sdcard.img][2]
2. **Flash the image to an SD card:** see [this guide][0]
3. **Set boot switches:**
@@ -53,8 +57,8 @@ The BPI-R3 has a 4-position DIP switch that controls boot media:
| Position | Mode | Description |
|----------|-------------|---------------------------------------|
| 0000 | SD card | Boot from microSD card (recommended) |
| 0110 | eMMC | Boot from internal eMMC storage |
| 0000 | SD card | Boot from microSD card |
| 0110 | eMMC | Boot from internal eMMC (recommended) |
| 1010 | SPI NAND | Boot from SPI NAND (advanced users) |
> [!NOTE]
@@ -67,10 +71,16 @@ For production deployments or better performance, you can install Infix
to the internal eMMC storage. This is more complex but provides faster
boot times and eliminates the external SD card.
> [!IMPORTANT]
> While Infix boots on both SD card and eMMC, we have observed stability
> issues with SD cards on this platform. **eMMC is recommended** for
> reliable operation.
### Why Use eMMC?
**Advantages:**
- More reliable than SD card (stability issues observed with SD cards)
- Faster boot and better performance
- No external SD card to manage
- More robust for industrial/embedded deployments
@@ -218,8 +218,8 @@
"symmetric-key": [
{
"name": "wifi",
"infix-keystore:symmetric-key": "infixinfix",
"infix-keystore:key-format": "infix-crypto-types:wifi-preshared-key-format"
"cleartext-symmetric-key": "aW5maXhpbmZpeA==",
"key-format": "infix-crypto-types:passphrase-key-format"
}
]
}
+4 -2
View File
@@ -107,8 +107,10 @@ To configure WiFi as a client, first store your WiFi password in the keystore:
```
admin@infix:/> configure
admin@infix:/config/> edit keystore symmetric-key mywifi
admin@infix:/config/keystore/…/mywifi/> set key-format wifi-preshared-key-format
admin@infix:/config/keystore/…/mywifi/> set symmetric-key YourWiFiPassword
admin@infix:/config/keystore/…/mywifi/> set key-format passphrase-key-format
admin@infix:/config/keystore/…/mywifi/> change cleartext-symmetric-key
Passphrase: ************
Retype passphrase: ************
admin@infix:/config/keystore/…/mywifi/> leave
```
+6 -7
View File
@@ -358,13 +358,12 @@ extract_squashfs()
run_qemu()
{
# Auto-extract rootfs.squashfs from rootfs.itb if needed for initrd mode
if [ "$CONFIG_QEMU_ROOTFS_INITRD" = "y" ]; then
if [ "$CONFIG_QEMU_ROOTFS" = "rootfs.squashfs" ] && [ ! -f "rootfs.squashfs" ]; then
if [ -f "rootfs.itb" ]; then
extract_squashfs "rootfs.itb" "rootfs.squashfs"
else
die "Missing rootfs.squashfs and cannot find rootfs.itb to extract it from"
fi
if [ "$CONFIG_QEMU_ROOTFS_INITRD" = "y" ] && [ ! -f "$CONFIG_QEMU_ROOTFS" ]; then
itb="${CONFIG_QEMU_ROOTFS%.squashfs}.itb"
if [ -f "$itb" ]; then
extract_squashfs "$itb" "$CONFIG_QEMU_ROOTFS"
else
die "Missing $CONFIG_QEMU_ROOTFS and cannot find $itb to extract it from"
fi
fi
+35 -9
View File
@@ -1,23 +1,49 @@
#!/bin/sh
# Prompt for a secret with confirmation, then encode and output it.
#
# Default mode: hash with mkpasswd (for system passwords)
# askpass [OUTPUT]
#
# Base64 mode (-b): base64-encode (for keystore passphrases)
# askpass -b [OUTPUT]
#
# If OUTPUT is given, result is written to the file.
# If omitted, result is written to stdout.
# shellcheck disable=SC3045
LABEL="New password"
MODE=hash
if [ "$1" = "-b" ]; then
LABEL="Passphrase"
MODE=base64
shift
fi
OUTPUT=$1
read -r -s -p "New password: " password
read -r -s -p "$LABEL: " secret
>&2 echo
read -r -s -p "Retype password: " password_again
read -r -s -p "Retype $LABEL: " secret_again
>&2 echo
if [ "$password" != "$password_again" ]; then
echo "Passwords do not match, try again."
if [ "$secret" != "$secret_again" ]; then
echo "${LABEL}s do not match, try again."
exit 1
fi
if [ -z "$OUTPUT" ]; then
echo "$password"
exit 0
if [ -z "$secret" ]; then
echo "Empty $LABEL, try again."
exit 1
fi
if [ "$MODE" = "base64" ]; then
encoded=$(printf '%s' "$secret" | base64 -w 0)
else
encoded=$(printf '%s\n' "$secret" | mkpasswd -s)
fi
umask 0177
echo "$password" | mkpasswd -s > "$OUTPUT"
exit 0
if [ -z "$OUTPUT" ]; then
echo "$encoded"
else
printf '%s' "$encoded" > "$OUTPUT"
fi
+18 -3
View File
@@ -63,6 +63,21 @@ calc_sha()
sha256sum "$1" 2>/dev/null | awk '{print $1}'
}
# Calculate a combined SHA256 over the container script and its
# optional env file. Environment variables are stored separately
# from the script, so both must be included in the checksum to
# detect configuration changes such as added/changed env vars.
calc_config_sha()
{
_envfile="/run/containers/args/${name}.env"
if [ -f "$_envfile" ]; then
cat "$1" "$_envfile" | sha256sum | awk '{print $1}'
else
calc_sha "$1"
fi
}
# Check image transport, return 0 if remote, 1 if local
is_remote()
{
@@ -124,7 +139,7 @@ is_uptodate()
# If SHA matches, check container instance
if [ "$stored_sha" = "$current_sha" ]; then
if podman container exists "$name"; then
config_sha=$(calc_sha "$script")
config_sha=$(calc_config_sha "$script")
container_sha=$(podman inspect "$name" --format '{{index .Config.Labels "config-sha256"}}' 2>/dev/null)
container_img_sha=$(podman inspect "$name" --format '{{index .Config.Labels "meta-image-sha256"}}' 2>/dev/null)
@@ -143,7 +158,7 @@ is_uptodate()
else
# Remote image optimization: check config-sha256 only
if podman container exists "$name"; then
config_sha=$(calc_sha "$script")
config_sha=$(calc_config_sha "$script")
container_sha=$(podman inspect "$name" --format '{{index .Config.Labels "config-sha256"}}' 2>/dev/null)
if [ "$container_sha" = "$config_sha" ]; then
@@ -459,7 +474,7 @@ create()
fi
# Add config checksum label
args="$args --label config-sha256=$(calc_sha "$script")"
args="$args --label config-sha256=$(calc_config_sha "$script")"
fi
# shellcheck disable=SC2048
+1 -1
View File
@@ -27,7 +27,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.7"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.8"
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
+1 -1
View File
@@ -27,7 +27,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.7"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.8"
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
+1 -1
View File
@@ -28,7 +28,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.7"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.8"
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
+1 -1
View File
@@ -28,7 +28,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.7"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.8"
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
+1
View File
@@ -37,5 +37,6 @@ BR2_PACKAGE_HOST_RAUC=y
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
+1
View File
@@ -37,5 +37,6 @@ BR2_PACKAGE_HOST_RAUC=y
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
+1
View File
@@ -37,5 +37,6 @@ BR2_PACKAGE_HOST_RAUC=y
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
+1
View File
@@ -43,6 +43,7 @@ BR2_PACKAGE_HOST_RAUC=y
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
BR2_PACKAGE_BOOTLOADER_SPLASHSCREEN=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
+1
View File
@@ -38,6 +38,7 @@ BR2_PACKAGE_HOST_RAUC=y
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
BR2_PACKAGE_BOOTLOADER_SPLASHSCREEN=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
+1 -1
View File
@@ -26,7 +26,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.7"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.8"
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
+1 -1
View File
@@ -26,7 +26,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.7"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.8"
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
+11 -2
View File
@@ -3,7 +3,7 @@ Change Log
All notable changes to the project are documented in this file.
[v26.01.0][UNRELEASED]
[v26.01.0][] - 2026-02-03
-------------------------
> [!IMPORTANT]
@@ -23,7 +23,7 @@ All notable changes to the project are documented in this file.
Noteworthy changes and additions in this release are marked below in bold text.
- Upgrade **Linux kernel to 6.18.7** (LTS)
- **Upgrade Linux kernel from 6.12.65 to 6.18.8 (LTS)**
- Upgrade Buildroot to 2025.02.10 (LTS)
- Upgrade libyang to 4.2.2
- Upgrade sysrepo to 4.2.10
@@ -53,6 +53,9 @@ Noteworthy changes and additions in this release are marked below in bold text.
removed during upgrade (for the rest of the configuration to apply) and you
need to reconfigure them again. See the [WiFi][] documentation for details
- Add support for **WireGuard VPN tunnels**.
- Updated CLI change command to support `cleartext-symmetric-key` (type binary).
Used by both WireGuard and WiFi, with application-specific `key-format` for
keys and passphrases
- New default NACM privilege levels (user levels) in `factory-config`:
`operator` (network & container manager) and `guest` (read-only). For
details, see the updated system configuration documentation, as well as a
@@ -66,12 +69,18 @@ Noteworthy changes and additions in this release are marked below in bold text.
### Fixes
- Fix #515: add per-interface IPv6 forwarding control using the Linux 6.17+
`force_forwarding` sysctl. This provides true per-interface IPv6 forwarding
similar to IPv4, correctly mapping to the ietf-ip.yang model semantics
- Fix #1082: Wi-Fi interfaces always scanned, introduce a `scan-mode` to the
Wi-Fi concept in Infix
- Fix #1313: Container is not restarted if environment variable is changed
- Fix #1314: Raspberry Pi 4B with 1 or 8 GiB RAM does not boot. This was due
newer EEPROM firmware in newer boards require a newer rpi-firmware package
- Fix #1345: firewall not updating when interfaces become bridge/lag ports
- Fix #1346: firewall complains in syslog, missing `/etc/firewalld/firewalld.conf`
- Fix Raspberry Pi 2B build, among other things, the `aarch32_defconfig` did
not include a dtb. Please note, the platform has now been renamed to `arm`
- Fix default password hash in `do password encrypt` command. New hash is the
same as the more commonly used `change password` command, *yescrypt*
- Prevent MOTD from showing on non-shell user login attempts
+1
View File
@@ -31,6 +31,7 @@ CLI has several keybindings, most significant first:
| Ctrl-q | Ctrl-v | Insert next character literally |
| Ctrl-r | | History, reversed interactive search (i-search) |
| Ctrl-t | | Transpose/Swap characters before and at cursor |
| Meta-# | Alt-Shift-3 | Prepend # to current line and submit to history |
## What is Meta?
+5 -6
View File
@@ -415,13 +415,12 @@ admin@example:/>
## IPv6 forwarding
Due to how the Linux kernel manages IPv6 forwarding, we can not fully
control it per interface via this setting like how IPv4 works. Instead,
IPv6 forwarding is globally enabled when at least one interface enable
forwarding, otherwise it is disabled.
Forwarding must be enabled on an interface for it to route IPv6
traffic (static or dynamic). The setting is per-interface and works
the same way as IPv4 forwarding.
The following table shows the system IPv6 features that the `forwarding`
setting control when it is *Enabled* or *Disabled:
The following table shows the IPv6 features that the `forwarding`
setting controls when it is *Enabled* or *Disabled*:
| **IPv6 Feature** | **Enabled** | **Disabled** |
|:-----------------------------------------|:------------|:-------------|
+193
View File
@@ -0,0 +1,193 @@
# Keystore
The Infix keystore is a centralized storage system for cryptographic keys
used throughout the system. It is based on the IETF standards [RFC 9641][1]
(Keystore) and [RFC 9640][2] (Cryptographic Types), with Infix extensions
for WiFi and WireGuard key formats.
## Overview
The keystore supports two types of cryptographic keys:
1. **Asymmetric Keys** — public/private key pairs used for:
- SSH host authentication (RSA keys)
- WireGuard VPN tunnels (X25519 keys)
2. **Symmetric Keys** — shared secrets used for:
- WiFi authentication (WPA2/WPA3 pre-shared keys)
- WireGuard VPN pre-shared keys
All keys are stored under the `ietf-keystore` configuration path and can be
managed via CLI, NETCONF, or RESTCONF.
### Supported Formats
| **Asymmetric Key Format** | **Use Case** | **Key Type** |
|----------------------------------------------------------|---------------|--------------|
| `rsa-private-key-format` / `ssh-public-key-format` | SSH host keys | RSA |
| `x25519-private-key-format` / `x25519-public-key-format` | WireGuard VPN | Curve25519 |
| **Symmetric Key Format** | **Use Case** |
|-----------------------------|-----------------------------------|
| `passphrase-key-format` | Human-readable passphrases (WiFi) |
| `octet-string-key-format` | Raw symmetric keys (WireGuard) |
## Asymmetric Keys
Asymmetric keys consist of a public/private key pair. The public key can be
shared freely, while the private key must be kept secure.
### SSH Host Keys
SSH host keys identify the system during SSH and NETCONF connections. The
default host key is automatically generated on first boot and stored in the
keystore with the name `genkey`.
See [SSH Management](management.md) for details on generating and importing
custom SSH host keys.
### WireGuard Keys
WireGuard uses X25519 elliptic curve cryptography for key exchange. Each
WireGuard interface requires a public/private key pair stored as an asymmetric
key in the keystore. Key pairs can be generated directly from the CLI:
<pre class="cli"><code>admin@example:/> <b>wireguard genkey</b>
Private: aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=
Public: bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=
</code></pre>
See [WireGuard VPN](vpn-wireguard.md) for key generation and configuration
examples.
## Symmetric Keys
Symmetric keys are shared secrets where the same key must be configured on
all systems that need to communicate.
### WiFi Pre-Shared Keys
WiFi networks secured with WPA2 or WPA3 use pre-shared keys stored as
symmetric keys in the keystore with `passphrase-key-format`. The
passphrase must be 8-63 printable ASCII characters.
Since symmetric keys are stored as binary (base64-encoded), the CLI
provides the `change` command to enter passphrases interactively:
<pre class="cli"><code>admin@example:/config/keystore/…/my-wifi-key/> <b>change cleartext-symmetric-key</b>
Passphrase: ************
Retype passphrase: ************
</code></pre>
See [WiFi](wifi.md) for complete configuration examples.
### WireGuard Pre-Shared Keys
WireGuard supports optional pre-shared keys (PSK) that add a layer of
symmetric encryption alongside Curve25519. PSKs use the standard IETF
`octet-string-key-format` (32 random bytes). This provides defense-in-depth
against future quantum computers that might break elliptic curve cryptography.
Note, however, that WireGuards authentication and initial key agreement
remain Curve25519-based, so PSKs only protect the session encryption,
not the handshake itself.
PSKs can be generated directly from the CLI:
<pre class="cli"><code>admin@example:/> <b>wireguard genpsk</b>
cO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=
</code></pre>
See [WireGuard VPN](vpn-wireguard.md) for PSK generation and usage examples.
## Viewing Keys
The `show keystore` command in admin-exec mode gives an overview of all
keys in the keystore. Passphrases (WiFi passwords) are decoded and shown
in cleartext, while binary keys (WireGuard PSKs) are shown as base64:
<pre class="cli"><code>admin@example:/> <b>show keystore</b>
────────────────────────────────────────────────────────────────────────
<span class="title">Symmetric Keys</span>
<span class="header">NAME FORMAT VALUE </span>
my-wifi-key passphrase MySecretPassword
wg-psk octet-string zYr83O4Ykj9i1gN+/aaosJxQx...
────────────────────────────────────────────────────────────────────────
<span class="title">Asymmetric Keys</span>
<span class="header">NAME TYPE PUBLIC KEY </span>
genkey rsa MIIBCgKCAQEAnj0YinjhYDgYbEGuh7...
wg-tunnel x25519 bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1...
</code></pre>
To see the full (untruncated) details of a specific key, use the
`symmetric` or `asymmetric` qualifier with the key name:
<pre class="cli"><code>admin@example:/> <b>show keystore symmetric my-wifi-key</b>
name : my-wifi-key
format : passphrase
value : MySecretPassword
admin@example:/> <b>show keystore asymmetric genkey</b>
name : genkey
algorithm : rsa
public key format : ssh-public-key
public key : MIIBCgKCAQEAnj0YinjhY...full key...IDAQAB
</code></pre>
> [!NOTE]
> The `show keystore` command is protected by NACM. Only users in the
> `admin` group can view keystore data. Operator-level users will see a
> message indicating that no keystore data is available.
The full configuration-mode view (including private keys) is still
available via `configure` and then `show keystore`:
<pre class="cli"><code>admin@example:/config/> <b>show keystore</b>
</code></pre>
> [!WARNING]
> The configuration-mode `show keystore` displays private keys in
> cleartext. Be careful when viewing keys on shared screens or in
> logged sessions. The admin-exec `show keystore` command never
> displays private keys.
## Deleting Keys
<pre class="cli"><code>admin@example:/> <b>configure</b>
admin@example:/config/> <b>delete keystore asymmetric-key mykey</b>
admin@example:/config/> <b>leave</b>
</code></pre>
> [!CAUTION]
> Deleting a key that is referenced by a service (SSH, WireGuard, WiFi) will
> cause that service to fail. Verify the key is not in use before deletion.
## Security Considerations
The keystore is protected by NACM (Network Access Control Model) rules.
Only users in the `admin` group can view or modify cryptographic keys.
See [NACM](nacm.md) for details on access control.
Private keys are stored in cleartext in the configuration database.
Configuration files and backups containing the keystore should be treated
as sensitive and protected accordingly.
### Key Validation
Symmetric key values are stored as binary (base64-encoded). The system
validates them based on their declared format:
- `passphrase-key-format`: Used by WiFi, must decode to 8-63 ASCII characters
- `octet-string-key-format`: Used by Wireguard, must decode to exactly 32 bytes (256 bits)
## References
- [RFC 9641 - A YANG Data Model for a Keystore][1]
- [RFC 9640 - YANG Data Types and Groupings for Cryptography][2]
- [WiFi Documentation](wifi.md)
- [WireGuard VPN Documentation](vpn-wireguard.md)
- [SSH Management](management.md)
- [NACM Access Control](nacm.md)
[1]: https://datatracker.ietf.org/doc/html/rfc9641
[2]: https://datatracker.ietf.org/doc/html/rfc9640
+5
View File
@@ -47,6 +47,11 @@ RSA for now, thus the private key must be
`ietf-crypto-types:rsa-private-key-format` and the public key
`ietf-crypto-types:ssh-public-key-format`
> [!TIP]
> For comprehensive information about the keystore, including key management,
> security considerations, and examples for different key types, see the
> [Keystore documentation](keystore.md).
### Use your own SSH hostkeys
Hostkeys can be generated with OpenSSL:
+17 -28
View File
@@ -26,31 +26,25 @@ Key features of WireGuard:
WireGuard uses public-key cryptography similar to SSH. Each WireGuard interface
requires a private key, and each peer is identified by its public key.
**Generate a WireGuard key pair using the `wg` command:**
```bash
admin@example:~$ wg genkey | tee privatekey | wg pubkey > publickey
admin@example:~$ cat privatekey
aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=
admin@example:~$ cat publickey
bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=
```
This generates a private key, saves it to `privatekey`, derives the public key,
and saves it to `publickey`.
**Import the private key into the keystore:**
**Import the key pair into the keystore:**
<pre class="cli"><code>admin@example:/> <b>configure</b>
admin@example:/config/> <b>edit keystore asymmetric-key wg-site-a</b>
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>do wireguard genkey</b>
Private: aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=
Public: bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set public-key-format x25519-public-key-format</b>
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set private-key-format x25519-private-key-format</b>
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set public-key bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=</b>
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set private-key aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=</b>
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>set cleartext-private-key aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=</b>
admin@example:/config/keystore/asymmetric-key/wg-site-a/> <b>leave</b>
admin@example:/>
</code></pre>
> [!TIP]
> The `do` prefix allows running admin-exec commands from configure context.
> Use the base for `wireguard genkey` when in admin-exec context.
**Import peer public keys into the truststore:**
<pre class="cli"><code>admin@example:/> <b>configure</b>
@@ -63,8 +57,7 @@ admin@example:/>
> [!IMPORTANT]
> Keep private keys secure! Never share your private key. Only exchange
> public keys with peers. Delete the `privatekey` file after importing it
> into the keystore.
> public keys with peers.
## Point-to-Point Configuration
@@ -394,20 +387,17 @@ recording traffic today would still need the PSK even if they break Curve25519
later. However, peer authentication still relies on Curve25519, so PSKs don't
provide complete post-quantum security.
**Generate a preshared key using `wg genpsk`:**
**Generate a preshared key:**
```bash
admin@example:~$ wg genpsk > preshared.key
admin@example:~$ cat preshared.key
<pre class="cli"><code>admin@example:/config/> <b>do wireguard genpsk</b>
cO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=
```
</code></pre>
**Import the preshared key into the keystore:**
<pre class="cli"><code>admin@example:/> <b>configure</b>
admin@example:/config/> <b>edit keystore symmetric-key wg-psk</b>
admin@example:/config/keystore/symmetric-key/wg-psk/> <b>set key-format wireguard-symmetric-key-format</b>
admin@example:/config/keystore/symmetric-key/wg-psk/> <b>set key cO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=</b>
<pre class="cli"><code>admin@example:/config/> <b>edit keystore symmetric-key wg-psk</b>
admin@example:/config/keystore/symmetric-key/wg-psk/> <b>set key-format octet-string-key-format</b>
admin@example:/config/keystore/symmetric-key/wg-psk/> <b>set cleartext-symmetric-key cO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=</b>
admin@example:/config/keystore/symmetric-key/wg-psk/> <b>end</b>
admin@example:/config/interface/wg0/> <b>edit wireguard peers wg-peers peer remote</b>
admin@example:/config/interface/…/wg-peers/peer/remote/> <b>set preshared-key wg-psk</b>
@@ -420,5 +410,4 @@ on both sides.
> [!IMPORTANT]
> Preshared keys must be kept secret and exchanged through a secure channel,
> just like passwords. Delete the `preshared.key` file after importing it
> into both peer keystores.
> just like passwords.
+45 -14
View File
@@ -241,6 +241,23 @@ 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.
## Passphrase Requirements
To ensure your connection is secure and compatible with all network
hardware, your passphrase must meet the following criteria:
- Length: Between 8 and 63 characters
- Characters: Use only standard English keyboard characters
- Allowed: Letters (A-Z, a-z), numbers (0-9), and common symbols (e.g., ! @ # $ % ^ & * ( ) _ + - = [ ] { } | ; : ' " , . < > / ? ~)
- Spaces: Spaces are allowed, but not at the very beginning or very end of the passphrase
- Prohibited: Emojis, accented characters (like á or ñ), and special "control" characters
> [!TIP] Why the limit?
> Standard WiFi security (WPA2/WPA3) requires a minimum of 8 characters to
> prevent "brute-force" hacking. The character limit ensures your password
> works on older routers and various operating systems.
> Tips for password strength, see [XKCD #936](https://xkcd.com/936/).
## Station Mode (Client)
Station mode connects to an existing Wi-Fi network. Before configuring station
@@ -253,11 +270,16 @@ Create a keystore entry for your WiFi password (8-63 characters):
<pre class="cli"><code>admin@example:/> <b>configure</b>
admin@example:/config/> <b>edit keystore symmetric-key my-wifi-key</b>
admin@example:/config/keystore/…/my-wifi-key/> <b>set key-format wifi-preshared-key-format</b>
admin@example:/config/keystore/…/my-wifi-key/> <b>set symmetric-key MyPassword123</b>
admin@example:/config/keystore/…/my-wifi-key/> <b>set key-format passphrase-key-format</b>
admin@example:/config/keystore/…/my-wifi-key/> <b>change cleartext-symmetric-key</b>
Passphrase: ************
Retype passphrase: ************
admin@example:/config/keystore/…/my-wifi-key/> <b>leave</b>
</code></pre>
The `change` command prompts for the passphrase interactively and
handles the base64 encoding required by the keystore automatically.
### Step 2: Connect to Network
Configure station mode with the SSID and password to connect:
@@ -276,8 +298,9 @@ name : wifi0
type : wifi
operational status : up
physical address : f0:09:0d:36:5f:86
SSID : MyHomeNetwork
Signal : excellent
mode : station
ssid : MyHomeNetwork
signal : -52 dBm (good)
</code></pre>
**Station configuration parameters:**
@@ -310,8 +333,10 @@ create a keystore entry for your WiFi password and configure the AP interface:
<pre class="cli"><code>admin@example:/> <b>configure</b>
admin@example:/config/> <b>edit keystore symmetric-key my-wifi-secret</b>
admin@example:/config/keystore/…/my-wifi-secret/> <b>set key-format wifi-preshared-key-format</b>
admin@example:/config/keystore/…/my-wifi-secret/> <b>set symmetric-key MySecurePassword123</b>
admin@example:/config/keystore/…/my-wifi-secret/> <b>set key-format passphrase-key-format</b>
admin@example:/config/keystore/…/my-wifi-secret/> <b>change cleartext-symmetric-key</b>
Passphrase: ************
Retype passphrase: ************
admin@example:/config/keystore/…/my-wifi-secret/> <b>end</b>
</code></pre>
@@ -371,14 +396,20 @@ admin@example:/config/hardware/component/radio0/wifi-radio/> <b>leave</b>
<pre class="cli"><code>admin@example:/> <b>configure</b>
admin@example:/config/> <b>edit keystore symmetric-key main-secret</b>
admin@example:/config/keystore/…/main-secret/> <b>set key-format wifi-preshared-key-format</b>
admin@example:/config/keystore/…/main-secret/> <b>set symmetric-key MyMainPassword</b>
admin@example:/config/keystore/…/main-secret/> <b>set key-format passphrase-key-format</b>
admin@example:/config/keystore/…/main-secret/> <b>change cleartext-symmetric-key</b>
Passphrase: ************
Retype passphrase: ************
admin@example:/config/> <b>edit keystore symmetric-key guest-secret</b>
admin@example:/config/keystore/…/guest-secret/> <b>set key-format wifi-preshared-key-format</b>
admin@example:/config/keystore/…/guest-secret/> <b>set symmetric-key GuestPassword123</b>
admin@example:/config/keystore/…/guest-secret/> <b>set key-format passphrase-key-format</b>
admin@example:/config/keystore/…/guest-secret/> <b>change cleartext-symmetric-key</b>
Passphrase: ************
Retype passphrase: ************
admin@example:/config/> <b>edit keystore symmetric-key iot-secret</b>
admin@example:/config/keystore/…/iot-secret/> <b>set key-format wifi-preshared-key-format</b>
admin@example:/config/keystore/…/iot-secret/> <b>set symmetric-key IoTDevices2025</b>
admin@example:/config/keystore/…/iot-secret/> <b>set key-format passphrase-key-format</b>
admin@example:/config/keystore/…/iot-secret/> <b>change cleartext-symmetric-key</b>
Passphrase: ************
Retype passphrase: ************
admin@example:/config/keystore/…/iot-secret/> <b>leave</b>
</code></pre>
@@ -466,8 +497,8 @@ If issues arise, try the following troubleshooting steps:
1. **Verify signal strength**: Check that the target network shows
"good" or "excellent" signal in scan results
2. **Check credentials**: Verify the preshared key in the keystore
matches the network password
2. **Check credentials**: Use `show keystore symmetric <name>` to verify
the passphrase matches the network password
3. **Review logs**: Check system logs with `show log` for Wi-Fi related
errors
4. **Regulatory compliance**: Ensure the country-code on the radio
+1
View File
@@ -53,6 +53,7 @@ nav:
- Access Control (NACM): nacm.md
- Hardware Info & Status: hardware.md
- Management: management.md
- Keystore: keystore.md
- Syslog Support: syslog.md
- Support Data: support.md
- Upgrade: upgrade.md
@@ -1,3 +1,3 @@
# Locally calculated
sha256 9d9d33b873917ca5d0bdcc47a36d2fd385971ab0c045d1472fcadf95ee5bcf5b LICENCE
sha256 6e098390be2a78a56cf94eecb9d28b11c1791dd2c364e59602bbd664b508fd57 klish-plugin-sysrepo-a4b1fae697b51614dc75989e8f4fc8d277689d16-git4.tar.gz
sha256 7bfdaef838ee8bd3995140c40144abe79ff1c6391c7b48445e6728cd4e7e56a8 klish-plugin-sysrepo-2f14503c7ea6eb24c8adaf0cf2cf7a511114b09e-git4.tar.gz
@@ -4,7 +4,7 @@
#
################################################################################
KLISH_PLUGIN_SYSREPO_VERSION = a4b1fae697b51614dc75989e8f4fc8d277689d16
KLISH_PLUGIN_SYSREPO_VERSION = 2f14503c7ea6eb24c8adaf0cf2cf7a511114b09e
KLISH_PLUGIN_SYSREPO_SITE = https://github.com/kernelkit/klish-plugin-sysrepo.git
#KLISH_PLUGIN_SYSREPO_VERSION = cdd3eb51a7f7ee0ed5bd925fa636061d3b1b85fb
#KLISH_PLUGIN_SYSREPO_SITE = https://src.libcode.org/pkun/klish-plugin-sysrepo.git
+1 -1
View File
@@ -1,3 +1,3 @@
# Locally calculated
sha256 9d9d33b873917ca5d0bdcc47a36d2fd385971ab0c045d1472fcadf95ee5bcf5b LICENCE
sha256 39a73fdaa7e41001e804e2bbdebdc885da79d540f0246777e8fd1c0dd9fc9475 klish-1c31f50ab775d467fa18f2e0a798006949536a2a-git4.tar.gz
sha256 cd9bc969350b8b30d9a7a31b0f19fb3218c602f04fe7e6a1d80682a75ed26d18 klish-3ae496c43d90354ffa94d364d7775c089f0e119a-git4.tar.gz
+1 -1
View File
@@ -4,7 +4,7 @@
#
################################################################################
KLISH_VERSION = 1c31f50ab775d467fa18f2e0a798006949536a2a
KLISH_VERSION = 3ae496c43d90354ffa94d364d7775c089f0e119a
KLISH_SITE = https://github.com/kernelkit/klish.git
#KLISH_VERSION = tags/3.0.0
#KLISH_SITE = https://src.libcode.org/pkun/klish.git
@@ -1,4 +1,4 @@
From 0da46e7eaa8896066c9ef549ee6ff95a713285f7 Mon Sep 17 00:00:00 2001
From 255ad0d9e977b007aa5099e52b5512e4f7a65e07 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/32] net: phy: marvell10g: Support firmware loading on
@@ -1,4 +1,4 @@
From 6ca7c41f05512829625df7f89c9cedfa8fce4954 Mon Sep 17 00:00:00 2001
From 928a14f7f60993bf67dce407fe213a19f7d653d6 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/32] net: phy: marvell10g: Fix power-up when strapped to
@@ -1,4 +1,4 @@
From 4ddca512b7f01fc8a3296b04252552e78d3aa084 Mon Sep 17 00:00:00 2001
From c3c6b12c6e6139dc887bf291ec3c805b7cd5388b 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/32] net: phy: marvell10g: Add LED support for 88X3310
@@ -1,4 +1,4 @@
From c064ebbc07a2fe41ea6d34f1c19804a26a37214b Mon Sep 17 00:00:00 2001
From d45fdac6168d2bc77bb95aa4b72488e3fa949abe 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/32] net: phy: marvell10g: Support LEDs tied to a single
@@ -1,4 +1,4 @@
From ac10b342937848aefe4c09ee054cb13a973564e5 Mon Sep 17 00:00:00 2001
From dfdde0010c609b8ea89be49d7bfc22750b12d92d 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/32] net: phy: Do not resume PHY when attaching
@@ -1,4 +1,4 @@
From 2ea2108430d61be4b2c607782c4d70d65606504c Mon Sep 17 00:00:00 2001
From a77410775141901ea1f400421395d0a9b558e205 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/32] net: bridge: avoid classifying unknown multicast as
@@ -1,4 +1,4 @@
From 0b500d7ce8fd3e9a64c1e61b64dc7e28f1441280 Mon Sep 17 00:00:00 2001
From ab2d786290e4760babe80f87aceccc05b43445b7 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/32] net: bridge: Ignore router ports when forwarding L2
@@ -1,4 +1,4 @@
From 11275834a80df2c7c9ebc3c185f7307c34177fe1 Mon Sep 17 00:00:00 2001
From 72a1cbf579a58d45b89338dd9d2144e9d5ec0ed4 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/32] net: bridge: drop delay for applying strict multicast
@@ -1,4 +1,4 @@
From 5c8691c3eeda59299e0dac6ef6df7ae2cbb247c0 Mon Sep 17 00:00:00 2001
From e9ec03fb91dcc5c55754562656d107a17194b8be 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/32] net: bridge: Differentiate MDB additions from
@@ -1,4 +1,4 @@
From 75a358d785acab5bc1fae8eaff98eb62bf61605a Mon Sep 17 00:00:00 2001
From fa6387518314725d640846514292153016efb53d 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/32] nvmem: layouts: onie-tlv: Let device probe even when
@@ -1,4 +1,4 @@
From 57d37c39d87b59ea3e808dcdd7ea762103dd3e3f Mon Sep 17 00:00:00 2001
From 4e6aaf7f4aaf1963a98bcc3a8e04169f901eb8fe 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/32] net: usb: r8152: add r8153b support for link/activity
@@ -1,4 +1,4 @@
From 9e770f7f46bd38c38b820c72bd4de0ff94b25f06 Mon Sep 17 00:00:00 2001
From 648987531072effe796ef3fac6b2f9259f082695 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/32] arm64: dts: mediatek: mt7986a: rename BPi R3 ports to
@@ -1,4 +1,4 @@
From 4935a1baf872efaa99a7fdbe4ba6d55634bec644 Mon Sep 17 00:00:00 2001
From 3e90bfcf47de8d09d8fe975315186beaf18b3c8f 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/32] drm/panel-simple: Add a timing for the Raspberry Pi 7"
@@ -1,4 +1,4 @@
From 4f1c58f67e64ef82b9bd3ec9b9c194d8ab8ce5c3 Mon Sep 17 00:00:00 2001
From 989adffc496130cf79e63e7486b6ce0dbdde38c3 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/32] input:touchscreen:edt-ft5x06: Add polled mode
@@ -1,4 +1,4 @@
From 0efc077ad790a598b18b4fab2b9dd614417e204b Mon Sep 17 00:00:00 2001
From 56b1b5c9cf340e3cc954180f18d2c455408eb8d4 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/32] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for
@@ -1,4 +1,4 @@
From d77bb7ae1ec02a191832ecff5a7e023980e2a02d Mon Sep 17 00:00:00 2001
From fd256efa7865ff09830bd18f326075242b773c98 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/32] net: dsa: mv88e6xxx: Improve indirect register access
@@ -1,4 +1,4 @@
From b876e4718bc9f28cf898bddb8fe5f127b2b582b6 Mon Sep 17 00:00:00 2001
From b6918d761fcd859f67fdc14429e269dda657e50d 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/32] net: dsa: mv88e6xxx: Honor ports being managed via
@@ -1,4 +1,4 @@
From 2d6aab8ab0ba0465a420cfd34f29dc80765d07e6 Mon Sep 17 00:00:00 2001
From 98176be49294c8d1a2298601e8e3678834ff9a0d 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/32] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user
@@ -1,4 +1,4 @@
From 29f5994c627c326f062a7e4b1c6ec8b00a30d53a Mon Sep 17 00:00:00 2001
From c758a83ae13bd4f4068d73d073033b3422ee616a 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/32] net: dsa: tag_dsa: Use tag priority as initial
@@ -1,4 +1,4 @@
From bc037d0641c9966c676f61080b400774b30bf26f Mon Sep 17 00:00:00 2001
From 4a6728ce6cd21d250ffa448fcea329c43a2fd8a0 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/32] net: dsa: Support MDB memberships whose L2 addresses
@@ -1,4 +1,4 @@
From e51540686e673fff781944b04397435c61e5fc81 Mon Sep 17 00:00:00 2001
From 3fc20bcfab7493df99038b9f0dbd9c08af2b80fd 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/32] net: dsa: Support EtherType based priority overrides
@@ -1,4 +1,4 @@
From 550559c5275610b58322692c121194b15cf1f9e6 Mon Sep 17 00:00:00 2001
From 116f78aa422f2396f3569fbd7b0f9376b4426166 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/32] net: dsa: mv88e6xxx: Support EtherType based priority
@@ -1,4 +1,4 @@
From 4411eea4232e445db1d9b35f62cc1168874617e4 Mon Sep 17 00:00:00 2001
From a3c810edd1110c3db1c4ea61be18469da3327b1e 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/32] net: dsa: mv88e6xxx: Add mqprio qdisc support
@@ -1,4 +1,4 @@
From 1acad33cba1d7a199ce6386d58b589307ff3ee8c Mon Sep 17 00:00:00 2001
From f73be899c92dbbb0749e126b52bafe719ad9bcfa 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/32] net: dsa: mv88e6xxx: Use VLAN prio over IP when both
@@ -1,4 +1,4 @@
From 463e8eb7bc4673c77f56628252c495ee840cf35d Mon Sep 17 00:00:00 2001
From 183267ad1ee42afbd6960550f6b75b60c93b3de6 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/32] [FIX] net: dsa: mv88e6xxx: Trap locally terminated
@@ -1,4 +1,4 @@
From 75795ed5e3d522c4185005a812679c2bd67010cb Mon Sep 17 00:00:00 2001
From fc64b3536d3e9b9db0d38ee812bf5f2d826e8a20 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/32] net: dsa: mv88e6xxx: collapse disabled state into
@@ -1,4 +1,4 @@
From 6226d56b75e1087881a150f511ab78457edf9a52 Mon Sep 17 00:00:00 2001
From b5f738be2b2d060722e40110c3f34ee495789c70 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/32] net: dsa: mv88e6xxx: Only activate LAG offloading when
@@ -1,4 +1,4 @@
From a3fd03baa707011d9013dc1110064ae9f7674e20 Mon Sep 17 00:00:00 2001
From f5635e0189dc4c6cf34adec22198231f3318ef5d 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/32] net: dsa: mv88e6xxx: Add LED support for 6393X
@@ -1,4 +1,4 @@
From 9bd08559ecb3dad89f67ab40c375d375d57ea019 Mon Sep 17 00:00:00 2001
From 1f73a56bf784cc6d48d19069a5ee5103bafa5355 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/32] wifi: brcmfmac: support deletion and recreation of
@@ -1,4 +1,4 @@
From 719c24648159177aacc03a8f5e4b6da63e6e0d28 Mon Sep 17 00:00:00 2001
From 8e17a213b71db25b4748cf73c7413b6b82c6fbf9 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/32] wifi: brcmfmac: check connection state before querying
@@ -1,4 +1,4 @@
From a32da01444346e8e1cd70bfc0dccc97d0b12bf56 Mon Sep 17 00:00:00 2001
From b95a87f2e34b4706b7beaa250020fe366af64520 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/32] wifi: brcmfmac: suppress log spam for
@@ -1,4 +1,4 @@
From 743891bbd05b33ee0ab139ab392472df23acbba7 Mon Sep 17 00:00:00 2001
From 2e9805533f12711a22fbca9551f1da5360ff4537 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/32] wifi: brcmfmac: reduce log noise during AP to station
+1 -1
View File
@@ -1,2 +1,2 @@
# Calculated with utils/kernel-refresh.sh
sha256 b726a4d15cf9ae06219b56d87820776e34d89fbc137e55fb54a9b9c3015b8f1e linux-6.18.7.tar.xz
sha256 37f0c5d5c242c1d604e87d48f08795e861a5a85f725b4ca11d0a538f12ff8cff linux-6.18.8.tar.xz
+21 -1
View File
@@ -190,7 +190,7 @@ def interface(args: List[str]) -> None:
elif len(args) == 1:
iface = args[0]
if is_valid_interface_name(iface):
cli_pretty(data, f"show-interfaces", "-n", iface)
cli_pretty(data, "show-interfaces", "-n", iface)
else:
print(f"Invalid interface name: {iface}")
else:
@@ -659,6 +659,25 @@ def nacm(args: List[str]) -> None:
print(f"Unknown NACM subcommand: {subcommand}")
def keystore(args: List[str]) -> None:
data = get_json("/ietf-keystore:keystore", "running", quiet=True)
if not data:
user = os.environ.get('USER', 'unknown')
print(f'No keystore data available (check NACM permissions for "{user}").')
return
if RAW_OUTPUT:
print(json.dumps(data, indent=2))
return
if len(args) == 0 or not args[0]:
cli_pretty(data, "show-keystore")
elif len(args) >= 2 and args[0] in ("symmetric", "asymmetric"):
cli_pretty(data, "show-keystore", "-t", args[0], "-n", args[1])
else:
print("Usage: show keystore [symmetric <name> | asymmetric <name>]")
def execute_command(command: str, args: List[str]):
command_mapping = {
'bfd': bfd,
@@ -667,6 +686,7 @@ def execute_command(command: str, args: List[str]):
'dhcp': dhcp,
'hardware': hardware,
'interface': interface,
'keystore': keystore,
'lldp': lldp,
'nacm': nacm,
'ntp': ntp,
@@ -1,5 +1,13 @@
#!/bin/sh
# Rename cleartext-key to symmetric-key
# Migrate symmetric keys from v1.6 (v25.11) to IETF standard format.
#
# v1.6 had WiFi keys stored as:
# - infix-keystore:cleartext-key (type string, plaintext)
# - infix-keystore:key-format wifi-preshared-key-format
#
# v1.7 uses the IETF standard leaf and updated format names:
# - cleartext-symmetric-key (type binary, base64-encoded)
# - key-format passphrase-key-format
file=$1
temp=${file}.tmp
@@ -7,12 +15,17 @@ temp=${file}.tmp
jq '
if .["ietf-keystore:keystore"]?."symmetric-keys"?."symmetric-key" then
.["ietf-keystore:keystore"]."symmetric-keys"."symmetric-key" |= map(
if ."infix-keystore:key-format" then
del(."infix-keystore:key-format") |
. + { "key-format": "infix-crypto-types:passphrase-key-format" }
else
.
end |
if ."infix-keystore:cleartext-key" then
# Rename cleartext-key to symmetric-key
."infix-keystore:cleartext-key" as $key_value |
del(."infix-keystore:cleartext-key") | . + {
"infix-keystore:symmetric-key": $key_value
}
."infix-keystore:cleartext-key" as $val |
del(."infix-keystore:cleartext-key") |
. + { "cleartext-symmetric-key": ($val | @base64) }
else
.
end
+13 -5
View File
@@ -12,6 +12,7 @@
#include "core.h"
#include "interfaces.h"
#include "dagger.h"
#include "base64.h"
#define XPATH_BASE_ "/ietf-hardware:hardware"
#define HOSTAPD_CONF "/etc/hostapd-%s.conf"
@@ -235,8 +236,9 @@ static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name,
/* Helper: Write SSID and security configuration (shared between primary and BSS) */
static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd_node *config, bool is_bss)
{
const char *ssid, *hidden, *security_mode, *secret_name, *secret;
const char *ssid, *hidden, *security_mode, *secret_name;
struct lyd_node *wifi, *ap, *security, *secret_node;
unsigned char *secret = NULL;
const char *ifname;
char bssid[18];
@@ -282,15 +284,19 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd
security_mode = "open";
/* Get secret from keystore if needed */
secret = NULL;
if (strcmp(security_mode, "open") != 0) {
secret_name = lydx_get_cattr(security, "secret");
if (secret_name) {
const char *b64;
secret_node = lydx_get_xpathf(config,
"/keystore/symmetric-keys/symmetric-key[name='%s']/symmetric-key",
"/keystore/symmetric-keys/symmetric-key[name='%s']/cleartext-symmetric-key",
secret_name);
if (secret_node)
secret = lyd_get_value(secret_node);
if (secret_node) {
b64 = lyd_get_value(secret_node);
if (b64)
secret = base64_decode((const unsigned char *)b64, strlen(b64), NULL);
}
}
}
@@ -329,6 +335,8 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd
/* ieee80211w=1: MFP capable but optional, for WPA2 client compatibility */
fprintf(hostapd, "ieee80211w=1\n");
}
free(secret);
}
/* Helper: Write radio-specific configuration */
+76 -8
View File
@@ -7,14 +7,76 @@
* configuration (hostapd) is handled by hardware.c.
*/
#include <ctype.h>
#include <srx/lyx.h>
#include <srx/srx_val.h>
#include "interfaces.h"
#include "base64.h"
#define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf"
int wifi_validate_secret(sr_session_ctx_t *session, struct lyd_node *cif)
{
struct lyd_node *wifi, *station, *security, *secret_node;
const char *ifname, *secret_name, *security_mode, *b64;
unsigned char *decoded;
size_t len;
ifname = lydx_get_cattr(cif, "name");
wifi = lydx_get_child(cif, "wifi");
if (!wifi)
return SR_ERR_OK;
station = lydx_get_child(wifi, "station");
if (!station)
return SR_ERR_OK;
security = lydx_get_child(station, "security");
security_mode = lydx_get_cattr(security, "mode");
secret_name = lydx_get_cattr(security, "secret");
if (!secret_name || !strcmp(security_mode, "disabled"))
return SR_ERR_OK;
secret_node = lydx_get_xpathf(cif,
"../../keystore/symmetric-keys/symmetric-key[name='%s']",
secret_name);
b64 = lydx_get_cattr(secret_node, "cleartext-symmetric-key");
if (!b64 || !*b64)
return SR_ERR_OK;
decoded = base64_decode((const unsigned char *)b64, strlen(b64), &len);
if (!decoded)
return SR_ERR_OK;
if (len < 8 || len > 63) {
if (session)
sr_session_set_error_message(session,
"%s: WiFi passphrase must be 8-63 characters, got %zu",
ifname, len);
free(decoded);
return SR_ERR_VALIDATION_FAILED;
}
for (size_t i = 0; i < len; i++) {
if (!isprint((unsigned char)decoded[i])) {
if (session)
sr_session_set_error_message(session,
"%s: WiFi passphrase contains non-printable "
"character at position %zu",
ifname, i + 1);
free(decoded);
return SR_ERR_VALIDATION_FAILED;
}
}
free(decoded);
return SR_ERR_OK;
}
wifi_mode_t wifi_get_mode(struct lyd_node *iface)
{
struct lyd_node *ap, *wifi;
@@ -59,8 +121,9 @@ int wifi_mode_changed(struct lyd_node *wifi)
*/
int wifi_gen_station(struct lyd_node *cif)
{
const char *ifname, *ssid, *secret_name, *secret, *security_mode, *radio;
const char *ifname, *ssid, *secret_name, *security_mode, *radio;
struct lyd_node *security, *secret_node, *radio_node, *station, *wifi;
unsigned char *secret = NULL;
FILE *wpa_supplicant = NULL;
char *security_str = NULL;
const char *country;
@@ -91,12 +154,14 @@ int wifi_gen_station(struct lyd_node *cif)
country = lydx_get_cattr(radio_node, "country-code");
if (secret_name && strcmp(security_mode, "disabled") != 0) {
const char *b64;
secret_node = lydx_get_xpathf(cif,
"../../keystore/symmetric-keys/symmetric-key[name='%s']",
secret_name);
secret = lydx_get_cattr(secret_node, "symmetric-key");
} else {
secret = NULL;
b64 = lydx_get_cattr(secret_node, "cleartext-symmetric-key");
if (b64)
secret = base64_decode((const unsigned char *)b64, strlen(b64), NULL);
}
oldmask = umask(0077);
@@ -121,11 +186,13 @@ int wifi_gen_station(struct lyd_node *cif)
/* If SSID is present, create network block. Otherwise, scan-only mode */
if (ssid) {
/* Station mode with network configured */
if (!strcmp(security_mode, "disabled")) {
if (!strcmp(security_mode, "disabled"))
asprintf(&security_str, "key_mgmt=NONE");
} else if (secret) {
asprintf(&security_str, "key_mgmt=SAE WPA-PSK\npsk=\"%s\"", secret);
}
else if (secret)
asprintf(&security_str,
"key_mgmt=SAE WPA-PSK\n"
" psk=\"%s\"", secret);
fprintf(wpa_supplicant,
"network={\n"
" bgscan=\"simple: 30:-45:300\"\n"
@@ -152,6 +219,7 @@ int wifi_gen_station(struct lyd_node *cif)
}
out:
free(secret);
if (wpa_supplicant)
fclose(wpa_supplicant);
umask(oldmask);
+72
View File
@@ -2,9 +2,81 @@
#include <srx/lyx.h>
#include "interfaces.h"
#include "base64.h"
#define WIREGUARD_CONFIG "/run/wireguard-%s.conf"
static int wireguard_validate_psk(sr_session_ctx_t *session, struct lyd_node *cif,
const char *ifname, const char *psk_ref)
{
struct lyd_node *psk_node;
const char *psk_data;
unsigned char *decoded;
size_t len;
psk_node = lydx_get_xpathf(cif,
"../../keystore/symmetric-keys/symmetric-key[name='%s']",
psk_ref);
if (!psk_node)
return SR_ERR_OK;
psk_data = lydx_get_cattr(psk_node, "cleartext-symmetric-key");
if (!psk_data || !*psk_data)
return SR_ERR_OK;
decoded = base64_decode((const unsigned char *)psk_data, strlen(psk_data), &len);
if (!decoded)
return SR_ERR_OK;
if (len != 32) {
if (session)
sr_session_set_error_message(session,
"%s: WireGuard preshared key '%s' must be "
"exactly 32 bytes, got %zu",
ifname, psk_ref, len);
free(decoded);
return SR_ERR_VALIDATION_FAILED;
}
free(decoded);
return SR_ERR_OK;
}
int wireguard_validate_peers(sr_session_ctx_t *session, struct lyd_node *cif)
{
const char *ifname = lydx_get_cattr(cif, "name");
struct lyd_node *wg, *bag_peer, *peer;
wg = lydx_get_child(cif, "wireguard");
if (!wg)
return SR_ERR_OK;
LYX_LIST_FOR_EACH(lyd_child(wg), bag_peer, "peers") {
const char *psk_ref;
int rc;
/* Validate bag-level PSK */
psk_ref = lydx_get_cattr(bag_peer, "preshared-key");
if (psk_ref) {
rc = wireguard_validate_psk(session, cif, ifname, psk_ref);
if (rc)
return rc;
}
/* Validate per-peer PSK overrides */
LYX_LIST_FOR_EACH(lyd_child(bag_peer), peer, "peer") {
psk_ref = lydx_get_cattr(peer, "preshared-key");
if (psk_ref) {
rc = wireguard_validate_psk(session, cif, ifname, psk_ref);
if (rc)
return rc;
}
}
}
return SR_ERR_OK;
}
/* Helper to get a peer setting with override logic:
* 1. Check peer-specific override
* 2. Fall back to key-bag level default
+44 -40
View File
@@ -335,9 +335,13 @@ static int netdag_gen_sysctl(struct dagger *net,
err = err ? : netdag_gen_sysctl_setting(net, ifname, &sysctl, 1, "0", node,
"net.ipv4.conf.%s.forwarding", ifname);
/*
* Use force_forwarding for IPv6 (available since Linux 6.17) which provides
* true per-interface control, unlike the legacy forwarding sysctl.
*/
node = lydx_get_descendant(lyd_child(dif), "ipv6", "forwarding", NULL);
err = err ? : netdag_gen_sysctl_setting(net, ifname, &sysctl, 1, "0", node,
"net.ipv6.conf.%s.forwarding", ifname);
"net.ipv6.conf.%s.force_forwarding", ifname);
if (!strcmp(ifname, "lo")) /* skip for now */
goto skip_mtu;
@@ -353,39 +357,6 @@ skip_mtu:
return err;
}
/*
* The global IPv6 forwarding lever is off by default, enabled when any
* interface has IPv6 forwarding enabled.
*/
static int netdag_ipv6_forwarding(struct lyd_node *cifs, struct dagger *net)
{
struct lyd_node *cif;
FILE *sysctl = NULL;
int ena = 0;
LYX_LIST_FOR_EACH(cifs, cif, "interface")
ena |= lydx_is_enabled(lydx_get_child(cif, "ipv6"), "forwarding");
if (ena)
sysctl = dagger_fopen_next(net, "init", "@post", NETDAG_INIT_POST, "ipv6.sysctl");
else
sysctl = dagger_fopen_current(net, "exit", "@pre", NETDAG_EXIT_PRE, "ipv6.sysctl");
if (!sysctl) {
/*
* Cannot create exit code in gen: -1. Safe to ignore
* since ipv6 forwarding is disabled by default.
*/
if (dagger_is_bootstrap(net) && !ena)
return 0;
return -EIO;
}
fprintf(sysctl, "net.ipv6.conf.all.forwarding = %d\n", ena);
fclose(sysctl);
return 0;
}
static int dummy_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip)
{
const char *ifname = lydx_get_cattr(cif, "name");
@@ -420,9 +391,11 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net,
case IFT_VXLAN:
return vxlan_gen(NULL, cif, ip);
case IFT_WIFI:
return wifi_add_iface(cif, net);
return wifi_validate_secret(session, cif)
? : wifi_add_iface(cif, net);
case IFT_WIREGUARD:
return wireguard_gen(NULL, cif, ip, net);
return wireguard_validate_peers(session, cif)
? : wireguard_gen(NULL, cif, ip, net);
case IFT_ETH:
return netdag_gen_ethtool(net, cif, dif);
case IFT_LO:
@@ -453,7 +426,8 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net,
return netdag_gen_ethtool(net, cif, dif);
case IFT_WIFI:
if (wifi_get_mode(cif) == wifi_station)
return wifi_gen_station(cif);
return wifi_validate_secret(session, cif)
? : wifi_gen_station(cif);
return 0;
case IFT_DUMMY:
case IFT_GRE:
@@ -782,9 +756,6 @@ static sr_error_t ifchange_post(sr_session_ctx_t *session, struct dagger *net,
{
int err = 0;
/* Figure out value of global IPv6 forwarding flag. Issue #785 */
err |= netdag_ipv6_forwarding(cifs, net);
/* For each configured bridge, the corresponding multicast
* querier settings depend on both the bridge config and on
* the presence of matching VLAN uppers. Since these can be
@@ -851,6 +822,39 @@ err_out:
return err;
}
int interfaces_validate_keys(sr_session_ctx_t *session, struct lyd_node *config)
{
struct lyd_node *ifaces, *iface;
int rc;
ifaces = lydx_get_descendant(config, "interfaces", "interface", NULL);
LYX_LIST_FOR_EACH(ifaces, iface, "interface") {
const char *ifname = lydx_get_cattr(iface, "name");
switch (iftype_from_iface(iface)) {
case IFT_WIFI:
rc = wifi_validate_secret(session, iface);
break;
case IFT_WIREGUARD:
rc = wireguard_validate_peers(session, iface);
break;
default:
rc = SR_ERR_OK;
break;
}
if (!rc)
continue;
if (session)
return rc;
ERROR("%s: key fails validation, check keystore", ifname);
}
return SR_ERR_OK;
}
int interfaces_get_all_l3(const struct lyd_node *tree, char ***ifaces)
{
struct lyd_node *interfaces, *cif;
+3
View File
@@ -105,6 +105,7 @@ const char *get_chassis_addr(void);
int interface_get_phys_addr(struct lyd_node *cif, char *mac);
int link_gen_address(struct lyd_node *cif, FILE *ip);
int interfaces_get_all_l3(const struct lyd_node *tree, char ***ifaces);
int interfaces_validate_keys(sr_session_ctx_t *session, struct lyd_node *config);
/* ip.c */
int netdag_gen_ipv6_autoconf(struct dagger *net, struct lyd_node *cif,
@@ -130,6 +131,7 @@ typedef enum wifi_mode_t {
wifi_unknown
} wifi_mode_t;
int wifi_validate_secret(sr_session_ctx_t *session, struct lyd_node *cif);
int wifi_add_iface(struct lyd_node *cif, struct dagger *net);
int wifi_del_iface(struct lyd_node *dif, struct dagger *net);
int wifi_mode_changed(struct lyd_node *wifi);
@@ -162,6 +164,7 @@ int ifchange_cand_infer_dhcp(sr_session_ctx_t *session, const char *path);
int vxlan_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
/* infix-if-wireguard */
int wireguard_validate_peers(sr_session_ctx_t *session, struct lyd_node *cif);
int wireguard_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, struct dagger *net);
#endif /* CONFD_INTERFACES_H_ */
+10 -2
View File
@@ -8,8 +8,10 @@
#include "base64.h"
#include "core.h"
#include "interfaces.h"
#define XPATH_KEYSTORE_ "/ietf-keystore:keystore/asymmetric-keys"
#define XPATH_KEYSTORE_ASYM "/ietf-keystore:keystore/asymmetric-keys"
#define XPATH_KEYSTORE_SYM "/ietf-keystore:keystore/symmetric-keys"
#define SSH_PRIVATE_KEY "/tmp/ssh.key"
#define SSH_PUBLIC_KEY "/tmp/ssh.pub"
@@ -166,7 +168,8 @@ int keystore_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
struct lyd_node *changes, *change;
int rc = SR_ERR_OK;
if (diff && !lydx_find_xpathf(diff, XPATH_KEYSTORE_))
if (diff && !lydx_find_xpathf(diff, XPATH_KEYSTORE_ASYM)
&& !lydx_find_xpathf(diff, XPATH_KEYSTORE_SYM))
return SR_ERR_OK;
switch (event) {
@@ -174,6 +177,9 @@ int keystore_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
rc = keystore_update(session, config, diff);
break;
case SR_EV_CHANGE:
if (diff && lydx_find_xpathf(diff, XPATH_KEYSTORE_SYM))
rc = interfaces_validate_keys(session, config);
break;
case SR_EV_ENABLED:
break;
case SR_EV_ABORT:
@@ -186,6 +192,8 @@ int keystore_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
if (rename(SSH_HOSTKEYS_NEXT, SSH_HOSTKEYS))
ERRNO("Failed switching to new %s", SSH_HOSTKEYS);
}
if (diff && lydx_find_xpathf(diff, XPATH_KEYSTORE_SYM))
interfaces_validate_keys(NULL, config);
return SR_ERR_OK;
default:
return SR_ERR_OK;
+21 -16
View File
@@ -15,32 +15,46 @@ module infix-crypto-types {
revision 2025-02-04 {
description "Initial";
}
identity private-key-format {
base ct:private-key-format;
description
"Base key-format identity for private keys.";
"Base for Infix private key format extensions.";
}
identity public-key-format {
base ct:public-key-format;
description
"Base key-format identity for public keys.";
"Base for Infix public key format extensions.";
}
identity rsa-private-key-format {
base private-key-format;
base ct:rsa-private-key-format;
description
"RSA private key in PKCS#1 format (RFC 8017).
Used for SSH host keys.";
}
identity ssh-public-key-format {
base public-key-format;
base ct:ssh-public-key-format;
description
"SSH public key format (RFC 4253, Section 6.6).
Used for SSH host keys.";
}
identity symmetric-key-format {
base ct:symmetric-key-format;
description
"Base for symmetric key format";
"Base for Infix symmetric key format extensions.";
}
identity wifi-preshared-key-format {
identity passphrase-key-format {
base ct:symmetric-key-format;
base symmetric-key-format;
description
"WiFi secret key";
"Human-readable passphrase, e.g., WiFi WPA2/WPA3 passwords.";
}
identity x25519-public-key-format {
@@ -48,7 +62,7 @@ module infix-crypto-types {
base ct:public-key-format;
description
"X25519 (Curve25519) public key format for Diffie-Hellman key exchange.
This is the format used by WireGuard.";
This format is used by network protocols such as WireGuard VPN.";
}
identity x25519-private-key-format {
@@ -56,15 +70,6 @@ module infix-crypto-types {
base ct:private-key-format;
description
"X25519 (Curve25519) private key format for Diffie-Hellman key exchange.
This is the format used by WireGuard.";
}
identity wireguard-symmetric-key-format {
base ct:symmetric-key-format;
base symmetric-key-format;
description
"WireGuard pre-shared key format.
32-byte base64-encoded key used as an optional additional layer
of symmetric encryption for post-quantum resistance.";
This format is used by network protocols such as WireGuard VPN.";
}
}
+5 -2
View File
@@ -10,6 +10,9 @@ submodule infix-if-wireguard {
import ietf-inet-types {
prefix inet;
}
import ietf-crypto-types {
prefix ct;
}
import infix-crypto-types {
prefix ixct;
}
@@ -75,8 +78,8 @@ submodule infix-if-wireguard {
This provides post-quantum resistance as an attacker would need
to break both the Curve25519 key exchange and this symmetric key.";
must "derived-from-or-self(deref(.)/../infix-ks:key-format, 'ixct:wireguard-symmetric-key-format')" {
error-message "Preshared key must be in wireguard-symmetric-key-format";
must "derived-from-or-self(deref(.)/../ks:key-format, 'ct:octet-string-key-format')" {
error-message "Preshared key must be in octet-string-key-format";
}
}
+1 -59
View File
@@ -5,15 +5,12 @@ module infix-keystore {
import ietf-keystore {
prefix ks;
}
import ietf-crypto-types {
prefix ct;
}
import infix-crypto-types {
prefix infix-ct;
}
revision 2025-12-17 {
description "Add WireGuard support";
description "Add WireGuard support, see infix-crypto-types.yang";
}
revision 2025-12-10 {
description "Adapt to changes in final version of ietf-keystore";
@@ -24,59 +21,4 @@ module infix-keystore {
revision 2025-02-04 {
description "Initial";
}
deviation "/ks:keystore/ks:asymmetric-keys/ks:asymmetric-key/ks:public-key-format" {
deviate replace {
type identityref {
base infix-ct:public-key-format;
}
}
}
deviation "/ks:keystore/ks:asymmetric-keys/ks:asymmetric-key/ks:private-key-format" {
deviate replace {
type identityref {
base infix-ct:private-key-format;
}
}
}
deviation "/ks:keystore/ks:symmetric-keys/ks:symmetric-key/ks:key-format" {
deviate not-supported;
}
augment "/ks:keystore/ks:symmetric-keys/ks:symmetric-key" {
leaf key-format {
type identityref {
base infix-ct:symmetric-key-format;
}
description
"Identifies the symmetric key's format
Valid symmetric key formats are:
wifi-preshared-key-format - WiFi preshared key
wireguard-symmetric-key-format - WireGuard preshared key";
}
}
deviation "/ks:keystore/ks:symmetric-keys/ks:symmetric-key/ks:key-type/ks:cleartext-symmetric-key" {
deviate not-supported;
}
augment "/ks:keystore/ks:symmetric-keys/ks:symmetric-key/ks:key-type" {
case cleartext-symmetric-key {
leaf symmetric-key {
type string;
must "../infix-ks:key-format != 'infix-ct:wifi-preshared-key-format' or " +
"(string-length(.) >= 8 and string-length(.) <= 63)" {
error-message "WiFi pre-shared key must be 8-63 characters long";
}
must "../infix-ks:key-format != 'infix-ct:wireguard-symmetric-key-format' or " +
"string-length(.) = 44" {
error-message "WireGuard pre-shared key must be 44 characters (32-byte base64-encoded)";
}
description
"Cleartext symmetric key value.
Format depends on key-format:
- WiFi pre-shared key: 8-63 printable ASCII characters
- WireGuard pre-shared key: 32-byte base64-encoded key (44 chars with padding)";
}
}
}
}
+16
View File
@@ -413,6 +413,20 @@ int infix_groups(kcontext_t *ctx)
"| jq -r '.\"ietf-netconf-acm:nacm\".groups.group[].name'");
}
int infix_sym_keys(kcontext_t *ctx)
{
(void)ctx;
return shellf("copy running -x /ietf-keystore:keystore/symmetric-keys "
"| jq -r '.\"ietf-keystore:keystore\".\"symmetric-keys\".\"symmetric-key\"[].name'");
}
int infix_asym_keys(kcontext_t *ctx)
{
(void)ctx;
return shellf("copy running -x /ietf-keystore:keystore/asymmetric-keys "
"| jq -r '.\"ietf-keystore:keystore\".\"asymmetric-keys\".\"asymmetric-key\"[].name'");
}
int kplugin_infix_fini(kcontext_t *ctx)
{
(void)ctx;
@@ -432,6 +446,8 @@ int kplugin_infix_init(kcontext_t *ctx)
kplugin_add_syms(plugin, ksym_new("ifaces", infix_ifaces));
kplugin_add_syms(plugin, ksym_new("users", infix_users));
kplugin_add_syms(plugin, ksym_new("groups", infix_groups));
kplugin_add_syms(plugin, ksym_new("sym_keys", infix_sym_keys));
kplugin_add_syms(plugin, ksym_new("asym_keys", infix_asym_keys));
kplugin_add_syms(plugin, ksym_new("firewall_zones", infix_firewall_zones));
kplugin_add_syms(plugin, ksym_new("firewall_policies", infix_firewall_policies));
kplugin_add_syms(plugin, ksym_new("firewall_services", infix_firewall_services));
+43
View File
@@ -155,6 +155,20 @@
<ACTION sym="STRING"/>
</PTYPE>
<PTYPE name="SYM_KEYS">
<COMPL>
<ACTION sym="sym_keys@infix"/>
</COMPL>
<ACTION sym="STRING"/>
</PTYPE>
<PTYPE name="ASYM_KEYS">
<COMPL>
<ACTION sym="asym_keys@infix"/>
</COMPL>
<ACTION sym="STRING"/>
</PTYPE>
<VIEW name="main">
<HOTKEY key="^D" cmd="exit"/>
@@ -259,6 +273,21 @@
</COMMAND>
</COMMAND>
<COMMAND name="wireguard" help="WireGuard VPN key tools" mode="switch">
<COMMAND name="genkey" help="Generate a WireGuard private/public key pair">
<ACTION sym="script">
priv=$(wg genkey)
pub=$(printf '%s' "$priv" | wg pubkey)
echo "Private: $priv"
echo "Public: $pub"
</ACTION>
</COMMAND>
<COMMAND name="genpsk" help="Generate a WireGuard pre-shared key">
<ACTION sym="script">wg genpsk</ACTION>
</COMMAND>
</COMMAND>
<COMMAND name="set" help="Set operations, e.g., current date/time" mode="switch">
<COMMAND name="datetime" help="Set current date and time, ISO-8601 format">
<PARAM name="current-datetime" ptype="/STRING" help="yyyy-mm-ddThh:mm:ss(Z|+/-hh:mm)"/>
@@ -338,6 +367,20 @@
</ACTION>
</COMMAND>
<COMMAND name="keystore" help="Show keystore keys">
<SWITCH name="subcommands" min="0">
<COMMAND name="symmetric" help="Show details for a symmetric key">
<PARAM name="name" ptype="/SYM_KEYS" help="Key name"/>
<ACTION sym="script" in="tty" out="tty" interrupt="true">show keystore symmetric $KLISH_PARAM_name</ACTION>
</COMMAND>
<COMMAND name="asymmetric" help="Show details for an asymmetric key">
<PARAM name="name" ptype="/ASYM_KEYS" help="Key name"/>
<ACTION sym="script" in="tty" out="tty" interrupt="true">show keystore asymmetric $KLISH_PARAM_name</ACTION>
</COMMAND>
</SWITCH>
<ACTION sym="script" in="tty" out="tty" interrupt="true">show keystore</ACTION>
</COMMAND>
<COMMAND name="nacm" help="Show users and NACM status and groups">
<SWITCH name="subcommands" min="0">
<COMMAND name="group" help="Show details for a specific NACM group">
+133
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
import base64
import json
import argparse
import sys
@@ -1030,6 +1031,7 @@ class DhcpServer:
class Iface:
# Class variable to hold routing-enabled interfaces for the current display session
_routing_ifaces = set()
_keystore = {}
def __init__(self, data):
self.data = data
@@ -3381,6 +3383,131 @@ def show_nacm_user(json):
print("For detailed rules, use: show nacm group <name>")
def _keystore_format_name(key_format):
"""Simplify key-format to a short display name."""
fmt = key_format.split(':')[-1] if ':' in key_format else key_format
return fmt.replace('-key-format', '')
def _keystore_decode_symmetric(key):
"""Decode a symmetric key value for display."""
key_format = key.get('key-format', '')
fmt = _keystore_format_name(key_format)
b64val = key.get('cleartext-symmetric-key', '')
if fmt == 'passphrase' and b64val:
try:
return base64.b64decode(b64val).decode('utf-8')
except Exception:
return b64val
return b64val if b64val else '-'
def _keystore_find_key(keystore, kind, name):
"""Find a key by type and name in the keystore."""
if kind == 'symmetric':
keys = keystore.get('symmetric-keys', {}).get('symmetric-key', [])
else:
keys = keystore.get('asymmetric-keys', {}).get('asymmetric-key', [])
for key in keys:
if key.get('name') == name:
return key
return None
def _keystore_asym_type(key):
"""Derive asymmetric key algorithm from key format fields."""
for field in ('private-key-format', 'public-key-format'):
fmt = key.get(field, '')
name = fmt.split(':')[-1] if ':' in fmt else fmt
name = name.replace('-private-key-format', '').replace('-public-key-format', '')
if name:
return name
return ''
def show_keystore_detail(keystore, kind, name):
"""Display detailed information about a specific key."""
key = _keystore_find_key(keystore, kind, name)
if not key:
print(f'{kind.capitalize()} key "{name}" not found.')
return
print(f"{'name':<{20}}: {name}")
if kind == 'symmetric':
fmt = _keystore_format_name(key.get('key-format', ''))
value = _keystore_decode_symmetric(key)
print(f"{'format':<{20}}: {fmt}")
print(f"{'value':<{20}}: {value}")
else:
ktype = _keystore_asym_type(key)
if ktype:
print(f"{'algorithm':<{20}}: {ktype}")
pub_fmt = _keystore_format_name(key.get('public-key-format', ''))
if pub_fmt:
print(f"{'public key format':<{20}}: {pub_fmt}")
pub_key = key.get('public-key', '')
if pub_key:
print(f"{'public key':<{20}}: {pub_key}")
def show_keystore(json, kind=None, name=None):
"""Display keystore keys overview or detail for a specific key."""
keystore = json.get("ietf-keystore:keystore", {})
if not keystore:
print("Keystore is empty.")
return
if kind and name:
show_keystore_detail(keystore, kind, name)
return
TABLE_WIDTH = 72
# Symmetric keys
sym_keys_data = keystore.get('symmetric-keys', {}).get('symmetric-key', [])
if sym_keys_data:
Decore.title("Symmetric Keys", TABLE_WIDTH)
table = SimpleTable([
Column('NAME', flexible=True),
Column('FORMAT'),
Column('VALUE', flexible=True)
], min_width=TABLE_WIDTH)
for key in sym_keys_data:
name = key.get('name', '')
fmt = _keystore_format_name(key.get('key-format', ''))
value = _keystore_decode_symmetric(key)
table.row(name, fmt, value)
table.print()
# Asymmetric keys
asym_keys_data = keystore.get('asymmetric-keys', {}).get('asymmetric-key', [])
if asym_keys_data:
Decore.title("Asymmetric Keys", TABLE_WIDTH)
table = SimpleTable([
Column('NAME', flexible=True),
Column('TYPE'),
Column('PUBLIC KEY', flexible=True)
], min_width=TABLE_WIDTH)
for key in asym_keys_data:
name = key.get('name', '')
ktype = _keystore_asym_type(key)
pub_key = key.get('public-key', '')
if len(pub_key) > 40:
pub_key = pub_key[:37] + '...'
table.row(name, ktype, pub_key)
table.print()
if not sym_keys_data and not asym_keys_data:
print("Keystore is empty.")
def show_system(json):
"""System information overivew"""
if not json.get("ietf-system:system-state"):
@@ -5369,6 +5496,10 @@ def main():
subparsers.add_parser('show-nacm-group', help='Show NACM group details')
subparsers.add_parser('show-nacm-user', help='Show NACM user details')
ks_parser = subparsers.add_parser('show-keystore', help='Show keystore keys')
ks_parser.add_argument('-t', '--type', help='Key type (symmetric or asymmetric)')
ks_parser.add_argument('-n', '--name', help='Key name')
subparsers.add_parser('show-ntp', help='Show NTP status') \
.add_argument('-a', '--address', help='Show details for specific address')
subparsers.add_parser('show-ntp-tracking', help='Show NTP tracking status')
@@ -5438,6 +5569,8 @@ def main():
show_nacm_group(json_data)
elif args.command == "show-nacm-user":
show_nacm_user(json_data)
elif args.command == "show-keystore":
show_keystore(json_data, getattr(args, 'type', None), args.name)
elif args.command == "show-ntp":
show_ntp(json_data, args.address)
elif args.command == "show-ntp-tracking":
+5 -4
View File
@@ -125,7 +125,7 @@ def add_protocol(routes, proto):
def get_routing_interfaces():
"""Get list of interfaces with IPv4 forwarding enabled"""
"""Get list of interfaces with IPv4 or IPv6 forwarding enabled"""
import json
# Get all interfaces
@@ -139,11 +139,12 @@ def get_routing_interfaces():
continue
# Check if IPv4 forwarding is enabled
# Note: We only check IPv4 forwarding. IPv6 forwarding behaves differently
# and will be handled separately when Linux 6.17+ force_forwarding is available.
ipv4_fwd = HOST.run(tuple(['sysctl', '-n', f'net.ipv4.conf.{ifname}.forwarding']), default="0").strip()
if ipv4_fwd == "1":
# Check if IPv6 force_forwarding is enabled (available since Linux 6.17)
ipv6_fwd = HOST.run(tuple(['sysctl', '-n', f'net.ipv6.conf.{ifname}.force_forwarding']), default="0").strip()
if ipv4_fwd == "1" or ipv6_fwd == "1":
routing_ifaces.append(ifname)
return routing_ifaces
+5 -1
View File
@@ -5,11 +5,13 @@ ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/environment]
==== Description
Verify that environment variables can be set in container configuration
and are available inside the running container.
and are available inside the running container. Also verify that
changing an environment variable triggers a container restart.
1 Set up a container config with multiple environment variables
2. Serve variables back to host using a CGI script in container
3. Verify served content against environment variables
4. Change an environment variable and verify the container restarts
==== Topology
@@ -23,5 +25,7 @@ image::topology.svg[Container environment variables topology, align=center, scal
. Verify container has started
. Verify basic connectivity to data interface
. Verify environment variables in CGI response
. Change environment variable and verify container restarts
. Verify container has restarted with updated env
+23 -1
View File
@@ -3,11 +3,13 @@
Container environment variables
Verify that environment variables can be set in container configuration
and are available inside the running container.
and are available inside the running container. Also verify that
changing an environment variable triggers a container restart.
1 Set up a container config with multiple environment variables
2. Serve variables back to host using a CGI script in container
3. Verify served content against environment variables
4. Change an environment variable and verify the container restarts
"""
import infamy
from infamy.util import until, to_binary, curl
@@ -105,4 +107,24 @@ with infamy.Test() as test:
until(lambda: all(string in ns.call(lambda: curl(URL)) for string in expected_strings))
with test.step("Change environment variable and verify container restarts"):
UPDATED_ENV_VARS = [
{"key": "TEST_VAR", "value": "updated-value"},
{"key": "APP_PORT", "value": "8080"},
{"key": "DEBUG_MODE", "value": "true"},
{"key": "PATH_WITH_SPACES", "value": "/path with spaces/test"}
]
target.put_config_dict("infix-containers", {
"containers": {
"container": [{
"name": f"{NAME}",
"env": UPDATED_ENV_VARS,
}]
}
})
with test.step("Verify container has restarted with updated env"):
until(lambda: "TEST_VAR=updated-value" in ns.call(lambda: curl(URL)), attempts=60)
test.succeed()
+1 -1
View File
@@ -7,7 +7,7 @@ Tests verifying interface configuration and management:
- IPv4 and IPv6 address assignment and management
- IPv4 address auto-configuration mechanisms
- Interface aliases and physical address configuration
- Basic routing table configuration and operation
- IPv4 and IPv6 forwarding between interfaces
- Linux bridge creation, STP, and VLAN handling
- Link aggregation (LAG) setup and failover behavior
- IGMP multicast group management and forwarding
+9 -6
View File
@@ -4,8 +4,11 @@ ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/routing_basic]
==== Description
Verify routing between interfaces is possible. That enable/disable routing
in configuration has the expected result.
Verify that the ietf-ip forwarding setting controls whether IPv4
and IPv6 traffic is routed between interfaces. When forwarding is
enabled, hosts on separate subnets can reach each other through
the device. When forwarding is disabled, that connectivity is
expected to be lost.
==== Topology
@@ -14,11 +17,11 @@ image::topology.svg[Routing basic topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to target DUTs
. Setup host
. Set up host addresses and default routes
. Enable forwarding on target:data1 and target:data2
. Verify ping from host:data1 to 10.0.0.10
. Verify ping from host:data2 to 192.168.0.10
. Verify cross-subnet IPv4 connectivity
. Verify cross-subnet IPv6 connectivity
. Disable forwarding on target:data1 and target:data2
. Verify ping does not work host:data1 to 10.0.0.10 and host:data2 to 192.168.0.10
. Verify cross-subnet connectivity is lost
+57 -60
View File
@@ -1,58 +1,53 @@
#!/usr/bin/env python3
# ,------------------------------------------------,
# | [TARGET] |
# | |
# | |
# | target:mgmt target:data0 targetgt:data1 |
# | [192.168.0.1] [10.0.0.1] |
# '------------------------------------------------'
# | | |
# | | |
# ,----------------------------------------------,
# | host:mgmt host:data0 host:data1 |
# | [192.168.0.10] [10.0.0.10] |
# | (ns0) (ns1) |
# | |
# | [ HOST ] |
# '----------------------------------------------'
"""
Routing basic
Verify routing between interfaces is possible. That enable/disable routing
in configuration has the expected result.
Verify that the ietf-ip forwarding setting controls whether IPv4
and IPv6 traffic is routed between interfaces. When forwarding is
enabled, hosts on separate subnets can reach each other through
the device. When forwarding is disabled, that connectivity is
expected to be lost.
"""
import infamy
SUBNETS = [
{"ipv4": {"gw": "192.168.0.1", "host": "192.168.0.10", "prefix": 24},
"ipv6": {"gw": "2001:db8:0::1", "host": "2001:db8:0::10", "prefix": 64}},
{"ipv4": {"gw": "10.0.0.1", "host": "10.0.0.10", "prefix": 24},
"ipv6": {"gw": "2001:db8:1::1", "host": "2001:db8:1::10", "prefix": 64}},
]
def iface_cfg(port, subnet, enable_fwd):
"""Build interface config with both IPv4 and IPv6."""
cfg = {"name": port, "enabled": True}
for family in ("ipv4", "ipv6"):
af = subnet[family]
cfg[family] = {
"forwarding": enable_fwd,
"address": [{"ip": af["gw"], "prefix-length": af["prefix"]}],
}
return cfg
def config_target(target, tport0, tport1, enable_fwd):
"""Configure forwarding and addresses for both address families."""
target.put_config_dict("ietf-interfaces", {
"interfaces": {
"interface": [
{
"name": tport0,
"enabled": True,
"ipv4": {
"forwarding": enable_fwd,
"address": [{
"ip": "192.168.0.1",
"prefix-length": 24
}]
}
},
{
"name": tport1,
"enabled": True,
"ipv4": {
"forwarding": enable_fwd,
"address": [{
"ip": "10.0.0.1",
"prefix-length": 24
}]
}
}
]
}
})
"interfaces": {
"interface": [
iface_cfg(tport0, SUBNETS[0], enable_fwd),
iface_cfg(tport1, SUBNETS[1], enable_fwd),
]
}
})
def setup_host(ns, subnet):
"""Add IPv4 and IPv6 addresses and default routes."""
v4 = subnet["ipv4"]
ns.addip(v4["host"])
ns.addroute("default", v4["gw"])
v6 = subnet["ipv6"]
ns.addip(v6["host"], prefix_length=v6["prefix"], proto="ipv6")
ns.addroute("default", v6["gw"], proto="ipv6")
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
@@ -65,29 +60,31 @@ with infamy.Test() as test:
_, hport1 = env.ltop.xlate("host", "data2")
with infamy.IsolatedMacVlan(hport0) as ns0, \
infamy.IsolatedMacVlan(hport1) as ns1 :
infamy.IsolatedMacVlan(hport1) as ns1:
with test.step("Setup host"):
ns0.addip("192.168.0.10")
ns0.addroute("default", "192.168.0.1")
ns1.addip("10.0.0.10")
ns1.addroute("default", "10.0.0.1")
with test.step("Set up host addresses and default routes"):
setup_host(ns0, SUBNETS[0])
setup_host(ns1, SUBNETS[1])
with test.step("Enable forwarding on target:data1 and target:data2"):
config_target(target, tport0, tport1, True)
with test.step("Verify ping from host:data1 to 10.0.0.10"):
ns0.must_reach("10.0.0.10")
with test.step("Verify cross-subnet IPv4 connectivity"):
ns0.must_reach(SUBNETS[1]["ipv4"]["host"])
ns1.must_reach(SUBNETS[0]["ipv4"]["host"])
with test.step("Verify ping from host:data2 to 192.168.0.10"):
ns1.must_reach("192.168.0.10")
with test.step("Verify cross-subnet IPv6 connectivity"):
ns0.must_reach(SUBNETS[1]["ipv6"]["host"])
ns1.must_reach(SUBNETS[0]["ipv6"]["host"])
with test.step("Disable forwarding on target:data1 and target:data2"):
config_target(target, tport0, tport1, False)
with test.step("Verify ping does not work host:data1 to 10.0.0.10 and host:data2 to 192.168.0.10"):
infamy.parallel(lambda: ns0.must_not_reach("10.0.0.10"),
lambda: ns1.must_not_reach("192.168.0.10"))
with test.step("Verify cross-subnet connectivity is lost"):
infamy.parallel(
lambda: ns0.must_not_reach(SUBNETS[1]["ipv4"]["host"]),
lambda: ns1.must_not_reach(SUBNETS[0]["ipv4"]["host"]),
lambda: ns0.must_not_reach(SUBNETS[1]["ipv6"]["host"]),
lambda: ns1.must_not_reach(SUBNETS[0]["ipv6"]["host"]))
test.succeed()
@@ -19,6 +19,6 @@ graph "routing_basic" {
];
host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"]
host:data1 -- target:data1 [color=black, fontcolor=black, fontsize=12, taillabel=".10", label="192.168.0.0/24", headlabel=".1"]
host:data2 -- target:data2 [color=black, fontcolor=black, fontsize=12, taillabel=".10", label="10.0.0.0/24", headlabel=".1"]
}
host:data1 -- target:data1 [color=black, fontcolor=black, fontsize=12, taillabel=".10", label="192.168.0.0/24\n2001:db8:0::/64", headlabel=".1"]
host:data2 -- target:data2 [color=black, fontcolor=black, fontsize=12, taillabel=".10", label="10.0.0.0/24\n2001:db8:1::/64", headlabel=".1"]
}
+31 -29
View File
@@ -3,55 +3,57 @@
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: routing_basic Pages: 1 -->
<svg width="440pt" height="79pt"
viewBox="0.00 0.00 440.03 79.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 75)">
<svg width="440pt" height="92pt"
viewBox="0.00 0.00 440.03 92.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 88)">
<title>routing_basic</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-75 436.03,-75 436.03,4 -4,4"/>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-88 436.03,-88 436.03,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-1.5 0,-70.5 108,-70.5 108,-1.5 0,-1.5"/>
<text text-anchor="middle" x="25" y="-32.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-1.5 50,-70.5 "/>
<text text-anchor="middle" x="79" y="-55.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="50,-47.5 108,-47.5 "/>
<text text-anchor="middle" x="79" y="-32.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
<polyline fill="none" stroke="black" points="50,-24.5 108,-24.5 "/>
<text text-anchor="middle" x="79" y="-9.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
<polygon fill="none" stroke="black" points="0,-14.5 0,-83.5 108,-83.5 108,-14.5 0,-14.5"/>
<text text-anchor="middle" x="25" y="-45.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-14.5 50,-83.5 "/>
<text text-anchor="middle" x="79" y="-68.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="50,-60.5 108,-60.5 "/>
<text text-anchor="middle" x="79" y="-45.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
<polyline fill="none" stroke="black" points="50,-37.5 108,-37.5 "/>
<text text-anchor="middle" x="79" y="-22.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
</g>
<!-- target -->
<g id="node2" class="node">
<title>target</title>
<polygon fill="none" stroke="black" points="308.03,-1.5 308.03,-70.5 432.03,-70.5 432.03,-1.5 308.03,-1.5"/>
<text text-anchor="middle" x="337.03" y="-55.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="308.03,-47.5 366.03,-47.5 "/>
<text text-anchor="middle" x="337.03" y="-32.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
<polyline fill="none" stroke="black" points="308.03,-24.5 366.03,-24.5 "/>
<text text-anchor="middle" x="337.03" y="-9.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
<polyline fill="none" stroke="black" points="366.03,-1.5 366.03,-70.5 "/>
<text text-anchor="middle" x="399.03" y="-32.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
<polygon fill="none" stroke="black" points="308.03,-14.5 308.03,-83.5 432.03,-83.5 432.03,-14.5 308.03,-14.5"/>
<text text-anchor="middle" x="337.03" y="-68.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="308.03,-60.5 366.03,-60.5 "/>
<text text-anchor="middle" x="337.03" y="-45.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
<polyline fill="none" stroke="black" points="308.03,-37.5 366.03,-37.5 "/>
<text text-anchor="middle" x="337.03" y="-22.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
<polyline fill="none" stroke="black" points="366.03,-14.5 366.03,-83.5 "/>
<text text-anchor="middle" x="399.03" y="-45.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
</g>
<!-- host&#45;&#45;target -->
<g id="edge1" class="edge">
<title>host:mgmt&#45;&#45;target:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-59C108,-59 308.03,-59 308.03,-59"/>
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-72C108,-72 308.03,-72 308.03,-72"/>
</g>
<!-- host&#45;&#45;target -->
<g id="edge2" class="edge">
<title>host:data1&#45;&#45;target:data1</title>
<path fill="none" stroke="black" stroke-width="2" d="M108,-36C108,-36 308.03,-36 308.03,-36"/>
<text text-anchor="middle" x="162.52" y="-39.4" font-family="DejaVu Serif, Book" font-size="12.00">192.168.0.0/24</text>
<text text-anchor="middle" x="302.03" y="-39.4" font-family="DejaVu Serif, Book" font-size="12.00">.1</text>
<text text-anchor="middle" x="117.5" y="-26.4" font-family="DejaVu Serif, Book" font-size="12.00">.10</text>
<path fill="none" stroke="black" stroke-width="2" d="M108,-49C108,-49 308.03,-49 308.03,-49"/>
<text text-anchor="middle" x="161.02" y="-65.4" font-family="DejaVu Serif, Book" font-size="12.00">192.168.0.0/24</text>
<text text-anchor="middle" x="161.02" y="-52.4" font-family="DejaVu Serif, Book" font-size="12.00">2001:db8:0::/64</text>
<text text-anchor="middle" x="302.03" y="-52.4" font-family="DejaVu Serif, Book" font-size="12.00">.1</text>
<text text-anchor="middle" x="117.5" y="-39.4" font-family="DejaVu Serif, Book" font-size="12.00">.10</text>
</g>
<!-- host&#45;&#45;target -->
<g id="edge3" class="edge">
<title>host:data2&#45;&#45;target:data2</title>
<path fill="none" stroke="black" stroke-width="2" d="M108,-13C108,-13 308.03,-13 308.03,-13"/>
<text text-anchor="middle" x="174.02" y="-16.4" font-family="DejaVu Serif, Book" font-size="12.00">10.0.0.0/24</text>
<text text-anchor="middle" x="302.03" y="-16.4" font-family="DejaVu Serif, Book" font-size="12.00">.1</text>
<text text-anchor="middle" x="117.5" y="-3.4" font-family="DejaVu Serif, Book" font-size="12.00">.10</text>
<path fill="none" stroke="black" stroke-width="2" d="M108,-26C108,-26 308.03,-26 308.03,-26"/>
<text text-anchor="middle" x="161.02" y="-16.4" font-family="DejaVu Serif, Book" font-size="12.00">10.0.0.0/24</text>
<text text-anchor="middle" x="161.02" y="-3.4" font-family="DejaVu Serif, Book" font-size="12.00">2001:db8:1::/64</text>
<text text-anchor="middle" x="302.03" y="-29.4" font-family="DejaVu Serif, Book" font-size="12.00">.1</text>
<text text-anchor="middle" x="117.5" y="-29.4" font-family="DejaVu Serif, Book" font-size="12.00">.10</text>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -75,12 +75,12 @@ def configure_server(dut):
"symmetric-keys": {
"symmetric-key": [{
"name": "psk-client1",
"infix-keystore:symmetric-key": psk_client1,
"infix-keystore:key-format": "infix-crypto-types:wireguard-symmetric-key-format"
"cleartext-symmetric-key": psk_client1,
"key-format": "ietf-crypto-types:octet-string-key-format"
}, {
"name": "psk-client2",
"infix-keystore:symmetric-key": psk_client2,
"infix-keystore:key-format": "infix-crypto-types:wireguard-symmetric-key-format"
"cleartext-symmetric-key": psk_client2,
"key-format": "ietf-crypto-types:octet-string-key-format"
}]
}
}
@@ -226,8 +226,8 @@ def configure_client1(dut):
"symmetric-keys": {
"symmetric-key": [{
"name": "psk-server",
"infix-keystore:symmetric-key": psk_client1,
"infix-keystore:key-format": "infix-crypto-types:wireguard-symmetric-key-format"
"cleartext-symmetric-key": psk_client1,
"key-format": "ietf-crypto-types:octet-string-key-format"
}]
}
}
@@ -360,8 +360,8 @@ def configure_client2(dut):
"symmetric-keys": {
"symmetric-key": [{
"name": "psk-server",
"infix-keystore:symmetric-key": psk_client2,
"infix-keystore:key-format": "infix-crypto-types:wireguard-symmetric-key-format"
"cleartext-symmetric-key": psk_client2,
"key-format": "ietf-crypto-types:octet-string-key-format"
}]
}
}
+13 -12
View File
@@ -5,21 +5,20 @@ ifdef::topdoc[:imagesdir: {topdoc}../../test/case/system/nacm-basic]
==== Description
Test that NACM groups (admin, operator, guest) correctly enforce
access control with path-based rules and default policies.
access control with permissive defaults and targeted denials.
Creates three user privilege levels from scratch:
The NACM design is "permit by default, deny sensitive items":
- admin: Full access (permit-all rule)
- operator: Can manage interfaces/routing, cannot modify system config
- guest: Read-only access (write-default: deny, exec deny rule)
- admin: Full unrestricted access (permit-all rule)
- operator: Can configure everything EXCEPT passwords, keystore, truststore
- guest: Read-only access (explicit deny of create/update/delete/exec)
Verifies that:
- All users can read configuration (read-default: permit)
- Operators can modify interfaces (path-specific permit rule)
- Operators cannot modify system configuration (write-default: deny)
- Guests cannot modify any configuration (write-default: deny)
- Admin can modify all configuration (permit-all rule bypasses defaults)
- Operators can read and modify most configuration (hostname, interfaces)
- Operators CANNOT read or write password hashes (protected path)
- Guests can read but cannot modify any configuration
- Admin can access everything including passwords
==== Topology
@@ -31,9 +30,11 @@ image::topology.svg[Basic NACM permissions topology, align=center, scaledwidth=7
. Configure NACM groups, rules, and test users
. Verify operator can read configuration
. Verify operator can modify interface configuration
. Verify operator cannot modify system configuration
. Verify operator can modify hostname
. Verify operator cannot read password hashes
. Verify operator cannot write password hashes
. Verify guest can read configuration
. Verify guest cannot modify configuration
. Verify admin can modify configuration
. Verify admin can access passwords
+9
View File
@@ -22,6 +22,12 @@ may be supplied in places where a hostname is otherwise expected.
Commands:
scan [-a] [<iface>]
Discover Infix devices on the LAN using mDNS-SD (avahi-browse).
Use -a to show all mDNS devices, not just Infix. Falls back to
IPv6 link-local multicast ping if avahi-browse is not installed
and <iface> is specified.
peer <iface>
Return the address of the first IPv6 neighbor to respond on
<iface>'s local LAN.
@@ -75,6 +81,9 @@ case "$cmd" in
help)
usage && exit 0
;;
scan)
llscan "$@"
;;
peer)
llpeer "$@"
;;
+152
View File
@@ -144,3 +144,155 @@ llscp()
$sshpasscmd scp $LLSCP_OPTS "$src" "$dst"
}
# Usage: llscan [-a] [<iface>]
#
# Discover Infix devices (or all mDNS devices with -a) on the LAN.
# Prefers mDNS-SD via avahi-browse; falls back to IPv6 link-local
# multicast ping if avahi-browse is not available and an interface
# is specified.
llscan()
{
local all=0
local iface=
while [ $# -gt 0 ]; do
case "$1" in
-a)
all=1
shift
;;
*)
iface="$1"
shift
;;
esac
done
if [ "$iface" ] && [ ! -d "/sys/class/net/$iface" ]; then
echo "Error: Interface \"$iface\" does not exist." >&2
return 1
fi
if command -v avahi-browse >/dev/null 2>&1; then
llscan_mdns "$all"
elif [ "$iface" ]; then
echo "Note: avahi-browse not found, falling back to link-local scan." >&2
llscan_ll "$iface"
else
cat >&2 <<-EOF
Error: avahi-browse not found.
Install avahi-utils to scan for Infix devices via mDNS:
sudo apt install avahi-utils
Or specify an interface to use IPv6 link-local fallback:
$(basename $0) scan <iface>
EOF
return 1
fi
}
llscan_mdns()
{
local all="$1"
local flags="-tarp"
if avahi-browse --help 2>&1 | grep -q -- '-k'; then
flags="-tarpk"
fi
avahi-browse $flags | awk -F';' -v show_all="$all" '
$1 == "=" {
host = $7
proto = $3
addr = $8
txt = $10
on = ""; ov = ""; product = ""; serial = ""; devid = ""
n = split(txt, parts, "\" \"")
for (i = 1; i <= n; i++) {
gsub(/"/, "", parts[i])
if (parts[i] ~ /^on=/) { split(parts[i], kv, "="); on = kv[2] }
else if (parts[i] ~ /^ov=/) { split(parts[i], kv, "="); ov = kv[2] }
else if (parts[i] ~ /^product=/) { split(parts[i], kv, "="); product = kv[2] }
else if (parts[i] ~ /^serial=/) { split(parts[i], kv, "="); serial = kv[2] }
else if (parts[i] ~ /^deviceid=/) { split(parts[i], kv, "="); devid = kv[2] }
}
if (!show_all && on != "Infix") next
# Use deviceid (MAC) as unique key; fall back to hostname
key = devid ? devid : host
if (!product) product = on ? on : "-"
if (!ov) ov = "-"
if (!serial || serial == "null") serial = "-"
# Prefer IPv4 for display address
if (proto == "IPv4") {
ipv4[key] = addr
} else if (proto == "IPv6" && !ipv6[key]) {
ipv6[key] = addr
}
if (!seen[key]++) {
keys[++ndevs] = key
hosts[key] = host
products[key] = product
versions[key] = ov
serials[key] = serial
} else if (length(host) > length(hosts[key])) {
# Prefer the unique hostname (e.g., infix-c0-ff-ee.local)
# over the generic one (e.g., infix.local)
hosts[key] = host
}
}
END {
if (ndevs == 0) {
if (show_all)
print "No mDNS devices found." | "cat >&2"
else
print "No Infix devices found. Use -a to show all mDNS devices." | "cat >&2"
exit 1
}
fmt = "%-26s %-18s %-24s %-22s %s\n"
hdr = sprintf(fmt, "HOSTNAME", "ADDRESS", "PRODUCT", "VERSION", "SERIAL")
sub(/\n$/, "", hdr)
printf "\033[7m%s\033[0m\n", hdr
for (i = 1; i <= ndevs; i++) {
k = keys[i]
a = (ipv4[k] ? ipv4[k] : (ipv6[k] ? ipv6[k] : "-"))
p = products[k]; if (length(p) > 23) p = substr(p, 1, 22) "~"
v = versions[k]; if (length(v) > 21) v = substr(v, 1, 20) "~"
printf fmt, hosts[k], a, p, v, serials[k]
}
printf "\n%d device(s) found.\n", ndevs
}
'
}
llscan_ll()
{
local iface="$1"
printf "Scanning %s for IPv6 link-local neighbors ...\n\n" "$iface"
printf "\033[7m%-6s %s\033[0m\n" "#" "ADDRESS"
llping "$iface" -c3 -w3 2>/dev/null | awk '
/bytes from/ {
sub(/:$/, "", $4)
addr = $4
if (!seen[addr]++) {
printf "%-6d %s\n", ++n, addr
}
}
END {
printf "\n%d neighbor(s) found on '"$iface"'.\n", n+0
}
'
}