mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 17:23:00 +02:00
Merge pull request #1563 from kernelkit/misc
Firewall address-set support
This commit is contained in:
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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 0–9,
|
||||
a–z, ".", "_" 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 0–9,
|
||||
a–z, ".", "_" 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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
sha256 49c4d5edbd3ee0a0f6b15ef097b79539530a574ceed8a4dffec1d02455933029 LICENSE
|
||||
@@ -0,0 +1 @@
|
||||
sha256 8cdb4def6e91fde66ed0880a6e6ecc364aa96012e53b3cfb990b53b9dab976d4 LICENSE
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -37,3 +37,6 @@
|
||||
- "json/bloated.json"
|
||||
- "show-interfaces"
|
||||
- "-n br0"
|
||||
|
||||
- case: firewall_overview.sh
|
||||
name: "firewall-overview"
|
||||
|
||||
Executable
+34
@@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
@@ -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
|
||||
|
||||
|
||||
Executable
+194
@@ -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--target -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt--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--target -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:data--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 |
@@ -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
@@ -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
|
||||
|
||||
Executable
+86
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user