Merge pull request #1563 from kernelkit/misc

Firewall address-set support
This commit is contained in:
Joachim Wiberg
2026-07-06 09:37:21 +02:00
committed by GitHub
95 changed files with 2725 additions and 525 deletions
+1 -1
View File
@@ -78,7 +78,7 @@ jobs:
- name: Configure ${{ matrix.defconfig }}_defconfig
run: |
make ${{ matrix.defconfig }}_defconfig
make ${{ matrix.defconfig }}_defconfig apply-mirror
- name: Build ${{ matrix.defconfig }}_defconfig
run: |
+1 -1
View File
@@ -173,7 +173,7 @@ jobs:
export BUILD_DIR=$PWD/build
export BR2_EXTERNAL_INFIX_PATH=$PWD
export RELEASE=""
export INFIX_ID="infix"
export IX_ID="infix"
for target in $TARGETS; do
export BINARIES_DIR=$PWD/output_${target}/images
+25 -1
View File
@@ -54,7 +54,7 @@ jobs:
run: |
target=${{ matrix.target }}_defconfig
echo "Building $target ..."
make $target
make $target apply-mirror
make
- name: Generate SBOM from Build
@@ -70,6 +70,25 @@ jobs:
run: |
make test-spec
# rootfs.squashfs is still present here; it is stripped from the
# tarball below. mkimage pulls the rpi64 bootloader from the
# latest-boot release and uses the freshly built host genimage.
- name: Build Raspberry Pi image
if: matrix.target == 'aarch64'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
./utils/mkimage.sh -d -r output raspberrypi-rpi64
mkdir -p rpi-image
mv output/images/*-rpi64-sdcard.img rpi-image/
xz -T0 rpi-image/*-rpi64-sdcard.img
- uses: actions/upload-artifact@v7
if: matrix.target == 'aarch64'
with:
name: artifact-rpi64-image
path: rpi-image/*.img.xz
- name: Prepare Artifacts
run: |
cd output/
@@ -98,3 +117,8 @@ jobs:
with:
name: artifact-disk-image-${{ matrix.target }}
path: output/images/*.qcow2
- uses: actions/upload-artifact@v7
with:
name: artifact-pkg-${{ matrix.target }}
path: output/images/*.pkg
+1 -1
View File
@@ -129,7 +129,7 @@ jobs:
- name: Configure ${{ env.TARGET }}
run: |
make ${{ env.TARGET }}_defconfig
make ${{ env.TARGET }}_defconfig apply-mirror
- name: Cleanup stale containers and ports
run: |
+1 -1
View File
@@ -95,7 +95,7 @@ jobs:
makeLatest: ${{ steps.rel.outputs.latest }}
discussionCategory: ${{ steps.rel.outputs.cat }}
bodyFile: release.md
artifacts: "*.tar.gz,*.qcow2"
artifacts: "*.tar.gz,*.qcow2,*.pkg,*.img.xz"
- name: Summary
run: |
+19 -19
View File
@@ -2,7 +2,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/board/Config.in"
menu "Branding"
config INFIX_VENDOR
config IX_VENDOR
string "Vendor name"
default "KernelKit"
help
@@ -15,7 +15,7 @@ config INFIX_VENDOR
Used for VENDOR_NAME in /etc/os-release and GNS3 appliance files.
config INFIX_VENDOR_HOME
config IX_VENDOR_HOME
string "Vendor URL"
help
The homepage of the OS vendor. The value should be in RFC3986
@@ -24,14 +24,14 @@ config INFIX_VENDOR_HOME
Optional, used for VENDOR_HOME in /etc/os-release
config INFIX_NAME
config IX_NAME
string "Operating system name"
default "Infix"
help
Mandatory. Used for identifying the OS as NAME in /etc/os-release
and product_name in GNS3 appliance files.
config INFIX_ID
config IX_ID
string "Operating system identifier"
default "infix"
help
@@ -43,9 +43,9 @@ config INFIX_ID
Mandatory. Used for identifying the OS as ID in /etc/os-release and
in the generated image name: ID-ARCH-VERSION.img
config INFIX_IMAGE_ID
config IX_IMAGE_ID
string "Operating system image name"
default "${INFIX_ID}-${BR2_ARCH}"
default "${IX_ID}-${BR2_ARCH}"
help
A lower-case string (no spaces or other characters outside of 09,
az, ".", "_" and "-"), for naming critical image files, directories
@@ -54,18 +54,18 @@ config INFIX_IMAGE_ID
Mandatory. When INFIX_RELEASE is set, this string is appended to
the IMAGE_ID with a '-' separator.
config INFIX_COMPATIBLE
config IX_COMPATIBLE
string "Operating system compatible string"
default "${INFIX_IMAGE_ID}"
default "${IX_IMAGE_ID}"
help
A lower-case string (no spaces or other characters outside of 09,
az, ".", "_" and "-"), used for image identification at upgrade.
E.g., the RAUC [system] compatible string.
Mandatory. Defaults to $INFIX_IMAGE_ID, which in turn is composed
of $INFIX_ID-$BR2_ARCH.
Mandatory. Defaults to $IX_IMAGE_ID, which in turn is composed
of $IX_ID-$BR2_ARCH.
config INFIX_TAGLINE
config IX_TAGLINE
string "Operating system tagline"
default "Infix OS — Immutable.Friendly.Secure"
help
@@ -74,25 +74,25 @@ config INFIX_TAGLINE
This is also show at boot when the system init process starts.
config INFIX_DESC
config IX_DESC
string "Operating system description"
help
Optional. Used for long description texts about the OS. E.g.,
the GNS3 appliance file description field. Saved in the file
/etc/os-release as INFIX_DESC.
/etc/os-release as IX_DESC.
config INFIX_HOME
config IX_HOME
string "Operating system URL"
help
Used for identifying the OS, e.g. as HOME_URL in /etc/os-release
config INFIX_DOC
config IX_DOC
string "Operating system docs"
help
Optional. Main documentation URL, will be shown in /etc/os-release
as DOCUMENTATION_URL.
config INFIX_SUPPORT
config IX_SUPPORT
string "Operating system support"
help
Main support page for the operating system, if there is any. This
@@ -102,13 +102,13 @@ config INFIX_SUPPORT
Optional. Shown, e.g., as SUPPORT_URL in /etc/os-release or
maintainer_email in .gns3a.
config INFIX_OEM_PATH
config IX_OEM_PATH
string "Path to OEM br2-external"
help
A br2-external using Infix will likely want to version the branded
OS using their own GIT tags. Set this variable to point to the base
directory (absolute path) and the Infix post-build.sh will call `git
describe -C $INFIX_OEM_PATH`.
describe -C $IX_OEM_PATH`.
Note: the OS version (VERSION, VERSION_ID, BUILD_ID in
/etc/os-release) is always derived from `git describe`. The global
@@ -118,7 +118,7 @@ config INFIX_OEM_PATH
endmenu
# For /etc/os-release, uses CondtionArchitechture= from systemd.unit(5)
config INFIX_ARCH
config IX_ARCH
string
default "arm" if BR2_arm
default "arm64" if BR2_aarch64
@@ -22,7 +22,7 @@ image var.ext4 {
}
}
image #INFIX_ID##VERSION#-vero-w-#TARGET#.img {
image #IX_ID##VERSION#-vero-w-#TARGET#.img {
hdimage {
partition-table-type = "gpt"
gpt-no-backup = true
@@ -24,7 +24,7 @@ image var.ext4 {
}
}
image #INFIX_ID##VERSION#-bpi-r3-#TARGET#.img {
image #IX_ID##VERSION#-bpi-r3-#TARGET#.img {
hdimage {
partition-table-type = "gpt"
@@ -24,7 +24,7 @@ image var.ext4 {
}
}
image #INFIX_ID##VERSION#-bpi-r4-#TARGET#.img {
image #IX_ID##VERSION#-bpi-r4-#TARGET#.img {
hdimage {
partition-table-type = "gpt"
gpt-no-backup = true
@@ -24,7 +24,7 @@ image var.ext4 {
}
}
image #INFIX_ID##VERSION#-bpi-r64-#TARGET#.img {
image #IX_ID##VERSION#-bpi-r64-#TARGET#.img {
hdimage {
partition-table-type = "hybrid"
# MT7622 TF-A partition driver detects GPT by checking MBR[0]
@@ -26,7 +26,7 @@ image var.ext4 {
}
}
image #INFIX_ID##VERSION#-nanopi-r2s-sdcard.img {
image #IX_ID##VERSION#-nanopi-r2s-sdcard.img {
hdimage {
partition-table-type = "gpt"
}
@@ -24,7 +24,7 @@ image var.ext4 {
}
}
image #INFIX_ID##VERSION#-espressobin-#TARGET#.img {
image #IX_ID##VERSION#-espressobin-#TARGET#.img {
hdimage {
partition-table-type = "gpt"
}
@@ -36,7 +36,7 @@ image var.ext4 {
}
}
image #INFIX_ID##VERSION#-rpi64-sdcard.img {
image #IX_ID##VERSION#-rpi64-sdcard.img {
hdimage {
partition-table-type = "hybrid"
}
@@ -39,7 +39,7 @@ image var.ext4 {
}
}
image #INFIX_ID##VERSION#-sama7g54-ek-#TARGET#.img {
image #IX_ID##VERSION#-sama7g54-ek-#TARGET#.img {
hdimage {
partition-table-type = "hybrid"
}
+1 -1
View File
@@ -36,7 +36,7 @@ image var.ext4 {
}
}
image #INFIX_ID##VERSION#-rpi2-sdcard.img {
image #IX_ID##VERSION#-rpi2-sdcard.img {
hdimage {
partition-table-type = "hybrid"
}
+8 -8
View File
@@ -11,7 +11,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-readme/Config.in"
endmenu
config QEMU_SCRIPTS
config IX_QEMU_SCRIPTS
bool "QEMU scripts"
default y
help
@@ -19,20 +19,20 @@ config QEMU_SCRIPTS
directory, which can be used to launch virtual Infix
instances.
menuconfig TRUSTED_KEYS
menuconfig IX_TRUSTED_KEYS
bool "Trusted keys for image"
help
Keys that will be accepted for this image
config TRUSTED_KEYS_DEVELOPMENT
config IX_TRUSTED_KEYS_DEVELOPMENT
bool "Development key"
depends on TRUSTED_KEYS
depends on IX_TRUSTED_KEYS
config TRUSTED_KEYS_DEVELOPMENT_PATH
config IX_TRUSTED_KEYS_DEVELOPMENT_PATH
string
depends on TRUSTED_KEYS_DEVELOPMENT
depends on IX_TRUSTED_KEYS_DEVELOPMENT
default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.crt"
config TRUSTED_KEYS_EXTRA_PATH
config IX_TRUSTED_KEYS_EXTRA_PATH
string "Path to extra keys to include in image"
depends on TRUSTED_KEYS
depends on IX_TRUSTED_KEYS
+3 -3
View File
@@ -1,14 +1,14 @@
include $(BR2_EXTERNAL_INFIX_PATH)/board/common/image/image.mk
include $(BR2_EXTERNAL_INFIX_PATH)/board/common/qemu/qemu.mk
ifeq ($(TRUSTED_KEYS),y)
ifeq ($(IX_TRUSTED_KEYS),y)
include $(BR2_EXTERNAL_INFIX_PATH)/board/common/uboot/uboot.mk
TRUSTED_KEYS=$(TRUSTED_KEYS_DEVELOPMENT_PATH) $(TRUSTED_KEYS_EXTRA_PATH)
IX_TRUSTED_KEYS=$(IX_TRUSTED_KEYS_DEVELOPMENT_PATH) $(IX_TRUSTED_KEYS_EXTRA_PATH)
define RAUC_POST_BUILD_INSTALL_CERT
@$(call IXMSG,"Installing signing cert for RAUC")
mkdir -p $(TARGET_DIR)/etc/rauc/keys
$(foreach crt,$(shell ls $(TRUSTED_KEYS)), \
$(foreach crt,$(shell ls $(IX_TRUSTED_KEYS)), \
cp $(crt) $(TARGET_DIR)/etc/rauc/keys/$(shell openssl x509 -hash -noout <$(crt)).0;)
endef
+5 -5
View File
@@ -1,4 +1,4 @@
config IMAGE_EXT4_RAUC
config IX_IMAGE_EXT4_RAUC
bool "RAUC upgrade bundle (ext4)"
depends on BR2_TARGET_ROOTFS_EXT2
select BR2_PACKAGE_HOST_RAUC
@@ -7,17 +7,17 @@ config IMAGE_EXT4_RAUC
image. Intended for development boards whose bootloader does
not support squashfs.
config IMAGE_EXT4_RAUC_KEY
config IX_IMAGE_EXT4_RAUC_KEY
string "signing key"
depends on IMAGE_EXT4_RAUC
depends on IX_IMAGE_EXT4_RAUC
default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.key"
help
Path to the private key, in PKCS#8 format, used to sign
the RAUC bundle; or a PKCS#11 URI.
config IMAGE_EXT4_RAUC_CERT
config IX_IMAGE_EXT4_RAUC_CERT
string "signing certificate"
depends on IMAGE_EXT4_RAUC
depends on IX_IMAGE_EXT4_RAUC
default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.crt"
help
Path to the X509 certificate which will be associated with
+2 -2
View File
@@ -1,6 +1,6 @@
config IMAGE_ITB_AUX
config IX_IMAGE_ITB_AUX
bool "aux partition"
depends on IMAGE_ITB_ROOTFS
depends on IX_IMAGE_ITB_ROOTFS
select BR2_PACKAGE_HOST_UBOOT_TOOLS
select BR2_PACKAGE_HOST_GENIMAGE
help
@@ -1,4 +1,4 @@
menuconfig IMAGE_ITB_DL_RELEASE
menuconfig IX_IMAGE_ITB_DL_RELEASE
bool "Download existing release"
depends on !BR2_TARGET_ROOTFS_SQUASHFS
help
@@ -7,9 +7,9 @@ menuconfig IMAGE_ITB_DL_RELEASE
Infix image, to create a full disk image that can be
provisioned to an SD-card or eMMC.
config IMAGE_ITB_DL_RELEASE_URL
config IX_IMAGE_ITB_DL_RELEASE_URL
string "URL"
depends on IMAGE_ITB_DL_RELEASE
depends on IX_IMAGE_ITB_DL_RELEASE
default "https://github.com/kernelkit/infix/releases/download/latest/infix-${BR2_ARCH}.tar.gz"
help
URL to release tarball.
+6 -6
View File
@@ -1,23 +1,23 @@
menuconfig IMAGE_ITB_GNS3A
menuconfig IX_IMAGE_ITB_GNS3A
bool "GNS3 Appliance (ITB)"
depends on BR2_x86_64
select IMAGE_ITB_QCOW
select IX_IMAGE_ITB_QCOW
help
Create a GNS3 appliance description that, together with the
disk image, can be imported into GNS3.
config IMAGE_ITB_GNS3A_RAM
config IX_IMAGE_ITB_GNS3A_RAM
int "Reserved RAM (MiB)"
depends on IMAGE_ITB_GNS3A
depends on IX_IMAGE_ITB_GNS3A
default "192"
help
Amount of host RAM reserved for an appliance instance.
Minimum supported size is 192M.
config IMAGE_ITB_GNS3A_IFNUM
config IX_IMAGE_ITB_GNS3A_IFNUM
int "Number of interfaces"
depends on IMAGE_ITB_GNS3A
depends on IX_IMAGE_ITB_GNS3A
default "1"
help
Number of Ethernet interfaces to create for an appliance instance.
+16 -16
View File
@@ -1,6 +1,6 @@
menuconfig IMAGE_ITB_QCOW
menuconfig IX_IMAGE_ITB_QCOW
bool "QEMU disk image (ITB)"
depends on (IMAGE_ITB_ROOTFS && IMAGE_ITB_AUX) || IMAGE_ITB_DL_RELEASE
depends on (IX_IMAGE_ITB_ROOTFS && IX_IMAGE_ITB_AUX) || IX_IMAGE_ITB_DL_RELEASE
select BR2_PACKAGE_HOST_GENIMAGE
help
Compose a full disk image with redundant Linux OS partitions,
@@ -11,9 +11,9 @@ menuconfig IMAGE_ITB_QCOW
- Creating a GNS3 appliance
- Developing/debugging issues in the boot process in QEMU
menuconfig IMAGE_ITB_QCOW_SIZE
menuconfig IX_IMAGE_ITB_QCOW_SIZE
string "Image size"
depends on IMAGE_ITB_QCOW
depends on IX_IMAGE_ITB_QCOW
default "512M"
help
Create a disk image of this size. A K/M/G suffix may be used
@@ -26,22 +26,22 @@ menuconfig IMAGE_ITB_QCOW_SIZE
choice
prompt "Bootloader"
depends on IMAGE_ITB_QCOW
default IMAGE_ITB_QCOW_BOOT_EFI if BR2_x86_64
default IMAGE_ITB_QCOW_BOOT_NONE
depends on IX_IMAGE_ITB_QCOW
default IX_IMAGE_ITB_QCOW_BOOT_EFI if BR2_x86_64
default IX_IMAGE_ITB_QCOW_BOOT_NONE
config IMAGE_ITB_QCOW_BOOT_NONE
config IX_IMAGE_ITB_QCOW_BOOT_NONE
bool "None"
help
Do not create any bootloader partition in the disk image.
config IMAGE_ITB_QCOW_BOOT_EFI
config IX_IMAGE_ITB_QCOW_BOOT_EFI
bool "EFI"
help
Create a boot partition from a directory containing an EFI
boot application, e.g. GRUB.
config IMAGE_ITB_QCOW_BOOT_BIN
config IX_IMAGE_ITB_QCOW_BOOT_BIN
bool "Binary"
help
Create a boot partition from a raw image containing the boot
@@ -49,18 +49,18 @@ config IMAGE_ITB_QCOW_BOOT_BIN
endchoice
config IMAGE_ITB_QCOW_BOOT_DATA
config IX_IMAGE_ITB_QCOW_BOOT_DATA
string "Bootloader data"
depends on IMAGE_ITB_QCOW
depends on IMAGE_ITB_QCOW_BOOT_EFI || IMAGE_ITB_QCOW_BOOT_BIN
depends on IX_IMAGE_ITB_QCOW
depends on IX_IMAGE_ITB_QCOW_BOOT_EFI || IX_IMAGE_ITB_QCOW_BOOT_BIN
default "${BINARIES_DIR}/efi-part/EFI" if BR2_x86_64
help
Path to the directory or file holding the bootloader data.
config IMAGE_ITB_QCOW_BOOT_OFFSET
config IX_IMAGE_ITB_QCOW_BOOT_OFFSET
hex "Bootloader offset"
depends on IMAGE_ITB_QCOW
depends on IMAGE_ITB_QCOW_BOOT_EFI || IMAGE_ITB_QCOW_BOOT_BIN
depends on IX_IMAGE_ITB_QCOW
depends on IX_IMAGE_ITB_QCOW_BOOT_EFI || IX_IMAGE_ITB_QCOW_BOOT_BIN
default 0x8000
help
Offset at which the bootloader partition is placed. Remember
@@ -6,8 +6,8 @@
# We can source the rootfs+aux from a local build, or from a
# downloaded release; so adjust our dependencies accordingly.
IMAGE_ITB_QCOW_SRC-$(IMAGE_ITB_ROOTFS) := image-itb-rootfs image-itb-aux
IMAGE_ITB_QCOW_SRC-$(IMAGE_ITB_DL_RELEASE) := image-itb-dl-release
IMAGE_ITB_QCOW_SRC-$(IX_IMAGE_ITB_ROOTFS) := image-itb-rootfs image-itb-aux
IMAGE_ITB_QCOW_SRC-$(IX_IMAGE_ITB_DL_RELEASE) := image-itb-dl-release
IMAGE_ITB_QCOW_DEPENDENCIES := host-genimage $(IMAGE_ITB_QCOW_SRC-y)
IMAGE_ITB_QCOW_CONFIG_VARS := BOOT_DATA BOOT_OFFSET SIZE
+6 -6
View File
@@ -1,23 +1,23 @@
menuconfig IMAGE_ITB_RAUC
menuconfig IX_IMAGE_ITB_RAUC
bool "RAUC upgrade bundle (ITB)"
select IMAGE_ITB_ROOTFS
select IX_IMAGE_ITB_ROOTFS
select BR2_PACKAGE_HOST_RAUC
help
Create RAUC upgrade bundle, for targets using ITB images,
that can be used to upgrade a running system to this version
of Infix.
config IMAGE_ITB_RAUC_KEY
config IX_IMAGE_ITB_RAUC_KEY
string "signing key"
depends on IMAGE_ITB_RAUC
depends on IX_IMAGE_ITB_RAUC
default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.key"
help
Path to the private key, in PKCS#8 format, used to sign
the RAUC bundle; or a PKCS#11 URI.
config IMAGE_ITB_RAUC_CERT
config IX_IMAGE_ITB_RAUC_CERT
string "signing certificate"
depends on IMAGE_ITB_RAUC
depends on IX_IMAGE_ITB_RAUC
default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.crt"
help
Path to the X509 certificate which will be associated with
@@ -1,4 +1,4 @@
menuconfig IMAGE_ITB_ROOTFS
menuconfig IX_IMAGE_ITB_ROOTFS
bool "rootfs.itb+.itbh"
select BR2_TARGET_ROOTFS_SQUASHFS
select BR2_PACKAGE_HOST_UBOOT_TOOLS
@@ -7,9 +7,9 @@ menuconfig IMAGE_ITB_ROOTFS
and extract detached header (.itbh) for U-Boot based
targets.
config IMAGE_ITB_ROOTFS_KEY
config IX_IMAGE_ITB_ROOTFS_KEY
string "signing key"
depends on IMAGE_ITB_ROOTFS
depends on IX_IMAGE_ITB_ROOTFS
default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.key"
help
Path to the private RSA key, in PKCS#8 format, used to sign
+1 -1
View File
@@ -1,4 +1,4 @@
config IMAGE_README
config IX_IMAGE_README
bool "Install README.md in images"
help
Install a README.md with useful information about getting
@@ -13,6 +13,6 @@ $(BINARIES_DIR)/README.md: $(IMAGE_README_DIR)/README.md
@mkdir -p $(BINARIES_DIR)
@cp $< $@
ifeq ($(IMAGE_README),y)
ifeq ($(IX_IMAGE_README),y)
TARGETS_ROOTFS += image-readme
endif
+6 -4
View File
@@ -12,15 +12,17 @@ $(1): $$($(2)_DEPENDENCIES)
BINARIES_DIR=$$(BINARIES_DIR) \
BR2_EXTERNAL_INFIX_PATH=$$(BR2_EXTERNAL_INFIX_PATH) \
ARTIFACT=$$(INFIX_ARTIFACT) \
COMPATIBLE=$$(INFIX_COMPATIBLE) \
COMPATIBLE=$$(IX_COMPATIBLE) \
VERSION=$$(INFIX_VERSION) \
$$(foreach var,$$($(2)_CONFIG_VARS),$$(var)=$$($(2)_$$(var)) ) \
$$(foreach var,$$($(2)_CONFIG_VARS),$$(var)=$$($(3)_$$(var)) ) \
$$($(2)_DIR)/generate.sh $$($(2)_OPTS)
ifeq ($$($(2)),y)
ifeq ($$($(3)),y)
TARGETS_ROOTFS += $(1)
endif
endef
ix-image = $(call inner-ix-image,$(pkgname),$(call UPPERCASE,$(pkgname)))
# $(2) is the package-local make-var prefix (UPPERCASE pkgname); $(3) is
# the IX_ prefixed Kconfig symbol name, see issue #1305.
ix-image = $(call inner-ix-image,$(pkgname),$(call UPPERCASE,$(pkgname)),IX_$(call UPPERCASE,$(pkgname)))
+1 -1
View File
@@ -16,7 +16,7 @@ die()
# DISK_IMAGE_SIZE="512"
# etc.
#
# Nested variables, like INFIX_COMPATIBLE="${INFIX_IMAGE_ID}"
# Nested variables, like IX_COMPATIBLE="${IX_IMAGE_ID}"
# are handled by sourcing the file in a subshell.
#
# shellcheck disable=SC1090
+19 -19
View File
@@ -37,7 +37,7 @@ EOF
}
if [ -f "$TARGET_DIR/etc/rauc/system.conf" ]; then
sed -i "s/compatible=.*/compatible=$INFIX_COMPATIBLE/" "$TARGET_DIR/etc/rauc/system.conf"
sed -i "s/compatible=.*/compatible=$IX_COMPATIBLE/" "$TARGET_DIR/etc/rauc/system.conf"
fi
if [ -n "${ID_LIKE}" ]; then
@@ -53,40 +53,40 @@ cp "$TARGET_DIR/etc/hostname" "$TARGET_DIR/etc/hostname.d/10-default"
ixmsg "Creating /etc/os-release"
rm -f "$TARGET_DIR/etc/os-release"
{
echo "NAME=\"$INFIX_NAME\""
echo "ID=$INFIX_ID"
echo "PRETTY_NAME=\"$INFIX_TAGLINE $INFIX_VERSION\""
echo "NAME=\"$IX_NAME\""
echo "ID=$IX_ID"
echo "PRETTY_NAME=\"$IX_TAGLINE $INFIX_VERSION\""
echo "ID_LIKE=\"${ID}\""
echo "DEFAULT_HOSTNAME=$BR2_TARGET_GENERIC_HOSTNAME"
echo "VERSION=\"${INFIX_VERSION}\""
echo "VERSION_ID=${INFIX_VERSION}"
echo "BUILD_ID=\"${INFIX_BUILD_ID}\""
if [ -n "$INFIX_IMAGE_ID" ]; then
echo "IMAGE_ID=\"$INFIX_IMAGE_ID\""
if [ -n "$IX_IMAGE_ID" ]; then
echo "IMAGE_ID=\"$IX_IMAGE_ID\""
fi
if [ -n "$INFIX_RELEASE" ]; then
echo "IMAGE_VERSION=\"$INFIX_RELEASE\""
fi
echo "ARCHITECTURE=\"${INFIX_ARCH}\""
echo "HOME_URL=$INFIX_HOME"
if [ -n "$INFIX_VENDOR" ]; then
echo "VENDOR_NAME=\"$INFIX_VENDOR\""
echo "ARCHITECTURE=\"${IX_ARCH}\""
echo "HOME_URL=$IX_HOME"
if [ -n "$IX_VENDOR" ]; then
echo "VENDOR_NAME=\"$IX_VENDOR\""
fi
if [ -n "$INFIX_VENDOR_HOME" ]; then
echo "VENDOR_HOME=\"$INFIX_VENDOR_HOME\""
if [ -n "$IX_VENDOR_HOME" ]; then
echo "VENDOR_HOME=\"$IX_VENDOR_HOME\""
fi
if [ -n "$INFIX_DOC" ]; then
echo "DOCUMENTATION_URL=\"$INFIX_DOC\""
if [ -n "$IX_DOC" ]; then
echo "DOCUMENTATION_URL=\"$IX_DOC\""
fi
if [ -n "$INFIX_SUPPORT" ]; then
echo "SUPPORT_URL=\"$INFIX_SUPPORT\""
if [ -n "$IX_SUPPORT" ]; then
echo "SUPPORT_URL=\"$IX_SUPPORT\""
fi
if [ -n "$INFIX_DESC" ]; then
echo "INFIX_DESC=\"$INFIX_DESC\""
if [ -n "$IX_DESC" ]; then
echo "INFIX_DESC=\"$IX_DESC\""
fi
} > "$TARGET_DIR/etc/os-release"
echo "$INFIX_TAGLINE $INFIX_VERSION -- $(date +"%b %e %H:%M %Z %Y")" > "$TARGET_DIR/etc/version"
echo "$IX_TAGLINE $INFIX_VERSION -- $(date +"%b %e %H:%M %Z %Y")" > "$TARGET_DIR/etc/version"
ixmsg "Creating /etc/version: $(cat "$TARGET_DIR/etc/version")"
# In case of ambguities, this is what the image was built from
+92 -92
View File
@@ -1,85 +1,85 @@
mainmenu "QEMU Virtualization"
config QEMU_ARCH_IS_32
config IX_QEMU_ARCH_IS_32
bool
config QEMU_ARCH_IS_64
config IX_QEMU_ARCH_IS_64
bool
choice
prompt "Target Architecture"
default @ARCH@
config QEMU_riscv64
bool "risv64"
select QEMU_ARCH_IS_64
config IX_QEMU_riscv64
bool "riscv64"
select IX_QEMU_ARCH_IS_64
config QEMU_x86_64
config IX_QEMU_x86_64
bool "x86_64"
select QEMU_ARCH_IS_64
select IX_QEMU_ARCH_IS_64
config QEMU_arm
config IX_QEMU_arm
bool "AArch32 (little endian)"
select QEMU_ARCH_IS_32
select IX_QEMU_ARCH_IS_32
config QEMU_aarch64
config IX_QEMU_aarch64
bool "AArch64 (little endian)"
select QEMU_ARCH_IS_64
select IX_QEMU_ARCH_IS_64
endchoice
choice
prompt "Loader"
default QEMU_LOADER_KERNEL
default IX_QEMU_LOADER_KERNEL
config QEMU_LOADER_KERNEL
config IX_QEMU_LOADER_KERNEL
bool "Kernel"
config QEMU_LOADER_UBOOT
config IX_QEMU_LOADER_UBOOT
bool "U-Boot"
depends on QEMU_aarch64
depends on IX_QEMU_aarch64
config QEMU_LOADER_OVMF
config IX_QEMU_LOADER_OVMF
bool "OVMF (UEFI)"
depends on QEMU_x86_64
depends on IX_QEMU_x86_64
endchoice
choice
prompt "Rootfs type"
default QEMU_ROOTFS_INITRD
default IX_QEMU_ROOTFS_INITRD
config QEMU_ROOTFS_MMC
config IX_QEMU_ROOTFS_MMC
bool "MMC"
depends on QEMU_aarch64
depends on IX_QEMU_aarch64
config QEMU_ROOTFS_INITRD
config IX_QEMU_ROOTFS_INITRD
bool "Initrd"
depends on QEMU_LOADER_KERNEL
depends on IX_QEMU_LOADER_KERNEL
config QEMU_ROOTFS_VSCSI
config IX_QEMU_ROOTFS_VSCSI
bool "Virtio SCSI"
endchoice
choice
prompt "Console"
default QEMU_CONSOLE_SERIAL if QEMU_arm
default QEMU_CONSOLE_VIRTIO
default IX_QEMU_CONSOLE_SERIAL if IX_QEMU_arm
default IX_QEMU_CONSOLE_VIRTIO
config QEMU_CONSOLE_VIRTIO
config IX_QEMU_CONSOLE_VIRTIO
bool "Virtio (hvc0)"
config QEMU_CONSOLE_SERIAL
config IX_QEMU_CONSOLE_SERIAL
bool "Serial (ttyS0/ttyAMA0)"
depends on !QEMU_LOADER_OVMF
depends on !IX_QEMU_LOADER_OVMF
endchoice
config QEMU_MACHINE
config IX_QEMU_MACHINE
string "Select emulated machine"
default "qemu-system-arm -M virt,accel=kvm:tcg -cpu max" if QEMU_arm
default "qemu-system-aarch64 -M virt,accel=kvm:tcg -cpu cortex-a53" if QEMU_aarch64
default "qemu-system-x86_64 -M pc,accel=kvm:tcg -cpu max" if QEMU_x86_64
default "qemu-system-arm -M virt,accel=kvm:tcg -cpu max" if IX_QEMU_arm
default "qemu-system-aarch64 -M virt,accel=kvm:tcg -cpu cortex-a53" if IX_QEMU_aarch64
default "qemu-system-x86_64 -M pc,accel=kvm:tcg -cpu max" if IX_QEMU_x86_64
help
You should not have to change this setting, although you may
want to tweak it, or change the acceleration.
@@ -88,7 +88,7 @@ config QEMU_MACHINE
the defconfig you started with. Currently Infix supports
arm (AArch32), aarch64 (ARM64), and x86_64 (AMD64).
config QEMU_MACHINE_RAM
config IX_QEMU_MACHINE_RAM
string "RAM size (k/M/G)"
default "448M"
help
@@ -96,62 +96,62 @@ config QEMU_MACHINE_RAM
if you get kernel panic with: "System is deadlocked on memory",
try increasing this one.
config QEMU_KERNEL
config IX_QEMU_KERNEL
string
depends on QEMU_LOADER_KERNEL
default "../zImage" if QEMU_arm
default "../Image" if QEMU_aarch64
default "../bzImage" if QEMU_x86_64
depends on IX_QEMU_LOADER_KERNEL
default "../zImage" if IX_QEMU_arm
default "../Image" if IX_QEMU_aarch64
default "../bzImage" if IX_QEMU_x86_64
config QEMU_BIOS
config IX_QEMU_BIOS
string
depends on !QEMU_LOADER_KERNEL
default "../u-boot.bin" if QEMU_LOADER_UBOOT
default "../OVMF.fd" if QEMU_LOADER_OVMF
depends on !IX_QEMU_LOADER_KERNEL
default "../u-boot.bin" if IX_QEMU_LOADER_UBOOT
default "../OVMF.fd" if IX_QEMU_LOADER_OVMF
config QEMU_ROOTFS
config IX_QEMU_ROOTFS
string
default "@DISK_IMG@" if !QEMU_ROOTFS_INITRD
default "../rootfs.squashfs" if QEMU_ROOTFS_INITRD
default "@DISK_IMG@" if !IX_QEMU_ROOTFS_INITRD
default "../rootfs.squashfs" if IX_QEMU_ROOTFS_INITRD
config QEMU_DTB_EXTEND
config IX_QEMU_DTB_EXTEND
bool
depends on QEMU_LOADER_UBOOT
default y if QEMU_aarch64
depends on IX_QEMU_LOADER_UBOOT
default y if IX_QEMU_aarch64
if QEMU_ROOTFS_INITRD
if IX_QEMU_ROOTFS_INITRD
config QEMU_RW
config IX_QEMU_RW
string "Writable /cfg layer"
depends on QEMU_ROOTFS_INITRD
depends on IX_QEMU_ROOTFS_INITRD
default "cfg.ext4"
config QEMU_RW_VAR_OPT
config IX_QEMU_RW_VAR_OPT
bool "Separate writable /var"
if QEMU_RW_VAR_OPT
config QEMU_RW_VAR_SIZE
if IX_QEMU_RW_VAR_OPT
config IX_QEMU_RW_VAR_SIZE
string "Size of /var"
default "256M"
config QEMU_RW_VAR
config IX_QEMU_RW_VAR
string "Writable /var layer"
default "var.ext4"
endif
endif
config QEMU_VPD
config IX_QEMU_VPD
bool "Emulate a Vital Product Data (VPD) Memory"
config QEMU_HOST
config IX_QEMU_HOST
string "Export host filesystem path"
default "/tmp"
config QEMU_APPEND
config IX_QEMU_APPEND
string "Extra kernel options"
depends on !QEMU_ROOTFS_MMC
depends on !IX_QEMU_ROOTFS_MMC
config QEMU_EXTRA
config IX_QEMU_EXTRA
string "Extra QEMU options"
@@ -159,72 +159,72 @@ comment "RTC"
choice
prompt "Mode"
default QEMU_RTC_UTC
default IX_QEMU_RTC_UTC
config QEMU_RTC_UTC
config IX_QEMU_RTC_UTC
bool "UTC"
config QEMU_RTC_LOCAL
config IX_QEMU_RTC_LOCAL
bool "Local time"
config QEMU_RTC_RANDOM
config IX_QEMU_RTC_RANDOM
bool "Random"
endchoice
choice
prompt "Clock"
default QEMU_CLOCK_HOST
default IX_QEMU_CLOCK_HOST
config QEMU_CLOCK_HOST
config IX_QEMU_CLOCK_HOST
bool "Host clock"
config QEMU_CLOCK_RT
config IX_QEMU_CLOCK_RT
bool "Independent (monotonic)"
config QEMU_CLOCK_VM
config IX_QEMU_CLOCK_VM
bool "Virtual"
endchoice
config QEMU_RTC
config IX_QEMU_RTC
string
default "utc" if QEMU_RTC_UTC
default "localtime" if QEMU_RTC_LOCAL
default "random" if QEMU_RTC_RANDOM
default "utc" if IX_QEMU_RTC_UTC
default "localtime" if IX_QEMU_RTC_LOCAL
default "random" if IX_QEMU_RTC_RANDOM
config QEMU_CLOCK
config IX_QEMU_CLOCK
string
default "host" if QEMU_CLOCK_HOST
default "rt" if QEMU_CLOCK_RT
default "vm" if QEMU_CLOCK_VM
default "host" if IX_QEMU_CLOCK_HOST
default "rt" if IX_QEMU_CLOCK_RT
default "vm" if IX_QEMU_CLOCK_VM
comment "Networking"
choice
prompt "Network Mode"
default QEMU_NET_USER
default IX_QEMU_NET_USER
config QEMU_NET_NONE
config IX_QEMU_NET_NONE
bool "None"
config QEMU_NET_BRIDGE
config IX_QEMU_NET_BRIDGE
bool "Bridged"
config QEMU_NET_USER
config IX_QEMU_NET_USER
bool "User"
config QEMU_NET_TAP
config IX_QEMU_NET_TAP
bool "TAP"
config QEMU_NET_ROCKER
config IX_QEMU_NET_ROCKER
bool "Rocker"
endchoice
config QEMU_NET_MODEL
config IX_QEMU_NET_MODEL
string "Interface model"
default "virtio-net-device" if QEMU_arm
default "virtio-net-device" if IX_QEMU_arm
default "virtio-net-pci"
help
The default, virtio-net-pci, NIC works for most use-cases, but
@@ -233,23 +233,23 @@ config QEMU_NET_MODEL
Note: ARM 32-bit uses virtio-net-device (MMIO) by default.
config QEMU_NET_BRIDGE_DEV
config IX_QEMU_NET_BRIDGE_DEV
string "Bridge device"
depends on QEMU_NET_BRIDGE
depends on IX_QEMU_NET_BRIDGE
default "virbr0"
config QEMU_NET_USER_OPTS
config IX_QEMU_NET_USER_OPTS
string "User mode options"
depends on QEMU_NET_USER
depends on IX_QEMU_NET_USER
help
Extra -nic user,<OPTIONS>
config QEMU_NET_TAP_N
config IX_QEMU_NET_TAP_N
int "Number of TAPs"
depends on QEMU_NET_TAP
depends on IX_QEMU_NET_TAP
default 1
config QEMU_NET_PORTS
config IX_QEMU_NET_PORTS
int "Number of Rocker switch ports"
depends on QEMU_NET_ROCKER
depends on IX_QEMU_NET_ROCKER
default 10
+2 -2
View File
@@ -10,7 +10,7 @@ qemu-kconfig = \
BR2_CONFIG="$(BINARIES_DIR)/qemu/.config" \
$(BUILD_DIR)/buildroot-config/$(1) $(2) "$(BINARIES_DIR)/qemu/Config.in"
ifeq ($(QEMU_SCRIPTS),y)
ifeq ($(IX_QEMU_SCRIPTS),y)
.PHONY: run
run:
@@ -33,7 +33,7 @@ $(BINARIES_DIR)/qemu/run.sh: $(QEMU_SCRIPTS_DIR)/run.sh
$(BINARIES_DIR)/qemu/Config.in: $(QEMU_SCRIPTS_DIR)/Config.in.in
@mkdir -p $(dir $@)
@sed \
-e "s:@ARCH@:QEMU_$(BR2_ARCH):" \
-e "s:@ARCH@:IX_QEMU_$(BR2_ARCH):" \
-e "s:@DISK_IMG@:../$(INFIX_ARTIFACT).qcow2:" \
< $< >$@
+73 -73
View File
@@ -40,7 +40,7 @@ usage()
echo " ARGS1 Args before the '--' separator are for kernel space"
echo " -- Separator"
echo " ARGS2 Args after the '--' separator are for the init process"
echo " Also, qemu.cfg has QEMU_APPEND which can affect this."
echo " Also, qemu.cfg has IX_QEMU_APPEND which can affect this."
echo
echo "Example:"
echo " $prognm -- finit.debug"
@@ -62,45 +62,45 @@ load_qemucfg()
# shellcheck disable=SC1090
. "./.config"
[ "$CONFIG_QEMU_MACHINE" ] || die "Missing QEMU_MACHINE"
[ "$CONFIG_QEMU_ROOTFS" ] || die "Missing QEMU_ROOTFS"
[ "$CONFIG_IX_QEMU_MACHINE" ] || die "Missing IX_QEMU_MACHINE"
[ "$CONFIG_IX_QEMU_ROOTFS" ] || die "Missing IX_QEMU_ROOTFS"
[ -n "$CONFIG_QEMU_KERNEL" ] && [ -n "$CONFIG_QEMU_BIOS" ] \
&& die "QEMU_KERNEL conflicts with QEMU_BIOS"
[ -n "$CONFIG_IX_QEMU_KERNEL" ] && [ -n "$CONFIG_IX_QEMU_BIOS" ] \
&& die "IX_QEMU_KERNEL conflicts with IX_QEMU_BIOS"
[ -z "$CONFIG_QEMU_KERNEL" ] && [ -z "$CONFIG_QEMU_BIOS" ] \
&& die "QEMU_KERNEL or QEMU_BIOS must be set"
[ -z "$CONFIG_IX_QEMU_KERNEL" ] && [ -z "$CONFIG_IX_QEMU_BIOS" ] \
&& die "IX_QEMU_KERNEL or IX_QEMU_BIOS must be set"
}
loader_args()
{
if [ "$CONFIG_QEMU_BIOS" ]; then
echo -n "-bios $CONFIG_QEMU_BIOS "
elif [ "$CONFIG_QEMU_KERNEL" ]; then
echo -n "-kernel $CONFIG_QEMU_KERNEL "
if [ "$CONFIG_IX_QEMU_BIOS" ]; then
echo -n "-bios $CONFIG_IX_QEMU_BIOS "
elif [ "$CONFIG_IX_QEMU_KERNEL" ]; then
echo -n "-kernel $CONFIG_IX_QEMU_KERNEL "
fi
}
append_args()
{
# ARM 32-bit doesn't support virtio console properly, always use serial
if [ "$CONFIG_QEMU_arm" ]; then
if [ "$CONFIG_IX_QEMU_arm" ]; then
echo -n "console=ttyAMA0 "
elif [ "$CONFIG_QEMU_CONSOLE_VIRTIO" ]; then
elif [ "$CONFIG_IX_QEMU_CONSOLE_VIRTIO" ]; then
echo -n "console=hvc0 "
elif [ "$CONFIG_QEMU_x86_64" ]; then
elif [ "$CONFIG_IX_QEMU_x86_64" ]; then
echo -n "console=ttyS0 "
elif [ "$CONFIG_QEMU_aarch64" ]; then
elif [ "$CONFIG_IX_QEMU_aarch64" ]; then
echo -n "console=ttyAMA0 "
else
die "Unknown console"
fi
if [ "$CONFIG_QEMU_ROOTFS_INITRD" = "y" ]; then
if [ "$CONFIG_IX_QEMU_ROOTFS_INITRD" = "y" ]; then
# Size of initrd, rounded up to nearest kb
size=$((($(stat -c %s "$CONFIG_QEMU_ROOTFS") + 1023) >> 10))
size=$((($(stat -c %s "$CONFIG_IX_QEMU_ROOTFS") + 1023) >> 10))
echo -n "root=/dev/ram0 ramdisk_size=${size} "
elif [ "$CONFIG_QEMU_ROOTFS_VSCSI" = "y" ]; then
elif [ "$CONFIG_IX_QEMU_ROOTFS_VSCSI" = "y" ]; then
echo -n "root=PARTLABEL=primary "
fi
@@ -110,20 +110,20 @@ append_args()
echo -n "debug "
fi
echo -n "${QEMU_APPEND} ${QEMU_EXTRA_APPEND} "
echo -n "${IX_QEMU_APPEND} ${QEMU_EXTRA_APPEND} "
}
rootfs_args()
{
if [ "$CONFIG_QEMU_ROOTFS_INITRD" = "y" ]; then
echo -n "-initrd $CONFIG_QEMU_ROOTFS "
elif [ "$CONFIG_QEMU_ROOTFS_MMC" = "y" ]; then
if [ "$CONFIG_IX_QEMU_ROOTFS_INITRD" = "y" ]; then
echo -n "-initrd $CONFIG_IX_QEMU_ROOTFS "
elif [ "$CONFIG_IX_QEMU_ROOTFS_MMC" = "y" ]; then
echo -n "-device sdhci-pci "
echo -n "-device sd-card,drive=mmc "
echo -n "-drive id=mmc,file=$CONFIG_QEMU_ROOTFS,if=none,format=raw "
elif [ "$CONFIG_QEMU_ROOTFS_VSCSI" = "y" ]; then
echo -n "-drive id=mmc,file=$CONFIG_IX_QEMU_ROOTFS,if=none,format=raw "
elif [ "$CONFIG_IX_QEMU_ROOTFS_VSCSI" = "y" ]; then
# ARM 32-bit virt machine uses MMIO virtio devices, not PCI
if [ "$CONFIG_QEMU_arm" ]; then
if [ "$CONFIG_IX_QEMU_arm" ]; then
echo -n "-drive file=qemu.qcow2,if=none,format=qcow2,id=rootfs "
echo -n "-device virtio-blk-device,drive=rootfs "
else
@@ -140,9 +140,9 @@ serial_args()
echo -n "-chardev stdio,id=console0,mux=on "
echo -n "-mon chardev=console0 "
if [ "$CONFIG_QEMU_CONSOLE_VIRTIO" ]; then
if [ "$CONFIG_IX_QEMU_CONSOLE_VIRTIO" ]; then
echo -n "-device virtconsole,nr=0,name=console,chardev=console0 "
elif [ "$CONFIG_QEMU_CONSOLE_SERIAL" ]; then
elif [ "$CONFIG_IX_QEMU_CONSOLE_SERIAL" ]; then
echo -n "-serial chardev:console0 "
else
die "Unknown console"
@@ -175,7 +175,7 @@ usb_args()
rw_args()
{
[ "$CONFIG_QEMU_RW" ] || return
[ "$CONFIG_IX_QEMU_RW" ] || return
command -v mkfs.ext4 >/dev/null || die "$prognm: cannot find mkfs.ext4"
@@ -184,45 +184,45 @@ rw_args()
mkfs.ext4 -L aux "aux.ext4" >/dev/null 2>&1
fi
if ! [ -f "$CONFIG_QEMU_RW" ]; then
dd if=/dev/zero of="$CONFIG_QEMU_RW" bs=16M count=1 >/dev/null 2>&1
mkfs.ext4 -L cfg "$CONFIG_QEMU_RW" >/dev/null 2>&1
if ! [ -f "$CONFIG_IX_QEMU_RW" ]; then
dd if=/dev/zero of="$CONFIG_IX_QEMU_RW" bs=16M count=1 >/dev/null 2>&1
mkfs.ext4 -L cfg "$CONFIG_IX_QEMU_RW" >/dev/null 2>&1
fi
# ARM 32-bit virt machine uses MMIO virtio devices, not PCI
if [ "$CONFIG_QEMU_arm" ]; then
if [ "$CONFIG_IX_QEMU_arm" ]; then
echo -n "-drive file=aux.ext4,if=none,format=raw,id=aux "
echo -n "-device virtio-blk-device,drive=aux "
echo -n "-drive file=$CONFIG_QEMU_RW,if=none,format=raw,id=cfg "
echo -n "-drive file=$CONFIG_IX_QEMU_RW,if=none,format=raw,id=cfg "
echo -n "-device virtio-blk-device,drive=cfg "
if [ "$CONFIG_QEMU_RW_VAR_OPT" ]; then
if ! [ -f "$CONFIG_QEMU_RW_VAR" ]; then
dd if=/dev/zero of="$CONFIG_QEMU_RW_VAR" bs=$CONFIG_QEMU_RW_VAR_SIZE count=1 >/dev/null 2>&1
mkfs.ext4 -L var "$CONFIG_QEMU_RW_VAR" >/dev/null 2>&1
if [ "$CONFIG_IX_QEMU_RW_VAR_OPT" ]; then
if ! [ -f "$CONFIG_IX_QEMU_RW_VAR" ]; then
dd if=/dev/zero of="$CONFIG_IX_QEMU_RW_VAR" bs=$CONFIG_IX_QEMU_RW_VAR_SIZE count=1 >/dev/null 2>&1
mkfs.ext4 -L var "$CONFIG_IX_QEMU_RW_VAR" >/dev/null 2>&1
fi
echo -n "-drive file=$CONFIG_QEMU_RW_VAR,if=none,format=raw,id=var "
echo -n "-drive file=$CONFIG_IX_QEMU_RW_VAR,if=none,format=raw,id=var "
echo -n "-device virtio-blk-device,drive=var "
fi
else
echo -n "-drive file=aux.ext4,if=virtio,format=raw,bus=0,unit=3 "
echo -n "-drive file=$CONFIG_QEMU_RW,if=virtio,format=raw,bus=0,unit=1 "
echo -n "-drive file=$CONFIG_IX_QEMU_RW,if=virtio,format=raw,bus=0,unit=1 "
if [ "$CONFIG_QEMU_RW_VAR_OPT" ]; then
if ! [ -f "$CONFIG_QEMU_RW_VAR" ]; then
dd if=/dev/zero of="$CONFIG_QEMU_RW_VAR" bs=$CONFIG_QEMU_RW_VAR_SIZE count=1 >/dev/null 2>&1
mkfs.ext4 -L var "$CONFIG_QEMU_RW_VAR" >/dev/null 2>&1
if [ "$CONFIG_IX_QEMU_RW_VAR_OPT" ]; then
if ! [ -f "$CONFIG_IX_QEMU_RW_VAR" ]; then
dd if=/dev/zero of="$CONFIG_IX_QEMU_RW_VAR" bs=$CONFIG_IX_QEMU_RW_VAR_SIZE count=1 >/dev/null 2>&1
mkfs.ext4 -L var "$CONFIG_IX_QEMU_RW_VAR" >/dev/null 2>&1
fi
echo -n "-drive file=$CONFIG_QEMU_RW_VAR,if=virtio,format=raw,bus=0,unit=2 "
echo -n "-drive file=$CONFIG_IX_QEMU_RW_VAR,if=virtio,format=raw,bus=0,unit=2 "
fi
fi
}
host_args()
{
[ "$CONFIG_QEMU_HOST" ] || return
[ "$CONFIG_IX_QEMU_HOST" ] || return
echo -n "-virtfs local,path=$CONFIG_QEMU_HOST,security_model=none,writeout=immediate,mount_tag=hostfs "
echo -n "-virtfs local,path=$CONFIG_IX_QEMU_HOST,security_model=none,writeout=immediate,mount_tag=hostfs "
}
net_dev_args()
@@ -230,7 +230,7 @@ net_dev_args()
name="e$1"
mac=$(printf "02:00:00:00:00:%02x" "$1")
echo -n "-device $CONFIG_QEMU_NET_MODEL,netdev=$name,mac=$mac "
echo -n "-device $CONFIG_IX_QEMU_NET_MODEL,netdev=$name,mac=$mac "
echo "$name $mac" >>"$mactab"
}
@@ -252,29 +252,29 @@ net_args()
:> "$mactab"
echo -n "-fw_cfg name=opt/mactab,file=$mactab "
if [ "$CONFIG_QEMU_NET_BRIDGE" = "y" ]; then
echo -n "-netdev bridge,id=e1,br=$CONFIG_QEMU_NET_BRIDGE_DEV "
if [ "$CONFIG_IX_QEMU_NET_BRIDGE" = "y" ]; then
echo -n "-netdev bridge,id=e1,br=$CONFIG_IX_QEMU_NET_BRIDGE_DEV "
net_dev_args 1
elif [ "$CONFIG_QEMU_NET_TAP" = "y" ]; then
for i in $(seq 1 "$CONFIG_QEMU_NET_TAP_N"); do
elif [ "$CONFIG_IX_QEMU_NET_TAP" = "y" ]; then
for i in $(seq 1 "$CONFIG_IX_QEMU_NET_TAP_N"); do
echo -n "-netdev tap,id=e$i,ifname=qtap$i "
net_dev_args "$i"
done
elif [ "$CONFIG_QEMU_NET_ROCKER" = "y" ]; then
elif [ "$CONFIG_IX_QEMU_NET_ROCKER" = "y" ]; then
sw=sw0 # Only single switch support atm.
echo -n "-device '{\"driver\":\"rocker\", \"name\":\"${sw}\", "
echo -n "\"fp_start_macaddr\":\"02:00:00:00:00:01\", "
echo -n "\"ports\":["
for i in $(seq 1 "$CONFIG_QEMU_NET_PORTS"); do
for i in $(seq 1 "$CONFIG_IX_QEMU_NET_PORTS"); do
[ "$i" -gt 1 ] && echo -n ", "
echo -n "\"${sw}p${i}\""
done
echo -n "]}' "
for i in $(seq 1 "$CONFIG_QEMU_NET_PORTS"); do
for i in $(seq 1 "$CONFIG_IX_QEMU_NET_PORTS"); do
rocker_port_args 0 "$i"
done
elif [ "$CONFIG_QEMU_NET_USER" = "y" ]; then
[ "$CONFIG_QEMU_NET_USER_OPTS" ] && useropts=",$CONFIG_QEMU_NET_USER_OPTS"
elif [ "$CONFIG_IX_QEMU_NET_USER" = "y" ]; then
[ "$CONFIG_IX_QEMU_NET_USER_OPTS" ] && useropts=",$CONFIG_IX_QEMU_NET_USER_OPTS"
echo -n "-netdev user,id=e1${useropts} "
net_dev_args 1
else
@@ -285,7 +285,7 @@ net_args()
# Vital Product data
vpd_args()
{
[ "$CONFIG_QEMU_VPD" = "y" ] || return
[ "$CONFIG_IX_QEMU_VPD" = "y" ] || return
vpd_file="${qdir}/vpd"
@@ -324,8 +324,8 @@ random_date()
rtc_args()
{
rtc="${CONFIG_QEMU_RTC:-utc}"
clock="${CONFIG_QEMU_CLOCK:-host}"
rtc="${CONFIG_IX_QEMU_RTC:-utc}"
clock="${CONFIG_IX_QEMU_CLOCK:-host}"
if [ "$rtc" = "random" ]; then
rtc=$(random_date)
fi
@@ -358,28 +358,28 @@ extract_squashfs()
run_qemu()
{
# Auto-extract rootfs.squashfs from rootfs.itb if needed for initrd mode
if [ "$CONFIG_QEMU_ROOTFS_INITRD" = "y" ] && [ ! -f "$CONFIG_QEMU_ROOTFS" ]; then
itb="${CONFIG_QEMU_ROOTFS%.squashfs}.itb"
if [ "$CONFIG_IX_QEMU_ROOTFS_INITRD" = "y" ] && [ ! -f "$CONFIG_IX_QEMU_ROOTFS" ]; then
itb="${CONFIG_IX_QEMU_ROOTFS%.squashfs}.itb"
if [ -f "$itb" ]; then
extract_squashfs "$itb" "$CONFIG_QEMU_ROOTFS"
extract_squashfs "$itb" "$CONFIG_IX_QEMU_ROOTFS"
else
die "Missing $CONFIG_QEMU_ROOTFS and cannot find $itb to extract it from"
die "Missing $CONFIG_IX_QEMU_ROOTFS and cannot find $itb to extract it from"
fi
fi
if [ "$CONFIG_QEMU_ROOTFS_VSCSI" = "y" ]; then
if [ "$CONFIG_IX_QEMU_ROOTFS_VSCSI" = "y" ]; then
if ! qemu-img check "qemu.qcow2"; then
rm -f "qemu.qcow2"
fi
if [ ! -f "qemu.qcow2" ]; then
echo "Creating qcow2 disk image for Qemu ..."
qemu-img create -f qcow2 -o backing_file="$CONFIG_QEMU_ROOTFS" \
qemu-img create -f qcow2 -o backing_file="$CONFIG_IX_QEMU_ROOTFS" \
-F qcow2 "qemu.qcow2" > /dev/null
fi
fi
read -r qemu <<EOF
$CONFIG_QEMU_MACHINE -nodefaults -m $CONFIG_QEMU_MACHINE_RAM \
$CONFIG_IX_QEMU_MACHINE -nodefaults -m $CONFIG_IX_QEMU_MACHINE_RAM \
$(loader_args) \
$(rootfs_args) \
$(serial_args) \
@@ -391,14 +391,14 @@ run_qemu()
$(rtc_args) \
$(vpd_args) \
$(gdb_args) \
$CONFIG_QEMU_EXTRA
$CONFIG_IX_QEMU_EXTRA
EOF
# Save resulting command to a script, because I cannot for the life
# of me figure out how to embed the JSON snippet for Rocker and run
# it here without issues, spent way too much time on it -- Joachim
run=$(mktemp -t run.qemu.XXX)
echo "#!/bin/sh" > "$run"
if [ "$CONFIG_QEMU_KERNEL" ]; then
if [ "$CONFIG_IX_QEMU_KERNEL" ]; then
echo "$qemu -append \"$(append_args)\" $*" >> "$run"
else
echo "$qemu $*" >> "$run"
@@ -419,9 +419,9 @@ EOF
dtb_args()
{
[ "$CONFIG_QEMU_LOADER_UBOOT" ] || return
[ "$CONFIG_IX_QEMU_LOADER_UBOOT" ] || return
if [ "$CONFIG_QEMU_DTB_EXTEND" ]; then
if [ "$CONFIG_IX_QEMU_DTB_EXTEND" ]; then
# On the current architecture, QEMU will generate an internal
# DT based on the system configuration.
@@ -445,12 +445,12 @@ dtb_args()
generate_dot()
{
[ "$CONFIG_QEMU_NET_TAP" = "y" ] || return
[ "$CONFIG_IX_QEMU_NET_TAP" = "y" ] || return
hostports="<qtap1> qtap1"
targetports="<e1> e1"
edges="host:qtap1 -- target:e1 [kind=mgmt];"
for tap in $(seq 2 $((CONFIG_QEMU_NET_TAP_N - 1))); do
for tap in $(seq 2 $((CONFIG_IX_QEMU_NET_TAP_N - 1))); do
hostports="$hostports | <qtap$tap> qtap$tap "
targetports="$targetports | <e$tap> e$tap "
edges="$edges host:qtap$tap -- target:e$tap;"
@@ -485,7 +485,7 @@ EOF
menuconfig()
{
grep -q QEMU_MACHINE Config.in || die "$prognm: must be run from the $$O/images/qemu directory"
grep -q IX_QEMU_MACHINE Config.in || die "$prognm: must be run from the $$O/images/qemu directory"
command -v kconfig-mconf >/dev/null || die "$prognm: cannot find kconfig-mconf for menuconfig"
exec kconfig-mconf Config.in
}
@@ -24,6 +24,10 @@ net.ipv4.conf.all.arp_ignore=1
net.ipv4.ip_forward=1
net.ipv4.ip_forward_update_priority=0
# Allow binding to non-local addresses, e.g. floating VIPs not yet
# configured on an interface, see issue #1022
net.ipv4.ip_nonlocal_bind=1
net.ipv4.conf.all.forwarding=0
net.ipv4.conf.default.forwarding=0
@@ -7,6 +7,10 @@ net.ipv6.conf.all.ignore_routes_with_linkdown=1
net.ipv6.conf.all.forwarding=0
net.ipv6.conf.default.forwarding=0
# Allow binding to non-local addresses, e.g. floating VIPs not yet
# configured on an interface, see issue #1022
net.ipv6.ip_nonlocal_bind=1
# Accept router advertisements even when forwarding is enabled
net.ipv6.conf.all.accept_ra=2
net.ipv6.conf.default.accept_ra=2
+10 -1
View File
@@ -25,7 +25,7 @@ define UBOOT_PRE_BUILD_INSTALL_KEY
$(HOST_DIR)/bin/dtc -a 1024 <(echo '/dts-v1/; / { signature {}; };') \
>$(@D)/infix-key.dtb
$(foreach key, \
$(call qstrip,$(TRUSTED_KEYS_DEVELOPMENT_PATH)) $(call qstrip,$(TRUSTED_KEYS_EXTRA_PATH)),\
$(call qstrip,$(IX_TRUSTED_KEYS_DEVELOPMENT_PATH)) $(call qstrip,$(IX_TRUSTED_KEYS_EXTRA_PATH)),\
$(call uboot-add-pubkey,$(key),$(@D)/infix-key.dtb))
$(HOST_DIR)/bin/dtc -I dtb -O dts \
<$(@D)/infix-key.dtb \
@@ -46,3 +46,12 @@ define UBOOT_PRE_BUILD_INSTALL_ENV
$(@D)/arch/$(UBOOT_ARCH)/dts/
endef
UBOOT_PRE_BUILD_HOOKS += UBOOT_PRE_BUILD_INSTALL_ENV
# Stamp non-release builds so they cannot be mistaken for a release,
# U-Boot's setlocalversion picks up .scmversion, see issue #919.
define UBOOT_PRE_BUILD_DEVEL_VERSION
echo "-DEVEL" >$(@D)/.scmversion
endef
ifeq ($(INFIX_RELEASE),)
UBOOT_PRE_BUILD_HOOKS += UBOOT_PRE_BUILD_DEVEL_VERSION
endif
+11 -11
View File
@@ -143,11 +143,11 @@ BR2_PACKAGE_MARVELL_CN9130_CRB=y
BR2_PACKAGE_MARVELL_ESPRESSOBIN=y
BR2_PACKAGE_RASPBERRYPI_RPI64=y
BR2_PACKAGE_STYX_DCP_SC_28P=y
INFIX_VENDOR_HOME="https://www.kernelkit.org"
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
IX_VENDOR_HOME="https://www.kernelkit.org"
IX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
IX_HOME="https://github.com/kernelkit/infix/"
IX_DOC="https://www.kernelkit.org/infix/"
IX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_FEATURE_GPS=y
BR2_PACKAGE_FEATURE_WIFI=y
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
@@ -191,9 +191,9 @@ BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
BR2_PACKAGE_PCIUTILS=y
IMAGE_ITB_AUX=y
IMAGE_ITB_QCOW=y
IMAGE_ITB_RAUC=y
IMAGE_README=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
IX_IMAGE_ITB_AUX=y
IX_IMAGE_ITB_QCOW=y
IX_IMAGE_ITB_RAUC=y
IX_IMAGE_README=y
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+11 -11
View File
@@ -121,11 +121,11 @@ BR2_PACKAGE_MARVELL_CN9130_CRB=y
BR2_PACKAGE_MARVELL_ESPRESSOBIN=y
BR2_PACKAGE_RASPBERRYPI_RPI64=y
BR2_PACKAGE_STYX_DCP_SC_28P=y
INFIX_VENDOR_HOME="https://www.kernelkit.org"
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
IX_VENDOR_HOME="https://www.kernelkit.org"
IX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
IX_HOME="https://github.com/kernelkit/infix/"
IX_DOC="https://www.kernelkit.org/infix/"
IX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_CONFD=y
BR2_PACKAGE_NETD=y
BR2_PACKAGE_CONFD_TEST_MODE=y
@@ -151,9 +151,9 @@ BR2_PACKAGE_MDNS_ALIAS=y
BR2_PACKAGE_ONIEPROM=y
BR2_PACKAGE_ROUSETTE=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
IMAGE_ITB_AUX=y
IMAGE_ITB_QCOW=y
IMAGE_ITB_RAUC=y
IMAGE_README=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
IX_IMAGE_ITB_AUX=y
IX_IMAGE_ITB_QCOW=y
IX_IMAGE_ITB_RAUC=y
IX_IMAGE_README=y
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -25,5 +25,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+12 -11
View File
@@ -129,6 +129,7 @@ BR2_PACKAGE_HOST_DOSFSTOOLS=y
BR2_PACKAGE_HOST_E2FSPROGS=y
BR2_PACKAGE_HOST_ENVIRONMENT_SETUP=y
BR2_PACKAGE_HOST_GENEXT2FS=y
BR2_PACKAGE_HOST_GO_BIN=y
BR2_PACKAGE_HOST_KMOD_XZ=y
BR2_PACKAGE_HOST_MTOOLS=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
@@ -136,11 +137,11 @@ BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
BR2_PACKAGE_MICROCHIP_SAMA7G54_EK=y
BR2_PACKAGE_RASPBERRYPI_RPI2=y
INFIX_VENDOR_HOME="https://www.kernelkit.org"
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
IX_VENDOR_HOME="https://www.kernelkit.org"
IX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
IX_HOME="https://github.com/kernelkit/infix/"
IX_DOC="https://www.kernelkit.org/infix/"
IX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_FEATURE_GPS=y
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
BR2_PACKAGE_FEATURE_WIFI_REALTEK=y
@@ -173,9 +174,9 @@ BR2_PACKAGE_ROUSETTE=y
BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
IMAGE_ITB_AUX=y
IMAGE_ITB_QCOW=y
IMAGE_ITB_RAUC=y
IMAGE_README=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
IX_IMAGE_ITB_AUX=y
IX_IMAGE_ITB_QCOW=y
IX_IMAGE_ITB_RAUC=y
IX_IMAGE_README=y
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+11 -11
View File
@@ -119,11 +119,11 @@ BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
BR2_PACKAGE_MICROCHIP_SAMA7G54_EK=y
BR2_PACKAGE_RASPBERRYPI_RPI2=y
INFIX_VENDOR_HOME="https://www.kernelkit.org"
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
IX_VENDOR_HOME="https://www.kernelkit.org"
IX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
IX_HOME="https://github.com/kernelkit/infix/"
IX_DOC="https://www.kernelkit.org/infix/"
IX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_CONFD=y
BR2_PACKAGE_NETD=y
BR2_PACKAGE_CONFD_TEST_MODE=y
@@ -149,9 +149,9 @@ BR2_PACKAGE_MDNS_ALIAS=y
BR2_PACKAGE_ONIEPROM=y
BR2_PACKAGE_ROUSETTE=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
IMAGE_ITB_AUX=y
IMAGE_ITB_QCOW=y
IMAGE_ITB_RAUC=y
IMAGE_README=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
IX_IMAGE_ITB_AUX=y
IX_IMAGE_ITB_QCOW=y
IX_IMAGE_ITB_RAUC=y
IX_IMAGE_README=y
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -40,5 +40,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -38,5 +38,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -38,5 +38,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -38,5 +38,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -38,5 +38,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -38,5 +38,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -38,5 +38,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -45,5 +45,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+12 -11
View File
@@ -164,14 +164,15 @@ BR2_TARGET_UBOOT_SPL=y
BR2_TARGET_UBOOT_SPL_NAME="spl/u-boot-spl.bin.normal.out"
BR2_TARGET_UBOOT_CUSTOM_DTS_PATH="$(BR2_EXTERNAL_INFIX_PATH)/board/riscv64/visionfive2/uboot/visionfive2-env.dtsi"
BR2_PACKAGE_HOST_BMAP_TOOLS=y
BR2_PACKAGE_HOST_GO_BIN=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
INFIX_VENDOR_HOME="https://www.kernelkit.org"
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
IX_VENDOR_HOME="https://www.kernelkit.org"
IX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
IX_HOME="https://github.com/kernelkit/infix/"
IX_DOC="https://www.kernelkit.org/infix/"
IX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_FEATURE_GPS=y
BR2_PACKAGE_FEATURE_WIFI=y
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
@@ -210,9 +211,9 @@ BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
BR2_PACKAGE_PCIUTILS=y
IMAGE_ITB_AUX=y
IMAGE_ITB_QCOW=y
IMAGE_ITB_RAUC=y
IMAGE_README=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
IX_IMAGE_ITB_AUX=y
IX_IMAGE_ITB_QCOW=y
IX_IMAGE_ITB_RAUC=y
IX_IMAGE_README=y
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -40,5 +40,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -34,5 +34,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+2 -2
View File
@@ -34,5 +34,5 @@ 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
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+1 -1
View File
@@ -1,4 +1,4 @@
BR2_TARGET_ROOTFS_EXT2=y
BR2_TARGET_ROOTFS_EXT2_4=y
BR2_TARGET_ROOTFS_EXT2_SIZE="512M"
IMAGE_EXT4_RAUC=y
IX_IMAGE_EXT4_RAUC=y
+5
View File
@@ -0,0 +1,5 @@
# Prefer internal download mirror over official upstream sites. If
# the mirror is unreachable, e.g., off-site without VPN, Buildroot
# falls back to the upstream URL and then sources.buildroot.net
# Set up a local mirror and add IP to /etc/hosts to override
BR2_PRIMARY_SITE="http://mirror.internal/pub"
+11 -11
View File
@@ -139,11 +139,11 @@ BR2_PACKAGE_HOST_MTOOLS=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
INFIX_VENDOR_HOME="https://www.kernelkit.org"
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
IX_VENDOR_HOME="https://www.kernelkit.org"
IX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
IX_HOME="https://github.com/kernelkit/infix/"
IX_DOC="https://www.kernelkit.org/infix/"
IX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_FEATURE_GPS=y
BR2_PACKAGE_FEATURE_WIFI=y
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
@@ -185,9 +185,9 @@ BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
BR2_PACKAGE_PCIUTILS=y
IMAGE_ITB_AUX=y
IMAGE_ITB_QCOW=y
IMAGE_ITB_RAUC=y
IMAGE_README=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
IX_IMAGE_ITB_AUX=y
IX_IMAGE_ITB_QCOW=y
IX_IMAGE_ITB_RAUC=y
IX_IMAGE_README=y
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+11 -11
View File
@@ -118,11 +118,11 @@ BR2_PACKAGE_HOST_MTOOLS=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
INFIX_VENDOR_HOME="https://www.kernelkit.org"
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
IX_VENDOR_HOME="https://www.kernelkit.org"
IX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
IX_HOME="https://github.com/kernelkit/infix/"
IX_DOC="https://www.kernelkit.org/infix/"
IX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_CONFD=y
BR2_PACKAGE_NETD=y
BR2_PACKAGE_CONFD_TEST_MODE=y
@@ -148,9 +148,9 @@ BR2_PACKAGE_MDNS_ALIAS=y
BR2_PACKAGE_ONIEPROM=y
BR2_PACKAGE_ROUSETTE=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
IMAGE_ITB_AUX=y
IMAGE_ITB_QCOW=y
IMAGE_ITB_RAUC=y
IMAGE_README=y
TRUSTED_KEYS=y
TRUSTED_KEYS_DEVELOPMENT=y
IX_IMAGE_ITB_AUX=y
IX_IMAGE_ITB_QCOW=y
IX_IMAGE_ITB_RAUC=y
IX_IMAGE_README=y
IX_TRUSTED_KEYS=y
IX_TRUSTED_KEYS_DEVELOPMENT=y
+16
View File
@@ -3,6 +3,21 @@ Change Log
All notable changes to the project are documented in this file.
[v26.08.0][UNRELEASED]
-------------------------
### Changes
- Add support for firewall address-set (ipset): named sets of IP addresses and
networks, usable as zone sources for per-IP access control, issue #1189
- Build RPi64 SD card images in release builds
- Include .pkg files in release builds
### Fixes
- Fix annoying "cannot deselect all services" or reset to YANG default in the
web interface's firewall configuration page
[v26.06.0][] - 2026-07-01
-------------------------
@@ -2206,6 +2221,7 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
[buildroot]: https://buildroot.org/
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v26.06.0...HEAD
[v26.08.0]: https://github.com/kernelkit/infix/compare/v26.06.0...v26.08.0
[v26.06.0]: https://github.com/kernelkit/infix/compare/v26.05.0...v26.06.0
[v26.05.0]: https://github.com/kernelkit/infix/compare/v26.04.0...v26.05.0
[v26.04.0]: https://github.com/kernelkit/infix/compare/v26.03.0...v26.04.0
+2 -2
View File
@@ -28,7 +28,7 @@ Verify the result after a build by inspecting:
> [!IMPORTANT]
> To get a proper GIT revision (hash) from your OS spin, remember to set
> in menuconfig `INFIX_OEM_PATH`. When unset, the Infix `post-build.sh`
> in menuconfig `IX_OEM_PATH`. When unset, the Infix `post-build.sh`
> script defaults to the Infix base path. The revision is stored in the
> file `/etc/os-release` as `BUILD_ID`, also in the file `/etc/version`.
> See below for more info.
@@ -427,7 +427,7 @@ Used for `BUILD_ID` in `/etc/os-release`.
**Default:** `$(git describe --always --dirty --tags)`, from the _top
directory_. By default, the top directory refers to the root of the
Infix source tree, but this can be changed by setting the branding
variable `INFIX_OEM_PATH`, e.g. in a `defconfig` file or via `make
variable `IX_OEM_PATH`, e.g. in a `defconfig` file or via `make
menuconfig`, to the path of an enclosing br2-external.
#### `INFIX_RELEASE`
+3
View File
@@ -190,6 +190,9 @@ To apply a single snippet to the current output directory:
make apply-ext4 # build an ext4 rootfs (needed for boards
# whose bootloader lacks squashfs support,
# e.g. Marvell ESPRESSObin)
make apply-mirror # prefer an internal download mirror
# (BR2_PRIMARY_SITE) over upstream sites,
# see utils/mirror-sync.sh for populating it
The `apply-*` targets require an existing `.config` (i.e. you must have
already run a `make <board>_defconfig`). The snippet is merged using
+90
View File
@@ -223,6 +223,96 @@ The firewall includes over 100 pre-defined services, such as:
> See the YANG model for the full list, or tap the ++question++ key
> when setting up an allowed host service in a zone `set service`
## Address Sets
Address sets are named collections of IP addresses and networks that can be
used as zone *sources*, alongside the `network` setting. Traffic from a
member of the set is classified into that zone regardless of which interface
it arrives on. Since source matching takes precedence over interface
matching, an address set in a trusted zone can selectively lift devices out
of a restrictive interface zone.
This enables per-IP access control: block everything by default and grant
individual end devices access at runtime.
> [!IMPORTANT]
> Assigning an address set to a zone only decides which zone the source IP
> belongs to. It does **not** by itself grant access to the device. Access
> to HOST services is still controlled by the zone's `action` and `service`
> settings. A common pattern is to keep the interface or default zone
> restrictive (`reject`/`drop`) and attach the address set to a separate
> trusted zone with `action accept`, as shown below.
<pre class="cli"><code>admin@example:/> <b>configure</b>
admin@example:/config/> <b>edit firewall address-set allowed</b>
admin@example:/config/firewall/…/allowed/> <b>set description "End devices granted access"</b>
admin@example:/config/firewall/…/allowed/> <b>set entry 192.168.1.40</b>
admin@example:/config/firewall/…/allowed/> <b>end</b>
admin@example:/config/firewall/> <b>edit zone trusted</b>
admin@example:/config/firewall/…/trusted/> <b>set action accept</b>
admin@example:/config/firewall/…/trusted/> <b>set address-set allowed</b>
admin@example:/config/firewall/…/trusted/> <b>leave</b>
</code></pre>
### Static and Dynamic Entries
Entries come in two kinds:
- **Static** entries are set in the configuration, like `192.168.1.40`
above, and are restored at boot
- **Dynamic** entries are added and removed at runtime using the `add`,
`remove`, and `flush` actions. They take effect immediately and survive
firewall configuration changes, but are *not* saved to the configuration,
so a reboot starts from a clean slate
From admin-exec context in the CLI:
<pre class="cli"><code>admin@example:/> <b>firewall address-set allowed add 192.168.1.42</b>
admin@example:/> <b>show firewall address-set allowed</b>
name : allowed
family : ipv4
timeout : none
ENTRY TYPE EXPIRES
192.168.1.40 static
192.168.1.42 dynamic
admin@example:/> <b>firewall address-set allowed remove 192.168.1.42</b>
</code></pre>
The same actions are available over NETCONF and RESTCONF, e.g., allowing a
device from a network management system:
```json
~$ curl -kX POST -u admin:admin -H "Content-Type: application/yang-data+json" \
-d '{"infix-firewall:input": {"entry": "192.168.1.42"}}' \
https://example.local/restconf/data/infix-firewall:firewall/address-set=allowed/add
```
Static entries can only be removed by changing the configuration, the
`remove` action manages dynamic entries only. The `flush` action removes
all dynamic entries at once, leaving static entries in place.
### Expiring Entries
An address set can be created with a `timeout`, giving every dynamic entry a
limited lifetime. Such sets are dynamic-only: static entries cannot be
configured, and entries cannot be removed manually, they expire on their
own. This suits time-limited access grants and automated ban lists.
<pre class="cli"><code>admin@example:/config/firewall/> <b>edit address-set banned</b>
admin@example:/config/firewall/…/banned/> <b>set timeout 3600</b>
admin@example:/config/firewall/…/banned/> <b>leave</b>
admin@example:/> <b>firewall address-set banned add 203.0.113.99</b>
</code></pre>
The remaining lifetime of each entry is shown in the `EXPIRES` column of
<kbd>show firewall address-set</kbd>.
> [!NOTE]
> Entries in timeout sets do not survive firewall configuration changes,
> the set is flushed when the firewall configuration is rebuilt. Regular
> (non-timeout) sets keep their dynamic entries over configuration changes.
## Examples
### End Device Protection
+2 -2
View File
@@ -1,6 +1,6 @@
IXMSG = printf "\e[37;44m>>> $(call qstrip,$(1))\e[0m\n"
oem-dir := $(call qstrip,$(INFIX_OEM_PATH))
oem-dir := $(call qstrip,$(IX_OEM_PATH))
INFIX_TOPDIR = $(if $(oem-dir),$(oem-dir),$(BR2_EXTERNAL_INFIX_PATH))
# Unless the user specifies an explicit build id, source it from git.
@@ -10,6 +10,6 @@ INFIX_TOPDIR = $(if $(oem-dir),$(oem-dir),$(BR2_EXTERNAL_INFIX_PATH))
# artifacts (see INFIX_ARTIFACT below).
export INFIX_BUILD_ID ?= $(shell git -C $(INFIX_TOPDIR) describe --dirty --always --tags --exclude 'latest*')
export INFIX_VERSION = $(INFIX_BUILD_ID)
export INFIX_ARTIFACT = $(call qstrip,$(INFIX_IMAGE_ID)$(if $(INFIX_RELEASE),-$(INFIX_RELEASE)))
export INFIX_ARTIFACT = $(call qstrip,$(IX_IMAGE_ID)$(if $(INFIX_RELEASE),-$(INFIX_RELEASE)))
INFIX_CFLAGS:=-Wall -Werror -Wextra -Wno-unused-parameter -Wformat=2 -Wformat-overflow=2 -Winit-self -Wstrict-overflow=4 -Wno-format-truncation -Wno-format-nonliteral
+1
View File
@@ -0,0 +1 @@
sha256 49c4d5edbd3ee0a0f6b15ef097b79539530a574ceed8a4dffec1d02455933029 LICENSE
+1
View File
@@ -0,0 +1 @@
sha256 8cdb4def6e91fde66ed0880a6e6ecc364aa96012e53b3cfb990b53b9dab976d4 LICENSE
+30
View File
@@ -103,6 +103,20 @@ panic_off()
logger -p user.emerg "LOCKDOWN MODE DEACTIVATED - Normal network operation restored"
}
ipset_call()
{
method=$1
name=$2
entry=$3
if ! output=$(gdbus call --system --dest "$DEST" --object-path "$OBJECT" \
--method "$INTERFACE.ipset.$method" "$name" "$entry" 2>&1); then
logger -t firewall -p daemon.err "ipset $method $name $entry: $output"
print "Error: $output" >&2
return 1
fi
}
panic_status()
{
if is_panic_enabled; then
@@ -294,6 +308,7 @@ OPTIONS:
COMMANDS:
reload Reload firewall configuration
panic OPERATION Emergency panic mode: <on | off | status>
ipset CMD SET ENTRY Runtime ipset operation: <add | del> SET ENTRY
show Show comprehensive firewall status and configuration
help Show this help message
@@ -386,6 +401,21 @@ main()
;;
esac
;;
ipset)
case "${2:-}" in
add)
ipset_call addEntry "$3" "$4"
;;
del)
ipset_call removeEntry "$3" "$4"
;;
*)
echo "Error: Invalid ipset operation '$2'" >&2
echo "Use: $0 ipset {add|del} SET ENTRY" >&2
exit 1
;;
esac
;;
show)
show_status
;;
+370 -1
View File
@@ -6,6 +6,7 @@
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <arpa/inet.h>
#include <srx/common.h>
#include <srx/lyx.h>
@@ -24,6 +25,11 @@
#define FIREWALLD_ZONES_DIR FIREWALLD_DIR_NEXT "/zones"
#define FIREWALLD_SERVICES_DIR FIREWALLD_DIR_NEXT "/services"
#define FIREWALLD_POLICIES_DIR FIREWALLD_DIR_NEXT "/policies"
#define FIREWALLD_IPSETS_DIR FIREWALLD_DIR_NEXT "/ipsets"
#define IPSETS_ACTIVE_DIR FIREWALLD_DIR "/ipsets"
#define ADDRSET_RUNDIR "/run/confd/address-sets"
#define ENTRY_STRLEN 64 /* worst-case ip-prefix + margin */
static struct {
const char *yang;
@@ -64,6 +70,148 @@ static const char *policy_action_to_target(const char *action)
return policy_action_map[0].yang;
}
struct prefix {
int af;
uint8_t addr[16];
int len;
};
static int prefix_parse(const char *str, struct prefix *p)
{
char buf[ENTRY_STRLEN];
char *sep;
strlcpy(buf, str, sizeof(buf));
sep = strchr(buf, '/');
if (sep) {
*sep++ = 0;
p->len = atoi(sep);
} else {
p->len = -1;
}
if (inet_pton(AF_INET, buf, p->addr) == 1) {
p->af = AF_INET;
if (p->len < 0)
p->len = 32;
return 0;
}
if (inet_pton(AF_INET6, buf, p->addr) == 1) {
p->af = AF_INET6;
if (p->len < 0)
p->len = 128;
return 0;
}
return -1;
}
static bool prefix_overlap(const char *a, const char *b)
{
struct prefix pa, pb;
int len, i;
if (prefix_parse(a, &pa) || prefix_parse(b, &pb) || pa.af != pb.af)
return false;
len = pa.len < pb.len ? pa.len : pb.len;
for (i = 0; i < len / 8; i++) {
if (pa.addr[i] != pb.addr[i])
return false;
}
if (len % 8) {
uint8_t mask = 0xff << (8 - len % 8);
if ((pa.addr[i] & mask) != (pb.addr[i] & mask))
return false;
}
return true;
}
static bool shadow_has(const char *name, const char *entry)
{
char line[ENTRY_STRLEN];
bool found = false;
FILE *fp;
fp = fopenf("r", ADDRSET_RUNDIR "/%s", name);
if (!fp)
return false;
while (fgets(line, sizeof(line), fp)) {
chomp(line);
if (!strcmp(line, entry)) {
found = true;
break;
}
}
fclose(fp);
return found;
}
static int shadow_add(const char *name, const char *entry)
{
char line[ENTRY_STRLEN];
FILE *fp;
if (fmkpath(0755, ADDRSET_RUNDIR) && errno != EEXIST) {
ERRNO("Failed creating " ADDRSET_RUNDIR);
return -1;
}
fp = fopenf("a+", ADDRSET_RUNDIR "/%s", name);
if (!fp) {
ERRNO("Failed recording dynamic entry for address-set %s", name);
return -1;
}
while (fgets(line, sizeof(line), fp)) {
chomp(line);
if (!strcmp(line, entry)) {
fclose(fp);
return 0;
}
}
fprintf(fp, "%s\n", entry);
fclose(fp);
return 0;
}
static int shadow_del(const char *name, const char *entry)
{
char curr[sizeof(ADDRSET_RUNDIR) + ENTRY_STRLEN], next[sizeof(curr) + 1];
char line[ENTRY_STRLEN];
FILE *in, *out;
snprintf(curr, sizeof(curr), ADDRSET_RUNDIR "/%s", name);
snprintf(next, sizeof(next), "%s+", curr);
in = fopen(curr, "r");
if (!in)
return -1;
out = fopen(next, "w");
if (!out) {
fclose(in);
return -1;
}
while (fgets(line, sizeof(line), in)) {
chomp(line);
if (!strcmp(line, entry))
continue;
fprintf(out, "%s\n", line);
}
fclose(in);
fclose(out);
return rename(next, curr);
}
static void mark_interfaces_used(struct lyd_node *cfg, char **ifaces)
{
struct lyd_node *node;
@@ -177,6 +325,9 @@ static int generate_zone(struct lyd_node *cfg, const char *name, char **ifaces)
LYX_LIST_FOR_EACH(lyd_child(cfg), node, "network")
fprintf(fp, " <source address=\"%s\"/>\n", lyd_get_value(node));
LYX_LIST_FOR_EACH(lyd_child(cfg), node, "address-set")
fprintf(fp, " <source ipset=\"%s\"/>\n", lyd_get_value(node));
LYX_LIST_FOR_EACH(lyd_child(cfg), node, "service")
fprintf(fp, " <service name=\"%s\"/>\n", lyd_get_value(node));
@@ -219,6 +370,85 @@ static int generate_zone(struct lyd_node *cfg, const char *name, char **ifaces)
return close_file(fp);
}
/*
* Dynamic entries, added at runtime with the add action, are folded
* into the generated ipset as regular entries so they survive the
* firewalld reload triggered by configuration changes. Entries that
* overlap new static configuration are dropped -- config wins, and
* nftables refuses overlapping elements in interval sets.
*/
static void merge_dynamic(FILE *fp, struct lyd_node *cfg, const char *name)
{
char line[ENTRY_STRLEN];
FILE *sf;
sf = fopenf("r", ADDRSET_RUNDIR "/%s", name);
if (!sf)
return;
while (fgets(line, sizeof(line), sf)) {
struct lyd_node *node;
bool skip = false;
chomp(line);
if (!line[0])
continue;
LYX_LIST_FOR_EACH(lyd_child(cfg), node, "entry") {
if (prefix_overlap(line, lyd_get_value(node))) {
skip = true;
break;
}
}
if (skip) {
NOTE("address-set %s: dropping dynamic entry %s, overlaps static entry",
name, line);
continue;
}
fprintf(fp, " <entry>%s</entry>\n", line);
}
fclose(sf);
}
static int generate_ipset(struct lyd_node *cfg, const char *name)
{
const char *family, *timeout, *desc;
struct lyd_node *node;
FILE *fp;
fp = open_file(FIREWALLD_IPSETS_DIR, name);
if (!fp)
return SR_ERR_SYS;
desc = lydx_get_cattr(cfg, "description");
family = lydx_get_cattr(cfg, "family");
timeout = lydx_get_cattr(cfg, "timeout");
fprintf(fp, "<ipset type=\"hash:net\">\n");
fprintf(fp, " <short>%s</short>\n", name);
if (desc)
fprintf(fp, " <description>%s</description>\n", desc);
fprintf(fp, " <option name=\"family\" value=\"%s\"/>\n",
family && !strcmp(family, "ipv6") ? "inet6" : "inet");
if (timeout)
fprintf(fp, " <option name=\"timeout\" value=\"%s\"/>\n", timeout);
LYX_LIST_FOR_EACH(lyd_child(cfg), node, "entry")
fprintf(fp, " <entry>%s</entry>\n", lyd_get_value(node));
if (!timeout)
merge_dynamic(fp, cfg, name);
fprintf(fp, "</ipset>\n");
return close_file(fp);
}
static int generate_service(struct lyd_node *cfg, const char *name)
{
const char *desc;
@@ -511,9 +741,17 @@ int firewall_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
if (!fisdir(FIREWALLD_DIR_NEXT)) {
/* Firewall is disabled */
finit_disable("firewalld");
rmrf(ADDRSET_RUNDIR);
return SR_ERR_OK;
}
/* Drop dynamic state of deleted address-sets */
clist = lydx_get_descendant(diff, "firewall", "address-set", NULL);
LYX_LIST_FOR_EACH(clist, cnode, "address-set") {
if (lydx_get_op(cnode) == LYDX_OP_DELETE)
erasef(ADDRSET_RUNDIR "/%s", lydx_get_cattr(cnode, "name"));
}
/* Firewall is enabled, roll in new configuration */
rmrf(FIREWALLD_DIR);
if (rename(FIREWALLD_DIR_NEXT, FIREWALLD_DIR)) {
@@ -551,7 +789,8 @@ int firewall_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
if (fmkpath(0755, FIREWALLD_DIR_NEXT) ||
fmkpath(0755, FIREWALLD_ZONES_DIR) ||
fmkpath(0755, FIREWALLD_SERVICES_DIR) ||
fmkpath(0755, FIREWALLD_POLICIES_DIR)) {
fmkpath(0755, FIREWALLD_POLICIES_DIR) ||
fmkpath(0755, FIREWALLD_IPSETS_DIR)) {
ERRNO("Failed creating " FIREWALLD_DIR_NEXT " directory structure");
err = SR_ERR_SYS;
goto done;
@@ -622,6 +861,11 @@ int firewall_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
LYX_LIST_FOR_EACH(clist, cnode, "service")
generate_service(cnode, lydx_get_cattr(cnode, "name"));
/* Regenerate all address-sets, incl. dynamic entries */
clist = lydx_get_descendant(tree, "firewall", "address-set", NULL);
LYX_LIST_FOR_EACH(clist, cnode, "address-set")
generate_ipset(cnode, lydx_get_cattr(cnode, "name"));
/* Regenerate all policies with sequential priority allocation */
clist = lydx_get_descendant(tree, "firewall", "policy", NULL);
LYX_LIST_FOR_EACH(clist, cnode, "policy") {
@@ -701,6 +945,128 @@ static int cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
return SR_ERR_OK;
}
static int addrset_flush(const char *name)
{
char line[ENTRY_STRLEN];
FILE *fp;
fp = fopenf("r", ADDRSET_RUNDIR "/%s", name);
if (!fp)
return SR_ERR_OK; /* no dynamic entries */
while (fgets(line, sizeof(line), fp)) {
chomp(line);
if (!line[0])
continue;
if (systemf("firewall ipset del %s %s", name, line))
ERROR("address-set %s: failed removing dynamic entry %s", name, line);
}
fclose(fp);
erasef(ADDRSET_RUNDIR "/%s", name);
return SR_ERR_OK;
}
/*
* Canonicalize like firewalld: host entries lose their prefix length,
* IPv6 is compressed. Keeps shadow file lookups exact-match.
*/
static const char *entry_canon(const char *entry, char *buf, size_t len)
{
char addr[INET6_ADDRSTRLEN];
struct prefix p;
if (prefix_parse(entry, &p) || !inet_ntop(p.af, p.addr, addr, sizeof(addr)))
return entry;
if ((p.af == AF_INET && p.len == 32) || (p.af == AF_INET6 && p.len == 128))
snprintf(buf, len, "%s", addr);
else
snprintf(buf, len, "%s/%d", addr, p.len);
return buf;
}
static int addrset(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath,
const sr_val_t *input, const size_t input_cnt, sr_event_t event,
uint32_t request_id, sr_val_t **output, size_t *output_cnt, void *priv)
{
char buf[strlen(xpath) + 1], canon[ENTRY_STRLEN], name[65];
const char *cmd = (const char *)priv;
const char *entry = NULL;
sr_xpath_ctx_t state = {};
sr_session_ctx_t *cfg;
char *val;
bool timeout;
/* /infix-firewall:firewall/address-set[name='allowed']/add */
strlcpy(buf, xpath, sizeof(buf));
val = sr_xpath_key_value(buf, "address-set", "name", &state);
if (!val)
return SR_ERR_INTERNAL;
strlcpy(name, val, sizeof(name));
if (input_cnt > 0)
entry = entry_canon(input[0].data.string_val, canon, sizeof(canon));
if (sr_session_start(sr_session_get_connection(session), SR_DS_RUNNING, &cfg))
return SR_ERR_INTERNAL;
val = srx_get_str(cfg, XPATH "/address-set[name='%s']/name", name);
if (!val) {
sr_session_stop(cfg);
sr_session_set_error_message(session, "No such address-set: %s", name);
return SR_ERR_INVAL_ARG;
}
free(val);
val = srx_get_str(cfg, XPATH "/address-set[name='%s']/timeout", name);
timeout = val != NULL;
free(val);
sr_session_stop(cfg);
DEBUG("address-set %s: %s %s", name, cmd, entry ?: "");
if (!strcmp(cmd, "add")) {
if (systemf("firewall ipset add %s %s", name, entry)) {
sr_session_set_error_message(session, "Failed adding %s to address-set %s, "
"see log for details", entry, name);
return SR_ERR_OPERATION_FAILED;
}
if (!timeout)
shadow_add(name, entry);
return SR_ERR_OK;
}
if (timeout) {
sr_session_set_error_message(session, "Entries in address-set %s expire on "
"their own (timeout set)", name);
return SR_ERR_UNSUPPORTED;
}
if (!strcmp(cmd, "flush"))
return addrset_flush(name);
/* remove */
if (!shadow_has(name, entry)) {
sr_session_set_error_message(session, "%s is not a dynamic entry in address-set %s, "
"static entries are removed via configuration", entry, name);
return SR_ERR_INVAL_ARG;
}
if (systemf("firewall ipset del %s %s", name, entry)) {
sr_session_set_error_message(session, "Failed removing %s from address-set %s, "
"see log for details", entry, name);
return SR_ERR_OPERATION_FAILED;
}
shadow_del(name, entry);
return SR_ERR_OK;
}
static int lockdown(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath,
const sr_val_t *input, const size_t input_cnt, sr_event_t event,
uint32_t request_id, sr_val_t **output, size_t *output_cnt, void *priv)
@@ -723,6 +1089,9 @@ int firewall_rpc_init(struct confd *confd)
int rc;
REGISTER_RPC(confd->session, XPATH "/lockdown-mode", lockdown, NULL, &confd->sub);
REGISTER_RPC(confd->session, XPATH "/address-set/add", addrset, "add", &confd->sub);
REGISTER_RPC(confd->session, XPATH "/address-set/remove", addrset, "remove", &confd->sub);
REGISTER_RPC(confd->session, XPATH "/address-set/flush", addrset, "flush", &confd->sub);
return SR_ERR_OK;
fail:
+1 -1
View File
@@ -38,7 +38,7 @@ MODULES=(
"infix-dhcp-client@2025-11-09.yang"
"infix-dhcpv6-client@2025-11-09.yang"
"infix-dhcp-server@2025-10-28.yang"
"infix-firewall@2025-04-26.yang"
"infix-firewall@2026-07-02.yang"
"infix-firewall-services@2025-04-26.yang"
"infix-firewall-icmp-types@2025-04-26.yang"
"infix-meta@2025-12-10.yang"
+153
View File
@@ -27,6 +27,13 @@ module infix-firewall {
contact "kernelkit@googlegroups.com";
description "Zone-based firewall inspired by firewalld concepts.";
revision 2026-07-02 {
description "Add address-set support: named sets of IP addresses and
networks, usable as zone sources. Includes add/remove/flush
actions for managing dynamic entries at runtime.";
reference "internal";
}
revision 2025-04-26 {
description "Initial revision.";
reference "internal";
@@ -95,6 +102,14 @@ module infix-firewall {
description "Action for traffic that does not match any specific service or port entry.";
}
typedef set-entry {
description "A member of an address-set: a host address or a network prefix.";
type union {
type inet:ip-address;
type inet:ip-prefix;
}
}
typedef protocol-type {
description "Network protocols supported for services and port definitions.";
@@ -220,6 +235,21 @@ module infix-firewall {
type inet:ip-prefix;
}
leaf-list address-set {
description "Address sets whose members are sources for this zone.
Like 'network', but members can also be added and removed
at runtime using the address-set add/remove actions.
Source matching takes precedence over interface matching,
so a member of an address set is classified into this zone
even when its traffic arrives on an interface assigned to
another zone.";
type leafref {
path "../../address-set/name";
}
}
leaf-list service {
description "Services allowed from this zone to HOST (INPUT chain only).
@@ -460,6 +490,129 @@ module infix-firewall {
}
}
list address-set {
description "Named sets of IP addresses and networks, usable as zone sources.
Entries configured here are static: part of the configuration
and restored at boot. Entries can also be added and removed
at runtime with the add/remove actions. Dynamic entries are
not saved to the configuration and are lost on reboot.
The 'current' list shows the actual contents of the set,
both static and dynamic entries.";
key "name";
must "not(timeout) or count(entry) = 0" {
error-message "Address sets with a timeout cannot have static entries";
}
leaf name {
description "Name of the address set.";
type ident;
}
leaf description {
description "Free-form description of the address set.";
type string {
pattern '[^\p{Cc}<>&]*';
}
}
leaf family {
description "Address family of the set. A set holds either IPv4 or
IPv6 entries, never both.";
type enumeration {
enum ipv4;
enum ipv6;
}
default ipv4;
}
leaf timeout {
description "Lifetime for dynamic entries, making the set dynamic-only.
Entries added with the add action expire after this many
seconds. Static entries cannot be configured, and dynamic
entries cannot be removed or flushed, they expire on their
own. Changes to the firewall configuration flush all
entries from timeout sets early.";
type uint32 {
range "1..max";
}
units "seconds";
}
leaf-list entry {
description "Static entries, part of the configuration.";
type set-entry;
must "not(contains(., ':')) or ../family = 'ipv6'" {
error-message "IPv6 entry in an IPv4 address-set";
}
must "contains(., ':') or ../family = 'ipv4'" {
error-message "IPv4 entry in an IPv6 address-set";
}
}
list current {
description "Entries currently active in the set.";
config false;
key "entry";
leaf entry {
description "Set member.";
type set-entry;
}
leaf dynamic {
description "Entry was added at runtime, not part of the configuration.";
type boolean;
}
leaf expires {
description "Seconds until this entry expires and is removed.";
type uint32;
units "seconds";
}
}
action add {
description "Add a dynamic entry to the set.
The entry takes effect immediately but is not saved to the
configuration, i.e., it is lost on reboot. For permanent
entries, use the 'entry' list instead.";
input {
leaf entry {
description "Host address or network prefix to add.";
type set-entry;
mandatory true;
}
}
}
action remove {
description "Remove a dynamic entry from the set.
Static entries cannot be removed with this action, only
by changing the configuration. Entries in timeout sets
cannot be removed, they expire on their own.";
input {
leaf entry {
description "Host address or network prefix to remove.";
type set-entry;
mandatory true;
}
}
}
action flush {
description "Remove all dynamic entries from the set. Static entries
remain. Not supported for timeout sets, their entries
expire on their own.";
}
}
leaf lockdown {
description "Current state of emergency lockdown mode.";
config false;
+60
View File
@@ -297,6 +297,64 @@ int infix_firewall_services(kcontext_t *ctx)
"| sed 's/^(//; s/,)$//' | tr \"'\" '\"' | jq -r '.[]' 2>/dev/null");
}
/*
* Completion function for firewall address-sets (ipsets).
* D-Bus returns variant format: (['set1', 'set2'],)
*/
int infix_firewall_addrsets(kcontext_t *ctx)
{
(void)ctx;
return firewall_dbus_completion("ipset", "getIPSets",
"sed 's/^(//; s/,)$//' | tr \"'\" '\"' | jq -r '.[]' 2>/dev/null");
}
/*
* Send address-set action (add/remove/flush) using the rpc tool, as the
* logged-in user to honor NACM. srp_rpc from klish-plugin-sysrepo only
* handles fixed xpaths, these actions live on list entries.
*/
int infix_firewall_addrset_action(kcontext_t *ctx)
{
kpargv_t *pargv = kcontext_pargv(ctx);
const char *name, *op;
char xpath[128];
char *argv[8];
kparg_t *parg;
int i = 0;
parg = kpargv_find(pargv, "setname");
if (!parg) {
fprintf(stderr, ERRMSG "missing address-set name\n");
return -1;
}
name = kparg_value(parg);
if (kpargv_find(pargv, "add"))
op = "add";
else if (kpargv_find(pargv, "remove"))
op = "remove";
else
op = "flush";
snprintf(xpath, sizeof(xpath),
"/infix-firewall:firewall/address-set[name='%s']/%s", name, op);
argv[i++] = "doas";
argv[i++] = "-u";
argv[i++] = (char *)cd_home(ctx);
argv[i++] = "rpc";
argv[i++] = xpath;
parg = kpargv_find(pargv, "entry");
if (parg) {
argv[i++] = "entry";
argv[i++] = (char *)kparg_value(parg);
}
argv[i] = NULL;
return run(argv);
}
int infix_copy(kcontext_t *ctx)
{
kpargv_t *pargv = kcontext_pargv(ctx);
@@ -689,6 +747,8 @@ int kplugin_infix_init(kcontext_t *ctx)
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));
kplugin_add_syms(plugin, ksym_new("firewall_addrsets", infix_firewall_addrsets));
kplugin_add_syms(plugin, ksym_new("firewall_addrset_action", infix_firewall_addrset_action));
kplugin_add_syms(plugin, ksym_new("set_boot_order", infix_set_boot_order));
kplugin_add_syms(plugin, ksym_new("shell", infix_shell));
kplugin_add_syms(plugin, ksym_new("ssh_connect", infix_ssh_connect));
+31
View File
@@ -134,6 +134,13 @@
<ACTION sym="STRING"/>
</PTYPE>
<PTYPE name="FIREWALL_ADDRSETS">
<COMPL>
<ACTION sym="firewall_addrsets@infix"/>
</COMPL>
<ACTION sym="STRING"/>
</PTYPE>
<PTYPE name="BOOT_TARGET">
<COMPL>
<ACTION sym="boot_targets@infix"/>
@@ -811,6 +818,14 @@ echo "Public: $pub"
copy operational -x /infix-firewall:firewall | /usr/libexec/statd/cli-pretty show-firewall-service "$KLISH_PARAM_name" |pager
</ACTION>
</COMMAND>
<COMMAND name="address-set" help="Show firewall address-sets">
<SWITCH name="optional" min="0">
<PARAM name="name" ptype="/FIREWALL_ADDRSETS" help="Address-set name"/>
</SWITCH>
<ACTION sym="script" in="tty" out="tty" interrupt="true">
copy operational -x /infix-firewall:firewall | /usr/libexec/statd/cli-pretty show-firewall-address-set "$KLISH_PARAM_name" |pager
</ACTION>
</COMMAND>
</SWITCH>
<ACTION sym="script" in="tty" out="tty" interrupt="true">
copy operational -x /infix-firewall:firewall | /usr/libexec/statd/cli-pretty show-firewall |pager
@@ -826,6 +841,22 @@ echo "Public: $pub"
</COMMAND>
<COMMAND name="firewall" help="Control the firewall" mode="switch">
<COMMAND name="address-set" help="Manage dynamic address-set entries">
<PARAM name="setname" ptype="/FIREWALL_ADDRSETS" help="Address-set name"/>
<SWITCH name="op">
<COMMAND name="add" help="Add dynamic entry, not saved to configuration">
<PARAM name="entry" ptype="/STRING" help="IP address or network prefix"/>
<ACTION sym="firewall_addrset_action@infix"/>
</COMMAND>
<COMMAND name="remove" help="Remove dynamic entry">
<PARAM name="entry" ptype="/STRING" help="IP address or network prefix"/>
<ACTION sym="firewall_addrset_action@infix"/>
</COMMAND>
<COMMAND name="flush" help="Remove all dynamic entries">
<ACTION sym="firewall_addrset_action@infix"/>
</COMMAND>
</SWITCH>
</COMMAND>
<COMMAND name="lockdown" help="Emergency lockdown mode">
<PARAM name="operation" ptype="/STRING" help="Lockdown commands.">
<COMPL>
+103 -11
View File
@@ -4149,6 +4149,7 @@ def show_firewall(json):
# Create tables
zone_table = firewall_zone_table(json)
policy_table = firewall_policy_table(json)
address_set_table = firewall_address_set_table(json)
# Add zone table
if zone_table:
@@ -4156,6 +4157,11 @@ def show_firewall(json):
canvas.add_table(zone_table)
canvas.add_spacing()
if address_set_table:
canvas.add_title("Address Sets")
canvas.add_table(address_set_table)
canvas.add_spacing()
# Add policy table
if policy_table:
canvas.add_title("Policies")
@@ -4447,13 +4453,13 @@ def firewall_matrix(fw, width=None):
zones = fw.get('zone', [])
policies = fw.get('policy', [])
# Build zone list - include zones with interfaces OR networks
# Build zone list - include zones with interfaces, networks, or address-set sources
zone_names = []
for z in zones:
interfaces = z.get('interface', [])
networks = z.get('network', [])
# Include if zone has interfaces OR networks (non-empty lists)
if interfaces or networks:
address_sets = z.get('address-set', [])
if interfaces or networks or address_sets:
zone_names.append(z['name'])
# Always add the implicit HOST zone
@@ -4558,6 +4564,7 @@ def firewall_zone_table(json):
Column('NAME', flexible=True),
Column('TYPE'),
Column('DATA', flexible=True),
Column('ADDR SET'),
Column('ALLOWED HOST SERVICES', flexible=True)
])
@@ -4566,6 +4573,7 @@ def firewall_zone_table(json):
action = zone.get('action', 'reject')
interface_list = zone.get('interface', [])
network_list = zone.get('network', [])
address_set_list = zone.get('address-set', [])
port_forwards = zone.get('port-forward', [])
services = zone.get('service', [])
@@ -4586,7 +4594,7 @@ def firewall_zone_table(json):
if interface_list:
interfaces = compress_interface_list(interface_list)
config_lines.append(("iif", interfaces))
else:
elif not network_list and not address_set_list and not port_forwards:
config_lines.append(("iif", "(none)"))
# Networks
@@ -4599,14 +4607,21 @@ def firewall_zone_table(json):
pf_display = format_port_forwards(port_forwards)
config_lines.append(("fwd", pf_display))
# Add first line with zone name and services
if config_lines:
first_type, first_data = config_lines[0]
zone_table.row(locked, name, first_type, first_data, services_display)
addr_rows = address_set_list[:] if address_set_list else ["(none)"]
row_count = max(len(config_lines), len(addr_rows))
if not config_lines:
config_lines = [("", "")]
# Add additional configuration lines as separate rows
for config_type, config_data in config_lines[1:]:
zone_table.row('', '', config_type, config_data, '')
for i in range(row_count):
if i < len(config_lines):
config_type, config_data = config_lines[i]
else:
config_type, config_data = "", ""
addr_set = addr_rows[i] if i < len(addr_rows) else ""
if i == 0:
zone_table.row(locked, name, config_type, config_data, addr_set, services_display)
else:
zone_table.row('', '', config_type, config_data, addr_set, '')
return zone_table
@@ -4645,6 +4660,9 @@ def show_firewall_zone(json, zone_name=None):
networks = zone.get('network', [])
if not networks:
networks = ""
address_sets = zone.get('address-set', [])
if not address_sets:
address_sets = ""
services = zone.get('service', [])
action = zone.get('action', 'reject')
@@ -4660,6 +4678,7 @@ def show_firewall_zone(json, zone_name=None):
print(f"{'action':<20}: {action}")
print(f"{'interface':<20}: {compress_interface_list(interfaces)}")
print(f"{'networks':<20}: {', '.join(networks)}")
print(f"{'address-sets':<20}: {', '.join(address_sets)}")
print(f"{'services (to HOST)':<20}: {services_display}")
# Show port forwards if any
@@ -4928,6 +4947,75 @@ def show_firewall_service(json, name=None):
service_table.print()
def firewall_address_set_table(json):
"""Create firewall address-sets table (returns SimpleTable or None)"""
fw = json.get('infix-firewall:firewall', {})
sets = fw.get('address-set', [])
if not sets:
return None
set_table = SimpleTable([
Column('NAME', flexible=True),
Column('FAMILY'),
Column('TIMEOUT'),
Column('STATIC'),
Column('DYNAMIC')
])
for aset in sets:
current = aset.get('current', [])
dynamic = sum(1 for cur in current if cur.get('dynamic'))
timeout = aset.get('timeout')
set_table.row(aset.get('name', ''),
aset.get('family', 'ipv4'),
f"{timeout} sec" if timeout else '',
str(len(current) - dynamic),
str(dynamic))
return set_table
def show_firewall_address_set(json, name=None):
"""Show firewall address-sets table or specific set details"""
fw = json.get('infix-firewall:firewall', {})
sets = fw.get('address-set', [])
if name:
aset = next((s for s in sets if s.get('name') == name), None)
if not aset:
print(f"Address-set '{name}' not found")
return
timeout = aset.get('timeout')
print(format_description('description', aset.get('description', '')))
print(f"{'name':<20}: {name}")
print(f"{'family':<20}: {aset.get('family', 'ipv4')}")
print(f"{'timeout':<20}: {f'{timeout} sec' if timeout else 'none'}")
print()
entry_table = SimpleTable([
Column('ENTRY'),
Column('TYPE'),
Column('EXPIRES')
])
for cur in aset.get('current', []):
expires = cur.get('expires')
entry_table.row(cur.get('entry', ''),
'dynamic' if cur.get('dynamic') else 'static',
f"{expires} sec" if expires is not None else '')
if entry_table.rows:
entry_table.min_width = 56
entry_table.print()
else:
print("(no entries)")
else:
set_table = firewall_address_set_table(json)
if set_table and set_table.rows:
set_table.min_width = 72
set_table.print()
else:
print("No address-sets configured")
def show_ospf(json_data):
"""Show OSPF general instance information"""
routing = json_data.get('ietf-routing:routing', {})
@@ -6018,6 +6106,8 @@ def main():
.add_argument('name', nargs='?', help='Policy name')
subparsers.add_parser('show-firewall-service', help='Show firewall services') \
.add_argument('name', nargs='?', help='Service name')
subparsers.add_parser('show-firewall-address-set', help='Show firewall address-sets') \
.add_argument('name', nargs='?', help='Address-set name')
subparsers.add_parser('show-firewall-log', help='Show firewall log') \
.add_argument('limit', nargs='?', help='Last N lines, default: all')
@@ -6095,6 +6185,8 @@ def main():
show_firewall_policy(json_data, args.name)
elif args.command == "show-firewall-service":
show_firewall_service(json_data, args.name)
elif args.command == "show-firewall-address-set":
show_firewall_address_set(json_data, args.name)
elif args.command == "show-firewall-log":
show_firewall_logs(args.limit)
elif args.command == "show-nacm":
+133 -2
View File
@@ -7,8 +7,37 @@ for the full API, see:
--object-path /org/fedoraproject/FirewallD1
"""
import dbus
import ipaddress
import re
from . import common
from .host import HOST
SHADOW_DIR = "/run/confd/address-sets"
def normalize_entry(entry):
"""Match firewalld entry normalization: bare address for host entries"""
try:
net = ipaddress.ip_network(str(entry), strict=False)
except ValueError:
return str(entry)
if net.prefixlen == net.max_prefixlen:
return str(net.network_address)
return str(net)
def split_sources(sources):
"""Zone sources are IP networks or 'ipset:NAME' address-set references"""
networks = []
ipsets = []
for src in sources:
src = str(src)
if src.startswith("ipset:"):
ipsets.append(src[len("ipset:"):])
else:
networks.append(src)
return networks, ipsets
def get_interface(interface="org.fedoraproject.FirewallD1"):
@@ -52,13 +81,15 @@ def get_zone_data(fw, name):
elif not short:
short = ""
networks, ipsets = split_sources(settings.get('sources', []))
zone = {
"name": name,
"short": short,
"immutable": immutable,
"description": settings.get('description', ''),
"interface": list(settings.get('interfaces', [])),
"network": list(settings.get('sources', [])),
"network": networks,
"address-set": ipsets,
"action": action.get(target, "accept"),
"service": list(settings.get('services', []))
}
@@ -132,8 +163,10 @@ def get_zones(fw):
for name, zone_info in active_zones.items():
zone_data = get_zone_data(fwz, name)
if zone_data:
networks, ipsets = split_sources(zone_info.get('sources', []))
zone_data['interface'] = list(zone_info.get('interfaces', []))
zone_data['network'] = list(zone_info.get('sources', []))
zone_data['network'] = networks
zone_data['address-set'] = ipsets
zones.append(zone_data)
except Exception as e:
@@ -281,6 +314,100 @@ def get_policies(fw):
return policies
def nft_set_elems(name):
"""Live contents of firewalld's nftables set
The kernel is the only source that sees entries in timeout sets,
and the only one tracking per-entry expiry. The firewalld table
is owner-protected, but reading is fine.
"""
data = HOST.run_json(("nft", "-j", "list", "set", "inet", "firewalld", name),
default={})
for obj in data.get("nftables", []):
if "set" in obj:
return obj["set"].get("elem", [])
return []
def nft_elem_parse(elem):
"""Return (entry, expires) from an nft JSON set element"""
expires = None
if isinstance(elem, dict) and "elem" in elem:
expires = elem["elem"].get("expires")
elem = elem["elem"].get("val")
if isinstance(elem, dict) and "prefix" in elem:
entry = f"{elem['prefix']['addr']}/{elem['prefix']['len']}"
elif isinstance(elem, dict) and "range" in elem:
entry = f"{elem['range'][0]}-{elem['range'][1]}"
else:
entry = str(elem)
return normalize_entry(entry), expires
def get_address_set(fwi, name):
try:
settings = fwi.getIPSetSettings(name)
# (version, short, description, type, options, entries)
options = settings[4]
tracked = [normalize_entry(e) for e in settings[5]]
except Exception as e:
common.LOG.warning("Failed querying ipset %s via D-Bus: %s", name, e)
return None
aset = {"name": str(name)}
description = str(settings[2])
if description:
aset["description"] = description
aset["family"] = "ipv6" if options.get("family") == "inet6" else "ipv4"
timeout = int(options.get("timeout", 0))
if timeout:
aset["timeout"] = timeout
lines = HOST.read_multiline(f"{SHADOW_DIR}/{name}", default=[])
shadow = {normalize_entry(line) for line in lines if line}
static = [e for e in tracked if e not in shadow]
if static:
aset["entry"] = static
current = []
for elem in nft_set_elems(name):
entry, expires = nft_elem_parse(elem)
cur = {"entry": entry, "dynamic": bool(timeout) or entry in shadow}
if expires is not None:
cur["expires"] = int(expires)
current.append(cur)
if current:
aset["current"] = current
return aset
def get_address_sets():
sets = []
fwi = get_interface("org.fedoraproject.FirewallD1.ipset")
if not fwi:
return sets
try:
names = fwi.getIPSets()
except Exception as e:
common.LOG.warning("Failed querying ipsets: %s", e)
return sets
for name in names:
data = get_address_set(fwi, name)
if data:
sets.append(data)
return sets
def get_service_data(fw, name):
try:
settings = fw.getServiceSettings2(name)
@@ -360,4 +487,8 @@ def operational():
if services:
data["infix-firewall:firewall"]["service"] = services
address_sets = get_address_sets()
if address_sets:
data["infix-firewall:firewall"]["address-set"] = address_sets
return data
+186 -81
View File
@@ -26,11 +26,6 @@ type cfgFwWrapper struct {
Firewall *firewallJSON `json:"infix-firewall:firewall,omitempty"`
}
// cfgFwZoneWrapper is used when reading a single zone by path.
type cfgFwZoneWrapper struct {
Zone []zoneJSON `json:"infix-firewall:zone"`
}
// ─── Template display rows ────────────────────────────────────────────────────
type cfgZoneRow struct {
@@ -38,8 +33,10 @@ type cfgZoneRow struct {
IfaceCount int
IfaceSet map[string]bool
ServiceSet map[string]bool
AddrSetSet map[string]bool
ServicesTxt string // fallback when ServiceOptions unavailable
NetworksTxt string // comma-separated, shown read-only when zone uses networks
AddrSetsTxt string // comma-separated address-set sources
}
type cfgPolicyRow struct {
@@ -56,9 +53,43 @@ type cfgServiceRow struct {
PortsDisplay string // "tcp:80,443; udp:53" — at-a-glance
}
// cfgFwSvcWrapper is used when reading a single service by path.
type cfgFwSvcWrapper struct {
Service []fwServiceJSON `json:"infix-firewall:service"`
type cfgAddrSetRow struct {
addressSetJSON
EntriesTxt string // one entry per line for the textarea
}
func zoneConfigBody(cur zoneJSON) map[string]any {
zone := map[string]any{
"name": cur.Name,
}
if cur.Action != "" {
zone["action"] = cur.Action
}
if cur.Description != "" {
zone["description"] = cur.Description
}
if len(cur.Interface) > 0 {
zone["interface"] = cur.Interface
}
if len(cur.Network) > 0 {
zone["network"] = cur.Network
}
if len(cur.AddressSet) > 0 {
zone["address-set"] = cur.AddressSet
}
if len(cur.Service) > 0 {
zone["service"] = cur.Service
}
return zone
}
// toSet builds a membership map for template "index" lookups.
func toSet(ss []string) map[string]bool {
set := make(map[string]bool, len(ss))
for _, s := range ss {
set[s] = true
}
return set
}
// ─── Template data ────────────────────────────────────────────────────────────
@@ -74,12 +105,15 @@ type cfgFirewallPageData struct {
ZoneNames []string // for policy ingress/egress multi-select
Policies []cfgPolicyRow
Services []cfgServiceRow
AddressSets []cfgAddrSetRow
AddressSetNames []string // for zone source multi-select
ProtoOptions []schema.IdentityOption
Desc map[string]string
LoggingOptions []schema.IdentityOption
ActionOptions []schema.IdentityOption
PolicyActionOptions []schema.IdentityOption
ServiceOptions []schema.IdentityOption
FamilyOptions []schema.IdentityOption
AllInterfaces []string
Error string
}
@@ -107,6 +141,7 @@ func (h *ConfigureFirewallHandler) Overview(w http.ResponseWriter, r *http.Reque
zPath := fwPath + "/zone"
pPath := fwPath + "/policy"
sPath := fwPath + "/service"
aPath := fwPath + "/address-set"
data.Desc = map[string]string{
"enabled": schema.DescriptionOf(mgr, fwPath+"/enabled"),
"default": schema.DescriptionOf(mgr, fwPath+"/default"),
@@ -127,12 +162,19 @@ func (h *ConfigureFirewallHandler) Overview(w http.ResponseWriter, r *http.Reque
"service-port-lower": schema.DescriptionOf(mgr, sPath+"/port/lower"),
"service-port-upper": schema.DescriptionOf(mgr, sPath+"/port/upper"),
"service-port-proto": schema.DescriptionOf(mgr, sPath+"/port/proto"),
"zone-address-set": schema.DescriptionOf(mgr, zPath+"/address-set"),
"addrset-name": schema.DescriptionOf(mgr, aPath+"/name"),
"addrset-description": schema.DescriptionOf(mgr, aPath+"/description"),
"addrset-family": schema.DescriptionOf(mgr, aPath+"/family"),
"addrset-timeout": schema.DescriptionOf(mgr, aPath+"/timeout"),
"addrset-entry": schema.DescriptionOf(mgr, aPath+"/entry"),
}
data.LoggingOptions = schema.OptionsFor(mgr, fwPath+"/logging")
data.ActionOptions = schema.OptionsFor(mgr, zPath+"/action")
data.PolicyActionOptions = schema.OptionsFor(mgr, pPath+"/action")
data.ServiceOptions = schema.OptionsFor(mgr, zPath+"/service")
data.ProtoOptions = schema.OptionsFor(mgr, sPath+"/port/proto")
data.FamilyOptions = schema.OptionsFor(mgr, aPath+"/family")
}
fw, active, err := h.fetchFirewall(r.Context())
@@ -150,44 +192,45 @@ func (h *ConfigureFirewallHandler) Overview(w http.ResponseWriter, r *http.Reque
data.Default = fw.Default
data.Logging = fw.Logging
for _, z := range fw.Zone {
ifaceSet := make(map[string]bool, len(z.Interface))
for _, iface := range z.Interface {
ifaceSet[iface] = true
}
svcSet := make(map[string]bool, len(z.Service))
for _, svc := range z.Service {
svcSet[svc] = true
}
data.Zones = append(data.Zones, cfgZoneRow{
zoneJSON: z,
IfaceCount: len(z.Interface),
IfaceSet: ifaceSet,
ServiceSet: svcSet,
IfaceSet: toSet(z.Interface),
ServiceSet: toSet(z.Service),
AddrSetSet: toSet(z.AddressSet),
ServicesTxt: strings.Join(z.Service, "\n"),
NetworksTxt: strings.Join(z.Network, ", "),
AddrSetsTxt: strings.Join(z.AddressSet, ", "),
})
data.ZoneNames = append(data.ZoneNames, z.Name)
}
for _, s := range fw.AddressSet {
if s.Family == "" {
// Display the schema default when the leaf is unset.
for _, opt := range data.FamilyOptions {
if opt.IsDefault {
s.Family = opt.Value
}
}
}
data.AddressSets = append(data.AddressSets, cfgAddrSetRow{
addressSetJSON: s,
EntriesTxt: strings.Join(s.Entry, "\n"),
})
data.AddressSetNames = append(data.AddressSetNames, s.Name)
}
for _, p := range fw.Policy {
masq := "—"
if p.Masquerade {
masq = "Yes"
}
ingressSet := make(map[string]bool, len(p.Ingress))
for _, z := range p.Ingress {
ingressSet[z] = true
}
egressSet := make(map[string]bool, len(p.Egress))
for _, z := range p.Egress {
egressSet[z] = true
}
data.Policies = append(data.Policies, cfgPolicyRow{
policyJSON: p,
IngressDisplay: strings.Join(p.Ingress, ", "),
EgressDisplay: strings.Join(p.Egress, ", "),
MasqDisplay: masq,
IngressSet: ingressSet,
EgressSet: egressSet,
IngressSet: toSet(p.Ingress),
EgressSet: toSet(p.Egress),
})
}
for _, s := range fw.Service {
@@ -303,7 +346,7 @@ func (h *ConfigureFirewallHandler) DeleteZone(w http.ResponseWriter, r *http.Req
renderSavedRedirect(w, "Zone deleted", "/configure/firewall")
}
// SaveZone updates a zone's action, description, interfaces, and services.
// SaveZone updates a zone's action, description, interfaces, address-sets, and services.
// Uses read-modify-write to preserve fields not managed by this UI (network,
// port-forward). Note: port-forward entries are lost on save (Phase 3 limitation).
// POST /configure/firewall/zones/{name}
@@ -314,15 +357,15 @@ func (h *ConfigureFirewallHandler) SaveZone(w http.ResponseWriter, r *http.Reque
}
name := r.PathValue("name")
var wrap cfgFwZoneWrapper
var wrap firewallWrapper // keyed GETs nest the zone under its full parent path
if err := h.RC.Get(r.Context(), fwConfigPath+"/zone="+url.PathEscape(name), &wrap); err != nil {
log.Printf("configure firewall zone save %q: GET: %v", name, err)
renderSaveError(w, err)
return
}
cur := zoneJSON{Name: name}
if len(wrap.Zone) > 0 {
cur = wrap.Zone[0]
if len(wrap.Firewall.Zone) > 0 {
cur = wrap.Firewall.Zone[0]
}
cur.Action = r.FormValue("action")
@@ -340,20 +383,13 @@ func (h *ConfigureFirewallHandler) SaveZone(w http.ResponseWriter, r *http.Reque
svcs = []string{}
}
cur.Service = svcs
sets := r.Form["address-sets"]
if sets == nil {
sets = []string{}
}
cur.AddressSet = sets
zone := map[string]any{
"name": cur.Name,
"action": cur.Action,
"interface": cur.Interface,
"service": cur.Service,
}
if cur.Description != "" {
zone["description"] = cur.Description
}
if len(cur.Network) > 0 {
zone["network"] = cur.Network
}
body := map[string]any{"infix-firewall:zone": []map[string]any{zone}}
body := map[string]any{"infix-firewall:zone": []map[string]any{zoneConfigBody(cur)}}
if err := h.RC.Put(r.Context(), fwConfigPath+"/zone="+url.PathEscape(name), body); err != nil {
log.Printf("configure firewall zone save %q: PUT: %v", name, err)
renderSaveError(w, err)
@@ -362,52 +398,38 @@ func (h *ConfigureFirewallHandler) SaveZone(w http.ResponseWriter, r *http.Reque
renderSavedRedirect(w, "Zone saved", "/configure/firewall")
}
// ResetZoneLeafList clears a leaf-list (interface or service) on a zone by
// re-PUTting the zone container without that field. RFC 8040 leaf-list
// DELETE requires per-entry key predicates, so a bulk clear has to go
// through the parent.
// ResetZoneLeafList clears a leaf-list (interface or service) on a zone.
// RFC 8040 leaf-list DELETE requires per-entry key predicates, so each
// instance is deleted individually. Deliberately avoids rebuilding the
// zone with PUT: the reset must not be able to touch anything else.
func (h *ConfigureFirewallHandler) resetZoneLeafList(w http.ResponseWriter, r *http.Request, leaf string) {
name := r.PathValue("name")
var wrap cfgFwZoneWrapper
var wrap firewallWrapper // keyed GETs nest the zone under its full parent path
if err := h.RC.Get(r.Context(), fwConfigPath+"/zone="+url.PathEscape(name), &wrap); err != nil {
log.Printf("configure firewall zone reset %s/%s: GET: %v", name, leaf, err)
renderSaveError(w, err)
return
}
cur := zoneJSON{Name: name}
if len(wrap.Zone) > 0 {
cur = wrap.Zone[0]
}
switch leaf {
case "interface":
cur.Interface = nil
case "service":
cur.Service = nil
var values []string
if len(wrap.Firewall.Zone) > 0 {
switch leaf {
case "interface":
values = wrap.Firewall.Zone[0].Interface
case "service":
values = wrap.Firewall.Zone[0].Service
}
}
zone := map[string]any{
"name": cur.Name,
"action": cur.Action,
for _, val := range values {
path := fmt.Sprintf("%s/zone=%s/%s=%s", fwConfigPath,
restconf.EscapeKey(name), leaf, restconf.EscapeKey(val))
if err := h.RC.Delete(r.Context(), path); err != nil && !restconf.IsNotFound(err) {
log.Printf("configure firewall zone reset %s/%s=%s: %v", name, leaf, val, err)
renderSaveError(w, err)
return
}
}
if cur.Description != "" {
zone["description"] = cur.Description
}
if len(cur.Interface) > 0 {
zone["interface"] = cur.Interface
}
if len(cur.Network) > 0 {
zone["network"] = cur.Network
}
if len(cur.Service) > 0 {
zone["service"] = cur.Service
}
body := map[string]any{"infix-firewall:zone": []map[string]any{zone}}
if err := h.RC.Put(r.Context(), fwConfigPath+"/zone="+url.PathEscape(name), body); err != nil {
log.Printf("configure firewall zone reset %s/%s: PUT: %v", name, leaf, err)
renderSaveError(w, err)
return
}
renderSaved(w, "Reset to default")
renderSavedRedirect(w, "Reset to default", "/configure/firewall")
}
// ResetZoneInterfaces clears the zone's interface leaf-list.
@@ -692,6 +714,89 @@ func (h *ConfigureFirewallHandler) DeleteService(w http.ResponseWriter, r *http.
renderSavedRedirect(w, "Service deleted", "/configure/firewall")
}
// ─── Address-sets CRUD ───────────────────────────────────────────────────────
// parseAddressSet builds the RESTCONF address-set body from the add/save
// form. Entries come from a textarea, one address or prefix per line.
func parseAddressSet(r *http.Request, name string) (map[string]any, error) {
set := map[string]any{"name": name}
if desc := strings.TrimSpace(r.FormValue("description")); desc != "" {
set["description"] = desc
}
if fam := r.FormValue("family"); fam != "" {
set["family"] = fam
}
if t := strings.TrimSpace(r.FormValue("timeout")); t != "" {
tv, err := strconv.Atoi(t)
if err != nil || tv < 1 {
return nil, fmt.Errorf("timeout must be a positive number of seconds")
}
set["timeout"] = tv
}
entries := []string{}
for _, line := range strings.Split(r.FormValue("entries"), "\n") {
if line = strings.TrimSpace(line); line != "" {
entries = append(entries, line)
}
}
if len(entries) > 0 {
set["entry"] = entries
}
return set, nil
}
func (h *ConfigureFirewallHandler) putAddressSet(w http.ResponseWriter, r *http.Request, name, saved string) {
set, err := parseAddressSet(r, name)
if err != nil {
renderSaveError(w, err)
return
}
body := map[string]any{"infix-firewall:address-set": []map[string]any{set}}
if err := h.RC.Put(r.Context(), fwConfigPath+"/address-set="+restconf.EscapeKey(name), body); err != nil {
log.Printf("configure firewall address-set save %q: %v", name, err)
renderSaveError(w, err)
return
}
renderSavedRedirect(w, saved, "/configure/firewall")
}
// AddAddressSet creates a new address-set.
// POST /configure/firewall/address-sets
func (h *ConfigureFirewallHandler) AddAddressSet(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
name := strings.TrimSpace(r.FormValue("name"))
if name == "" {
renderSaveError(w, fmt.Errorf("address-set name is required"))
return
}
h.putAddressSet(w, r, name, "Address-set added")
}
// SaveAddressSet updates an existing address-set.
// POST /configure/firewall/address-sets/{name}
func (h *ConfigureFirewallHandler) SaveAddressSet(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
h.putAddressSet(w, r, r.PathValue("name"), "Address-set saved")
}
// DeleteAddressSet removes an address-set.
// DELETE /configure/firewall/address-sets/{name}
func (h *ConfigureFirewallHandler) DeleteAddressSet(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
if err := h.RC.Delete(r.Context(), fwConfigPath+"/address-set="+restconf.EscapeKey(name)); err != nil {
log.Printf("configure firewall address-set delete %q: %v", name, err)
renderSaveError(w, err)
return
}
renderSavedRedirect(w, "Address-set deleted", "/configure/firewall")
}
// ─── Helpers ─────────────────────────────────────────────────────────────────
// fetchInterfaceNames returns configured interface names from candidate (fallback running).
@@ -0,0 +1,277 @@
// SPDX-License-Identifier: MIT
package handlers
import (
"context"
"encoding/json"
"html/template"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
"testing"
"infix/webui/internal/restconf"
"infix/webui/internal/schema"
"infix/webui/internal/security"
"infix/webui/internal/testutil"
)
var minimalCfgFwTmpl = template.Must(template.New("configure-firewall.html").Parse(
`{{define "configure-firewall.html"}}{{template "content" .}}{{end}}` +
`{{define "content"}}sets={{len .AddressSets}}` +
`{{range .AddressSets}};{{.Name}}:{{.EntriesTxt}}:{{if .Timeout}}{{.Timeout}}{{end}}{{end}}` +
`{{range .Zones}};zone-{{.Name}}:{{.AddrSetsTxt}}{{end}}{{end}}`,
))
func TestConfigureFirewallOverview_AddressSets(t *testing.T) {
mock := testutil.NewMockFetcher()
mock.SetResponse(candidatePath+"/infix-firewall:firewall", map[string]any{
"infix-firewall:firewall": map[string]any{
"default": "trusted",
"zone": []map[string]any{{
"name": "trusted",
"action": "accept",
"address-set": []string{"allowed"},
}},
"address-set": []map[string]any{{
"name": "allowed",
"entry": []string{"192.168.1.40", "10.0.0.0/24"},
}, {
"name": "banned",
"timeout": 3600,
}},
},
})
h := &ConfigureFirewallHandler{
Template: minimalCfgFwTmpl,
RC: mock,
Schema: schema.NewCache(mock, t.TempDir()),
}
req := httptest.NewRequest(http.MethodGet, "/configure/firewall", nil)
ctx := restconf.ContextWithCredentials(req.Context(), restconf.Credentials{
Username: "admin",
Password: "admin",
})
ctx = security.WithToken(ctx, "test-csrf-token")
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.Overview(w, req)
if w.Code != http.StatusOK {
t.Fatalf("want 200 got %d; body: %s", w.Code, w.Body.String())
}
body := w.Body.String()
for _, want := range []string{
"sets=2",
";allowed:192.168.1.40\n10.0.0.0/24:",
";banned::3600",
";zone-trusted:allowed",
} {
if !strings.Contains(body, want) {
t.Errorf("body missing %q; body: %s", want, body)
}
}
}
type recordingFetcher struct {
*testutil.MockFetcher
putCalls int
lastPath string
lastBody any
deletePaths []string
}
func (r *recordingFetcher) Put(_ context.Context, path string, body any) error {
r.putCalls++
r.lastPath = path
r.lastBody = body
return nil
}
func (r *recordingFetcher) Delete(_ context.Context, path string) error {
r.deletePaths = append(r.deletePaths, path)
return nil
}
// zoneGetResponse mimics the server's response shape for a keyed zone GET:
// the zone is nested under its full parent path, not returned bare.
func zoneGetResponse(zone map[string]any) map[string]any {
return map[string]any{
"infix-firewall:firewall": map[string]any{
"zone": []map[string]any{zone},
},
}
}
func TestConfigureFirewallSaveZoneAllowsInterfacesWithAddressSets(t *testing.T) {
mock := &recordingFetcher{MockFetcher: testutil.NewMockFetcher()}
mock.SetResponse(candidatePath+"/infix-firewall:firewall/zone=public", zoneGetResponse(map[string]any{
"name": "public",
"action": "drop",
"interface": []string{"eth0"},
"network": []string{"10.0.0.0/24"},
}))
h := &ConfigureFirewallHandler{
Template: minimalCfgFwTmpl,
RC: mock,
Schema: schema.NewCache(mock, t.TempDir()),
}
form := url.Values{
"action": {"drop"},
"description": {"Public zone"},
"interfaces": {"eth0"},
"address-sets": {"allowed"},
}
req := httptest.NewRequest(http.MethodPost, "/configure/firewall/zones/public", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetPathValue("name", "public")
ctx := restconf.ContextWithCredentials(req.Context(), restconf.Credentials{
Username: "admin",
Password: "admin",
})
ctx = security.WithToken(ctx, "test-csrf-token")
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.SaveZone(w, req)
if mock.putCalls != 1 {
t.Fatalf("want 1 PUT call got %d", mock.putCalls)
}
if w.Code != http.StatusNoContent {
t.Fatalf("want 204 got %d; body: %s", w.Code, w.Body.String())
}
if got, want := mock.lastPath, candidatePath+"/infix-firewall:firewall/zone=public"; got != want {
t.Fatalf("want PUT path %q got %q", want, got)
}
body, ok := mock.lastBody.(map[string]any)
if !ok {
t.Fatalf("unexpected PUT body type %T", mock.lastBody)
}
zones, ok := body["infix-firewall:zone"].([]map[string]any)
if !ok || len(zones) != 1 {
t.Fatalf("unexpected PUT zone payload %#v", body["infix-firewall:zone"])
}
zone := zones[0]
if got, want := zone["interface"], []string{"eth0"}; !reflect.DeepEqual(got, want) {
t.Fatalf("want interfaces %#v got %#v", want, got)
}
if got, want := zone["address-set"], []string{"allowed"}; !reflect.DeepEqual(got, want) {
t.Fatalf("want address-sets %#v got %#v", want, got)
}
if got, want := zone["network"], []string{"10.0.0.0/24"}; !reflect.DeepEqual(got, want) {
t.Fatalf("want networks preserved %#v got %#v", want, got)
}
var trig map[string]string
if err := json.Unmarshal([]byte(w.Header().Get("HX-Trigger")), &trig); err != nil {
t.Fatalf("unmarshal HX-Trigger: %v", err)
}
if got := trig["cfgSaved"]; !strings.Contains(got, "Zone saved") {
t.Fatalf("unexpected success message %q", got)
}
}
func TestConfigureFirewallSaveZoneClearsAllServices(t *testing.T) {
mock := &recordingFetcher{MockFetcher: testutil.NewMockFetcher()}
mock.SetResponse(candidatePath+"/infix-firewall:firewall/zone=public", zoneGetResponse(map[string]any{
"name": "public",
"action": "drop",
"interface": []string{"eth0"},
"service": []string{"ssh", "http"},
}))
h := &ConfigureFirewallHandler{
Template: minimalCfgFwTmpl,
RC: mock,
Schema: schema.NewCache(mock, t.TempDir()),
}
form := url.Values{
"action": {"drop"},
"description": {"Public zone"},
"interfaces": {"eth0"},
}
req := httptest.NewRequest(http.MethodPost, "/configure/firewall/zones/public", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetPathValue("name", "public")
ctx := restconf.ContextWithCredentials(req.Context(), restconf.Credentials{
Username: "admin",
Password: "admin",
})
ctx = security.WithToken(ctx, "test-csrf-token")
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.SaveZone(w, req)
if mock.putCalls != 1 {
t.Fatalf("want 1 PUT call got %d", mock.putCalls)
}
body, ok := mock.lastBody.(map[string]any)
if !ok {
t.Fatalf("unexpected PUT body type %T", mock.lastBody)
}
zones, ok := body["infix-firewall:zone"].([]map[string]any)
if !ok || len(zones) != 1 {
t.Fatalf("unexpected PUT zone payload %#v", body["infix-firewall:zone"])
}
if _, ok := zones[0]["service"]; ok {
t.Fatalf("expected cleared services to be omitted from payload, got %#v", zones[0]["service"])
}
}
func TestConfigureFirewallResetZoneServicesOnlyDeletesServices(t *testing.T) {
mock := &recordingFetcher{MockFetcher: testutil.NewMockFetcher()}
mock.SetResponse(candidatePath+"/infix-firewall:firewall/zone=public", zoneGetResponse(map[string]any{
"name": "public",
"action": "drop",
"interface": []string{"eth0"},
"address-set": []string{"allowed"},
"service": []string{"ssh", "dhcpv6-client"},
}))
h := &ConfigureFirewallHandler{
Template: minimalCfgFwTmpl,
RC: mock,
Schema: schema.NewCache(mock, t.TempDir()),
}
req := httptest.NewRequest(http.MethodDelete, "/configure/firewall/zones/public/services", nil)
req.SetPathValue("name", "public")
ctx := restconf.ContextWithCredentials(req.Context(), restconf.Credentials{
Username: "admin",
Password: "admin",
})
ctx = security.WithToken(ctx, "test-csrf-token")
req = req.WithContext(ctx)
w := httptest.NewRecorder()
h.ResetZoneServices(w, req)
if w.Code != http.StatusNoContent {
t.Fatalf("want 204 got %d; body: %s", w.Code, w.Body.String())
}
if got, want := w.Header().Get("HX-Location"), `{"path":"/configure/firewall","target":"#content"}`; got != want {
t.Fatalf("want HX-Location %q got %q", want, got)
}
if mock.putCalls != 0 {
t.Fatalf("reset must not rewrite the zone, got %d PUT call(s) with body %#v",
mock.putCalls, mock.lastBody)
}
want := []string{
candidatePath + "/infix-firewall:firewall/zone=public/service=ssh",
candidatePath + "/infix-firewall:firewall/zone=public/service=dhcpv6-client",
}
if !reflect.DeepEqual(mock.deletePaths, want) {
t.Fatalf("want DELETE paths %#v got %#v", want, mock.deletePaths)
}
}
@@ -1369,15 +1369,22 @@ func (h *ConfigureInterfacesHandler) ResetEthernetAdvertised(w http.ResponseWrit
name := r.PathValue("name")
base := ifacePath(name) + "/ieee802-ethernet-interface:ethernet"
// Keyed GETs nest the requested node under its full parent path.
var resp struct {
AN struct {
Enable *bool `json:"enable"`
} `json:"ieee802-ethernet-interface:auto-negotiation"`
Interfaces struct {
Interface []struct {
Ethernet struct {
AN struct {
Enable *bool `json:"enable"`
} `json:"auto-negotiation"`
} `json:"ieee802-ethernet-interface:ethernet"`
} `json:"interface"`
} `json:"ietf-interfaces:interfaces"`
}
enable := true // YANG default
if err := h.RC.Get(r.Context(), base+"/auto-negotiation", &resp); err == nil {
if resp.AN.Enable != nil {
enable = *resp.AN.Enable
if ifs := resp.Interfaces.Interface; len(ifs) > 0 && ifs[0].Ethernet.AN.Enable != nil {
enable = *ifs[0].Ethernet.AN.Enable
}
} else if !restconf.IsNotFound(err) {
log.Printf("configure interfaces %s reset advertised get: %v", name, err)
+5 -7
View File
@@ -46,11 +46,6 @@ type containerListWrapper struct {
} `json:"infix-containers:containers"`
}
// containerResourceUsageWrapper wraps the RESTCONF resource-usage response.
type containerResourceUsageWrapper struct {
ResourceUsage containerResourceUsageJSON `json:"infix-containers:resource-usage"`
}
// ContainerEntry holds display-ready data for a single container row.
type ContainerEntry struct {
Name string
@@ -106,13 +101,16 @@ func (h *ContainersHandler) Overview(w http.ResponseWriter, r *http.Request) {
defer wg.Done()
path := fmt.Sprintf("/data/infix-containers:containers/container=%s/resource-usage",
url.PathEscape(name))
var w containerResourceUsageWrapper
var w containerListWrapper // keyed GETs nest under the full parent path
if err := h.RC.Get(ctx, path, &w); err != nil {
log.Printf("restconf resource-usage %s: %v", name, err)
return
}
if len(w.Containers.Container) == 0 {
return
}
mu.Lock()
usages[idx] = w.ResourceUsage
usages[idx] = w.Containers.Container[0].ResourceUsage
mu.Unlock()
}(i, c.Name)
}
+77 -17
View File
@@ -19,13 +19,32 @@ type firewallWrapper struct {
}
type firewallJSON struct {
Enabled *yangBool `json:"enabled"` // YANG default: true; nil means enabled
Default string `json:"default"`
Logging string `json:"logging"`
Lockdown yangBool `json:"lockdown"`
Zone []zoneJSON `json:"zone"`
Policy []policyJSON `json:"policy"`
Service []fwServiceJSON `json:"service"`
Enabled *yangBool `json:"enabled"` // YANG default: true; nil means enabled
Default string `json:"default"`
Logging string `json:"logging"`
Lockdown yangBool `json:"lockdown"`
Zone []zoneJSON `json:"zone"`
Policy []policyJSON `json:"policy"`
Service []fwServiceJSON `json:"service"`
AddressSet []addressSetJSON `json:"address-set"`
}
// addressSetJSON models a named set of IP addresses/networks usable as zone
// source. The current list is operational state: the live set contents,
// including dynamic entries added at runtime.
type addressSetJSON struct {
Name string `json:"name"`
Description string `json:"description"`
Family string `json:"family"`
Timeout yangInt64 `json:"timeout"`
Entry []string `json:"entry"`
Current []addrSetCurJSON `json:"current"`
}
type addrSetCurJSON struct {
Entry string `json:"entry"`
Dynamic bool `json:"dynamic"`
Expires *yangInt64 `json:"expires"`
}
// fwServiceJSON models a user-defined firewall service (port + protocol bundle).
@@ -50,6 +69,7 @@ type zoneJSON struct {
Description string `json:"description"`
Interface []string `json:"interface"`
Network []string `json:"network"`
AddressSet []string `json:"address-set"`
Service []string `json:"service"`
PortForward []portForwardJSON `json:"port-forward"`
Immutable bool `json:"immutable"`
@@ -92,6 +112,7 @@ type firewallData struct {
Matrix []matrixRow
Zones []zoneEntry
Policies []policyEntry
AddressSets []addressSetEntry
Error string
}
@@ -110,11 +131,25 @@ type matrixCell struct {
}
type zoneEntry struct {
Name string
Action string
Interfaces string
Networks string
Services string // services allowed to HOST from this zone
Name string
Action string
Interfaces string
Networks string
AddressSets string
Services string // services allowed to HOST from this zone
}
type addressSetEntry struct {
Name string
Family string
Timeout string // "3600 s" for expiring sets, "" otherwise
Entries []addressSetEntryRow
}
type addressSetEntryRow struct {
Entry string
Dynamic bool
Expires string // remaining lifetime, "" when not expiring
}
type policyEntry struct {
@@ -169,14 +204,39 @@ func (h *FirewallHandler) Overview(w http.ResponseWriter, r *http.Request) {
for _, z := range f.Zone {
data.Zones = append(data.Zones, zoneEntry{
Name: z.Name,
Action: z.Action,
Interfaces: strings.Join(z.Interface, ", "),
Networks: strings.Join(z.Network, ", "),
Services: strings.Join(z.Service, ", "),
Name: z.Name,
Action: z.Action,
Interfaces: strings.Join(z.Interface, ", "),
Networks: strings.Join(z.Network, ", "),
AddressSets: strings.Join(z.AddressSet, ", "),
Services: strings.Join(z.Service, ", "),
})
}
for _, s := range f.AddressSet {
set := addressSetEntry{
Name: s.Name,
Family: s.Family,
}
if set.Family == "" {
set.Family = "ipv4" // YANG default
}
if s.Timeout > 0 {
set.Timeout = formatDuration(int64(s.Timeout))
}
for _, cur := range s.Current {
row := addressSetEntryRow{
Entry: cur.Entry,
Dynamic: cur.Dynamic,
}
if cur.Expires != nil {
row.Expires = formatDuration(int64(*cur.Expires))
}
set.Entries = append(set.Entries, row)
}
data.AddressSets = append(data.AddressSets, set)
}
for _, p := range f.Policy {
data.Policies = append(data.Policies, policyEntry{
Name: p.Name,
+9 -3
View File
@@ -21,8 +21,13 @@ type wgConfigJSON struct {
}
// wgIfaceConfigWrapper is used to fetch per-interface WireGuard config.
// Keyed GETs nest the requested node under its full parent path.
type wgIfaceConfigWrapper struct {
WireGuard *wgConfigJSON `json:"infix-interfaces:wireguard"`
Interfaces struct {
Interface []struct {
WireGuard *wgConfigJSON `json:"infix-interfaces:wireguard"`
} `json:"interface"`
} `json:"ietf-interfaces:interfaces"`
}
// WGPeer holds display-ready data for a single WireGuard peer.
@@ -128,8 +133,9 @@ func buildWGTunnel(ctx context.Context, rc *restconf.Client, iface ifaceJSON) WG
// Fetch ListenPort from config endpoint (separate from oper-state).
var cfgWrap wgIfaceConfigWrapper
path := fmt.Sprintf("/data/ietf-interfaces:interfaces/interface=%s/infix-interfaces:wireguard", iface.Name)
if err := rc.Get(ctx, path, &cfgWrap); err == nil && cfgWrap.WireGuard != nil {
tunnel.ListenPort = cfgWrap.WireGuard.ListenPort
if err := rc.Get(ctx, path, &cfgWrap); err == nil &&
len(cfgWrap.Interfaces.Interface) > 0 && cfgWrap.Interfaces.Interface[0].WireGuard != nil {
tunnel.ListenPort = cfgWrap.Interfaces.Interface[0].WireGuard.ListenPort
}
// Build peers from embedded peer-status.
+22 -1
View File
@@ -9,8 +9,10 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"reflect"
"strings"
"time"
)
@@ -79,6 +81,12 @@ func (c *Client) doRequest(ctx context.Context, method, path string) (*http.Resp
// Get fetches a RESTCONF resource, decoding the JSON response into target.
// User credentials are taken from the request context (set by auth middleware).
// Get sends a GET request and decodes the JSON response into target.
//
// NOTE: deep-path GETs (keyed list entries, nested containers) return the
// requested node nested under its full parent path, not bare as the RFC
// 8040 examples may suggest. Wrapper structs must model the nesting from
// the top-level container down, or the decode silently matches nothing.
func (c *Client) Get(ctx context.Context, path string, target any) error {
resp, err := c.doRequest(ctx, http.MethodGet, path)
if err != nil {
@@ -88,7 +96,20 @@ func (c *Client) Get(ctx context.Context, path string, target any) error {
if resp.StatusCode != http.StatusOK {
return parseError(resp)
}
return json.NewDecoder(resp.Body).Decode(target)
data, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if err := json.Unmarshal(data, target); err != nil {
return err
}
// Tripwire for the nesting trap above: content arrived but nothing
// in the wrapper matched it.
if v := reflect.ValueOf(target); len(data) > 2 &&
v.Kind() == reflect.Pointer && v.Elem().IsZero() {
log.Printf("restconf: GET %s decoded to zero %T, wrapper shape mismatch?", path, target)
}
return nil
}
// Post sends a POST request to a RESTCONF RPC endpoint.
+3
View File
@@ -424,6 +424,9 @@ func New(
mux.HandleFunc("POST /configure/firewall/services", cfgFw.AddService)
mux.HandleFunc("POST /configure/firewall/services/{name}", cfgFw.SaveService)
mux.HandleFunc("DELETE /configure/firewall/services/{name}", cfgFw.DeleteService)
mux.HandleFunc("POST /configure/firewall/address-sets", cfgFw.AddAddressSet)
mux.HandleFunc("POST /configure/firewall/address-sets/{name}", cfgFw.SaveAddressSet)
mux.HandleFunc("DELETE /configure/firewall/address-sets/{name}", cfgFw.DeleteAddressSet)
mux.HandleFunc("GET /configure/hardware", cfgHw.Overview)
mux.HandleFunc("POST /configure/hardware", cfgHw.CreateHardware)
mux.HandleFunc("POST /configure/hardware/usb/{name}", cfgHw.SaveUSBPort)
@@ -112,6 +112,7 @@
<th>Action{{template "field-info" (index $d "zone-action")}}</th>
<th>Description{{template "field-info" (index $d "zone-description")}}</th>
<th style="text-align:center">Interfaces</th>
<th>Address sets{{template "field-info" (index $d "zone-address-set")}}</th>
<th></th>
</tr>
</thead>
@@ -128,6 +129,7 @@
<td>{{if .Action}}{{.Action}}{{else}}reject{{end}}</td>
<td>{{if .Description}}{{.Description}}{{else}}<span class="text-muted"></span>{{end}}</td>
<td style="text-align:center">{{.IfaceCount}}</td>
<td>{{if .AddressSet}}{{.AddrSetsTxt}}{{else}}<span class="text-muted"></span>{{end}}</td>
<td style="text-align:right">
{{if not .Immutable}}
<button type="button" class="btn-icon btn-icon-danger"
@@ -141,12 +143,13 @@
</td>
</tr>
<tr class="key-detail-row" id="key-detail-zone-{{$i}}">
<td colspan="5" class="key-detail-cell">
<td colspan="6" class="key-detail-cell">
<div class="key-detail-body">
{{if .Immutable}}
<p class="empty-message">This zone is system-defined and cannot be modified.</p>
{{if .Interface}}<p class="text-muted" style="margin-top:0.5rem">Interfaces: {{range $j, $iface := .Interface}}{{if $j}}, {{end}}{{$iface}}{{end}}</p>{{end}}
{{if .Network}}<p class="text-muted" style="margin-top:0.25rem">Networks: {{.NetworksTxt}}</p>{{end}}
{{if .AddressSet}}<p class="text-muted" style="margin-top:0.25rem">Address sets: {{.AddrSetsTxt}}</p>{{end}}
{{if .Service}}<p class="text-muted" style="margin-top:0.25rem">Services: {{.ServicesTxt}}</p>{{end}}
{{else}}
<form hx-post="/configure/firewall/zones/{{.Name}}" hx-swap="none">
@@ -214,6 +217,24 @@
</td>
</tr>
{{end}}
{{if $.AddressSetNames}}
<tr>
<th>Address sets{{template "field-info" (index $d "zone-address-set")}}</th>
<td colspan="2">
<details class="cfg-multi">
<summary class="cfg-multi-summary">{{if .AddressSet}}{{.AddrSetsTxt}}{{else}}(None){{end}}</summary>
<div class="cfg-multi-body">
{{range $.AddressSetNames}}
<label class="cfg-multi-item">
<input type="checkbox" name="address-sets" value="{{.}}" {{if index $z.AddrSetSet .}}checked{{end}}>
{{.}}
</label>
{{end}}
</div>
</details>
</td>
</tr>
{{end}}
<tr>
<th>Services{{template "field-info" (index $d "zone-service")}}</th>
<td>
@@ -723,6 +744,160 @@
</div>
</section>
{{/* ── Address Sets ─────────────────────────────────────────────────── */}}
<section class="info-card info-grid-span-2">
<div class="card-header">Address Sets</div>
<p class="text-muted" style="font-size:0.85em;margin:0 1rem 0.5rem">
Named sets of IP addresses and networks, usable as zone sources.
Entries configured here are static; dynamic entries can be added at
runtime from the CLI or over NETCONF/RESTCONF, see the Firewall
status page for the live contents.
</p>
<div class="data-table-wrap">
<table class="data-table cfg-table">
<thead>
<tr>
<th>Name{{template "field-info" (index $d "addrset-name")}}</th>
<th>Description{{template "field-info" (index $d "addrset-description")}}</th>
<th>Family{{template "field-info" (index $d "addrset-family")}}</th>
<th>Timeout{{template "field-info" (index $d "addrset-timeout")}}</th>
<th style="text-align:center">Entries</th>
<th></th>
</tr>
</thead>
<tbody>
{{range $i, $s := .AddressSets}}
<tr>
<td>
<button class="key-row-toggle" type="button"
aria-expanded="false" data-target="addrset-{{$i}}">
<span class="key-row-arrow" aria-hidden="true"></span>{{.Name}}
</button>
</td>
<td>{{if .Description}}{{.Description}}{{else}}<span class="text-muted"></span>{{end}}</td>
<td>{{.Family}}</td>
<td>{{if .Timeout}}{{.Timeout}} s{{else}}<span class="text-muted"></span>{{end}}</td>
<td style="text-align:center">{{len .Entry}}</td>
<td style="text-align:right">
<button type="button" class="btn-icon btn-icon-danger"
hx-delete="/configure/firewall/address-sets/{{.Name}}"
hx-confirm="Delete address-set {{.Name}}?"
hx-swap="none"
title="Delete address-set">
{{template "icon-trash"}}
</button>
</td>
</tr>
<tr class="key-detail-row" id="key-detail-addrset-{{$i}}">
<td colspan="6" class="key-detail-cell">
<div class="key-detail-body">
<form hx-post="/configure/firewall/address-sets/{{.Name}}" hx-swap="none">
<table class="info-table">
<tr>
<th>Description{{template "field-info" (index $d "addrset-description")}}</th>
<td colspan="2"><input class="cfg-input" type="text" name="description"
value="{{.Description}}" placeholder="Optional description"></td>
</tr>
<tr>
<th>Family{{template "field-info" (index $d "addrset-family")}}</th>
<td colspan="2">
{{$fam := .Family}}
<div class="yt-bool-group">
{{range $.FamilyOptions}}
<label><input type="radio" name="family" value="{{.Value}}"
{{if or (eq $fam .Value) (and (eq $fam "") .IsDefault)}}checked{{end}}> {{.Label}}</label>
{{end}}
</div>
</td>
</tr>
<tr>
<th>Timeout{{template "field-info" (index $d "addrset-timeout")}}</th>
<td colspan="2">
<input class="cfg-input cfg-input-sm" type="number" name="timeout"
min="1" value="{{if .Timeout}}{{.Timeout}}{{end}}" placeholder="—" style="width:8rem"> seconds
<span class="text-muted" style="font-size:0.8em;display:block;margin-top:0.25em">Timeout sets are dynamic-only: entries expire on their own and static entries cannot be configured.</span>
</td>
</tr>
<tr>
<th>Entries{{template "field-info" (index $d "addrset-entry")}}</th>
<td colspan="2">
<textarea class="cfg-input" name="entries" rows="5"
placeholder="One address or prefix per line, e.g. 192.168.1.40 or 10.0.0.0/24">{{.EntriesTxt}}</textarea>
</td>
</tr>
</table>
<div class="cfg-card-footer" style="padding-left:0">
<button class="btn btn-primary btn-sm" type="submit">Save</button>
<button type="button" class="btn btn-sm" data-close-detail="addrset-{{$i}}">Cancel</button>
<span class="cfg-save-status"></span>
</div>
</form>
</div>
</td>
</tr>
{{end}}
{{if not .AddressSets}}
<tr><td colspan="6" class="yt-table-empty">No address-sets configured</td></tr>
{{end}}
{{/* Hidden add-address-set row */}}
<tr id="add-addrset-row" hidden>
<td colspan="6" class="key-detail-cell">
<form hx-post="/configure/firewall/address-sets" hx-swap="none">
<table class="info-table">
<tr>
<th>Name{{template "field-info" (index $d "addrset-name")}}</th>
<td colspan="2"><input class="cfg-input" type="text" name="name" required
pattern="[a-zA-Z0-9\-_]+" placeholder="e.g. allowed"></td>
</tr>
<tr>
<th>Description{{template "field-info" (index $d "addrset-description")}}</th>
<td colspan="2"><input class="cfg-input" type="text" name="description"
placeholder="Optional description"></td>
</tr>
<tr>
<th>Family{{template "field-info" (index $d "addrset-family")}}</th>
<td colspan="2">
<div class="yt-bool-group">
{{range .FamilyOptions}}
<label><input type="radio" name="family" value="{{.Value}}" {{if .IsDefault}}checked{{end}}> {{.Label}}</label>
{{end}}
</div>
</td>
</tr>
<tr>
<th>Timeout{{template "field-info" (index $d "addrset-timeout")}}</th>
<td colspan="2">
<input class="cfg-input cfg-input-sm" type="number" name="timeout"
min="1" placeholder="—" style="width:8rem"> seconds
<span class="text-muted" style="font-size:0.8em;display:block;margin-top:0.25em">Leave blank for a regular set with static entries.</span>
</td>
</tr>
<tr>
<th>Entries{{template "field-info" (index $d "addrset-entry")}}</th>
<td colspan="2">
<textarea class="cfg-input" name="entries" rows="5"
placeholder="One address or prefix per line, e.g. 192.168.1.40 or 10.0.0.0/24"></textarea>
</td>
</tr>
</table>
<div class="cfg-card-footer" style="padding-left:0">
<button class="btn btn-primary btn-sm" type="submit">Add</button>
<button type="button" class="btn btn-sm" data-hide="add-addrset-row">Cancel</button>
<span class="cfg-save-status"></span>
</div>
</form>
</td>
</tr>
</tbody>
</table>
</div>
<div class="cfg-card-footer">
<button type="button" class="btn-add-row" data-show="add-addrset-row">+ Add Address Set</button>
</div>
</section>
</section>
{{end}}
@@ -731,4 +906,3 @@
{{end}}{{/* end {{else}} — schema ready */}}
{{end}}
+36
View File
@@ -90,6 +90,7 @@
<th>Action</th>
<th>Interfaces</th>
<th>Networks</th>
<th>Address Sets</th>
<th>Host Services</th>
</tr>
</thead>
@@ -100,6 +101,7 @@
<td><span class="badge badge-{{.Action}}">{{.Action}}</span></td>
<td>{{.Interfaces}}</td>
<td>{{if .Networks}}{{.Networks}}{{else}}<span class="text-muted"></span>{{end}}</td>
<td>{{if .AddressSets}}{{.AddressSets}}{{else}}<span class="text-muted"></span>{{end}}</td>
<td>{{if .Services}}{{.Services}}{{else}}<span class="text-muted"></span>{{end}}</td>
</tr>
{{end}}
@@ -109,6 +111,40 @@
</div>
{{end}}
{{if .AddressSets}}
<div class="info-card info-grid-span-2">
<div class="card-header">Address Sets</div>
<div class="data-table-wrap">
<table class="data-table">
<thead>
<tr>
<th>Name</th>
<th>Family</th>
<th>Timeout</th>
<th>Entries</th>
</tr>
</thead>
<tbody>
{{range .AddressSets}}
<tr>
<td>{{.Name}}</td>
<td>{{.Family}}</td>
<td>{{if .Timeout}}{{.Timeout}}{{else}}<span class="text-muted"></span>{{end}}</td>
<td>
{{if .Entries}}
{{range $i, $e := .Entries}}{{if $i}}, {{end}}<code>{{.Entry}}</code>{{if .Dynamic}} <span class="badge badge-neutral" title="Added at runtime, not saved to configuration{{if .Expires}}, expires in {{.Expires}}{{end}}">dyn{{if .Expires}} {{.Expires}}{{end}}</span>{{end}}{{end}}
{{else}}
<span class="text-muted">(empty)</span>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
{{end}}
{{if .Policies}}
<div class="info-card info-grid-span-2">
<div class="card-header">Policies</div>
+3
View File
@@ -37,3 +37,6 @@
- "json/bloated.json"
- "show-interfaces"
- "-n br0"
- case: firewall_overview.sh
name: "firewall-overview"
+34
View File
@@ -0,0 +1,34 @@
#!/bin/sh
SCRIPT_PATH="$(dirname "$(readlink -f "$0")")"
JSON="$SCRIPT_PATH/json/firewall-overview.json"
CLI="$SCRIPT_PATH/../../../src/statd/python/cli_pretty/cli_pretty.py"
strip_ansi() {
sed 's/\x1b\[[0-9;]*m//g'
}
echo "1..2"
OUT1="$(cat "$JSON" | "$CLI" show-firewall | strip_ansi)"
if printf '%s\n' "$OUT1" | grep -q "Address Sets" &&
printf '%s\n' "$OUT1" | grep -q "allowed" &&
printf '%s\n' "$OUT1" | grep -q "greylist" &&
printf '%s\n' "$OUT1" | grep -q "ADDR SET" &&
printf '%s\n' "$OUT1" | grep -q "trusted" &&
printf '%s\n' "$OUT1" | grep -q "greylist" ; then
echo "ok 1 - show-firewall includes address-set summaries"
else
echo "not ok 1 - show-firewall missing address-set overview"
exit 1
fi
OUT2="$(cat "$JSON" | "$CLI" show-firewall-zone trusted | strip_ansi)"
if printf '%s\n' "$OUT2" | grep -q "address-sets" &&
printf '%s\n' "$OUT2" | grep -q "allowed, greylist" ; then
echo "ok 2 - show-firewall-zone shows zone address-sets"
exit 0
fi
echo "not ok 2 - show-firewall-zone missing zone address-sets"
exit 1
@@ -0,0 +1,61 @@
{
"infix-firewall:firewall": {
"default": "public",
"logging": "all",
"zone": [
{
"name": "mgmt",
"action": "accept",
"interface": ["e0"],
"service": ["ssh", "netconf", "restconf"]
},
{
"name": "public",
"action": "drop",
"interface": ["e1"]
},
{
"name": "trusted",
"action": "accept",
"address-set": ["allowed", "greylist"]
}
],
"address-set": [
{
"name": "allowed",
"family": "ipv4",
"current": [
{
"entry": "192.168.1.40",
"dynamic": false
},
{
"entry": "192.168.1.50",
"dynamic": true
}
]
},
{
"name": "greylist",
"family": "ipv4",
"timeout": 10,
"current": [
{
"entry": "192.168.1.60",
"dynamic": true,
"expires": 7
}
]
}
],
"policy": [
{
"name": "public-to-mgmt",
"action": "reject",
"priority": 100,
"ingress": ["public"],
"egress": ["mgmt"]
}
]
}
}
+1
View File
@@ -0,0 +1 @@
test.adoc
+39
View File
@@ -0,0 +1,39 @@
=== Firewall Address-Sets with Dynamic Entries
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/firewall/address-set]
==== Description
Verifies firewall address-sets used as zone sources, in a setup where
all traffic from the data network is dropped by default and end devices
are granted access per-IP at runtime.
image::topology.svg[align=center, scaledwidth=50%]
- The default zone "public" drops all traffic on the data interface
- The "trusted" zone accepts traffic from members of the "allowed"
address-set: one static entry from the configuration, and dynamic
entries managed at runtime with the add/remove/flush actions
- Dynamic entries must survive unrelated firewall configuration
changes, but are not saved to the configuration
- Static entries cannot be removed with the remove action
- Entries in timeout sets ("greylist") expire on their own
==== Topology
image::topology.svg[Firewall Address-Sets with Dynamic Entries topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to target
. Configure firewall with address-set as zone source
. Verify host is blocked by default
. Add dynamic entry for host, verify it is allowed
. Verify operational state distinguishes static/dynamic
. Verify dynamic entry survives configuration change
. Verify static entry cannot be removed with action
. Remove dynamic entry, verify host is blocked
. Flush dynamic entries, static remain
. Verify entries in timeout set expire on their own
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""Firewall Address-Sets with Dynamic Entries
Verifies firewall address-sets used as zone sources, in a setup where
all traffic from the data network is dropped by default and end devices
are granted access per-IP at runtime.
image::topology.svg[align=center, scaledwidth=50%]
- The default zone "public" drops all traffic on the data interface
- The "trusted" zone accepts traffic from members of the "allowed"
address-set: one static entry from the configuration, and dynamic
entries managed at runtime with the add/remove/flush actions
- Dynamic entries must survive unrelated firewall configuration
changes, but are not saved to the configuration
- Static entries cannot be removed with the remove action
- Entries in timeout sets ("greylist") expire on their own
"""
import infamy
from infamy.util import until
def get_address_set(target, name):
"""Fetch operational state for a named address-set"""
data = target.get_data("/infix-firewall:firewall")
sets = data["firewall"].get("address-set", [])
return next((s for s in sets if s["name"] == name), None)
def current_entries(target, name):
"""Return {entry: dynamic} for the live contents of an address-set"""
aset = get_address_set(target, name)
if not aset:
return {}
return {c["entry"]: c["dynamic"] for c in aset.get("current", [])}
def must_fail(fn, *args):
"""Assert that an action call raises an error"""
try:
fn(*args)
except Exception:
return
raise AssertionError(f"{args} unexpectedly succeeded")
with infamy.Test() as test:
ALLOWED = "/infix-firewall:firewall/address-set[name='allowed']"
GREYLIST = "/infix-firewall:firewall/address-set[name='greylist']"
with test.step("Set up topology and attach to target"):
env = infamy.Env()
target = env.attach("target", "mgmt")
_, data_if = env.ltop.xlate("target", "data")
_, mgmt_if = env.ltop.xlate("target", "mgmt")
_, host_data = env.ltop.xlate("host", "data")
TARGET_IP = "192.168.1.1"
HOST_IP = "192.168.1.42"
STATIC_IP = "192.168.1.40"
EXTRA_IP = "192.168.1.50"
with test.step("Configure firewall with address-set as zone source"):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": data_if,
"enabled": True,
"ipv4": {
"address": [{
"ip": TARGET_IP,
"prefix-length": 24
}]
}
}]
}
},
"infix-firewall": {
"firewall": {
"default": "public",
"logging": "all",
"address-set": [{
"name": "allowed",
"description": "End devices allowed to access the target",
"entry": [STATIC_IP]
}, {
"name": "greylist",
"description": "Entries expire on their own",
"timeout": 10
}],
"zone": [{
"name": "mgmt",
"description": "Management network - for test automation",
"action": "accept",
"interface": [mgmt_if],
"service": ["ssh", "netconf", "restconf"]
}, {
"name": "public",
"description": "Untrusted data network, drop everything",
"action": "drop",
"interface": [data_if]
}, {
"name": "trusted",
"description": "Allowed end devices",
"action": "accept",
"address-set": ["allowed"]
}]
}
}
})
infamy.Firewall.wait_for_operational(target, {
"public": {"action": "drop"},
"trusted": {"action": "accept"},
"mgmt": {"action": "accept"}
})
aset = get_address_set(target, "allowed")
assert aset, "Address-set 'allowed' not found in operational"
assert STATIC_IP in aset.get("entry", []), \
f"Static entry {STATIC_IP} missing from configuration"
with infamy.IsolatedMacVlan(host_data) as ns:
ns.addip(HOST_IP)
with test.step("Verify host is blocked by default"):
ns.must_not_reach(TARGET_IP, timeout=5)
with test.step("Add dynamic entry for host, verify it is allowed"):
target.call_action(f"{ALLOWED}/add", {"entry": HOST_IP})
ns.must_reach(TARGET_IP, timeout=10)
with test.step("Verify operational state distinguishes static/dynamic"):
def entries_settled():
current = current_entries(target, "allowed")
return current.get(STATIC_IP) is False and \
current.get(HOST_IP) is True
until(entries_settled, attempts=10)
with test.step("Verify dynamic entry survives configuration change"):
target.put_config_dicts({
"infix-firewall": {
"firewall": {
"logging": "unicast"
}
}
})
ns.must_reach(TARGET_IP, timeout=30)
current = current_entries(target, "allowed")
assert current.get(HOST_IP) is True, \
f"Dynamic entry {HOST_IP} lost in firewall reload"
with test.step("Verify static entry cannot be removed with action"):
must_fail(target.call_action, f"{ALLOWED}/remove",
{"entry": STATIC_IP})
with test.step("Remove dynamic entry, verify host is blocked"):
target.call_action(f"{ALLOWED}/remove", {"entry": HOST_IP})
ns.must_not_reach(TARGET_IP, timeout=5)
with test.step("Flush dynamic entries, static remain"):
target.call_action(f"{ALLOWED}/add", {"entry": HOST_IP})
target.call_action(f"{ALLOWED}/add", {"entry": EXTRA_IP})
target.call_action(f"{ALLOWED}/flush")
def only_static_left():
current = current_entries(target, "allowed")
return list(current.keys()) == [STATIC_IP]
until(only_static_left, attempts=10)
ns.must_not_reach(TARGET_IP, timeout=5)
with test.step("Verify entries in timeout set expire on their own"):
target.call_action(f"{GREYLIST}/add", {"entry": "10.0.0.1"})
aset = get_address_set(target, "greylist")
assert aset, "Address-set 'greylist' not found in operational"
current = {c["entry"]: c for c in aset.get("current", [])}
assert "10.0.0.1" in current, "Entry missing from timeout set"
assert current["10.0.0.1"].get("expires") is not None, \
"Entry in timeout set has no expiry"
must_fail(target.call_action, f"{GREYLIST}/remove",
{"entry": "10.0.0.1"})
def entry_expired():
return "10.0.0.1" not in current_entries(target, "greylist")
until(entry_expired, attempts=20)
test.succeed()
@@ -0,0 +1,23 @@
graph "1x2" {
layout = "neato";
overlap = false;
esep = "+30";
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="host | { <mgmt> mgmt | <data> data }",
pos="10,10!",
requires="controller"
];
target [
label="{ <mgmt> mgmt | <data> data } | target",
pos="40,10!",
requires="infix",
];
host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"]
host:data -- target:data [color=black]
}
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: 1x2 Pages: 1 -->
<svg width="299pt" height="55pt"
viewBox="0.00 0.00 299.02 55.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 51)">
<title>1x2</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-51 295.02,-51 295.02,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-0.5 0,-46.5 100,-46.5 100,-0.5 0,-0.5"/>
<text text-anchor="middle" x="25" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-0.5 50,-46.5 "/>
<text text-anchor="middle" x="75" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="50,-23.5 100,-23.5 "/>
<text text-anchor="middle" x="75" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
</g>
<!-- target -->
<g id="node2" class="node">
<title>target</title>
<polygon fill="none" stroke="black" points="175.02,-0.5 175.02,-46.5 291.02,-46.5 291.02,-0.5 175.02,-0.5"/>
<text text-anchor="middle" x="200.02" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="175.02,-23.5 225.02,-23.5 "/>
<text text-anchor="middle" x="200.02" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="225.02,-0.5 225.02,-46.5 "/>
<text text-anchor="middle" x="258.02" y="-19.8" 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="M100,-35.5C100,-35.5 175.02,-35.5 175.02,-35.5"/>
</g>
<!-- host&#45;&#45;target -->
<g id="edge2" class="edge">
<title>host:data&#45;&#45;target:data</title>
<path fill="none" stroke="black" stroke-width="2" d="M100,-11.5C100,-11.5 175.02,-11.5 175.02,-11.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+3
View File
@@ -2,6 +2,9 @@
- name: Basic Firewall for End Devices
case: basic/test.py
- name: Firewall Address-Sets with Dynamic Entries
case: address-set/test.py
- name: LAN-WAN Firewall with Masquerading
case: lan-wan/test.py
+2 -2
View File
@@ -58,7 +58,7 @@ SPEC_Q :=
endif
test-spec:
@esc_infix_name="$(echo $(INFIX_NAME) | sed 's/\//\\\//g')"; \
@esc_infix_name="$(echo $(IX_NAME) | sed 's/\//\\\//g')"; \
sed 's/{REPLACE}/$(subst ",,$(esc_infix_name)) $(INFIX_VERSION)/' \
$(spec-dir)/Readme.adoc.in > $(spec-dir)/Readme.adoc
$(SPEC_Q)$(spec-dir)/generate_spec.py -s $(test-dir)/case/all.yaml -r $(BR2_EXTERNAL_INFIX_PATH) $(SPEC_DEBUG)
@@ -86,7 +86,7 @@ xpath-coverage-report:
$(xpath_coverage_report_md) \
$(xpath_coverage_report_css) \
$(xpath_coverage_logo) \
"$(subst ",,$(INFIX_NAME)) $(INFIX_VERSION)" \
"$(subst ",,$(IX_NAME)) $(INFIX_VERSION)" \
$(xpath_coverage_report_pdf)
# Unit tests run with random (-r) hostname and container name to
+86
View File
@@ -0,0 +1,86 @@
#!/bin/sh
# Populate the shared Buildroot download cache (dl/) with the source
# artifacts needed by all defconfigs, then optionally publish it to a
# download mirror using rsync. The dl/ directory layout is exactly what
# BR2_PRIMARY_SITE expects (dl/<package>/<tarball>), the mirror root is
# a plain copy of dl/, see configs/snippets/mirror.conf
#
# Intended to run nightly from cron on the file server, on every branch
# that should remain buildable from the mirror:
#
# 0 3 * * * cd $HOME/src/infix && git pull --ff-only -q && \
# ./utils/mirror-sync.sh /srv/pub
#
set -u
usage()
{
cat <<-EOF >&2
Usage: $0 [-o DIR] [DEST]
Run 'make <defconfig> source' for every defconfig in configs/,
downloading all required source artifacts to the shared dl/
directory. All tarball hashes are verified by Buildroot.
If DEST is given, dl/ is then published there with rsync. Git
caches (dl/*/git) are skipped and nothing is ever deleted from
DEST -- old release branches still reference old tarballs.
Options:
-o DIR Directory for scratch build trees. Speeds up
repeated runs (default: temporary, removed on exit)
EOF
exit "$1"
}
outdir=
while getopts "ho:" opt; do
case $opt in
h) usage 0;;
o) outdir=$OPTARG;;
*) usage 1;;
esac
done
shift $((OPTIND - 1))
dest=${1:-}
cd "$(dirname "$0")/.." || exit 1
exec 9> "${TMPDIR:-/tmp}/infix-mirror-sync.lock"
if ! flock -n 9; then
echo "$0: another instance is already running, skipping." >&2
exit 0
fi
if [ -z "$outdir" ]; then
outdir=$(mktemp -d)
trap 'rm -rf "$outdir"' EXIT INT TERM
fi
rc=0
for cfg in configs/*_defconfig; do
cfg=$(basename "$cfg")
O="$outdir/${cfg%_defconfig}"
echo "=== $cfg"
if ! make O="$O" "$cfg" >/dev/null; then
echo "$0: failed configuring $cfg, skipping." >&2
rc=1
continue
fi
if ! make O="$O" source; then
echo "$0: failed downloading sources for $cfg." >&2
rc=1
fi
# Drop this defconfig's build tree; the downloads live in the
# shared dl/ dir, and keeping every tree would exhaust the disk.
rm -rf "$O"
done
if [ -n "$dest" ]; then
rsync -a --exclude='/*/git' --exclude='*.lock' dl/ "$dest" || rc=1
fi
exit $rc
+5 -5
View File
@@ -33,7 +33,7 @@ Arguments:
Description:
When called from Buildroot (no options), uses environment variables:
BINARIES_DIR, BUILD_DIR, BR2_EXTERNAL_INFIX_PATH, RELEASE, INFIX_ID
BINARIES_DIR, BUILD_DIR, BR2_EXTERNAL_INFIX_PATH, RELEASE, IX_ID
When called with -b/-r options, enters standalone mode and combines artifacts
from separate boot and rootfs sources. Useful for CI or manual image creation.
@@ -484,7 +484,7 @@ if [ -n "$STANDALONE" ]; then
export BUILD_DIR="$BOOT_DIR/build"
export BR2_EXTERNAL_INFIX_PATH
export RELEASE="${RELEASE:-""}"
export INFIX_ID="${INFIX_ID:-"infix"}"
export IX_ID="${IX_ID:-"infix"}"
# Add host tools to PATH (for genimage, bmaptool, etc.)
for dir in "$BOOT_DIR" "$ROOT_DIR"; do
@@ -550,7 +550,7 @@ fi
# Set defaults for optional variables
: "${RELEASE:=""}"
: "${INFIX_ID:="infix"}"
: "${IX_ID:="infix"}"
# Download bootloader if requested
if [ -n "$DOWNLOAD_BOOT" ]; then
@@ -626,12 +626,12 @@ fi
# For boot-only mode, append "-boot" to the target name in the image filename
if [ -n "$BOOT_ONLY" ]; then
sed "s|#VERSION#|${RELEASE}|" "$GENIMAGE_TEMPLATE" | \
sed "s|#INFIX_ID#|${INFIX_ID}|" | \
sed "s|#IX_ID#|${IX_ID}|" | \
sed "s|#TARGET#|${TARGET}|" | \
sed "s|-${TARGET}\.img|-${TARGET}-boot.img|" > "$GENIMAGE_CFG"
else
sed "s|#VERSION#|${RELEASE}|" "$GENIMAGE_TEMPLATE" | \
sed "s|#INFIX_ID#|${INFIX_ID}|" | \
sed "s|#IX_ID#|${IX_ID}|" | \
sed "s|#TARGET#|${TARGET}|" > "$GENIMAGE_CFG"
fi