From 66b94d98f76bfbbd771c001a04c19e893415e44b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 30 Apr 2026 11:14:35 +0000 Subject: [PATCH] Deployed a3c6ec0c to dev with MkDocs 1.6.1 and mike 2.2.0 --- dev/bridging/index.html | 59 +++++++++++++++ dev/iface/index.html | 94 ++++++++++++++++++++++++ dev/ip/index.html | 134 +++++++++++++++++++++++++++++++++++ dev/search/search_index.json | 2 +- dev/sitemap.xml | 102 +++++++++++++------------- dev/sitemap.xml.gz | Bin 524 -> 524 bytes 6 files changed, 339 insertions(+), 52 deletions(-) diff --git a/dev/bridging/index.html b/dev/bridging/index.html index 0f8ba750..12d34eea 100644 --- a/dev/bridging/index.html +++ b/dev/bridging/index.html @@ -853,6 +853,17 @@ + + +
  • + + + + Dummy Interface + + + + + +
  • @@ -2632,6 +2688,44 @@ admin@example:/config/interface/veth0a/> set custom-phys-address chassis o => 02:53:00:c0:ff:f0 +

    Dummy Interface

    +

    A dummy interface is a virtual interface that is always administratively +and operationally UP, regardless of any physical link state. It can +hold IP addresses just like any other interface.

    +

    The two most common uses are:

    + +
    +

    Tip

    +

    WiFi interfaces also use dummies as placeholders when the radio +hardware is not detected at boot (e.g., a USB dongle that was +unplugged). See WiFi for details.

    +
    +

    Example: Stable OSPF Router-ID

    +

    Create a dummy interface with a /32 address and use it as the OSPF +router-ID so that the ID never changes when physical ports bounce:

    +
    admin@example:/> configure
    +admin@example:/config/> edit interface lo0
    +admin@example:/config/interface/lo0/> set type dummy
    +admin@example:/config/interface/lo0/> set ipv4 address 192.0.2.1 prefix-length 32
    +admin@example:/config/interface/lo0/> leave
    +admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf
    +admin@example:/config/routing/…/ospf/> set explicit-router-id 192.0.2.1
    +admin@example:/config/routing/…/ospf/> leave
    +admin@example:/> copy running-config startup-config
    +
    + +

    To also make the address reachable by other routers, redistribute +connected routes (or add lo0 as an OSPF interface):

    +
    admin@example:/config/routing/…/ospf/> set redistribute connected
    +
    +

      diff --git a/dev/ip/index.html b/dev/ip/index.html index 39c1018b..d63393e1 100644 --- a/dev/ip/index.html +++ b/dev/ip/index.html @@ -1039,6 +1039,45 @@ + + +
    1. + + + + ARP and Neighbor Cache + + + + + +
    2. @@ -2535,6 +2574,45 @@ + + +
    3. + + + + ARP and Neighbor Cache + + + + + +
    4. @@ -3195,6 +3273,62 @@ admin@example:/config/interface/eth0/> leave admin@example:/> +

      ARP and Neighbor Cache

      +

      Static ARP entries (IPv4) and neighbor cache entries (IPv6) can be +configured per interface. The most common reasons to do so are:

      +
        +
      • Security — prevent ARP/NDP spoofing by locking critical hosts + (e.g., a default gateway) to their known MAC addresses
      • +
      • Reliability — ensure reachability of a host even when ARP/NDP + traffic is suppressed or filtered (e.g., across a strict firewall)
      • +
      +

      Dynamic entries are learned automatically by the kernel using ARP and +Neighbor Discovery Protocol (NDP), and are visible as read-only +operational state alongside the static ones.

      +

      Static IPv4 ARP Entry

      +
      admin@example:/> configure
      +admin@example:/config/> edit interface eth0 ipv4
      +admin@example:/config/interface/eth0/ipv4/> set neighbor 192.168.1.100 link-layer-address 00:11:22:33:44:55
      +admin@example:/config/interface/eth0/ipv4/> diff
      ++interfaces {
      ++  interface eth0 {
      ++    ipv4 {
      ++      neighbor 192.168.1.100 {
      ++        link-layer-address 00:11:22:33:44:55;
      ++      }
      ++    }
      ++  }
      ++}
      +admin@example:/config/interface/eth0/ipv4/> leave
      +admin@example:/>
      +
      + +

      Static IPv6 Neighbor Entry

      +
      admin@example:/> configure
      +admin@example:/config/> edit interface eth0 ipv6
      +admin@example:/config/interface/eth0/ipv6/> set neighbor 2001:db8::100 link-layer-address 00:11:22:33:44:55
      +admin@example:/config/interface/eth0/ipv6/> diff
      ++interfaces {
      ++  interface eth0 {
      ++    ipv6 {
      ++      neighbor 2001:db8::100 {
      ++        link-layer-address 00:11:22:33:44:55;
      ++      }
      ++    }
      ++  }
      ++}
      +admin@example:/config/interface/eth0/ipv6/> leave
      +admin@example:/>
      +
      + +

      The full neighbor table — including dynamically learned entries (origin: +dynamic) — is available as operational state via NETCONF or RESTCONF.

      +
      +

      Note

      +

      Static neighbor entries take effect immediately on leave (commit). +They are installed as permanent entries in the kernel neighbor table, +which means they are never evicted by the normal ARP/NDP aging process.

      +

        diff --git a/dev/search/search_index.json b/dev/search/search_index.json index 2ff1c030..2c4dc60d 100644 --- a/dev/search/search_index.json +++ b/dev/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Introduction","text":""},{"location":"#introduction","title":"Introduction","text":"

        Welcome to Infix, your immutable, friendly, and secure operating system! On these pages you can find both user and developer documentation.

        Most topics on configuring the system include CLI examples, but every setting, as well as status read-back from the operational datastore, is also possible to perform using NETCONF or RESTCONF. In fact, the Infix regression test system solely relies on NETCONF and RESTCONF.

        Tip

        The CLI documentation is also available from inside the CLI itself using the help command in admin-exec mode.

        This document provides an introduction of key concepts, details how the system boots, including failure modes, and provides links to other documents for further study.

        "},{"location":"#project-links","title":"Project Links","text":"Topic URL Home page & blog https://www.kernelkit.org GitHub https://github.com/kernelkit/infix Latest release https://github.com/kernelkit/infix/releases/latest"},{"location":"#command-line-interface","title":"Command Line Interface","text":"

        The command line interface (CLI, see-ell-i) is the traditional way of interacting with single network equipment like switches and routers. Today users have come to expect more advanced graphical GUIs, like a web interface, to manage a device or NETCONF-based tools that allow for managing entire fleets of installed equipment.

        Nevertheless, when it comes to initial deployment and debugging, it is very useful to know how to navigate and use the CLI.

        Info

        For more information, see the CLI Introduction and the CLI Configuration Tutorial.

        "},{"location":"#key-concepts","title":"Key Concepts","text":"

        The two modes in the CLI are the admin-exec and the configure context.

        However, when logging in to the system, from the console port or SSH, you land in a standard UNIX shell, Bash. This is for advanced users and remote scripting purposes (production equipment):

            Run the command 'cli' for interactive OAM\n\n    admin@example:~$\n

        To enter the CLI, follow the instructions, for interactive Operations, Administration, and Management (OAM), type:

            admin@example:~$ cli\n    admin@example:/>\n

        The prompt, constructed from your username and the device's hostname, changes slightly. You are now in the admin-exec context of the CLI. Here you can inspect system status and do operations to debug networking issues, e.g. ping. You can also enter configure context by typing: configure followed by commands to set, edit, apply changes using leave, or abort and return to admin-exec.

        Tip

        If you haven't already, the CLI Introduction would be useful to skim through at this point.

        "},{"location":"#datastores","title":"Datastores","text":"

        The system has several datastores (or files):

        • factory-config consists of a set of default configurations, some static and others generated per-device, e.g., a unique hostname and number of ports/interfaces. This file is generated at boot.
        • failure-config is also generated at boot, from the same YANG models as factory-config, and holds the system Fail Secure Mode
        • startup-config is created from factory-config at boot if it does not exist. It is loaded as the system configuration on each boot.
        • running-config is what is actively running on the system. If no changes have been made since the system booted, it is the same as startup-config.
        • candidate-config is created from running-config when entering the configure context. Any changes made here can be discarded (abort, rollback) or committed (commit, leave) to running-config.

        Tip

        Please see the Branding & Releases document for more in-depth information on how factory-config and failure-config can be adapted to different customer requirements. Including how you can override the generated versions of these files with plain per-product ones -- this may even protect against some of the failure modes below.

        "},{"location":"boot/","title":"Boot Procedure","text":""},{"location":"boot/#boot-procedure","title":"Boot Procedure","text":"

        Systems running Infix will typically boot in multiple phases, forming a boot chain. Each link in the chain has three main responsibilities:

        1. Ensuring the integrity of the next link before passing control to it. This avoids silent failures stemming from data corruption.
        2. Ensuring the authenticity of the next link before passing control to it, commonly referred to as Secure Boot. This protects against malicious attempts to modify a system's firmware.
        3. Preparing the system state according to the requirements of the next link. E.g. the Linux kernel requires the system's RAM to be operational.

        A typical chain consists of four stages:

            .---------.\n    |   ROM   >---.   Determine the location of and load the SPL\n    '---------'   |\n.-----------------'\n|   .---------.\n'--->   SPL   >---.   Perform DDR training and load the TPL\n    '---------'   |\n.-----------------'\n|   .---------.\n'--->   TPL   >---.   Load Linux kernel, device tree, and root filesystem\n    '---------'   |\n.-----------------'\n|   .---------.\n'--->  Infix  |       Get down to business\n    '---------'\n

        After a reset, hardware will pass control to a program (ROM) which is almost always programmed into the SoC by the vendor. This program will determine the location of the Secondary Program Loader (SPL), typically by reading a set of Sample at Reset (SaR) pins.

        The SPL is sometimes provided by the SoC vendor in binary form, and is sometimes built as a part of the Tertiary Program Loader (TPL) build. Its main responsibility is usually to set up the system's memory controller and perform DDR training, if required, before loading the TPL.

        Commonly referred to as the system's bootloader, the TPL is responsible for preparing the execution environment required by the Linux kernel.

        This document's focus is to describe the final two phases of the boot chain, as the initial phases are very hardware dependent, better described by existing documentation provided by the SoC vendor.

        "},{"location":"boot/#bootloader","title":"Bootloader","text":""},{"location":"boot/#configuration","title":"Configuration","text":"

        To mitigate the risk of a malicious user being able to circumvent the bootloader's validation procedure, user configuration is kept to a minimum. Two settings are available:

        Boot order: Since Infix maintains two copies of its software image, and as some bootloaders support netbooting, the order in which boot sources are considered can be configured. To select the active source, use RAUC:

        root@example:~# rauc status mark-active <slot>\n...\n

        Where <slot> is one of:

        <slot> Source rootfs.0 Primary partition rootfs.1 Secondary partition net.0 Netboot (where supported)

        Debug: By default, the kernel will only output errors to the console during boot. Optionally, this can be altered such that all enabled messages are logged.

        On systems using U-Boot, this can be enabled by running fw_setenv DEBUG 1. To restore the default behavior, run fw_setenv DEBUG.

        On systems running GRUB, this can be enabled by running:

        root@example:~# grub-editenv /mnt/aux/grub/grubenv set DEBUG=1\n

        To restore the default behavior, run:

        root@example:~# grub-editenv /mnt/aux/grub/grubenv unset DEBUG\n
        "},{"location":"boot/#u-boot","title":"U-Boot","text":"

        Used on aarch64 based systems. It is able to verify both the integrity and authenticity of an Infix image. As such, it can be used as a part of a Secure Boot chain, given that the preceding links are able to do the same.

        Supports booting Infix from a block device using the Disk Image layout. Currently, Virtio and MMC disks are supported.

        An FIT Framed Squash Image can be used to boot Infix over the network. DHCP is used to configure the network and TFTP to transfer the image to the system's RAM.

        Access to U-Boot's shell is disabled to prevent side-loading of malicious software. To configure the active boot partition, refer to the Bootloader Configuration section.

        "},{"location":"boot/#grub","title":"GRUB","text":"

        Used on x86_64 based systems. Neither the integrity nor the authenticity of the Infix image is verified. It is only intended to provide a way of booting a Disk Image, such that a standard System Upgrade can be performed on virtualized instances.

        Access to the GRUB shell is not limited in any way, and the boot partition can be selected interactively at boot using the arrow keys. It is also possible to permanently configure the default partition from Infix using the Bootloader Configuration.

        "},{"location":"boot/#system-boot","title":"System Boot","text":"

        After the system firmware (BIOS or and boot loader start Linux the following happens. The various failure modes, e.g., missing password in VPD, are detailed later in this section.

        1. Before mounting /cfg and /var partitions, hosting read-writable data like startup-config and container images, the system first checks if a factory reset has been requested by the user, if so it wipes the contents of these partitions
        2. Linux boots with a device tree which is used for detecting generic make and model of the device, e.g., number of interfaces. It may also reference an EEPROM with Vital Product Data. That is where the base MAC address and per-device password hash is stored. (Generic builds use the same MAC address and password)
        3. On every boot the system's factory-config and failure-config are generated from the YANG2 models of the current firmware version. This ensures that a factory reset device can always boot, and that there is a working fail safe, or rather fail secure, mode
        4. On first power-on, and after a factory reset, the system does not have a startup-config, in which case factory-config is copied to startup-config -- if a per-product specific version exists it is preferred over the generated one
        5. Provided the integrity of the startup-config is OK, a system service loads and activates the configuration
        "},{"location":"boot/#failure-modes","title":"Failure Modes","text":"

        So, what happens if any of the steps above fail?

        "},{"location":"boot/#vpd-fail","title":"VPD Fail","text":"

        The per-device password cannot be read, or is corrupt, so the system factory-config and failure-config are not generated:

        1. First boot, or after factory reset: startup-config cannot be created or loaded, and failure-config cannot be loaded. The system ends up in an unrecoverable state, i.e., RMA3 Mode
        2. The system has booted (at least) once with correct VPD and password and already has a startup-config. Provided the startup-config is OK (see below), it is loaded and system boots successfully

        In both cases, external factory reset modes/button will not help, and in the second case will cause the device to fail on the next boot.

        Note

        The second case does not yet have any warning or event that can be detected from the outside. This is planned for a later release.

        "},{"location":"boot/#broken-startup-config","title":"Broken startup-config","text":"

        If loading startup-config fails for some reason, e.g., invalid JSON syntax, failed validation against the system's YANG model, or a bug in the system's confd service, the Fail Secure Mode is triggered and failure-config is loaded (unless VPD Failure, see above).

        Tip

        Please see the Branding & Releases document for how to provide per-product failure-config, or factory-config to suit your product's preferences.

        Fail Secure Mode is a fail-safe mode provided for debugging the system. The default4 creates a setup of isolated interfaces with communication only to the management CPU, SSH and console login using the device's factory reset password, IP connectivity only using IPv6 link-local, and device discovery protocols: LLDP, mDNS-SD. The login and shell prompt are set to failure-c0-ff-ee, the last three octets of the device's base MAC address.

        "},{"location":"boot/#system-upgrade","title":"System Upgrade","text":"

        Much of the minutiae of software upgrades is delegated to RAUC, which offers lots of benefits out-of-the-box:

        • Upgrade Bundles are always signed, such that their authenticity can be verified by the running operating system, before the new one is installed.
        • The bureaucracy of interfacing with different bootloaders, manage the boot order, is a simple matter of providing a compatible configuration.
        • Updates can be sourced from the local filesystem (including external media like USB sticks or SD-cards) and from remote servers using FTP or HTTP(S).

        To initiate a system upgrade from the shell1, run:

        root@example:~# rauc install <file|url>\n...\n

        Where the file or URL points to a RAUC Upgrade Bundle.

        This will upgrade the partition not currently running. After a successful upgrade is completed, you can reboot your system, which will then boot from the newly installed image. Since the partition from which you were originally running is now inactive, running the same upgrade command again will bring both partitions into sync.

        "},{"location":"boot/#image-formats","title":"Image Formats","text":""},{"location":"boot/#squashfs-image","title":"SquashFS Image","text":"

        Canonical Name: rootfs.squashfs

        The central read-only filesystem image containing Infix's Linux kernel, device trees, and root filesystem. All other images bundle this image, or is dependent on it, in one way or another.

        On its own, it can be used as an initrd to efficiently boot a virtual instance of Infix.

        "},{"location":"boot/#fit-framed-squash-image","title":"FIT Framed Squash Image","text":"

        Canonical Name: rootfs.itb

        As the name suggests, this is essentially the Squash FS Image with a Flattened Image Tree (FIT) header. Being a native format to U-Boot, using this framing allows us to verify the integrity and authenticity of the SquashFS image using standard U-Boot primitives.

        In contrast to most FIT images, the kernel and device trees are not stored as separate binaries in the image tree. Instead, Infix follows the standard Linux layout where the kernel and related files are stored in the /boot directory of the filesystem.

        On disk, this image is then stored broken up into its two components; the FIT header (rootfs.itbh) and the SquashFS image. The header is stored on the Auxiliary Data partition of the Disk Image, while the SquashFS image is stored in one of the Root Filesystem partitions.

        When the system boots, U-Boot will concatenate the two parts to validate the SquashFS's contents. This path was chosen because:

        • Having a separate raw SquashFS means Linux can directly mount it as the root filesystem.

        • It decouples Infix from U-Boot. If a better way of validating our image is introduced, we can switch to it without major changes to Infix's boot process, as we can still use a regular SquashFS as the root filesystem.

        • It lets us use standard interfaces to boot Linux, like SYSLINUX. It also plays well with traditional bootloaders, like GRUB.

        In its full form, it can be used to netboot Infix, as it contains all the information needed by U-Boot in a single file.

        "},{"location":"boot/#rauc-upgrade-bundle","title":"RAUC Upgrade Bundle","text":"

        Canonical Name: infix-${ARCH}.pkg

        Itself a SquashFS image, this bundle (sometimes referred to package) contains the Infix SquashFS Image along with the header of the FIT Framed Squash Image, and some supporting files to let RAUC know how install it on the target system.

        When performing a System Upgrade, this is the format to use.

        "},{"location":"boot/#disk-image","title":"Disk Image","text":"

        Canonical Name: disk.img

        Infix runs from a block device (e.g. eMMC or virtio disk) with the following layout. The disk is expected to use the GPT partitioning scheme. Partitions marked with an asterisk are optional.

        .-----------.\n| GPT Table |\n:-----------:\n|    boot*  |\n:-----------:\n|    aux    |\n:-----------:\n|           |\n|  primary  |\n|           |\n:-----------:\n|           |\n| secondary |\n|           |\n:-----------:\n|    cfg    |\n:-----------:\n|           |\n|    var*   |\n|           |\n'-----------'\n
        "},{"location":"boot/#boot-bootloader","title":"boot - Bootloader","text":"Parameter Value Required No Size 4 MiB Format Raw binary, as dictated by the hardware

        Optional partition containing the system's bootloader. May also reside in a separate storage device, e.g. a serial FLASH.

        On x86_64, this partition holds the EFI system partition, containing the GRUB bootloader.

        "},{"location":"boot/#aux-auxiliary-data","title":"aux - Auxiliary Data","text":"Parameter Value Required Yes Size 4 MiB Format EXT4 filesystem

        Holds information that is shared between Infix and its bootloader, such as image signatures required to validate the chain of trust, bootloader configuration etc.

        Typical layout when using U-Boot bootloader:

        /\n\u251c primary.itbh\n\u251c secondary.itbh\n\u2514 uboot.env\n

        During boot, an ITB header along with the corresponding root filesystem image are concatenated in memory, by U-Boot, to form a valid FIT image that is used to verify its integrity and origin before any files are extracted from it.

        Note that the bootloader's primary environment is bundled in the binary - uboot.env is only used to import a few settings that is required to configure the boot order.

        "},{"location":"boot/#primarysecondary-root-filesystems","title":"primary/secondary - Root Filesystems","text":"Parameter Value Required Yes Size >= 256 MiB Format Squash filesystem

        Holds the SquashFS Image. Two copies exist so that an incomplete upgrade does not brick the system, and to allow fast rollbacks when upgrading to a new version.

        "},{"location":"boot/#cfg-configuration-data","title":"cfg - Configuration Data","text":"Parameter Value Required Yes Size >= 16 MiB Format EXT4 filesystem

        Non-volatile storage of the system configuration and user data. Concretely, user data is everything stored under /root and /home.

        "},{"location":"boot/#var-variable-data","title":"var - Variable Data","text":"Parameter Value Required No Size >= 16 MiB Format EXT4 filesystem

        Persistent storage for everything under /var. This is maintained as a separate filesystem from the data in cfg, because while the system can funtion reasonably well without a persistent /var, loosing /cfg or /etc is much more difficult.

        If var is not available, Infix will still persist /var/lib using cfg as the backing storage.

        1. See Upgrade & Boot Order for more information.\u00a0\u21a9

        2. YANG is a modeling language from IETF, replacing that used for SNMP (MIB), used to describe the subsystems and properties of the system.\u00a0\u21a9

        3. Return Merchandise Authorization (RMA), i.e., broken beyond repair by end-user and eligible for return to manufacturer.\u00a0\u21a9

        4. Customer specific builds can define their own failure-config. It may be the same as factory-config, with the hostname set to failure, or a dedicated configuration that isolates interfaces, or even disables ports, to ensure that the device does not cause any security problems on the network. E.g., start forwarding traffic between previously isolated VLANs.\u00a0\u21a9

        "},{"location":"branding/","title":"Branding & Releases","text":""},{"location":"branding/#branding-releases","title":"Branding & Releases","text":"

        This document is for projects using Infix as a br2-external, i.e., OEMs that want to create their own \"Spin\" of Infix.

        "},{"location":"branding/#branding","title":"Branding","text":"

        Branding is done in menuconfig, there are several settings affecting it, most are in the Infix external subsection called \"Branding\", but there is also BR2_TARGET_GENERIC_HOSTNAME, which deserves a special mention.

        The hostname is used for the system default /etc/hostname, which is the base name for the \"unique:ified\" hostname + the last three octets of the base MAC1 address, e.g., infix-c0-ff-ee. This in turn is the hostname that is set at first boot and also advertised by device discovery protocols like mDNS-SD and LLDP.

        See the help texts for the Infix Branding settings to understand which ones are mandatory and which are optional, menuconfig does not check this for you and you may end up with odd results.

        Verify the result after a build by inspecting:

        • output/images/*: names, missing prefix, etc.
        • output/target/etc/os-release: this file is sourced by other build scripts, e.g., mkgns3a.sh. For reference, see os-release(5)

        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 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.

        "},{"location":"branding/#factory-failure-config","title":"Factory & Failure Config","text":"

        To support booting the same image (CPU architecture) on multiple boards, Infix by default generates the device's initial configuration every time at boot. This also ensures the device can always be restored to a known state after a factory reset, since the factory-config is guaranteed to be compatible with the YANG models for the given software version. (For more information on how the system boots, please see the section Key Concepts in the Introduction document.)

        However, for custom builds of Infix it is possible to override this with a single static /etc/factory-config.cfg (and failure-config) in your rootfs overlay -- with a VPD you can even support several!

        "},{"location":"branding/#variables-format-specifiers","title":"Variables & Format Specifiers","text":"

        Parts of the configuration you likely always want to generated, like the SSH hostkey used by SSH server and NETCONF, a unique hostname, or the admin user's unique (per-device with a VPD) password hash. This section lists the available keywords, see the next section for examples of how to use them:

        • Default password hash: $factory$ (from VPD, .dtb, or built-in) XPath: /ietf-system:system/authentication/user/password
        • Default SSH and NETCONF hostkey: genkey (regenerated at factory reset) XPath: /ietf-keystore:keystore/asymmetric-keys/asymmetric-key[name='genkey']
        • Default HTTPS certificate: gencert (self-signed, regenerated at factory reset) XPath: /ietf-keystore:keystore/asymmetric-keys/asymmetric-key[name='gencert']
        • Hostname format specifiers: XPath: /ietf-system:system/hostname
        • %i: OS ID, from /etc/os-release, from Menuconfig branding
        • %h: Default hostname, from /etc/os-release, from branding
        • %m: NIC specific part of base MAC, e.g., to c0-ff-ee
        • %%: Literal %
        "},{"location":"branding/#static-files","title":"Static Files","text":"

        Caution

        Maintaining a static factory-config and failure-config may seem like an obvious choice, but as YANG models evolve (even the IETF models get upgraded), you may need to upgrade your static files.

        First, for one-off builds (one image per product), the simplest way is to override the location where the system looks for the files, /etc already at build time. This can be done using a Buildroot rootfs overlay providing, e.g., /etc/factory-config.cfg. Example: NanoPi R2S in ${INFIX}/board/aarch64/r2s/rootfs/etc/factory-config.cfg.

        Second, to support multiple products in a single image, we can employ another method to install a /etc/factory-config.cfg override -- at runtime. This relies on the very early system probe that detects the specific product from VPD data.

        The probe consists of several sequential steps that currently run from ${INFIX}/board/common/rootfs/usr/libexec/infix/init.d/. One of them check if /usr/share/product/<PRODUCT> exists, and if so attempts to copy the entire contents to /. Here, <PRODUCT> is determined from the VPD, which is available in /run/system.json as \"product-name\", after 00-probe has run. The lower case version of the string is used.

        I.e., create a rootfs overlay that provides any combination of:

        • /usr/share/product/<PRODUCT>/etc/factory-config.cfg
        • /usr/share/product/<PRODUCT>/etc/failure-config.cfg
        "},{"location":"branding/#dynamically-generated","title":"Dynamically Generated","text":"

        The generated factory-config and failure-config files consist of both static JSON files and part generated files at runtime for each device. The resulting files are written to the RAM disk in /run:

        • /run/confd/factory-config.gen
        • /run/confd/failure-config.gen

        Provided no custom overrides (see above) have been installed already, these files are then copied to:

        • /etc/factory-config.cfg
        • /etc/failure-config.cfg

        ... where the bootstrap process expects them to be in the next step.

        Examples of generated contents are the SSH hostkey and hostname. The latter is constructed from the file /etc/hostname, appended with the last three octets of the system's base MAC address. To override the base hostname, set BR2_TARGET_GENERIC_HOSTNAME in your defconfig.

        The static files are installed by Infix confd in /usr/share/confd/ at build time. It contains two subdirectories:

        /usr/share/confd/\n |- factory.d/\n |  |- 10-foo.json\n |  |- 10-bar.json\n |  `- 10-qux.json\n `- failure.d/\n    |- 10-xyzzy.json\n    `- 10-garply.json\n

        To override, or extend, these files in you br2-external, set up a rootfs overlay and add it last in BR2_ROOTFS_OVERLAY. Your overlay can look something like this:

        ./board/common/rootfs/\n  |- etc/\n  |  |- confdrc             # See below\n  |  `- confdrc.local\n  `- usr/\n     `- share/\n        `- confd/\n           |- 10-foo.json   # Override Infix foo\n           |- 30-bar.json   # Extend, probably 10-bar.json\n           `- 30-fred.json  # Extend, your own defaults\n

        Using the same filename in your overlay, here 10-foo.json, completely replaces the contents of the same file provided by Infix. If you just want to extend, or replace parts of an Infix default, use 30-....json. Here the file 30-bar.json is just a helpful hit to maintainers of your br2-external that it probably extends Infix' 10-bar.json.

        The reason for the jump in numbers is that 20 is reserved for files generated by Infix' gen-function scripts. Your br2-external can provide a few custom ones that the bootstrap knows about, e.g., gen-ifs-custom that overrides 20-interfaces.json. See the bootstrap script for more help, and up-to-date information.

        Tip

        You may not need to provide your own /etc/confdrc for your spin. The one installed by confd is usually enough. However, if you want to adjust the behavior of bootstrap you may want to override it. There is also confdrc.local, which usually is enough to change arguments to scripts like gen-interfaces, e.g., to create a bridge by default, you may want to look into GEN_IFACE_OPTS.

        "},{"location":"branding/#example-snippets","title":"Example Snippets","text":""},{"location":"branding/#ietf-system","title":"IETF System","text":"
          \"ietf-system:system\": {\n    \"hostname\": \"example-%m\",\n    \"ntp\": {\n      \"enabled\": true,\n      \"server\": [\n        {\n          \"name\": \"ntp.org\",\n          \"udp\": {\n            \"address\": \"pool.ntp.org\"\n          }\n        }\n      ]\n    },\n    \"authentication\": {\n      \"user\": [\n        {\n          \"name\": \"admin\",\n          \"password\": \"$factory$\",\n          \"infix-system:shell\": \"bash\"\n        }\n      ]\n    },\n    \"infix-system:motd-banner\": \"Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCAtLSBhIE5ldHdvcmsgT3BlcmF0aW5nIFN5c3RlbQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQuZ2l0aHViLmlvCictJy0tLSctJwo=\"\n  },            # <---- REMEMBER COMMA SEPARATORS IN SNIPPETS!\n                # <---- ... and no comments.\n

        The motd-banner is a binary type, which is basically a Base64 encoded text file without line breaks (-w0):

        $ echo \"Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCAtLSBhIE5ldHdvcmsgT3BlcmF0aW5nIFN5c3RlbQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQuZ2l0aHViLmlvCictJy0tLSctJwo=\" \\\n    | base64 -d\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://kernelkit.github.io\n'-'---'-'\n
        "},{"location":"branding/#ietf-keystore","title":"IETF Keystore","text":"

        Notice how both the public and private keys are left empty here, this cause them to be always automatically regenerated after each factory reset. Keeping the factory-config snippet like this means we can use the same file on multiple devices, without risking them sharing the same host keys or TLS certificates. Sometimes you may want the same keys, but that is the easy use-case and not documented here.

        The genkey entry is the default SSH host key, and gencert is the default self-signed HTTPS certificate used by the web server (nginx). Both are regenerated on factory reset when their keys are empty.

          \"ietf-keystore:keystore\": {\n    \"asymmetric-keys\": {\n      \"asymmetric-key\": [\n        {\n          \"name\": \"genkey\",\n          \"public-key-format\": \"ietf-crypto-types:ssh-public-key-format\",\n          \"public-key\": \"\",\n          \"private-key-format\": \"ietf-crypto-types:rsa-private-key-format\",\n          \"cleartext-private-key\": \"\",\n          \"certificates\": {}\n        },\n        {\n          \"name\": \"gencert\",\n          \"public-key-format\": \"infix-crypto-types:x509-public-key-format\",\n          \"public-key\": \"\",\n          \"private-key-format\": \"infix-crypto-types:rsa-private-key-format\",\n          \"cleartext-private-key\": \"\",\n          \"certificates\": {\n            \"certificate\": [\n              { \"name\": \"self-signed\", \"cert-data\": \"\" }\n            ]\n          }\n        }\n      ]\n    }\n  },\n
        "},{"location":"branding/#ietf-netconf-server","title":"IETF NETCONF Server","text":"
          \"ietf-netconf-server:netconf-server\": {\n    \"listen\": {\n      \"endpoints\": {\n        \"endpoint\": [\n          {\n            \"name\": \"default-ssh\",\n            \"ssh\": {\n              \"tcp-server-parameters\": {\n                \"local-address\": \"::\"\n              },\n              \"ssh-server-parameters\": {\n                \"server-identity\": {\n                  \"host-key\": [\n                    {\n                      \"name\": \"default-key\",\n                      \"public-key\": {\n                        \"central-keystore-reference\": \"genkey\"\n                      }\n                    }\n                  ]\n                }\n              }\n            }\n          }\n        ]\n      }\n    }\n  },\n
        "},{"location":"branding/#infix-services","title":"Infix Services","text":"
          \"infix-services:ssh\": {\n    \"enabled\": true,\n    \"hostkey\": [\n      \"genkey\"\n    ],\n    \"listen\": [\n      {\n        \"name\": \"ipv4\",\n        \"address\": \"0.0.0.0\",\n        \"port\": 22\n      },\n      {\n        \"name\": \"ipv6\",\n        \"address\": \"::1\",\n        \"port\": 22\n      }\n    ]\n  },\n  \"infix-services:web\": {\n    \"certificate\": \"gencert\",\n    \"enabled\": true,\n    \"console\": { \"enabled\": true },\n    \"netbrowse\": { \"enabled\": true },\n    \"restconf\": { \"enabled\": true }\n  }\n

        The certificate leaf references an asymmetric key in the keystore that has an associated certificate. The default gencert entry uses a self-signed certificate. To use a custom (e.g., CA-signed) certificate, create a new keystore entry with x509-public-key-format and point the web certificate leaf to it.

        "},{"location":"branding/#integration","title":"Integration","text":"

        When integrating your software stack with Infix there may be protocols that want to change system settings like hostname and dynamically set IP address and default gateway, e.g. PROFINET. This section detail a few recommendations for maintaining co-existence in this scenario of the multiple producers problem.

        First, there's a clear difference between \"singleton\" like hostsname and an interface IP address. Consider the case of a static IP and a DHCP assigned IP, these can co-exist because of the proto NUM field available in iproute2. This is used in Infix so that static addresses can be flushed independently of DHCP addresses. The same can be done by other \"address providers\", e.g., PROFINET.

        Changing properties like hostname should be done by injecting a change into Infix, by for example calling sysrepocfg -Ediff.xml. Here is an example of how to get the current hostname and apply an XML diff:

        root@infix-00-00-00:~# sysrepocfg -X -x \"/system/hostname\" > hostnm.xml\nroot@infix-00-00-00:~# cat hostnm.xml\n<system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n  <hostname>infix-00-00-00</hostname>\n</system>\nroot@infix-00-00-00:~# edit hostnm.xml\nroot@infix-00-00-00:~# sysrepocfg -Ehostnm.xml\nroot@example:~#\n

        Second, perform all changes on running-config, the running datastore. That way you have a clear state to return to if your application needs to do a factory reset. E.g., in PROFINET a type 1/2 factory reset will reset only the PROFINET specific settings. That way you can actually have your system startup-config disable all physical ports and the PROFINET application enables only ports that are not deactivated. (On factory reset it will not know of any ports to deactivate so it will activate all.)

        You can consider the system composed of two entities:

        • NETCONF starts up the system using startup-config, then
        • Hands over control to your application at runtime

        Infix is prepared for this by already having two \"runlevels\" for these two states. The startup-config is applied in runlevel S (bootstrap) and the system then enters runlevel 2 for normal operation.

        This allow you to keep a set of functionality that is provided by the underlying system, and another managed by your application. You can of course in your br2-external provide a sysrepo plugin that block operations on certain datastores when your application is enabled. E.g., to prevent changes to startup after initial deployment. In that case a proper factory reset would be needed to get back to a \"pre-deployment\" state where you can reconfigure your baseline.

        "},{"location":"branding/#releases","title":"Releases","text":"

        A release build requires the global variable INFIX_RELEASE to be set. It can be derived from GIT, if the source tree is kept in GIT VCS. First, let us talk about versioning in general.

        "},{"location":"branding/#versioning","title":"Versioning","text":"

        Two popular scheme for versioning a product derived from Infix:

        1. Track Infix major.minor, e.g. Foobar v23.08.z, where z is your patch level. I.e., Foobar v23.08.0 could be based on Infix v23.08.0, or v23.08.12, it is up to you. Maybe you based it on v23.08.12 and then back ported changes from v23.10.0, but it was the first release you made to your customer(s).
        2. Start from v1.0.0 and step the major number every time you sync with a new Infix release, or every time Infix bumps to the next Buildroot LTS.

        The important thing is to be consistent, not only for your own sake, but also for your end customers. The major.minor.patch style is the most common and often recommended style, which usually maps well to other systems, e.g. PROFINET GSDML files require this (VX.Y.Z). But you can of course use only two numbers, major.minor, as well.

        Warning

        What could be confusing, however, is if you use the name Infix with your own versioning scheme.

        "},{"location":"branding/#specifying-versioning-information","title":"Specifying Versioning Information","text":"

        Two optional environment variables control the version information recorded in images. Both of these must be a lower-case string (no spaces or other characters outside of 0\u20139, a\u2013z, '.', '_' and '-') identifying the operating system version, excluding any OS name information or release code name, and suitable for processing by scripts or usage in generated filenames.

        "},{"location":"branding/#infix_build_id","title":"INFIX_BUILD_ID","text":"

        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 menuconfig, to the path of an enclosing br2-external.

        "},{"location":"branding/#infix_release","title":"INFIX_RELEASE","text":"

        Used for VERSION and VERSION_ID in /etc/os-release and generated file names like disk images, etc.

        Default: ${INFIX_BUILD_ID}

        1. The base MAC address is defined in the device's Vital Product Data (VPD) EEPROM, or similar, which is used by the kernel to create the system interfaces. This MAC address is usually also printed on a label on the device.\u00a0\u21a9

        "},{"location":"bridging/","title":"Bridging","text":""},{"location":"bridging/#bridging","title":"Bridging","text":"

        This is the most central part of the system. A bridge is a switch, and a switch is a bridge. In Linux, setting up a bridge with ports connected to physical switch fabric, means you manage the actual switch fabric!

        "},{"location":"bridging/#mac-bridge","title":"MAC Bridge","text":"

        In Infix ports are by default not switch ports, unless the customer specific factory config sets it up this way. To enable switching, with offloading if you have a switch chipset, between ports you create a bridge and then add ports to that bridge. Like this:

        admin@example:/> configure\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> up\nadmin@example:/config/> set interface eth0 bridge-port bridge br0\nadmin@example:/config/> set interface eth1 bridge-port bridge br0\nadmin@example:/config/> leave\n

        Here we add two ports to bridge br0: eth0 and eth1.

        Tip

        The CLI has several built-in helpers governed by convention. E.g., naming bridges brN, where N is a number, the type is inferred automatically and unlocks all bridge features. Other conventions are vethNA, where N is a number and A is a letter ('a' for access port and 'b' for bridge side is common), and ethN.M for VLAN M on top of ethN, or dockerN for a IP masquerading container bridge.

        Note, this inference only works with the CLI, configuring networking over NETCONF or RESTCONF requires setting the type explicitly.

        It is possible to create multiple MAC bridges, however, it is currently1 not recommended to use more than one MAC bridge on products with Marvell LinkStreet switching ASICs. A VLAN filtering bridge should be used instead.

        "},{"location":"bridging/#vlan-filtering-bridge","title":"VLAN Filtering Bridge","text":"

        By default bridges in Linux do not filter based on VLAN tags. This can be enabled when creating a bridge by adding a port to a VLAN as a tagged or untagged member. Use the port default VID (PVID) setting to control VLAN association for traffic ingressing a port untagged (default PVID: 1).

        admin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> up\nadmin@example:/config/> set interface eth0 bridge-port bridge br0\nadmin@example:/config/> set interface eth0 bridge-port pvid 10\nadmin@example:/config/> set interface eth1 bridge-port bridge br0\nadmin@example:/config/> set interface eth1 bridge-port pvid 20\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> set bridge vlans vlan 10 untagged eth0\nadmin@example:/config/interface/br0/> set bridge vlans vlan 20 untagged eth1\n

        This sets eth0 as an untagged member of VLAN 10 and eth1 as an untagged member of VLAN 20. Switching between these ports is thus prohibited.

        To terminate a VLAN in the switch itself, either for switch management or for routing, the bridge must become a (tagged) member of the VLAN.

        admin@example:/config/interface/br0/> set bridge vlans vlan 10 tagged br0\nadmin@example:/config/interface/br0/> set bridge vlans vlan 20 tagged br0\n

        To route or to manage via a VLAN, a VLAN interface needs to be created on top of the bridge, see section VLAN Interfaces for more on this topic.

        Note

        In some use-cases only a single management VLAN on the bridge is used. For the example above, if the bridge itself is an untagged member only in VLAN 10, IP addresses can be set directly on the bridge without the need for dedicated VLAN interfaces on top of the bridge.

        "},{"location":"bridging/#multicast-filtering-and-snooping","title":"Multicast Filtering and Snooping","text":"

        Multicast filtering in the bridge is handled by the bridge itself. It can filter both IP multicast and MAC multicast. For IP multicast it also supports \"snooping\", i.e., IGMP and MLD, to automatically reduce the broadcast effects of multicast. See the next section for a summary of the terminology used.

        Important

        Currently there is no way to just enable multicast filtering without also enabling snooping. This may change in the future, in which case a filtering enabled setting will be made available along with the existing snooping setting.

        When creating your bridge you must decide if you need a VLAN filtering bridge or a plain bridge (see previous section). Multicast filtering is supported for either, but take note that it must be enabled and set up per VLAN when VLAN filtering is enabled -- there are no global multicast settings in this operating mode.

        In the following example we have a regular 8-port bridge without VLAN filtering. We focus on the multicast specific settings:

        admin@example:/> configure\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> set bridge multicast snooping\nadmin@example:/config/interface/br0/> set ipv4 address 192.168.2.1 prefix-length 24\nadmin@example:/config/interface/br0/> leave\nadmin@example:/> copy running-config startup-config\n

        Here we enable snooping and set a static IPv4 address so that the switch can take part in IGMP querier elections. (MLD querier election currently not supported.) We can inspect the current state:

        admin@example:/> show ip multicast\nMulticast Overview\nQuery Interval (default): 125 sec\nRouter Timeout          : 255\nFast Leave Ports        :\nRouter Ports            :\nFlood Ports             : e0, e1, e2, e3, e4, e5, e6, e7\n\nInterface       VID  Querier                     State  Interval  Timeout  Ver\nbr0                  192.168.2.1                    Up       125     None    3\n\nBridge          VID  Multicast Group       Ports                              \nbr0                  224.1.1.1             e3, e2\nbr0                  ff02::6a              br0\n

        This is a rather small LAN, so our bridge has already become the elected IGMP querier. We see it is ours because the timeout is None, and we recognize the IP address the system has detected, as ours. We can also see two ports that have joined the same IPv4 multicast group, 224.1.1.1, and one join from the system itself for the IPv6 group ff02::6a.

        Now, let us see what happens when we add another bridge, this time with VLAN filtering enabled. We skip the boring parts about how to move ports e4-e7 to br1 and assign them to VLANs, and again, focus on the multicast bits only:

        admin@example:/> configure\nadmin@example:/config/> edit interface br1\nadmin@example:/config/interface/br1/> set bridge vlans vlan 1 multicast snooping\nadmin@example:/config/interface/br1/> set bridge vlans vlan 2 multicast snooping\nadmin@example:/config/interface/br1/> leave\nadmin@example:/> copy running-config startup-config\n

        Let us see what we get:

        admin@example:/> show ip multicast\nMulticast Overview\nQuery Interval (default): 125 sec\nRouter Timeout          : 255\nFast Leave Ports        : e5\nRouter Ports            : e1, e2, e5, e6, e7\nFlood Ports             : e1, e2, e3, e4, e5, e6, e7, e8\n\nInterface       VID  Querier                     State  Interval  Timeout  Ver\nbr0                  192.168.2.1                    Up       125     None    3\nbr1               1  0.0.0.0                        Up       125     None    3\nbr1               2  0.0.0.0                        Up       125     None    3\n\nBridge          VID  Multicast Group       Ports                              \nbr0                  224.1.1.1             e2\nbr0                  ff02::fb              br0\nbr0                  ff02::6a              br0\nbr0                  ff02::1:ff00:0        br0\nbr1               1  224.1.1.1             e5\nbr1               2  224.1.1.1             e7\nbr1               1  ff02::fb              br1\nbr1               1  ff02::1:ff00:0        br1\n

        In this setup we have a lot more going on. Multiple multicast router ports have been detected, and behind the scenes someone has also added an IGMP/MLD fast-leave port.

        "},{"location":"bridging/#terminology-abbreviations","title":"Terminology & Abbreviations","text":"
        • IGMP: Internet Group Membership Protocol, multicast subscription for IPv4, for details see RFC3376
        • MLD: Multicast Listener Discovery (Protocol), multicast subscription for IPv6, for details see RFC3810
        • Unknown/Unregistered multicast: multicast groups that are not in the multicast forwarding database (MDB)
        • Known/Registered multicast: multicast groups that are in the multicast forwarding database (MDB)
        • MDB: the multicast forwarding database, consists of filters for multicast groups, directing where multicast is allowed to egress. A filter entry consists of a group and a port list. The bridge filters with a unique database per VLAN, in the same was as the unicast FDB
        • Join/Leave: the terminology used in earlier versions of the two protocols to subscribe and unsubscribe to a multicast group. For more information, see Membership Report
        • Membership Report A membership report is sent by end-devices and forwarded by switches to the elected querier on the LAN. They consist of multiple \"join\" and \"leave\" operations on groups. They can also, per group, list which senders to allow or block. Switches usually only support the group subscription, and even more common also only support filtering on the MAC level2
        • Querier election: the process of determining who is the elected IGMP/MLD querier on a LAN. Lowest numerical IP address wins, the special address 0.0.0.0 (proxy querier) never wins
        • Proxy querier: when no better querier exists on a LAN, one or more devices can send proxy queries with source address 0.0.0.0 (or :: for IPv6). See Query Interval, below, why this is a good thing
        • Query interval: the time in seconds between two queries from an IGMP/MLD querier. It is not uncommon that end-devices do not send their membership reports unless they first hear a query
        • Fast Leave: set on a bridge port to ensure multicast is pruned as quickly as possible when a \"leave\" membership report is received. In effect, this option marks the port as directly connected to an end-device. When not set (default), a query with timeout is first sent to ensure no unintentional loss of multicast is incurred
        • Router port: can be both configured statically and detected at runtime based on connected devices, usually multicast routers. On a router port all multicast is forwarded, both known and unknown
        • Flood port: set on a bridge port (default: enabled) to ensure all unknown multicast is forwarded
        • Router timeout: the time in seconds until a querier is deemed to have been lost and another device (switch/router) takes over. In the tables shown above, a None timeout is declared when the current device is the active querier

        Tip

        The reason why multicast flooding is enabled by default is to ensure safe co-existence with MAC multicast, which is common in industrial networks. It also allows end devices that do not know of IGMP/MLD to communicate over multicast as long as the group they have chosen is not used by other IGMP/MLD aware devices on the LAN.

        As soon as an IGMP/MLD membership report to \"join\" a group is received the group is added to the kernel MDB and forwarding to other ports stop. The only exception to this rule is multicast router ports.

        If your MAC multicast forwarding is not working properly, it may be because an IP multicast group maps to the same MAC address. Please see RFC 1112 for details. Use static multicast router ports, or static multicast MAC filters, to mitigate.

        "},{"location":"bridging/#forwarding-of-ieee-reserved-group-addresses","title":"Forwarding of IEEE Reserved Group Addresses","text":"

        Addresses in the range 01:80:C2:00:00:0X are used by various bridge signaling protocols, and are not forwarded by default. Still, it is sometimes useful to let the bridge forward such packets, this can be done by specifying protocol names or the last address nibble as decimal value 0..15:

        admin@example:/config/> edit interface br0 bridge\nadmin@example:/config/interface/br0/bridge/> set ieee-group-forward     # Tap the ? key for alternatives\n  [0..15]  List of IEEE link-local protocols to forward, e.g., STP, LLDP\n  dot1x    802.1X Port-Based Network Access Control.\n  lacp     802.3 Slow Protocols, e.g., LACP.\n  lldp     802.1AB Link Layer Discovery Protocol (LLDP).\n  stp      Spanning Tree (STP/RSPT/MSTP).\nadmin@example:/config/interface/br0/bridge/> set ieee-group-forward\n

        The following example configures bridge br0 to forward LLDP packets.

        admin@example:/config/interface/br0/bridge/> set ieee-group-forward lldp\nadmin@example:/config/interface/br0/bridge/>\n
        1. MAC bridges on Marvell Linkstreet devices are currently limited to a single MAC database, this may be a problem if the same MAC address appears in different MAC bridges.\u00a0\u21a9

        2. For example, IPv4 groups are mapped to MAC multicast addresses by mapping the low-order 23-bits of the IP address in the low-order 23 bits of the Ethernet address 01:00:5E:00:00:00. Meaning, more than one IP multicast group maps to the same MAC multicast group.\u00a0\u21a9

        "},{"location":"container/","title":"Docker Containers","text":""},{"location":"container/#docker-container-support","title":"Docker Container Support","text":"

        Infix comes with native support for Docker containers using podman. The YANG model describes the current level of support, complete enough to run both system and application containers.

        Key design features of Infix, like using Linux switchdev, allow users to assign switch ports directly to containers, not just bridged VETH pairs. This is a rare and in many cases unique feature of Infix.

        All network specific settings are done using the IETF interfaces YANG model, with augments for containers to ensure smooth integration with container networking in podman.

        Important

        Even though the podman command can be used directly from a shell prompt, we strongly recommend using the CLI commands instead. They employ the services of a wrapper container script which handles the integration of Docker containers in the system.

        "},{"location":"container/#caution","title":"Caution","text":"

        A word of warning. Containers can run on your system in privileged mode, as root, giving them full access to devices on your system. Even though containers are fenced from the host with Linux namespaces, resource limited using cgroups, and normally run with capped privileges, a privileged container is relatively easy to break out of. A trivial example is given in the Advanced section of this document.

        We recommend avoiding privileged containers, if possible (they do have valid use-cases) and instead use capabilities.

        Remember:

        • If the system is compromised, containers can be used to easily install malicious software in your system and over the network
        • Your system is as secure as anything you run in the container
        • If you run containers, there is no security guarantee of any kind
        • Running 3rd party container images on your system could open a security hole/attack vector/surface
        • An expert with knowledge how to build exploits will be able to jailbreak/elevate to root even if best practices are followed

        This being said, a system suspected of being compromised can always be restored to a safe state with a factory reset. Provided, of course, that it has secure boot enabled.

        "},{"location":"container/#getting-started","title":"Getting Started","text":"

        In the CLI, containers can be run in one of two ways:

        1. container run IMAGE [COMMAND], or
        2. enter configure context, then edit container NAME

        The first is useful mostly for testing, or running single commands in an image. It is a wrapper for podman run -it --rm ....

        The second creates a read-only container that by default automatically start at every boot. It basically wraps podman create ....

        When non-volatile storage is needed two complementary options exist:

        • Volumes: data stored in a volume is persisted until explicitly removed from the configuration, i.e., across host reboots and container upgrades
        • Content Mounts: where the content of a file mounted into the container is kept along with the container configuration in the device's startup-config

        Podman ensures (using tmpfs) all containers have writable directories for certain critical file system paths: /dev, /dev/shm, /run, /tmp, and /var/tmp. Meaning, what you most often need is writable volumes for /var/lib and /etc, or only file mounts for a few files in /etc. The actual requirements depend on your container image and application to run.

        Important

        When running containers from public registries, double-check that they support the CPU architecture of your host system. Remember, unlike virtualization, containers reuse the host's CPU and kernel.

        "},{"location":"container/#example-hello-world","title":"Example: Hello World","text":"

        Classic Hello World:

        admin@example:/> container run docker://hello-world\nStarting docker://hello-world :: use Ctrl-p Ctrl-q to detach\nTrying to pull docker.io/library/hello-world:latest...\nGetting image source signatures\nCopying blob c1ec31eb5944 done\nCopying config d2c94e258d done\nWriting manifest to image destination\nStoring signatures\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n
        "},{"location":"container/#example-web-server","title":"Example: Web Server","text":"

        A web server with nginx, using standard docker bridge. Podman will automatically create a VETH pair for us, connecting the container to the docker0 bridge:

        admin@example:/> configure\nadmin@example:/config/> edit interface docker0\nadmin@example:/config/interface/docker0/> set container-network\nadmin@example:/config/interface/docker0/> end\nadmin@example:/config/> edit container web\nadmin@example:/config/container/web/> set image docker://nginx:alpine\nadmin@example:/config/container/web/> set network publish 8080:80\nadmin@example:/config/container/web/> set network interface docker0\nadmin@example:/config/container/web/> set volume cache target /var/cache\nadmin@example:/config/container/web/> leave\nadmin@example:/> show container\n

        Exit to the shell and verify the service with curl, or try to attach to your device's IP address using your browser:

        admin@example:~$ curl http://localhost:8080\n

        or connect to port 8080 of your running Infix system with a browser. See the following sections for how to add more interfaces and manage your container at runtime.

        "},{"location":"container/#container-images","title":"Container Images","text":"

        The underlying podman project supports importing and fetching images in a variety of ways, the most common ones are also supported by Infix. In this section we present how to use them and in the next section we show how to upgrade to a newer base image.

        The CLI help shows:

        admin@example:/config/container/system/> help image\nNAME\n    image <string>\n\nDESCRIPTION\n    Docker image for the container: [transport]name[:tag|@digest]\n\n    quay.io/username/myimage     -- Pull myimage:latest\n    docker://busybox             -- Pull busybox:latest from Docker Hub\n    docker://ghcr.io/usr/img     -- Pull img:latest from GitHub packages\n    dir:/media/usb/myimage:1.1   -- Use myimage v1.1 from USB media\n    docker-archive:/tmp/archive  -- Use archive:latest from tarball\n    oci-archive:/lib/oci/archive -- Use archive:latest from OCI archive\n                                    May be in .tar or .tar.gz format\n\n    Additionally, the following URIs are also supported for setups\n    that do not use a HUB or similar.  Recommend using 'checksum'!\n\n    ftp://addr/path/to/archive   -- Downloaded using wget\n    http://addr/path/to/archive  -- Downloaded using curl\n    https://addr/path/to/archive -- Downloaded using curl\n\n    Note: if a remote repository cannot be reached, the creation of the\n          container will be put on a queue that retries pull every time\n          there is a route change in the host's system.\n

        Tip

        The built-in help system in the CLI is generated from the YANG model, so the same information is also available for remote NETCONF users.

        The two most common variants are docker:// and oci-archive:/.

        The former requires a working Docker registry and the latter operates on a plain OCI archive. Infix does not come with a built-in registry, so the docker:// option is best used with external services, which in turn require networking to be up. In a deployment phase the easiest may be to set up a single interface on your host system with DHCP client.

        The default method is docker://, so when setting the image for your container, you can omit the docker:// prefix. You can also use the admin-exec command container pull docker://..., and when configuring a container podman will check first if it has the image before trying to download anything. (See also the upgrade section, below.)

        The oci-archive:/ is interesting since many users may not have, or do not want to, publish their images in a registry. Use the Docker OCI exporter or any other tool that supports generating OCI Image format. Infix supports loading both .tar or .tar.gz formats.

        Here we show a simple example of fetching an OCI image to the system, but many others exist, tools like wget, curl, and scp come to mind.

        Shell OCI Example:

        admin@example:~$ cd /var/tmp/\nadmin@example:/var/tmp$ sudo wget https://github.com/kernelkit/curiOS/releases/download/edge/curios-oci-amd64.tar.gz\nConnecting to github.com (140.82.121.3:443)\nwget: note: TLS certificate validation not implemented\nConnecting to objects.githubusercontent.com (185.199.109.133:443)\nsaving to 'curios-oci-amd64.tar.gz'\ncurios-oci-amd64.tar 100% |*********************************| 7091k  0:00:00 ETA\n'curios-oci-amd64.tar.gz' saved\nadmin@example:/var/tmp$ ll\ntotal 7104\ndrwxr-xr-x    3 root     root          4096 Mar 27 14:22 ./\ndrwxr-xr-x   14 root     root          4096 Mar 27 11:57 ../\n-rw-r--r--    1 root     root       7261785 Mar 27 14:22 curios-oci-amd64.tar.gz\ndrwx------    6 frr      frr           4096 Mar 27 11:57 frr/\n

        Importing the image into Podman can be done either from the CLI admin-exec context ...

        admin@example:/var/tmp$ cli\nadmin@example:/> container load /var/tmp/curios-oci-amd64.tar.gz name curios:edge\n

        Tip

        The name curios:edge is the tag you give the imported (raw) archive which you can then reference in your container image configuration: set image curios:edge.

        ... or by giving the container configuration the full path to the OCI archive, which helps greatly with container upgrades (see below):

        admin@example:/config/container/system/> set image oci-archive:/var/tmp/curios-oci-amd64.tar.gz\n

        Checksum Example:

        admin@example:/> configure\nadmin@example:/config/> edit container sys\nadmin@example:/config/container/sys/> set hostname sys\nadmin@example:/config/container/sys/> set image ftp://192.168.122.1/curios-oci-amd64-v24.05.0.tar.gz\nadmin@example:/config/container/sys/> set checksum\n    md5 sha256 sha512\nadmin@example:/config/container/sys/> set checksum sha256 4f01077036527498ed910f1a3e80645ae3eff629d10043cf80ebc6850c99c629\nadmin@example:/config/container/sys/> leave\nadmin@example:/> copy running-config startup-config\nadmin@example:/> show container\nNAME  STATUS         NETWORK  MEMORY (KiB)  CPU%\nsys   Up 5 seconds                  72/512  0.02\n\nadmin@example:/> show log\n...\nNov 20 07:24:56 example container[5040]: Fetching ftp://192.168.122.1/curios-oci-amd64-v24.05.0.tar.gz\nNov 20 07:24:56 example container[5040]: curios-oci-amd64-v24.05.0.tar.gz downloaded successfully.\nNov 20 07:24:56 example container[5040]: curios-oci-amd64-v24.05.0.tar.gz checksum verified OK.\nNov 20 07:24:57 example container[5040]: Cleaning up extracted curios-oci-amd64-v24.05.0\nNov 20 07:24:57 example container[5040]: podman create --name sys --conmon-pidfile=/run/container:sys.pid --read-only --replace --quiet --cgroup-parent=containers  --restart=always --systemd=false --tz=local --hostname sys --log-driver k8s-file --log-opt path=/run/containers/sys.fifo --network=none curios-oci-amd64-v24.05.0\nNov 20 07:24:57 example container[3556]: b02e945c43c9bce2c4be88e31d6f63cfdb1a3c8bdd02179376eb059a49ae05e4\n
        "},{"location":"container/#understanding-image-tags","title":"Understanding Image Tags","text":"

        Docker images use tags to identify different versions of the same image. Understanding the difference between mutable and immutable tags is important for managing container upgrades effectively.

        "},{"location":"container/#mutable-tags","title":"Mutable Tags","text":"

        Tags like :latest, :edge, or :stable are mutable \u2014 they point to different images over time as new versions are published to the registry.

        Advantages:

        • Convenient: upgrade without changing configuration
        • Simple: use the CLI command container upgrade NAME to get the latest version, there is even a convenient RPC for controlling the remotely
        • Good for: development, testing, and systems that auto-update

        Trade-offs:

        • Less reproducible: different systems may run different versions
        • Less predictable: upgrades happen when you pull, not when you plan
        • Harder to rollback: previous version may no longer be available

        Example mutable tags:

        docker://nginx:latest          # Always points to newest release\ndocker://myapp:edge            # Development/bleeding edge version\noci-archive:/var/tmp/app.tar   # Local archive that may be replaced\n
        "},{"location":"container/#immutable-tags","title":"Immutable Tags","text":"

        Version-specific tags like :v1.0.1, :24.11.0, or digest references like @sha256:abc123... are immutable \u2014 they always reference the exact same image content.

        Advantages:

        • Reproducible: all systems run identical versions
        • Predictable: upgrades only happen when you change configuration
        • Auditable: clear history of what ran when
        • Good for: production, compliance, and controlled deployments

        Trade-offs:

        • More explicit: must update configuration to upgrade
        • Requires planning: need to know which version to use

        Example immutable tags:

        docker://nginx:1.25.3          # Specific version number\ndocker://myapp:v2.1.0          # Semantic version tag\ndocker://nginx@sha256:abc123   # Cryptographic digest (most immutable)\n

        Tip

        Best practice for production: Use specific version tags (:v1.0.1) rather than mutable tags (:latest). This ensures all your systems run identical software and upgrades happen only when you decide.

        "},{"location":"container/#upgrading-container-images","title":"Upgrading Container Images","text":"

        The applications in your container are an active part of the system as a whole, so make it a routine to keep your container images up-to-date!

        "},{"location":"container/#how-container-lifecycle-works","title":"How Container Lifecycle Works","text":"

        Infix intelligently manages container lifecycles to provide a smooth experience while minimizing unnecessary work:

        At first setup: When you configure a container for the first time, Infix fetches the image (if needed) and creates the container instance.

        At boot time: Infix checks if the container needs to be recreated by comparing checksums for:

        • The image archive that the container was built from
        • The container configuration script

        When configuration changes: If you modify any container settings (network, volumes, environment, etc.), the container is automatically recreated with the new configuration.

        When explicitly upgraded: Using the container upgrade command forces a fresh pull of the image and recreates the container.

        This means that in most cases, containers persist across reboots and are only recreated when actually necessary. Your container's state stored in volumes is preserved across recreations. Since Infix containers use a read-only root filesystem, any changes written outside of volumes or the writable paths provided by Podman (/dev, /dev/shm, /run, /tmp, /var/tmp) will be lost when the container is recreated.

        "},{"location":"container/#method-1-upgrading-immutable-tags","title":"Method 1: Upgrading Immutable Tags","text":"

        When using version-specific tags, you upgrade by explicitly changing the image reference in your configuration:

        admin@example:/> configure\nadmin@example:/config/> edit container web\nadmin@example:/config/container/web/> set image docker://nginx:1.25.3\nadmin@example:/config/container/web/> leave\n

        What happens:

        1. Podman pulls the new image in the background (if not already present)
        2. Your container is automatically stopped
        3. The container is recreated with the new image
        4. The container is started with your existing volumes intact

        Example: Upgrading from one version to another:

        admin@example:/> configure\nadmin@example:/config/> edit container system\nadmin@example:/config/container/system/> show image\nimage ghcr.io/kernelkit/curios:v24.11.0;\nadmin@example:/config/container/system/> set image ghcr.io/kernelkit/curios:v24.12.0\nadmin@example:/config/container/system/> leave\nadmin@example:/> show log\n...\nDec 13 14:32:15 example container[1523]: Pulling ghcr.io/kernelkit/curios:v24.12.0...\nDec 13 14:32:18 example container[1523]: Stopping old container instance...\nDec 13 14:32:19 example container[1523]: Creating new container with updated image...\nDec 13 14:32:20 example container[1523]: Container system started successfully\n
        "},{"location":"container/#method-2-upgrading-mutable-tags","title":"Method 2: Upgrading Mutable Tags","text":"

        For images using mutable tags like :latest or :edge, use the container upgrade command:

        admin@example:/> container upgrade NAME\n

        This command:

        1. Stops the running container
        2. Pulls the latest version of the image from the registry
        3. Recreates the container with the new image
        4. Starts the container automatically

        Example using registry:

        admin@example:/> container upgrade system\nsystem\nTrying to pull ghcr.io/kernelkit/curios:edge...\nGetting image source signatures\nCopying blob 07bfba95fe93 done\nCopying config 0cb6059c0f done\nWriting manifest to image destination\nStoring signatures\n0cb6059c0f4111650ddbc7dbc4880c64ab8180d4bdbb7269c08034defc348f17\nsystem: not running.\n59618cc3c84bef341c1f5251a62be1592e459cc990f0b8864bc0f5be70e60719\n

        Example using local OCI archive:

        An OCI archive image can be upgraded in a similar manner. First, get the new archive onto the system (see Container Images section above), then, provided the oci-archive:/path/to/archive format is used in your configuration, call the upgrade command:

        admin@example:/> container upgrade system\nUpgrading container system with local archive: oci-archive:/var/tmp/curios-oci-amd64.tar.gz ...\n7ab4a07ee0c6039837419b7afda4da1527a70f0c60c0f0ac21cafee05ba24b52\n

        OCI archives can also be fetched from ftp/http/https URLs. In that case, the upgrade works the same way as a registry image \u2014 Infix downloads the new archive and recreates the container.

        "},{"location":"container/#embedded-container-images","title":"Embedded Container Images","text":"

        Tip

        Containers running from OCI images embedded in the operating system, e.g., /lib/oci/mycontainer.tar.gz, are automatically kept in sync with the Infix system image version.

        How it works: When you build a custom Infix image with embedded OCI archives, those containers will be upgraded whenever you upgrade the Infix operating system itself. At boot, Infix checks if the embedded image has changed and automatically recreates the container if needed.

        Example: default builds of Infix include a couple of OCI images for reference, one is /lib/oci/curios-nftables-v24.11.0.tar.gz, but there is also a symlink called curios-nftables-latest.tar.gz in the same directory, which is what the Infix regression tests use in the image configuration of the container. When the system is upgraded and the embedded image changes, the test containers are automatically recreated with the new version.

        This approach ensures your embedded containers always match your system version without any manual intervention.

        "},{"location":"container/#capabilities","title":"Capabilities","text":"

        An unprivileged container works for almost all use-cases, but there are occasions where they are too restricted and users start looking for the privileged flag. Capabilities offers a middle ground.

        For example, a system container from which ping does not work:

        admin@example:/config/container/system/> edit capabilities\nadmin@example:/config/container/system/capabilities/> set add net_raw\nadmin@example:/config/container/system/capabilities/> end\nadmin@infix-00-00-00:/config/container/system/> show\n...\ncapabilities {\n  add net_raw;\n}\n...\n

        Infix supports a subset of all capabilities that are relevant for containers. Please note, that this is an advanced topic that require time and analysis of your container application to figure out which capabilities you need.

        "},{"location":"container/#resource-limits","title":"Resource Limits","text":"

        Containers can be configured with resource limits to control their memory and CPU usage. This helps prevent containers from consuming excessive system resources and ensures fair resource allocation across multiple containers.

        "},{"location":"container/#configuring-resource-limits","title":"Configuring Resource Limits","text":"

        Resource limits are set per container and include:

        • Memory: Maximum memory usage in kibibytes (KiB)
        • CPU: Maximum CPU usage in millicores (1000 millicores = 1 CPU core)

        Example configuration limiting a container to 512 MiB of memory and 1.5 CPU cores:

        admin@example:/> configure\nadmin@example:/config/> edit container web\nadmin@example:/config/container/web/> edit resource-limit\nadmin@example:/config/container/web/resource-limit/> set memory 524288\nadmin@example:/config/container/web/resource-limit/> set cpu 1500\nadmin@example:/config/container/web/resource-limit/> leave\n

        Common CPU limit examples:

        • 500 = 0.5 cores (50% of one core)
        • 1000 = 1.0 cores (one full core)
        • 2000 = 2.0 cores (two full cores)
        "},{"location":"container/#monitoring-resource-usage","title":"Monitoring Resource Usage","text":"

        Runtime resource usage statistics are available in the operational datastore:

        admin@example:/> show container web\n...\n

        Use show container usage to see resource consumption across all containers, including memory, CPU, block I/O, network I/O, and process counts.

        "},{"location":"container/#networking-and-containers","title":"Networking and Containers","text":"

        By default, unlike other systems, persistent2 containers have no networking enabled. All network access has to be set up explicitly. Currently two types of of container networks are supported:

        • host: an managed host interface, e.g., one end of a VETH pair, or even a physical interface
        • bridge: an IP masquerading bridge

        In the former the interface is delegated to (moved into) the container, while in the latter a VETH pair is automatically created by Podman and one end delegated to the container, while the other end is assigned to the bridge (see the next section).

        Tip

        For more information on VETH pairs, see the Networking Guide.

        "},{"location":"container/#container-bridge","title":"Container Bridge","text":"

        A container bridge is what most container setups use and users want.

        The difference from a regular bridge is that the container runtime fully manages them -- connecting containers with automatically created VETH pairs (look at the bridge port names) and setting up firewall rules between the host and other containers, as well as managing port forwarding. This transparent background management is what makes container use seem to be so simple.

        All interface configuration is done in configure context.

        admin@example:/> configure\nadmin@example:/config> edit interface docker0\nadmin@example:/config/interface/docker0/> set container-network\nadmin@example:/config/interface/docker0/> leave\n

        There is more to this story. When using the CLI, and sticking to common interface nomenclature, Infix helps you with some of the boring stuff. E.g., creating a new interface with a name like brN or dockerN automatically infers the interface types, which you would otherwise have to set manually:

        admin@example:/config/interface/docker0/> set type bridge\nadmin@example:/config/interface/docker0/> set container-network type bridge\n

        Important

        When configuring the system via an API such as NETCONF or RESTCONF, no settings are inferred. Instead it is up to the caller to fully define the desired setup. This makes the CLI very useful for first setup and then extracting the resulting XML from the shell using the cfg -X command.

        We have to declare the interface as a container network, ensuring the interface cannot be used by the system for any other purpose. E.g., a container host interface is supposed to be used by a container, by declaring it as such we can guarantee that it would never accidentally be added as a bridge or lag port. Hence, to move an interface currently set as a bridge-port it must be removed from the bridge before being given to a container.

        The default subnet for a container bridge is 172.17.0.0/16, the bridge takes the .1 address and hand out the rest of the range to containers in a round-robin like fashion. A container with this network get an automatically created VETH pair connection to the bridge and a lot of other networking parameters (DNS, default route) are set up.

        Some of the defaults of a container bridge can be changed, e.g., instead of set container-network type bridge, above, do:

        admin@example:/config/interface/docker0/> edit container-network\nadmin@example:/config/\u2026/container-network/> set type bridge\nadmin@example:/config/\u2026/container-network/> edit subnet 192.168.0.0/16\nadmin@example:/config/\u2026/subnet/192.168.0.0/16/> set gateway 192.168.255.254\nadmin@example:/config/\u2026/subnet/192.168.0.0/16/> end\nadmin@example:/config/\u2026/container-network/> edit route 10.0.10.0/24\nadmin@example:/config/\u2026/route/10.0.10.0/24/> set gateway 192.168.10.254\nadmin@example:/config/\u2026/route/10.0.10.0/24/> end\nadmin@example:/config/\u2026/container-network/> end\nadmin@example:/config/interface/docker0/> leave\n

        Other network settings, like DNS and domain, use built-in defaults, but can be overridden from each container. Other common settings per container is the IP address and name of the network interface inside the container. The default, after each stop/start cycle, or reboot of the host, is to name the interfaces eth0, eth1, in the order they are given in the network list, and to give the container the next address in a bridge. Below an example of a system container calls set network interface docker0, here we show how to set options for that network:

        admin@example:/config/container/ntpd/> edit network interface docker0\nadmin@example:/config/\u2026/network/interface/docker0/>\nadmin@example:/config/\u2026/network/interface/docker0/> set option\n<string>  Options for masquerading container bridges.\nadmin@example:/config/\u2026/network/interface/docker0/> help option\nNAME\n        option <string>\n\nDESCRIPTION\n        Options for masquerading container bridges.\n\n        Example: ip=1.2.3.4            -- request a specific IP (IPv4 or IPv6)\n                 mac=00:01:02:c0:ff:ee -- set fixed MAC address in container\n                 interface_name=foo0   -- set interface name inside container\n\nadmin@example:/config/\u2026/network/interface/docker0/> set option ip=172.17.0.2\nadmin@example:/config/\u2026/network/interface/docker0/> set option interface_name=wan\nadmin@example:/config/\u2026/network/interface/docker0/> leave\n
        "},{"location":"container/#container-host-interface","title":"Container Host Interface","text":"

        Another common use-case is to move network interfaces into the network namespace of a container1. This of course works with plain Ethernet interfaces as well, but here we will use one end of a VETH pair as an example.

        The network option setting is available also for this case, but only the interface_name=foo0 option works. Which is still very useful. To set:

        • IP address, use IPv4/IPv6 settings in the interface settings
        • MAC address, to use the custom-phys-address in the interface settings

        For an example of both, see the next section.

        Important

        VETH Pair Limitation: When using VETH pairs with containers, at least one side of the pair must remain in the host namespace. It is currently not possible to create VETH pairs where both ends are assigned to different containers. One end must always be accessible from the host.

        "},{"location":"container/#routed-setup","title":"Routed Setup","text":"

        In this routed setup we reserve 192.168.0.0/24 for the network between the host and the ntpd container.

        Configuration is a straight-forward VETH pair setup where we name the container-end of pair ntpd. This is just a convenience for us when reading the configuration later. The real action happens on the last line where we declare the ntpd end as a container network interface:

        admin@example:/config/> edit interface veth0\nadmin@example:/config/interface/veth0/> set veth peer ntpd\nadmin@example:/config/interface/veth0/> set ipv4 address 192.168.0.1 prefix-length 24\nadmin@example:/config/interface/veth0/> end\nadmin@example:/config/> edit interface ntpd\nadmin@example:/config/interface/ntpd/> set ipv4 address 192.168.0.2 prefix-length 24\nadmin@example:/config/interface/ntpd/> set custom-phys-address static 00:c0:ff:ee:00:01\nadmin@example:/config/interface/ntpd/> set container-network\n

        Tip

        Notice how you can also set a custom MAC address at the same time.

        Adding the interface to the container is the same as before, but since everything for host interfaces is set up in the interfaces context, we can take a bit of a shortcut.

        admin@example:/config/container/ntpd/> set network interface ntpd\nadmin@example:/config/container/ntpd/> leave\n

        Tip

        Use the set network interface ntpd option interface_name=foo0 to set the name of the interface inside the container to foo0.

        The point of the routed case is that port forwarding from the container in this case is limited to a single interface, not all interfaces as is the default in the masquerading container bridge setup.

        "},{"location":"container/#bridged-setup","title":"Bridged Setup","text":"

        A perhaps more common case is to bridge the other end of the VETH pair with other physical ports. In this section we show how to add a new pair to give our container two interfaces:

        We start by adding the second VETH pair:

        admin@example:/config/> edit interface veth1a\nadmin@example:/config/interface/veth1a/> set veth peer veth1\nadmin@example:/config/interface/veth1a/> set ipv4 address 192.168.1.2 prefix-length 24\n

        Note

        The LAN bridge (br1) in this example has IP address 192.168.1.1.

        When a container has multiple host interfaces it can often be useful to have a default route installed. This can be added from the host with a 0.0.0.0/0 route on one of the interfaces:

        admin@example:/config/interface/veth1a/> set container-network route 0.0.0.0/0 gateway 192.168.1.1\nadmin@example:/config/interface/veth1a/> show\ntype veth;\ncontainer-network {\n  type host;\n  route 0.0.0.0/0 {\n    gateway 192.168.1.1;\n  }\n}\nveth {\n  peer veth1;\n}\nadmin@example:/config/interface/veth1a/> end\nadmin@example:/config/> set interface veth1 bridge-port bridge br1\n

        Please note, container network routes require the base interface also have a static IP address set. Setting only the route, but no address, means the route is skipped.

        "},{"location":"container/#host-networking","title":"Host Networking","text":"

        The third use-case is host networking, this is where a container share the network namespace of the host. An example here could be a nftables or ntpd container -- single applications which add core functionality to the host operating system.

        The host networking setup cannot be combined with any other network.

        For an example, see below.

        "},{"location":"container/#mounts-and-volumes","title":"Mounts and Volumes","text":"

        It is possible to mount files, directories, and even files matching a glob, into a container. This gives precise control over the container's file system:

        admin@example:/config/container/system/> edit mount leds\nadmin@example:/config/container/system/mount/leds> set source /sys/class/leds\nadmin@example:/config/container/system/mount/leds> set target /sys/class/leds\nadmin@example:/config/container/system/mount/leds> end\nadmin@example:/config/container/system/>\n

        Any type of file can be bind mounted into the container, just watch out for permissions though. In the example above, /sys/class/leds is not writable from a container unless it runs in privileged mode. For plain configuration files you get more freedom, and your container can rely on, e.g., inotify events to trigger reloading its services when you change the file on the host.

        So it depends on the container, and indeed your overall setup, what to use. An intriguing option is Content Mounts, which when changed also trigger a container restart.

        Other times volumes are a better fit. A volume is an automatically created read-writable entity that follows the life of your container.

        admin@example:/config/container/ntpd/> set volume varlib target /var/lib\n

        Volumes are persistent across both reboots and upgrades of the base image. They are created by Podman when the container first starts up, unlike a regular bind mount it synchronizes with the contents of the underlying container image's path at first use. I.e., \"bind-mount, if empty: then rsync\".

        Note

        Infix support named volumes (only), and it is not possible to share a volume between containers. All the tricks possible with volumes may be added in a later release.

        "},{"location":"container/#volume-management","title":"Volume Management","text":"

        Volumes are persistent storage that survive container restarts and image upgrades, making them ideal for application data. However, this also means they are not automatically removed when a container is deleted from the configuration.

        This design choice prevents accidental data loss, especially in scenarios where:

        • A container is temporarily removed and re-added with the same name
        • A container is replaced with a different configuration but same name
        • System upgrades or configuration changes affect container definitions

        To clean up unused volumes and reclaim disk space, use the admin-exec command:

        admin@example:/> container prune\nDeleted Images\n...\nDeleted Volumes\nntpd-varlib\nsystem-data\n\nTotal reclaimed space: 45.2MB\n

        The container prune command safely removes:

        • Unused container images
        • Volumes not attached to any container (running or stopped)
        • Other unused container resources

        Tip

        You can monitor container resource usage with the command:

        admin@example:/> show container usage\n

        This displays disk space used by images, containers, and volumes, helping you decide when to run the prune command.

        To see which volumes exist and which containers use them:

        admin@example:/> show container volumes\n

        "},{"location":"container/#content-mounts","title":"Content Mounts","text":"

        Content mounts are a special type of file mount where the file contents is stored with the container configuration. This can be very useful when deploying similar systems at multiple sites. When the host loads its startup-config (or even factory-config) a temporary file is created using the decoded base64 data from the content node.

        admin@example:/config/container/ntpd/> edit mount ntpd.conf\nadmin@example:/config/container/ntpd/mount/ntpd.conf> text-editor content\n... interactive editor starts up ...\nadmin@example:/config/container/ntpd/mount/ntpd.conf> set target /etc/ntpd.conf\nadmin@example:/config/container/ntpd/mount/ntpd.conf> end\nadmin@example:/config/container/ntpd/>\n

        The editor is a small Emacs clone called Mg, see the built-in help text, or press Ctrl-x Ctrl-c to exit and save. When the editor exits the contents are base64 encoded and stored in the candidate datastore.

        Note

        Since these files are always recreated when the host is restarted, changes made by the container are not preserved, or saved back to the host's startup-config.

        Infix has three different text editors available. For more information, see CLI Text Editor.

        "},{"location":"container/#example-containers","title":"Example Containers","text":""},{"location":"container/#system-container","title":"System Container","text":"

        Let's try out what we've learned by setting up a system container, a container providing multiple services, using the docker0 interface we created previously:

        admin@example:/> configure\nadmin@example:/config> edit container system\nadmin@example:/config/container/system/> set image ghcr.io/kernelkit/curios:edge\nadmin@example:/config/container/system/> set network interface docker0\nadmin@example:/config/container/system/> set publish 222:22\nadmin@example:/config/container/system/> leave\n

        Note

        Ensure you have a network connection to the registry. If the image cannot be pulled, creation of the container will be put in a queue and be retried every time there is a change in the routing table, e.g., default route is added, and every 60 seconds.

        Provided the image is downloaded successfully, a new system container now runs behind the docker0 interface, forwarding container port 22 to port 222 on all of the host's interfaces. (See help publish in the container configuration context for the full syntax.)

        Available containers can be accessed from admin-exec:

        admin@example:/> show container\nNAME     STATUS        NETWORK  MEMORY (KiB)  CPU%\nsystem   Up 16 hours   docker0       136/512  0.02\n

        This is a system container, so you can \"attach\" to it by starting a shell (or logging in with SSH):

        admin@example:/> container shell system\nroot@439af2917b44:/#\n

        Notice how the hostname inside the container changes. By default the container ID (hash) is used, but this can be easily changed:

        root@439af2917b44:/# exit\nadmin@example:/> configure\nadmin@example:/config/> edit container system\nadmin@example:/config/container/system/> set hostname sys101\nadmin@example:/config/container/system/> leave\nadmin@example:/> container shell system\nroot@sys101:/#\n

        In fact, the container hostname setting supports the same format specifiers as the host's hostname setting:

        • %i: OS ID, from /etc/os-release, from Menuconfig branding
        • %h: Default hostname, from /etc/os-release, from branding
        • %m: NIC specific part of base MAC, e.g., to c0-ff-ee
        • %%: Literal %

        The most useful combination is probably \"container-name-%m\", which in this example give the container hostname container-name-c0-ff-ee.

        "},{"location":"container/#application-container-nftables","title":"Application Container: nftables","text":"

        Infix currently does not have a native firewall configuration, and even when it does it will never expose the full capabilities of nftables. For advanced setups, the following is an interesting alternative.

        admin@example:/> configure\nadmin@example:/config> edit container nftables\nadmin@example:/config/container/nftables/> set image ghcr.io/kernelkit/curios-nftables:edge\nadmin@example:/config/container/nftables/> set network host\nadmin@example:/config/container/nftables/> set privileged\nadmin@example:/config/container/nftables/> edit mount nftables.conf\nadmin@example:/config/\u2026/mount/nftables.conf/> set target /etc/nftables.conf\nadmin@example:/config/\u2026/mount/nftables.conf/> text-editor content\n... interactive editor starts up where you can paste your rules ...\nadmin@example:/config/\u2026/mount/nftables.conf/> leave\n

        Notice how we set network host, so the container can see and act on all the host's interfaces, and that we also have to run the container in privileged mode.

        "},{"location":"container/#application-container-ntpd","title":"Application Container: ntpd","text":"

        The default NTP server/client in Infix is Chrony, a fully working and capable workhorse for most use-cases. However, it does not support a feature like multicasting, for that you need ISC ntpd.

        As we did with nftables, previously, we can use host networking and set up a read-only config file that is bind-mounted into the container's file system and store in the host's startup-config. However, ntpd also saves clock drift information in /var/lib/ntpd, so we will also use volumes in this example.

        admin@example:/> configure\nadmin@example:/config> edit container ntpd\nadmin@example:/config/container/ntpd/> set image ghcr.io/kernelkit/curios-ntpd:edge\nadmin@example:/config/container/ntpd/> set network interface ntpd    # From veth0 above\nadmin@example:/config/container/ntpd/> edit mount ntp.conf\nadmin@example:/config/container/ntpd/mount/ntp.conf/> set target /etc/ntp.conf\nadmin@example:/config/container/ntpd/mount/ntp.conf/> text-editor content\n... interactive editor starts up where you can paste your rules ...\nadmin@example:/config/container/ntpd/mount/ntp.conf/> end\nadmin@example:/config/container/ntpd/> edit volume varlib\nadmin@example:/config/container/ntpd/volume/varlib/> set target /var/lib\nadmin@example:/config/container/ntpd/volume/varlib/> leave\nadmin@example:/> copy running-config startup-config\n

        The ntp.conf file is stored in the host's startup-config and any state data in the container's /var/lib is retained between reboots and across image upgrades.

        "},{"location":"container/#advanced","title":"Advanced","text":"

        This section covers advanced, and sometimes dangerous, topics. Please read any warnings and always consider the security aspects.

        "},{"location":"container/#running-host-commands-from-container","title":"Running Host Commands From Container","text":"

        SSH login with keys is very handy, both remote scripting friendly and secure, but it does require a few extra configuration steps. The way to set it up is covered in part in SSH Authorized Key.

        Another insecure approach is to access the host system directly, bypassing the namespaces that make up the boundary between host and container.

        Caution

        Please note, this completely demolishes the isolation barrier between container and host operating system. It is only suitable in situations where the container serves more as a unit of distribution rather than as a separate component of the system. Strongly recommended to use this only in trusted setups! Consider also limiting the time frame in which this is active!

        First, enable Privileged mode, this unlocks the door and allows the container to manage resources on the host system. An example is the nftables container mentioned previously.

        admin@example:/config/container/system/> set privileged\n

        Second, mount the host's /proc/1 directory to somewhere inside your container. Here we pick /1:

        admin@example:/config/container/system/> edit mount host\nadmin@example:/config/container/system/mount/host/> set source /proc/1\nadmin@example:/config/container/system/mount/host/> set target /1\nadmin@example:/config/container/system/mount/host/> leave\n

        Third, from inside the container, use the host's PID 1 namespaces with the nsenter3 command to slide through the container's walls. Here we show two example calls to hostname, first the container's own name and then asking what the hostname is on the host:

        root@sys101:/# hostname\nsys101\nroot@sys101:/# nsenter -m/1/ns/mnt -u/1/ns/uts -i/1/ns/ipc -n/1/ns/net hostname\nexample\n

        One use-case for this method is when extending Infix with a management container that connects to other systems. For some tips on how to control an Infix system this way, see Scripting Infix.

        "},{"location":"container/#container-requirements","title":"Container Requirements","text":"

        In addition to general best practices for container images, there are a few more things to consider when targeting embedded systems:

        • Ensure the image targets the CPU architecture of the target system, learn more about Multi-platform Builds
        • Follow best practices for naming and tagging, e.g., :latest vs :1.0
        • Follow OCI recommendations and layout, learn more about OCI and Docker Exporters

        If the Docker documentation is not enough, there are plenty of guides online with examples on how to create your own container image. For the more advanced, please see the next section.

        "},{"location":"container/#advanced-users","title":"Advanced Users","text":"

        Most people prefer their system containers small, often based on Alpine Linux, or similar, with only a few small applications, including their own, and an SSH server perhaps. For some developers, even this is too big, so they roll their own from source. This section is for you.

        Depending on your needs, here is a checklist:

        • you need something that can forward signals, e.g.,
        • tini
        • Bash only, or
        • BusyBox init, a classic most embedded developers know, but read on ...
        • a system container only need the bare necessities of a system bringup
        • E.g., BusyBox's init, but not everything
        • Some of the networking is set up by Podman and CNI for you, but you may want to run a DHCP client?
        • Do not rename interfaces inside the container, use the dedicated interface_name option in the configuration instead
        • Remember, Podman provides a tmpfs for all critical system paths: /dev, /dev/shm, /run, /tmp, and /var/tmp, so you don't need to clean or set up any of these mount points

        Examples using tini and BusyBox init are available from the KernelKit curiOS project. It is a small Buildroot based container image builder that generates OCI compatible image tarballs without any tools from Docker or Podman -- ready-made images exist for testing on both AMD64 and ARM64 targets, as well as docker pull images and and OCI tarballs with SHA256 checksums for integrity checking.

        Finally, if you build your own version of Infix, and embed OCI tarballs in the system image, then see the tip at the end of Upgrading a Container Image (above).

        1. Something which the container bridge network type does behind the scenes with one end of an automatically created VETH pair.\u00a0\u21a9

        2. this does not apply to the admin-exec command container run. This command is intended to be used for testing and evaluating container images. Such containers are given a private network behind an IP masquerading bridge.\u00a0\u21a9

        3. The nsenter program is available from either the util-linux package in Debian/Ubuntu/Mint, or in BusyBox. Note, however, it may not be enabled by default in BusyBox.\u00a0\u21a9

        "},{"location":"developers-guide/","title":"Developer's Guide","text":""},{"location":"developers-guide/#developers-guide","title":"Developer's Guide","text":"

        Please note, by default the root account is disabled in Infix NETCONF builds. Meaning, the only way to access the system is with the admin account, which is created based on credentials found in the VPD area -- for Qemu devices this is emulated using qemu_fw_cfg.

        For developers this can be quite frustrating to be blocked from logging in to debug the system. The quickest way to enable root login is to apply the dev configuration snippet:

        make apply-dev\n

        See Configuration Snippets for more details.

        Important

        Please see the Contributing section, below, for details on how to fork and clone when contributing to Infix.

        "},{"location":"developers-guide/#cloning","title":"Cloning","text":"

        When pre-built releases are not enough, for instance when you want to add or modify some Open Source components, you can clone the Infix tree to your PC:

        $ mkdir ~/Projects; cd ~/Projects\n$ git clone https://github.com/kernelkit/infix.git\n..\n$ cd infix/\n$ git submodule update --init\n..\n
        "},{"location":"developers-guide/#customer-builds","title":"Customer Builds","text":"

        Customer builds add product specific device trees, more OSS packages, e.g., Frr and podman, and sometimes integrates proprietary software. What's important to remember, however, is that they are all made by setting up Infix as a GIT submodule, similar to how Infix set up a GIT submodule for Buildroot.

        So, in addition to using the customer's specific defconfig(s), one must also make sure to update all submodules, otherwise you will likely end up with a broken build.

        $ ...\n$ git submodule update --init --recursive\n                              ~~~~~~~~~~~\n

        Other caveats should be documented in the customer specific trees.

        "},{"location":"developers-guide/#building","title":"Building","text":"

        Tip

        For more details, see the Getting Started and System Requirements sections of the excellent Buildroot manual.

        Buildroot is almost stand-alone, it needs a few locally installed tools to bootstrap itself. The most common ones are usually part of the base install of the OS, but specific ones for building need the following. The instructions here are for Debian/Ubuntu based systems (YMMV):

        $ sudo apt install bc binutils build-essential bzip2 cpio \\\n                   diffutils file findutils git gzip      \\\n                   libncurses-dev libssl-dev perl patch   \\\n                   python3 rsync sed tar unzip wget       \\\n                   autopoint bison flex autoconf automake \\\n                   mtools\n

        To build an Infix image; select the target and then make:

        make x86_64_defconfig\nmake\n

        Online help is available:

        make help\n

        To see available defconfigs for supported targets, use:

        make list-defconfigs\n
        "},{"location":"developers-guide/#test","title":"Test","text":"

        Working with the regression test framework, Infamy, a few more tools and services are required on your system:

        $ sudo apt install jq graphviz qemu-system-x86 qemu-system-arm \\\n                   ethtool gdb-multiarch tcpdump tshark\n..\n

        To be able to build the test specification you also need:

        $ sudo apt-get install python3-graphviz ruby-asciidoctor-pdf\n..\n
        "},{"location":"developers-guide/#documentation","title":"Documentation","text":"

        The documentation is written in Markdown, with GitHub extensions, and published using MkDocs, material theme. This means some features require MkDocs hinting which may not render fully when previewing on GitHub -- this is OK.

        MkDocs is packaged and available to install via apt, but not all of the plugins and extensions we rely on are available, so instead we do recommend using pipx to install the necessary tooling:

        $ sudo apt install pipx\n$ pipx install mkdocs\n$ pipx inject mkdocs mkdocs-material pymdown-extensions mkdocs-callouts mike mkdocs-to-pdf\n

        The last two packages, mike and mkdocs-to-pdf, are used for online versioning and PDF generation by GitHub Actions, but since they are in the mkdocs.yml file, everyone who wants to preview the documentation have to install all the tooling.

        Preview with:

        $ cd ~/src/infix/\n$ mkdocs serve\n
        "},{"location":"developers-guide/#development","title":"Development","text":"

        Developing with Infix is the same as developing with Buildroot. When working with a package, be it locally kept sources, or when using local.mk, you only want to rebuild the parts you have modified:

        make foo-rebuild\n

        or

        make foo-reconfigure\n

        or, as a last resort when nothing seems to bite:

        make foo-dirclean foo-rebuild\n

        As shown here, you can combine multiple build targets and steps in one go, like this:

        make foo-rebuild bar-rebuild all run\n

        This rebuilds (and installs) foo and bar, the all target calls on Buildroot to finalize the target filesystem and generate the images. The final run argument is explained below.

        "},{"location":"developers-guide/#configuration-snippets","title":"Configuration Snippets","text":"

        Infix ships a set of Kconfig fragments in configs/snippets/ that can be merged into your active .config on demand. This avoids polluting defconfigs with settings that are only useful during development.

        To see what snippets are available:

        make list-snippets\n

        To apply a single snippet to the current output directory:

        make apply-dev          # enable root login\nmake apply-ext4         # build an ext4 rootfs (needed for boards\n                        # whose bootloader lacks squashfs support,\n                        # e.g. Marvell ESPRESSObin)\n

        The apply-* targets require an existing .config (i.e. you must have already run a make <board>_defconfig). The snippet is merged using Buildroot's merge_config.sh, so it behaves like make menuconfig: unrelated settings are preserved, conflicting ones are overridden.

        To apply all snippets at once and then build:

        make dev\n

        This is the recommended one-shot command for setting up a development build from a freshly selected defconfig.

        "},{"location":"developers-guide/#yang-model","title":"YANG Model","text":"

        When making changes to the confd and statd services, you will often need to update the YANG models. If you are adding a new YANG module, it's best to follow the structure of an existing one. However, before making any changes, always discuss them with the Infix core team. This helps avoid issues later in development and makes pull request reviews smoother.

        "},{"location":"developers-guide/#configuration-migration","title":"Configuration Migration","text":"

        Important

        Whenever a YANG model change makes existing startup-config files incompatible \u2014 removing a node, renaming a key, restructuring a container \u2014 you must include a migration script so that devices upgrading in the field are not left unbootable.

        Migration scripts live in src/confd/share/migrate/<version>/ where <version> is the confd version (defined in src/confd/configure.ac) that introduces the breaking change. Each script receives the path to the startup configuration file as its first argument and must edit it in-place. Scripts are run in lexicographic order, so prefix them with a number (e.g. 40-my-change.sh).

        See src/confd/share/migrate/1.6/40-bridge-port-remove-ip.sh for a worked example, and the Configuration Migration section of the Upgrade documentation for the user-facing side of this mechanism.

        "},{"location":"developers-guide/#confd","title":"confd","text":"

        The Infix src/confd/ is the engine of the system. Currently it is a plugin for systemd-plugind and contains XPath subscriptions to all the supported YANG models.

        There are essentially two ways of adding support for a new YANG model:

        • The sysrepo way, or
        • The Infix way, using libsrx (the lydx_*() functions)

        The former is well documented in sysrepo, and the latter is best taught by example, e.g., src/confd/src/infix-dhcp.c. Essentially libsrx is a way of traversing the libyang tree instead of fetching changes by XPath.

        When working with confd you likely want to enable full debug mode, this is how you do it:

        1. Open the file package/confd/confd.conf
        2. Uncomment the first line set DEBUG=1
        3. Change the following line to add -v3 at the end
          [S12345] sysrepo-plugind -f -p /run/confd.pid -n -- Configuration daemon\n

        to:

        [S12345] sysrepo-plugind -f -p /run/confd.pid -n -v3 -- Configuration daemon\n

        Now you can rebuild confd, just as described above, and restart Infix:

        make confd-rebuild all run\n
        "},{"location":"developers-guide/#statd","title":"statd","text":"

        The Infix status daemon, src/statd, is responsible for populating the sysrepo operational datastore. Like confd, it uses XPath subscriptions, but unlike confd, it relies entirely on yanger, a Python script that gathers data from local linux services and feeds it into sysrepo.

        To apply changes, rebuild the image:

        make statd-rebuild all\n

        Rebuilding the image and testing on target for every change during development process can be tedious. Instead, yanger allows remote execution, running the script directly on the host system (test container):

        infamy0:test # ../src/statd/python/yanger/yanger -x \"../utils/ixll -A ssh d3a\" ieee802-dot1ab-lldp\n

        ixll is a utility script that lets you run network commands using an interface name instead of a hostname. It makes operations like ssh, scp, and network discovery easier.

        Normally, yanger runs commands locally to retrieve data (e.g., lldpcli when handling ieee802-dot1ab-lldp). However, when executed with -x \"../utils/ixll -A ssh d3a\" it redirects these commands to a remote system connected to the local d3a interface via SSH. This setup is used for running yanger in an interactive test environment. The yanger script runs on the host system, but key commands are executed on the target system.

        For debugging or testing, you can capture system command output and replay it later without needing a live system.

        To capture:

        infamy0:test # ../src/statd/python/yanger/yanger -c /tmp/capture ieee802-dot1ab-lldp\n

        To replay:

        infamy0:test # ../src/statd/python/yanger/yanger -r /tmp/capture ieee802-dot1ab-lldp\n

        This is especially useful when working in isolated environments or debugging issues without direct access to the DUT.

        "},{"location":"developers-guide/#upgrading-packages","title":"Upgrading Packages","text":""},{"location":"developers-guide/#buildroot","title":"Buildroot","text":"

        The Kernelkit team maintains an internal fork of Buildroot, with branches following the naming scheme YYYY.MM.patch-kkit e.g. 2025.02.1-kkit, which means a new branch should be created whenever Buildroot is updated. These branches should contain only changes to existing packages (but no new patches), modifications to Buildroot itself or upstream backports.

        The team tracks the latest Buildroot LTS (Long-Term Support) release and updates. The impact of minor LTS release upgrades is expected to have a very low impact and should be done as soon there is a patch release of a Buildroot LTS available.

        Depending on your setup, follow the appropriate steps below.

        "},{"location":"developers-guide/#repo-locally-cloned-already","title":"Repo locally cloned already","text":"
        1. Navigate to the Buildroot directory

          cd buildroot/\n
        2. Pull the latest changes from KernelKit

          git pull\n
        3. Fetch the latest tags from upstream

          git fetch upstream --tags\n
        "},{"location":"developers-guide/#no-local-repo-yet","title":"No local repo yet","text":"
        1. Clone the Kernelkit Buildroot repository

          git clone git@github.com:kernelkit/buildroot.git\n
        2. Add the upstream remote

          git remote add upstream https://gitlab.com/buildroot.org/buildroot.git\n
        3. Checkout old KernelKit branch

          git checkout 2025.02.1-kkit\n

        Note

        Below, it is not allowed to rebase the branch when bumped in Infix.

        "},{"location":"developers-guide/#continue-here","title":"Continue Here","text":"
        1. Create a new branch based on the previous KernelKit Buildroot release (e.g. 2025.02.1-kkit) and name it according to the naming scheme (e.g. 2025.02.2-kkit)

          git checkout -b 2025.02.2-kkit\n
        2. Rebase the new branch onto the corresponding upstream release

          git rebase 2025.02.2\n
        3. Push the new branch and tags

          git push origin 2025.02.2-kkit --tags\n
        4. In Infix, checkout new branch of Buildroot

          cd buildroot\ngit fetch\ngit checkout 2025.02.2-kkit\n
        5. Commit and push the changes. Remember to update the ChangeLog!

        6. Create a pull request.

        Note

        Remember to set the pull request label to ci:main to ensure full CI coverage.

        "},{"location":"developers-guide/#linux-kernel","title":"Linux kernel","text":"

        The KernelKit team maintains an internal fork of Linux kernel, with branches following the naming scheme kkit-linux-[version].y, e.g. kkit-6.12.y, which means a new branch should be created whenever the major kernel version is updated. This branch should contain all kernel patches used by Infix.

        The team tracks the latest Linux kernel LTS (Long-Term Support) release and updates. The upgrade of LTS minor releases is expected to have low impact and should be done as soon as a patch release of the LTS Linux kernel is available.

        "},{"location":"developers-guide/#repo-locally-cloned-already_1","title":"Repo locally cloned already","text":"
        • ./utils/kernel-upgrade.sh /path/to/linux/tree
        • Update Changelog
        • push and create a pull request

        Note

        Remember to set the pull request label to ci:main to ensure full CI coverage.

        "},{"location":"developers-guide/#testing","title":"Testing","text":"

        Manual testing can be done using Qemu by calling make run, see also Infix in Virtual Environments, or on a physical device by upgrading to the latest build or \"netbooting\" and running the image from RAM. The latter is how most board porting work is done -- much quicker change-load-test cycles.

        The Infix automated test suite is built around Qemu and Qeneth, see:

        • Regression Testing with Infamy
        • Docker Image

        With any new feature added to Infix, it is essential to include relevant test case(s). See the Test Development section for guidance on adding test cases.

        "},{"location":"developers-guide/#reviewing","title":"Reviewing","text":"

        While reviewing a pull request, you might find yourself wanting to play around with a VM running that exact version. For such occasions, gh-dl-artifact.sh is your friend in need! It employs the GitHub CLI (gh) to locate a prebuilt image from our CI workflow, download it, and prepare a local output directory from which you can launch both make run instances, and run regression tests with make test and friends.

        For example, if you are curious about how PR 666 behaves in some particular situation, you can use gh to switch to that branch, from which gh-dl-artifact.sh can then download and prepare the corresponding image for execution with our normal tooling:

        gh pr checkout 666\n./utils/gh-dl-artifact.sh\ncd x-artifact-a1b2c3d4-x86_64\nmake run\n

        Note

        CI artifacts are built from a merge commit of the source and target branches. Therefore, the version in the Infix banner will not match the SHA of the commit you have checked out.

        "},{"location":"developers-guide/#contributing","title":"Contributing","text":"

        Infix is built from many components, when contributing you need to set up your own fork, create a local branch for your change, push to your fork, and then use GitHub to create a Pull Reqeuest.

        For this to work as painlessly as possible for everyone involved:

        1. Fork Infix to your own user or organization1
        2. Fork all the Infix submodules, e.g., kernelkit/buildroot to your own user or organization as well
        3. Clone your fork of Infix to your laptop/workstation
        4. Deactivate the Actions you don't want in your fork
        5. Please read the Contributing Guidelines as well!
        $ cd ~/Projects\n$ git clone https://github.com/YOUR_USER_NAME/infix.git\n...\n$ cd infix/\n$ git submodule update --init\n...\n

        Note

        When updating/synchronizing with upstream Infix changes you may have to synchronize your forks as well. GitHub have a Sync fork button in the GUI for your fork for this purpose. A cronjob on your server of choice can do this for you with the GitHub CLI tool.

        1. Organizations should make sure to lock the main (or master) branch of their clones to ensure members do not accidentally merge changes there. Keeping these branches in sync with upstream Infix is highly recommended as a baseline and reference. For integration of local changes another company-specific branch can be used instead.\u00a0\u21a9

        "},{"location":"dhcp/","title":"DHCP Server","text":""},{"location":"dhcp/#dhcp-server","title":"DHCP Server","text":"

        The DHCPv4 server provides automatic IP address assignment and network configuration for clients. It supports address pools, static host assignments, and customizable DHCP options. It also serves as a DNS proxy for local subnets and can even forward queries to upstream DNS servers1.

        Note

        When using the CLI, the system automatically enables essential options like DNS servers and default gateway based on the system's network configuration. These options can be disabled, changed or overridden, at any level: global, subnet, or per-host.

        "},{"location":"dhcp/#basic-configuration","title":"Basic Configuration","text":"

        The following example configures a DHCP server for subnet 192.168.2.0/24 with an address pool:

        admin@example:/> configure\nadmin@example:/config/> edit dhcp-server subnet 192.168.2.0/24\nadmin@example:/config/dhcp-server/\u2026/192.168.2.0/24/> set pool start-address 192.168.2.100 end-address 192.168.2.200\nadmin@example:/config/dhcp-server/\u2026/192.168.2.0/24/> leave\n

        When setting up the server from the CLI, the system automatically adds a few default DHCP options that will be sent to clients: both DNS server and default gateway will use the system address on the matching interface.

        admin@example:/> show running-config\n  \"infix-dhcp-server:dhcp-server\": {\n    \"subnet\": [\n      {\n        \"subnet\": \"192.168.2.0/24\",\n        \"option\": [\n          {\n            \"id\": \"dns-server\",\n            \"address\": \"auto\"\n          },\n          {\n            \"id\": \"router\",\n            \"address\": \"auto\"\n          }\n        ],\n        \"pool\": {\n          \"start-address\": \"192.168.2.100\",\n          \"end-address\": \"192.168.2.200\"\n        }\n      }\n    ]\n  }\n

        Important

        Remember to set up an interface in this subnet, avoid using addresses in the DHCP pool, or reserved for static hosts. In Class C networks the router usually has address .1. Depending on the use-case, you may also want to set up routing.

        "},{"location":"dhcp/#static-host-assignment","title":"Static Host Assignment","text":"

        To reserve specific IP addresses for clients based on their MAC address, hostname, or client ID:

        admin@example:/config/dhcp-server/\u2026/192.168.2.0/24/> edit host 192.168.2.10\nadmin@example:/config/dhcp-server/\u2026/192.168.2.10/> set match mac-address 00:11:22:33:44:55\nadmin@example:/config/dhcp-server/\u2026/192.168.2.10/> set hostname printer\nadmin@example:/config/dhcp-server/\u2026/192.168.2.10/> leave\n

        Match hosts using a client identifier instead of MAC address:

        admin@example:/config/dhcp-server/\u2026/192.168.1.0/24/> edit host 192.168.1.50\nadmin@example:/config/dhcp-server/\u2026/192.168.1.50/> edit match\nadmin@example:/config/dhcp-server/\u2026/match/> set client-id hex c0:ff:ee\nadmin@example:/config/dhcp-server/\u2026/match/> leave\nadmin@example:/config/dhcp-server/\u2026/192.168.1.50/> set lease-time infinite\nadmin@example:/config/dhcp-server/\u2026/192.168.1.50/> leave\n

        The hex prefix here ensures matching of client ID is done using the hexadecimal octets c0:ff:ee, three bytes. Without the prefix the ASCII string \"c0:ff:ee\", eight bytes, is used.

        Note

        The DHCP server is fully RFC conformant, in the case of option 61 this means that using the hex prefix will require the client to set the htype field of the option to 00. See RFC 2132 for details.

        "},{"location":"dhcp/#custom-dhcp-options","title":"Custom DHCP Options","text":"

        Configure additional DHCP options globally, per subnet, or per host:

        admin@example:/config/dhcp-server/> edit subnet 192.168.2.0/24\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/> edit option dns-server\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/option/dns-server/> set address 8.8.8.8\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/option/dns-server/> leave\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/> edit option ntp-server\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/option/ntp-server/> set address 192.168.2.1\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/option/ntp-server/> leave\n

        When configuring, e.g., dns-server, or router options with the value auto, the system uses the IP address from the interface matching the subnet. For example:

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv4                   192.168.1.1/24 (static)\neth1            ethernet   UP          02:00:00:00:00:01\n                ipv4                   192.168.2.1/24 (static)\n\nadmin@example:/config/dhcp-server/subnet/192.168.1.0/24/> edit option dns-server\nadmin@example:/config/dhcp-server/subnet/192.168.1.0/24/option/dns-server/> set address auto\nadmin@example:/config/dhcp-server/subnet/192.168.1.0/24/option/dns-server/> leave\n

        In this case, clients in subnet 192.168.1.0/24 will receive 192.168.1.1 as their DNS server address.

        "},{"location":"dhcp/#multiple-subnets","title":"Multiple Subnets","text":"

        Configure DHCP for multiple networks:

        admin@example:/> configure\nadmin@example:/config/> edit dhcp-server\nadmin@example:/config/dhcp-server/> edit subnet 192.168.1.0/24\nadmin@example:/config/dhcp-server/subnet/192.168.1.0/24/> set pool start-address 192.168.1.100 end-address 192.168.1.200\nadmin@example:/config/dhcp-server/subnet/192.168.1.0/24/> leave\nadmin@example:/config/dhcp-server/> edit subnet 192.168.2.0/24\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/> set pool start-address 192.168.2.100 end-address 192.168.2.200\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/> leave\n
        "},{"location":"dhcp/#monitoring","title":"Monitoring","text":"

        View active leases and server statistics:

        admin@example:/> show dhcp-server\nIP ADDRESS       MAC                HOSTNAME            CLIENT ID             EXPIRES\n192.168.2.22     00:a0:85:00:02:05                      00:c0:ff:ee           3591s\n192.168.1.11     00:a0:85:00:04:06  foo                 01:00:a0:85:00:04:06  3591s\n\nadmin@example:/> show dhcp-server statistics\nDHCP offers sent                : 6\nDHCP ACK messages sent          : 5\nDHCP NAK messages sent          : 0\nDHCP decline messages received  : 0\nDHCP discover messages received : 6\nDHCP request messages received  : 5\nDHCP release messages received  : 6\nDHCP inform messages received   : 6\n
        1. This requires the system DNS resolver to be configured.\u00a0\u21a9

        "},{"location":"discovery/","title":"Device Discovery","text":""},{"location":"discovery/#device-discovery","title":"Device Discovery","text":"

        Infix advertises itself via the mDNS-SD and LLDP discovery protocols. mDNS-SD has good client support in Windows, macOS and on Linux systems. More on these protocols later.

        An even simpler method is available when directly attached to an Infix device:

        .----.       Ethernet      .--------.\n| PC +---------------------+ Device |\n'----' if1              e1 '--------'\n

        With IPv6 you can ping the all-hosts address (ff02::1), the device's link-local IPv6 address is then seen in the response. In the following example, the PC here uses tap0 as if1, Infix responds with address fe80::ff:fec0:ffed.

        linux-pc:# ping -6 -L -c 3 ff02::1%tap0\nPING ff02::1%tap0(ff02::1%tap0) 56 data bytes\n64 bytes from fe80::ff:fec0:ffed%tap0: icmp_seq=1 ttl=64 time=0.558 ms\n64 bytes from fe80::ff:fec0:ffed%tap0: icmp_seq=2 ttl=64 time=0.419 ms\n64 bytes from fe80::ff:fec0:ffed%tap0: icmp_seq=3 ttl=64 time=0.389 ms\n\n--- ff02::1%tap0 ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2043ms\nrtt min/avg/max/mdev = 0.389/0.455/0.558/0.073 ms\nlinux-pc:#\n

        Tip

        The -L option ignores local responses from the PC.

        This address can then be used to connect to the device, e.g., using SSH. Notice the syntax username@address%interface:

        linux-pc:# ssh admin@fe80::ff:fec0:ffed%tap0\nadmin@fe80::ff:fec0:ffed%tap0's password: admin\nadmin@infix-c0-ff-ee:~$\n
        "},{"location":"discovery/#windows","title":"Windows","text":"

        Infix advertises a _workstation._tcp DNS-SD record alongside its other mDNS services. Windows 10 (build 1709+) and Windows 11 recognise this record and show the device in File Explorer \u2192 Network automatically \u2014 no extra software required.

        From the command line, use the .local hostname directly:

        C:\\> ping infix-c0-ff-ee.local\nC:\\> ssh admin@infix-c0-ff-ee.local\n

        Note

        IPv6 multicast ping (ping ff02::1%if1) may not display responses on Windows even though the Infix device replies correctly. If you need to confirm connectivity, Wireshark will show the ICMPv6 echo replies arriving. Use mDNS (see mDNS-SD below) as the reliable alternative.

        "},{"location":"discovery/#lldp","title":"LLDP","text":"

        Infix supports LLDP (IEEE 802.1AB). For a device with factory default settings, the link-local IPv6 address can be read from the Management Address TLV using tcpdump or other sniffing tools1:

        linux-pc:# tcpdump -i tap0 -Qin -v ether proto 0x88cc\ntcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes\n15:51:52.061071 LLDP, length 193\n    Chassis ID TLV (1), length 7\n      Subtype MAC address (4): 02:00:00:c0:ff:ee (oui Unknown)\n    Port ID TLV (2), length 7\n      Subtype MAC address (3): 02:00:00:c0:ff:ee (oui Unknown)\n    Time to Live TLV (3), length 2: TTL 120s\n    System Name TLV (5), length 14: infix-c0-ff-ee\n    System Description TLV (6), length 91\n      Infix by KernelKit Linux 5.19.17 #1 SMP PREEMPT_DYNAMIC Wed Jun 7 08:47:23 CEST 2023 x86_64\n    System Capabilities TLV (7), length 4\n      System  Capabilities [Bridge, WLAN AP, Router, Station Only] (0x009c)\n      Enabled Capabilities [Station Only] (0x0080)\n    Management Address TLV (8), length 24\n      Management Address length 17, AFI IPv6 (2): fe80::ff:fec0:ffed\n      Interface Index Interface Numbering (2): 2\n    Port Description TLV (4), length 4: eth0\n    Organization specific TLV (127), length 9: OUI IEEE 802.3 Private (0x00120f)\n      Link aggregation Subtype (3)\n        aggregation status [supported], aggregation port ID 0\n    Organization specific TLV (127), length 9: OUI IEEE 802.3 Private (0x00120f)\n      MAC/PHY configuration/status Subtype (1)\n        autonegotiation [none] (0x00)\n        PMD autoneg capability [unknown] (0x8000)\n        MAU type Unknown (0x0000)\n    End TLV (0), length 0\n^C\n1 packet captured\nlinux-pc:#\n

        If the device has an IPv4 address assigned, it is shown in an additional Management Address TLV.

        Note

        The Management Addresses shown by LLDP are not necessarily associated with the port transmitting the LLDP message.

        In the example below, the IPv4 address (10.0.1.1) happens to be assigned to eth0, while the IPv6 address (2001:db8::1) is not.

        linux-pc:# sudo tcpdump -i tap0 -Qin -v ether proto 0x88cc\ntcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes\n15:46:07.908665 LLDP, length 207\n    Chassis ID TLV (1), length 7\n      Subtype MAC address (4): 02:00:00:c0:ff:ee (oui Unknown)\n    Port ID TLV (2), length 7\n      Subtype MAC address (3): 02:00:00:c0:ff:ee (oui Unknown)\n    Time to Live TLV (3), length 2: TTL 120s\n    System Name TLV (5), length 14: infix-c0-ff-ee\n    System Description TLV (6), length 91\n      Infix by KernelKit Linux 5.19.17 #1 SMP PREEMPT_DYNAMIC Wed Jun 7 08:47:23 CEST 2023 x86_64\n    System Capabilities TLV (7), length 4\n      System  Capabilities [Bridge, WLAN AP, Router, Station Only] (0x009c)\n      Enabled Capabilities [Station Only] (0x0080)\n    Management Address TLV (8), length 12\n      Management Address length 5, AFI IPv4 (1): 10.0.1.1\n      Interface Index Interface Numbering (2): 2\n    Management Address TLV (8), length 24\n      Management Address length 17, AFI IPv6 (2): 2001:db8::1\n      Interface Index Interface Numbering (2): 3\n    Port Description TLV (4), length 4: eth0\n    Organization specific TLV (127), length 9: OUI IEEE 802.3 Private (0x00120f)\n      Link aggregation Subtype (3)\n        aggregation status [supported], aggregation port ID 0\n    Organization specific TLV (127), length 9: OUI IEEE 802.3 Private (0x00120f)\n      MAC/PHY configuration/status Subtype (1)\n        autonegotiation [none] (0x00)\n        PMD autoneg capability [unknown] (0x8000)\n        MAU type Unknown (0x0000)\n    End TLV (0), length 0\n^C\n1 packet captured\n2 packets received by filter\n0 packets dropped by kernel\nlinux-pc:#\n

        The following capabilities are available via NETCONF/RESTCONF or the Infix CLI.

        "},{"location":"discovery/#lldp-enabledisable","title":"LLDP Enable/Disable","text":"

        The LLDP service can be disabled using the following commands.

        admin@infix-c0-ff-ee:/> configure\nadmin@infix-c0-ff-ee:/config/> no lldp\nadmin@infix-c0-ff-ee:/config/> leave\nadmin@infix-c0-ff-ee:/>\n

        To reenable it from the CLI config mode:

        admin@test-00-01-00:/config/> set lldp enabled\nadmin@test-00-01-00:/config/> leave\n
        "},{"location":"discovery/#lldp-message-transmission-interval","title":"LLDP Message Transmission Interval","text":"

        By default, LLDP uses a message-tx-interval of 30 seconds, as defined by the IEEE standard. Infix allows this value to be customized. To change it using the CLI:

        admin@test-00-01-00:/config/> set lldp message-tx-interval 1\nadmin@test-00-01-00:/config/> leave\n
        "},{"location":"discovery/#lldp-administrative-status-per-interface","title":"LLDP Administrative Status per Interface","text":"

        Infix supports configuring the LLDP administrative status on a per-port basis. The default mode is tx-and-rx, but the following options are also supported:

        • rx-only \u2013 Receive LLDP packets only
        • tx-only \u2013 Transmit LLDP packets only
        • disabled \u2013 Disable LLDP on the interface

        Example configuration:

        admin@test-00-01-00:/config/> set lldp port e8 dest-mac-address 01:80:C2:00:00:0E admin-status disabled\nadmin@test-00-01-00:/config/> set lldp port e5 dest-mac-address 01:80:C2:00:00:0E admin-status rx-only\nadmin@test-00-01-00:/config/> set lldp port e6 dest-mac-address 01:80:C2:00:00:0E admin-status tx-only\nadmin@test-00-01-00:/config/> leave\n

        Note

        The destination MAC address must be the standard LLDP multicast address: 01:80:C2:00:00:0E.

        "},{"location":"discovery/#displaying-lldp-neighbor-information","title":"Displaying LLDP Neighbor Information","text":"

        In CLI mode, Infix also provides a convenient show lldp command to list LLDP neighbors detected on each interface:

        admin@test-00-01-00:/> show lldp\nINTERFACE       REM-IDX   TIME        CHASSIS-ID          PORT-ID                           \ne5              1         902         00:a0:85:00:04:01   00:a0:85:00:04:07\ne6              3         897         00:a0:85:00:03:01   00:a0:85:00:03:07\ne8              2         901         00:a0:85:00:02:01   00:a0:85:00:02:05\n
        "},{"location":"discovery/#mdns-sd","title":"mDNS-SD","text":"

        DNS-SD/mDNS-SD can be used to discover Infix devices and services. By default, Infix use the .local domain for advertising services. Some networks use .lan instead, so this configurable:

        admin@infix-c0-ff-ee:/> configure\nadmin@infix-c0-ff-ee:/config/> edit mdns\nadmin@infix-c0-ff-ee:/config/mdns/> set domain lan\n

        Other available settings include limiting the interfaces mDNS responder acts on, allow:

        admin@infix-c0-ff-ee:/config/> set interfaces allow e1\n

        or deny. The allow and deny settings are complementary, deny always wins.

        admin@infix-c0-ff-ee:/config/> set interfaces deny wan\n

        Use leave to activate the new settings, then inspect the operational state and any detected neighbors with show mdns from admin-exec context:

        admin@gateway:/> show mdns\nEnabled         : yes\nDomain          : local\nDeny            : wan\n\nHOSTNAME           ADDRESS        LAST SEEN  SERVICES                                       \nLiving-Room.local  192.168.0.139  17:28:43   trel(59813) sleep-proxy(61936) raop(7000) srpl-tls(853)\nfirefly-4.local    192.168.0.122  17:28:37   workstation(9)\ngimli.local        192.168.0.180  17:28:37   smb(445)\ninfix.local        192.168.0.1    17:28:38   https(443) workstation(9) ssh(22) https(443)\n

        In Linux, tools such as avahi-browse or mdns-scan2 can be used to search for devices advertising their services via mDNS.

        linux-pc:# avahi-browse -ar\n+   tap0 IPv6 infix-c0-ff-ee                                SFTP File Transfer   local\n+   tap0 IPv4 infix-c0-ff-ee                                SFTP File Transfer   local\n+   tap0 IPv6 infix-c0-ff-ee                                SSH Remote Terminal  local\n+   tap0 IPv4 infix-c0-ff-ee                                SSH Remote Terminal  local\n=   tap0 IPv4 infix-c0-ff-ee                                SFTP File Transfer   local\n   hostname = [infix-c0-ff-ee.local]\n   address = [10.0.1.1]\n   port = [22]\n   txt = []\n=   tap0 IPv4 infix-c0-ff-ee                                SSH Remote Terminal  local\n   hostname = [infix-c0-ff-ee.local]\n   address = [10.0.1.1]\n   port = [22]\n   txt = []\n=   tap0 IPv6 infix-c0-ff-ee                                SFTP File Transfer   local\n   hostname = [infix-c0-ff-ee.local]\n   address = [fe80::ff:fec0:ffed]\n   port = [22]\n   txt = []\n=   tap0 IPv6 infix-c0-ff-ee                                SSH Remote Terminal  local\n   hostname = [infix-c0-ff-ee.local]\n   address = [fe80::ff:fec0:ffed]\n   port = [22]\n   txt = []\n^C\nlinux-pc:#\n

        Tip

        The -t option is also very useful, it stops browsing automatically when a \"more or less complete list\" has been printed. However, some devices on the LAN may be in deep sleep so run the command again if you cannot find the device you are looking for.

        Additionally, avahi-resolve-host-name can be used to verify domain name mappings for IP addresses. By default, it translates from IPv4 addresses. This function allows users to confirm that addresses are mapped correctly.

        linux-pc:# avahi-resolve-host-name infix-c0-ff-ee.local\ninfix-c0-ff-ee.local    10.0.1.1\nlinux-pc:#\n

        Thanks to mDNS we can use the advertised name instead of the IP address for operations like ping and ssh as shown below:

        linux-pc:# ping infix-c0-ff-ee.local -c 3\nPING infix-c0-ff-ee.local (10.0.1.1) 56(84) bytes of data.\n64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.852 ms\n64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=1.12 ms\n64 bytes from 10.0.1.1: icmp_seq=3 ttl=64 time=1.35 ms\n\n--- infix-c0-ff-ee.local ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2003ms\nrtt min/avg/max/mdev = 0.852/1.105/1.348/0.202 ms\n\nlinux-pc:# ssh admin@infix-c0-ff-ee.local\n(admin@infix-c0-ff-ee.local) Password:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-\n\nRun the command 'cli' for interactive OAM\n\nlinux-pc:#\n

        To disable mDNS/mDNS-SD, type the commands:

        admin@infix-c0-ff-ee:/> configure\nadmin@infix-c0-ff-ee:/config/> no mdns\nadmin@infix-c0-ff-ee:/config/> leave\n
        "},{"location":"discovery/#human-friendly-hostname-alias","title":"Human-Friendly Hostname Alias","text":"

        Each Infix device advertises itself as infix.local, in addition to its full hostname (e.g., infix-c0-ff-ee.local or foo.local). This alias works seamlessly on a network with a single Infix device, and makes it easy to connect when the exact hostname is not known in advance. The examples below show how the alias can be used for actions such as pinging or establishing an SSH connection:

        linux-pc:# ping infix.local -c 3\nPING infix.local (10.0.1.1) 56(84) bytes of data.\n64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.751 ms\n64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=2.28 ms\n64 bytes from 10.0.1.1: icmp_seq=3 ttl=64 time=1.42 ms\n\n--- infix.local ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2003ms\nrtt min/avg/max/mdev = 0.751/1.482/2.281/0.626 ms\n\nlinux-pc:# ssh admin@infix.local\n(admin@infix.local) Password:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-\n\nRun the command 'cli' for interactive OAM\n\nadmin@infix-c0-ff-ee:~$\n

        When multiple Infix devices are present on the LAN the alias will not uniquely identify a device; infix.local will refer to any of the Infix devices, likely the one that first appeared.

        Note

        When multiple Infix devices are present on the LAN, use the full name, e.g., infix-c0-ff-ee.local or foo.local rather than the alias infix.local to deterministically connect to the device.

        "},{"location":"discovery/#browse-network-using-networklocal","title":"Browse Network Using network.local","text":"

        Another mDNS alias that all Infix devices advertise is network.local. This is a web service which basically runs avahi-browse and displays a table of other Infix devices and their services.

        With multiple Infix devices on the LAN, one will take the role of your portal to access all others, if it goes down another takes its place.

        To disable the netbrowse service, and the network.local alias, the following commands can be used:

        admin@infix-c0-ff-ee:/> configure\nadmin@infix-c0-ff-ee:/config/> edit web\nadmin@infix-c0-ff-ee:/config/web/> no netbrowse\nadmin@infix-c0-ff-ee:/config/web/> leave\n
        1. E.g., lldpd which includes the lldpcli too, handy to sniff and display LLDP packets.\u00a0\u21a9

        2. mdns-scan: a tool for scanning for mDNS/DNS-SD services on the local network.\u00a0\u21a9

        "},{"location":"eth-counters/","title":"RMON Counters","text":""},{"location":"eth-counters/#rmon-counters","title":"RMON Counters","text":"

        This document show the mapping between YANG and Linux / Ethtool counters.

        YANG Linux / Ethtool in-total-octets FramesReceivedOK, FrameCheckSequenceErrors, FramesLostDueToIntMACRcvError, AlignmentErrors, etherStatsOversizePkts, etherStatsJabbers in-frames FramesReceivedOK in-multicast-frames MulticastFramesReceivedOK in-broadcast-frames BroadcastFramesReceivedOK in-error-fcs-frames FrameCheckSequenceErrors in-error-undersize-frames undersize_pkts in-error-oversize-frames etherStatsJabbers, etherStatsOversizePkts in-error-mac-internal-frames FramesLostDueToIntMACRcvError out-frames FramesTransmittedOK out-multicast-frames MulticastFramesXmittedOK out-broadcast-frames BroadcastFramesXmittedOK infix-eth:out-good-octets OctetsTransmittedOK infix-eth:in-good-octets OctetsReceivedOK"},{"location":"ethernet/","title":"Ethernet Interfaces","text":""},{"location":"ethernet/#ethernet-interfaces","title":"Ethernet Interfaces","text":"

        This document covers VLAN interfaces, physical Ethernet interfaces, and virtual Ethernet (VETH) pairs.

        "},{"location":"ethernet/#vlan-interfaces","title":"VLAN Interfaces","text":"

        Creating a VLAN can be done in many ways. This section assumes VLAN interfaces created atop another Linux interface. E.g., the VLAN interfaces created on top of the Ethernet interface or bridge in the picture below.

        A VLAN interface is basically a filtering abstraction. When you run tcpdump on a VLAN interface you will only see the frames matching the VLAN ID of the interface, compared to all the VLAN IDs if you run tcpdump on the lower-layer interface.

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0.20\nadmin@example:/config/interface/eth0.20/> show\ntype vlan;\nvlan {\n  tag-type c-vlan;\n  id 20;\n  lower-layer-if eth0;\n}\nadmin@example:/config/interface/eth0.20/> leave\n

        The example below assumes bridge br0 is already created, see VLAN Filtering Bridge.

        admin@example:/> configure\nadmin@example:/config/> edit interface vlan10\nadmin@example:/config/interface/vlan10/> set vlan id 10\nadmin@example:/config/interface/vlan10/> set vlan lower-layer-if br0\nadmin@example:/config/interface/vlan10/> leave\n

        As conventions, a VLAN interface for VID 20 on top of an Ethernet interface eth0 is named eth0.20, and a VLAN interface for VID 10 on top of a bridge interface br0 is named vlan10.

        Note

        If you name your VLAN interface foo0.N or vlanN, where N is a number, the CLI infers the interface type automatically.

        "},{"location":"ethernet/#physical-ethernet-interfaces","title":"Physical Ethernet Interfaces","text":""},{"location":"ethernet/#ethernet-settings-and-status","title":"Ethernet Settings and Status","text":"

        Physical Ethernet interfaces provide low-level settings for speed/duplex as well as packet status and statistics.

        By default, Ethernet interfaces defaults to auto-negotiating speed/duplex modes, advertising all speed and duplex modes available. In the example below, the switch would by default auto-negotiate speed 1 Gbit/s on port eth1 and 100 Mbit/s on port eth4, as those are the highest speeds supported by H1 and H2 respectively.

        The speed and duplex status for the links can be listed as shown below, assuming the link operational status is 'up'.

        admin@example:/> show interface eth1\nname                : eth1\nindex               : 2\nmtu                 : 1500\noperational status  : up\nauto-negotiation    : on\nduplex              : full\nspeed               : 1000\nphysical address    : 00:53:00:06:11:01\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 75581\nout-octets          : 43130\n...\nadmin@example:/> show interface eth4\nname                : eth4\nindex               : 5\nmtu                 : 1500\noperational status  : up\nauto-negotiation    : on\nduplex              : full\nspeed               : 100\nphysical address    : 00:53:00:06:11:04\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 75439\nout-octets          : 550704\n...\nadmin@example:/>\n
        "},{"location":"ethernet/#configuring-fixed-speed-and-duplex","title":"Configuring fixed speed and duplex","text":"

        Auto-negotiation of speed/duplex mode is desired in almost all use-cases, but it is possible to disable auto-negotiation and specify a fixed speed and duplex mode.

        Important

        When setting a fixed speed and duplex mode, ensure both sides of the link have matching configuration. If speed does not match, the link will not come up. If duplex mode does not match, the result is reported collisions and/or bad throughput.

        The example below configures port eth3 to fixed speed 100 Mbit/s half-duplex mode.

        admin@example:/> configure\nadmin@example:/config/> edit interface eth3 ethernet\nadmin@example:/config/interface/eth3/ethernet/> set speed 0.1\nadmin@example:/config/interface/eth3/ethernet/> set duplex half\nadmin@example:/config/interface/eth3/ethernet/> set auto-negotiation enable false\nadmin@example:/config/interface/eth3/ethernet/> show\nauto-negotiation {\n  enable false;\n}\nduplex half;\nspeed 0.1;\nadmin@example:/config/interface/eth3/ethernet/> leave\nadmin@example:/>\n

        Speed metric is in Gbit/s. Auto-negotiation needs to be disabled in order for fixed speed/duplex to apply. Only speeds 0.1(100 Mbit/s) and 0.01 (10 Mbit/s) can be specified. 1 Gbit/s and higher speeds require auto-negotiation to be enabled.

        "},{"location":"ethernet/#ethernet-statistics","title":"Ethernet statistics","text":"

        Ethernet packet statistics1 can be listed as shown below.

        admin@example:/> show interface eth1\nname                : eth1\nindex               : 2\nmtu                 : 1500\noperational status  : up\nauto-negotiation    : on\nduplex              : full\nspeed               : 1000\nphysical address    : 00:53:00:06:11:0a\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 75581\nout-octets          : 43130\n\neth-in-frames                : 434\neth-in-multicast-frames      : 296\neth-in-broadcast-frames      : 138\neth-in-error-fcs-frames      : 0\neth-in-error-oversize-frames : 0\neth-out-frames               : 310\neth-out-multicast-frames     : 310\neth-out-broadcast-frames     : 0\neth-out-good-octets          : 76821\neth-in-good-octets           : 60598\nadmin@example:/>\n
        "},{"location":"ethernet/#veth-pairs","title":"VETH Pairs","text":"

        A Virtual Ethernet (VETH) pair is basically a virtual Ethernet cable. A cable can be \"plugged in\" to a bridge and the other end can be given to a container, or plugged into another bridge.

        The latter example is useful if you have multiple bridges in the system with different properties (VLAN filtering, IEEE group forwarding, etc.), but still want some way of communicating between these domains.

        admin@example:/> configure\nadmin@example:/config/> edit interface veth0a\nadmin@example:/config/interface/veth0a/> set veth peer veth0b\nadmin@example:/config/interface/veth0a/> end\nadmin@example:/config/> diff\ninterfaces {\n+  interface veth0a {\n+    type veth;\n+    veth {\n+      peer veth0b;\n+    }\n+  }\n+  interface veth0b {\n+    type veth;\n+    veth {\n+      peer veth0a;\n+    }\n+  }\n}\nadmin@example:/config/>\n

        Tip

        This is another example of the automatic inference of the interface type from the name. Any name can be used, but then you have to set the interface type to veth manually.

        1. Ethernet counters are described in ieee802-ethernet-interface.yang and infix-ethernet-interface.yang. There is a dedicated document on Ethernet Counters that provide additional details on the statistics support.\u00a0\u21a9

        "},{"location":"firewall/","title":"Firewall Configuration","text":""},{"location":"firewall/#firewall-documentation","title":"Firewall Documentation","text":""},{"location":"firewall/#introduction","title":"Introduction","text":"

        A zone-based firewall aims to simplify network security. Instead of complex per-interface rules, you work with zones and policies. Briefly, zones define a level of trust for all interfaces or networks assigned to it, and policies regulate the traffic flow between zones.

        Figure 1. Zones group interfaces, policies control traffic flows.

        Three distinct traffic flows exist: traffic destined for the host itself, traffic between interfaces within the same zone (intra-zone), and traffic between different zones (inter-zones).

        The zone approach is not just more intuitive and maintainable, it allows you to think more in terms of trust relationships:

        • \"internal networks can access the Internet\"
        • \"Internet cannot access my internal network, except this port forward\"

        When you add new interfaces to existing zones, they automatically inherit the established security policies. The amount of actual rules that matter to you is kept to a minimum.

        Impatient and ready to get going?

        Fast forward to the Examples: End Device, Home/Office Router, Enterprise Gateway

        "},{"location":"firewall/#visual-overview","title":"Visual Overview","text":"

        Use the zone matrix to quickly audit your firewall configuration and identify potential security gaps. It provides an overview and shows the relationship between zones and the policies that connect them. Each cell in the matrix represents a potential traffic flow, with rows indicating the ingress zone and columns the egress zone.

        Figure 2. Example output from show firewall command.

        The matrix uses visual indicators to show the status of each zone and policy:

        \u2713 Green checkmark \u2014 traffic is explicitly allowed by policy \u2717 Red cross \u2014 traffic is blocked (default behavior) \u26a0 Yellow warning \u2014 watch out! Some traffic allowed, such as port forwarding rules

        This visualization helps you quickly understand your firewall's behavior and identify any unintended gaps or overly permissive rules in your configuration.

        Use the ? key in the CLI

        This admin-exec (top-level) CLI command has sub-commands that you can use to drill down on the operational data. Tap the ? key once to see an overview after show firewall, or just use the classic UNIX Tab key to complete everything until you've found your command.

        "},{"location":"firewall/#zones","title":"Zones","text":"

        Zones are logical groupings of network interfaces or IP networks that share the same trust level. Each zone has a default action that determines what happens to traffic destined for the host itself (INPUT chain). A LAN zone may have this set to accept, while a DMZ zone may be set to reject by default and only allow a subset of available services (e.g., DHCP, DNS, SSH) that devices in the DMZ can use to reach the host.

        Important

        Interfaces and networks are mutually exclusive in zones \u2014 attempting to configure both will result in a validation error. When setting up port forwarding from one zone to another, the destination network must be declared in a zone.

        "},{"location":"firewall/#default-zone","title":"Default Zone","text":"

        You must specify a default zone. This serves as a safety net: any interface not explicitly assigned to a zone automatically belongs to the default zone, ensuring network interfaces remain protected by the firewall. This automatic assignment is particularly useful when configuring new interfaces (e.g., VLANs, bridges, or hotplugged devices).

        Choose your default zone carefully \u2014 it should be the most restrictive zone appropriate for unmanaged interfaces. For routers, this is typically the wan zone, but you can of course also set up a dedicated block zone. In the CLI, when first enabling the firewall, a public zone is created. See more about this in the example below.

        Remember IP forwarding on interfaces!

        Firewall policies only control whether traffic is allowed on input, to be forwarded, or blocked (default). For the actual routing between interfaces to work, you must also enable IP forwarding on the relevant interfaces.

        "},{"location":"firewall/#intra-zone-traffic","title":"Intra-Zone Traffic","text":"

        Traffic between different interfaces, or networks, in the same zone is not forwarded by default. In most cases, if devices on separate interfaces need to communicate, they should be in different zones with a policy between them. Alternatively, if you want true LAN-like behavior, bridge the interfaces at layer-2 instead of routing between them at layer-3.

        Intra-zone forwarding \u2014 routing traffic within a single zone \u2014 is rarely needed. But if you do require it, create a policy where both ingress and egress are set to the same zone, e.g., lan \u2192 lan.

        "},{"location":"firewall/#port-forwarding","title":"Port Forwarding","text":"

        Port forwarding, also known as destination NAT (DNAT), redirects inbound traffic to another address and/or port. This allows external access to internal services. See Enterprise Gateway for an example.

        Each zone can have port forwarding rules that apply to traffic arriving at that zone's interfaces or matching its networks. The forwarded traffic must then be allowed by appropriate policies to reach the destination zone.

        The Zone Matrix shows a \u26a0 conditional warning flag, coloring the cell yellow, when exceptions like port forwarding are active.

        "},{"location":"firewall/#policies","title":"Policies","text":"

        In short, policy rules control traffic between zones. By default all inter-zone (and intra-zone) traffic is rejected. Meaning you must explicitly allow the traffic flows you intend.

        Figure 3. Example of common traffic flows (policies) between zones.

        IP masquerading (SNAT) is a policy setting that applies to traffic egressing a target zone. (Essential for Internet access from private networks.)

        A policy, like zones, have a default action. If it is not set to accept you must specify which services on the host any zone interface and network are allowed access to.

        Note

        Policy rules apply in-order, the first matching rule with action drop will terminate the traffic flow. Use action continue to allow processing to go to the next rule, until the last (implicit) default-drop rule at the end.

        The CLI currently does not support reordering rules. As a workaround, save your running-config to startup-config, then exit to the shell and edit the file with edit /cfg/startup-config.cfg.

        See the examples below for how to set up a policy. The built-in help system can also be useful:

        admin@example:/config/firewall/policy/lan-to-dmz/> help masquerade\nNAME\n        masquerade \nDESCRIPTION\n        Enable masquerading (SNAT) for traffic matching this policy.\n        Matching traffic will have their source IP address changed on egress,\n        using the IP address of the interface the traffic egresses.\nadmin@example:/config/firewall/policy/lan-to-dmz/>\n
        "},{"location":"firewall/#symbolic-names","title":"Symbolic Names","text":"

        The symbolic names HOST and ANY are available for use in both ingress and egress zones. In fact, the CLI uses inference when first enabling the firewall to inject a default policy to allow automatic IPv6 address assignment.

        • HOST \u2192 ANY: Control device to any outbound connection (default: allowed)
        • ANY \u2192 HOST: Control what can reach device services (uncommon, usually per-zone)
        • Zone \u2192 HOST: Allow specific zone to access device services
        "},{"location":"firewall/#custom-filters","title":"Custom Filters","text":"

        For more advanced firewall scenarios custom filters can be used. Currently only various ICMP type traffic control is supported. Enough to support the built-in allow-host-ipv6 policy and allow certain ICMP types on input or forward.

        You can inspect this built-in (locked) policy from admin-exec level with the command: show firewall policy allow-host-ipv6.

        "},{"location":"firewall/#default-behavior","title":"Default Behavior","text":"

        ICMP messages (particularly echo-request and echo-reply) can be used to reveal network information for malicious purposes. Therefore, the firewall blocks ICMP requests by default. This applies unless the zone's default action is accept.

        To enable echo-request (IPv4) for any interface, or per zone when action is set to drop or reject, set up a dedicated policy with ingress ANY and egress HOST that use a custom filter to accept that ICMP type. Make this policy the first rule in your list of policies, and remember to use continue for non-matching traffic.

        Another built-in behavior is automatically allowing \"established,related\" return traffic flows. This uses connection tracking - the firewall remembers outbound connections you initiate and automatically allows the corresponding inbound response traffic. This means you only need to configure rules for new connections; the firewall handles return traffic automatically without additional rules.

        "},{"location":"firewall/#services","title":"Services","text":"

        Several pre-defined services exist, that cover most use-cases, but you can also define custom services for applications not covered by the built-in ones.

        The firewall includes over 100 pre-defined services, such as:

        • ssh \u2014 Secure Shell (port 22/tcp)
        • http \u2014 Web traffic (port 80/tcp)
        • https \u2014 Secure web traffic (port 443/tcp)
        • dns \u2014 Domain Name System (port 53/tcp and 53/udp)
        • dhcp \u2014 DHCP server (port 67/udp)
        • dhcpv6-client \u2014 DHCPv6 client traffic
        • netconf \u2014 Network Configuration Protocol (port 830/tcp)
        • restconf \u2014 REST-based Network Configuration Protocol (port 443/tcp)

        Use the ? key in the CLI

        See the YANG model for the full list, or tap the ? key when setting up an allowed host service in a zone set service

        "},{"location":"firewall/#examples","title":"Examples","text":""},{"location":"firewall/#end-device-protection","title":"End Device Protection","text":"

        This is the default firewall setup, useful for end devices on untrusted networks. It provides maximum protection while allowing essential connectivity.

        admin@example:/> configure\nadmin@example:/config/> edit firewall\nadmin@example:/config/firewall/> show\ndefault public;\nzone public {\n  action reject;\n  description \"Public, unknown network. Only SSH and DHCPv6 client allowed.\";\n  service dhcpv6-client;\n  service ssh;\n}\nadmin@example:/config/firewall/> leave\n

        The reject action differs from drop in that it responds to ICMP messages, although maybe not how you may think. Pinging the device we may1 see this:

        $ ping 192.168.122.161\nFrom 192.168.122.161 icmp_seq=1 Packet filtered\n

        If we run tcpdump it shows us why:

        $ tcpdump -lni eth0\n20:10:40.245707 IP 192.168.122.1 > 192.168.122.161: ICMP echo request, id 56838, seq 1, length 64\n20:10:40.245961 IP 192.168.122.161 > 192.168.122.1: ICMP host 192.168.122.161 unreachable - admin prohibited filter, length 92\n

        The key here is that, yes the device responds, but not with ICMP reply but ICMP unreachable, and a little helpful message.

        The default zone is public, so all interfaces that are not explicitly assigned to another zone will be operationally placed in this zone as a safeguard. Inspect this from admin-exec context with show firewall, as can be seen in the below screenshot, the only interface e1 has been assigned automatically to the public zone. This information is also saved to the system log.

        Figure 4. Zone matrix and firewall overview from show firewall.

        Important

        These defaults are inferred for interactive CLI users. Enabling the firewall using NETCONF/RESTCONF will not yield the same results.

        "},{"location":"firewall/#homeoffice-router","title":"Home/Office Router","text":"

        For typical routers that need to protect internal devices while providing internet access. The LAN zone trusts internal devices, while the WAN zone blocks external threats.

        admin@example:/> configure\nadmin@example:/config/> edit firewall\nadmin@example:/config/firewall/> set default wan\nadmin@example:/config/firewall/> edit zone lan\nadmin@example:/config/firewall/\u2026/lan/> set description \"Internal LAN network - trusted\"\nadmin@example:/config/firewall/\u2026/lan/> set action accept\nadmin@example:/config/firewall/\u2026/lan/> set interface eth1\nadmin@example:/config/firewall/\u2026/lan/> set service ssh\nadmin@example:/config/firewall/\u2026/lan/> set service dhcp\nadmin@example:/config/firewall/\u2026/lan/> set service dns\nadmin@example:/config/firewall/\u2026/lan/> end\nadmin@example:/config/firewall/> edit zone wan\nadmin@example:/config/firewall/\u2026/wan/> set description \"External WAN interface - untrusted\"\nadmin@example:/config/firewall/\u2026/wan/> set action drop\nadmin@example:/config/firewall/\u2026/wan/> set interface eth0\nadmin@example:/config/firewall/\u2026/wan/> end\nadmin@example:/config/firewall/> edit policy loc-to-wan\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set description \"Allow LAN traffic to WAN with SNAT\"\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set ingress lan\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set egress wan\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set action accept\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set masquerade\nadmin@example:/config/firewall/\u2026/loc-to-wan/> leave\n
        "},{"location":"firewall/#enterprise-gateway","title":"Enterprise Gateway","text":"

        For businesses that need to host public services while protecting internal resources. We can build upon the Home/Office Router example above and add a DMZ zone with additional policies for controlled access.

        admin@example:/> configure\nadmin@example:/config/> edit firewall zone dmz\nadmin@example:/config/firewall/\u2026/dmz/> set description \"Semi-trusted public services\"\nadmin@example:/config/firewall/\u2026/dmz/> set action drop\nadmin@example:/config/firewall/\u2026/dmz/> set interface eth1\nadmin@example:/config/firewall/\u2026/dmz/> set service ssh\nadmin@example:/config/firewall/\u2026/dmz/> end\nadmin@example:/config/firewall/> edit policy loc-to-wan\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set description \"Allow local networks (LAN+DMZ) to WAN with SNAT\"\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set ingress dmz\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set egress wan\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set action accept\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set masquerade\nadmin@example:/config/firewall/\u2026/loc-to-wan/> end\nadmin@example:/config/firewall/> edit policy lan-to-dmz\nadmin@example:/config/firewall/\u2026/lan-to-dmz/> set description \"Allow LAN to manage DMZ services\"\nadmin@example:/config/firewall/\u2026/lan-to-dmz/> set ingress lan\nadmin@example:/config/firewall/\u2026/lan-to-dmz/> set egress dmz\nadmin@example:/config/firewall/\u2026/lan-to-dmz/> set action accept\nadmin@example:/config/firewall/\u2026/lan-to-dmz/> end\nadmin@example:/config/firewall/> edit zone wan port-forward 8080 tcp\nadmin@example:/config/firewall/\u2026/tcp/> set to addr 192.168.2.10\nadmin@example:/config/firewall/\u2026/tcp/> set to port 80\nadmin@example:/config/firewall/\u2026/tcp/> leave\n

        This adds a DMZ zone for public services, updates the internet access policy to include DMZ traffic, allows LAN management of DMZ services, and forwards external web traffic to the DMZ server.

        "},{"location":"firewall/#logging-and-monitoring","title":"Logging and Monitoring","text":"

        Different log levels are available to monitor and debug firewall behavior. Configure logging using the CLI:

        admin@example:/> configure\nadmin@example:/config/> edit firewall\nadmin@example:/config/firewall/> set logging all\nadmin@example:/config/firewall/> leave\n

        Firewall logs help you understand traffic patterns and security events. The CLI admin-exec command show firewall shows the last 10 log messages in the overview:

        Figure 5. Summary of recent logs at the end of show firewall.

        Use the command show log firewall.log to display the full logfile (remember, the syslog daemon rotates and zips too big log files). You can also use the follow firewall.log command to continuously monitor firewall log messages.

        "},{"location":"firewall/#netfilter-integration","title":"Netfilter Integration","text":"

        The Infix firewall operates through Linux netfilter hooks. Understanding how the zones and policy concepts map to these hooks will hopefully help you understand the firewall's behavior and ease troubleshooting.

        "},{"location":"firewall/#packet-flow","title":"Packet Flow","text":"

        Figure 6. Linux netfilter hooks in layer-3 traffic flow.

        Netfilter Hook Function Description prerouting ZONE Classification of incoming traffic, match interfaces/networks with zones prerouting ZONE Port forwarding (DNAT) from zone configuration input ZONE Host input filtering (services) input ZONE Default action for non-matching services (action) forward POLICY Allow traffic between zones (inter-zone rules) postrouting POLICY Masquerade (SNAT) when traffic egresses a zone"},{"location":"firewall/#prerouting-hook","title":"PREROUTING Hook","text":"
        • Zone Classification: Traffic is tagged based on ingress interface or source network
        • Port Forwarding: DNAT from zone configuration occurs before routing decisions
        • Connection Tracking: Early state establishment for stateful filtering
        "},{"location":"firewall/#input-hook","title":"INPUT Hook","text":"
        • ANY-to-HOST Policies: Enforces policy rules for traffic destined to the host itself
        • Zone Services: Allows configured services (SSH, HTTP, etc.) based on zone trust level
        • Zone Action: Applies a default action (accept/reject/drop) for unmatched traffic
        "},{"location":"firewall/#forward-hook","title":"FORWARD Hook","text":"
        • Policy Enforcement: Primary location for inter-zone traffic filtering
        • Custom Filters: ICMP and other protocol-specific rules within policies
        • Service Matching: Allows or denies services based on policy configuration
        "},{"location":"firewall/#postrouting-hook","title":"POSTROUTING Hook","text":"
        • Masquerading: Source NAT for outbound traffic when policies enable masquerading
        "},{"location":"firewall/#emergency-lockdown","title":"Emergency Lockdown","text":"

        For security emergencies (active breaches, suspicious activity), the firewall supports an immediate lockdown mode that blocks ALL traffic.

        Danger

        This will immediately terminate all network connections, including SSH. Physical console access is required to restore normal operation. It is also possible to restore normal operation by power-cycling the device.

        To activate emergency lockdown:

        ~$ curl -kX POST -u admin:admin -H \"Content-Type: application/yang-data+json\" \\\n        -d '{\"infix-firewall:input\": {\"operation\": \"now\"}}'                   \\\n        https://example.local/restconf/operations/infix-firewall:firewall/lockdown-mode\n

        To cancel lockdown mode (requires console access):

        ~$ curl -kX POST -u admin:admin -H \"Content-Type: application/yang-data+json\" \\\n        -d '{\"infix-firewall:input\": {\"operation\": \"cancel\"}}'                \\\n        https://example.local/restconf/operations/infix-firewall:firewall/lockdown-mode\n

        You can check the current lockdown state:

        ~$ curl -kX GET -u admin:admin -H 'Accept: application/yang-data+json' \\\n        https://example.local/restconf/data/infix-firewall:firewall/lockdown\n{\n  \"infix-firewall:firewall\": {\n    \"lockdown\": false\n  }\n}\n
        1. The output from ping clients differ A LOT. Some do not consider ICMP unreachable to be a proper response and it will appear as if the device is not responding at all. Use tcpdump or wireshark to get to the bottom of network mysteries.\u00a0\u21a9

        "},{"location":"hardware/","title":"Hardware Info & Status","text":""},{"location":"hardware/#hardware-information-and-status","title":"Hardware Information and Status","text":"

        The hardware infomation and status is handled by the YANG model IETF hardware, with deviations and augmentations in infix-hardware.

        "},{"location":"hardware/#gpsgnss-receivers","title":"GPS/GNSS Receivers","text":"

        Infix supports GPS/GNSS receivers for hardware status monitoring and NTP time synchronization. USB GPS receivers using the USB ACM interface are supported (e.g., u-blox). When connected, devices are automatically discovered and named gps0, gps1, etc.

        "},{"location":"hardware/#current-status","title":"Current status","text":"
        admin@example:/> show hardware\nHARDWARE COMPONENTS                                           \n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nGPS/GNSS Receivers                                           \nName                : gps0\nDevice              : /dev/gps0\nDriver              : u-blox\nStatus              : Active\nFix                 : 3D\nSatellites          : 10/15 (used/visible)\nPosition            : 59.334567N 18.063456E 42.3m\nPPS                 : Available\n

        Available information per receiver:

        Field Description Name Component name (gps0, gps1, ...) Device Device path (/dev/gps0) Driver Protocol driver (e.g., u-blox, NMEA, SiRF) Status Active or Inactive Fix NONE, 2D, or 3D Satellites Used/visible count Position Latitude, longitude, altitude (when fix acquired) PPS Pulse Per Second signal availability"},{"location":"hardware/#configure-gps-receiver","title":"Configure GPS receiver","text":"

        GPS receivers are hardware components with class gps. The class is auto-inferred from the component name, similar to WiFi radios (radioN):

        admin@example:/> configure\nadmin@example:/config/> set hardware component gps0\nadmin@example:/config/> leave\n

        To use a GPS receiver as an NTP reference clock source, see NTP \u2014 GPS Reference Clock.

        "},{"location":"hardware/#usb-ports","title":"USB Ports","text":"

        For Infix to be able to control USB port(s), a device tree modification is needed (see alder.dtsi for full example).

         chosen {\n   infix {\n     usb-ports = <&cp0_usb3_1>;\n     usb-port-names = \"USB\";\n   };\n };\n

        Two USB ports are also exposed in QEMU for test purpose.

        All USB ports in the system will be disabled during boot due to the file board/common/rootfs/etc/modprobe.d/usbcore.conf. If you do not want Infix to control USB port(s), remove the file or manually enable the USB bus, here is an example:

        # Enable the bus\necho 1 > /sys/bus/usb/devices/usb1/authorized\n

        And then enable sub-devices (e.g. USB memory)

        # Enable a device plugged into usb1\necho 1 >  /sys/bus/usb/devices/usb1/1-1/authorized\n
        "},{"location":"hardware/#current-status_1","title":"Current status","text":"
        admin@example:/> show hardware\nHARDWARE COMPONENTS                                           \n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nBoard Information                                            \nModel               : FriendlyElec NanoPi R2S\nManufacturer        : FriendlyElec\nSerial Number       : 9d1fbfdab6d171ce\nBase MAC Address    : 4a:dc:d8:20:0d:85\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nUSB Ports                                                    \nNAME                                         STATE     OPER   \nUSB                                          unlocked  enabled\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nSensors                                                      \nNAME                          VALUE                 STATUS    \nsoc                           44.1 \u00b0C               ok        \nsoc                           44.5 \u00b0C               ok        \n

        An USB port can be in two states unlocked and locked. When a port is locked, all connected devices will get power, but never authorized by Linux to use.

        "},{"location":"hardware/#configure-usb-port","title":"Configure USB port","text":"

        Note

        You can only configure USB ports known to the system. See the CLI command show hardware in admin-exec context. (Use do prefix in configure context.)

        admin@example:/> configure\nadmin@example:/config/> set hardware component USB state admin-state unlocked\nadmin@example:/config/> leave\nadmin@example:/>\n
        "},{"location":"hardware/#using-a-usb-stick","title":"Using a USB Stick","text":"

        With the USB port unlocked, a memory stick can be used to expand the storage of the device. Plug it in, either directly, or via a USB hub. All partitions of VFAT or exFAT type are automatically mounted to the /media/<LABLEL> directory, e.g., /media/memorystick, or if a partition does not have a label, using the kernel device name, e.g., /media/sda.

        Depending on the partition type the the media is either mounted in flush or sync mode to ensure files are properly flushed to the backing store of the memory stick. This to protect against users yanking out a device before file(s) have been written to it.

        The only way currently to safely \"eject\" a USB memory stick is to use umount command from a UNIX shell, which explicitly synchronizes any cached data to disk before returning the prompt:

        admin@example:~$ sudo umount /media/log\n
        "},{"location":"iface/","title":"Common Settings","text":""},{"location":"iface/#common-interface-settings","title":"Common Interface Settings","text":"

        Common interface settings include name, type, enabled, description, and custom MAC address. Type-specific settings are covered in the dedicated sections for Bridging, Link Aggregation, Ethernet, IP Addressing, and Routing.

        "},{"location":"iface/#interface-name","title":"Interface Name","text":"

        The interface name is limited to 1-15 characters due to Linux kernel constraints. Physical interfaces use their system-assigned names (e.g., eth0, eth1), while user-created interfaces can be named freely within this limit.

        Tip

        Naming conventions like br0, lag0, vlan10, or eth0.20 allow the CLI to automatically infer the interface type.

        "},{"location":"iface/#interface-type","title":"Interface Type","text":"

        The type setting defines what kind of interface this is: bridge, lag, vlan, veth, etc. When configuring via the CLI, the type is often inferred from the interface name. However, when configuring remotely via NETCONF or RESTCONF, the type must be set explicitly.

        admin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> set type bridge\n

        Available types can be listed from the CLI:

        admin@example:/config/interface/br0/> set type ?\n  bridge     IEEE bridge interface.\n  dummy      Linux dummy interface.  Useful mostly for testing.\n  ethernet   Any Ethernet interfaces, regardless of speed, RFC 3635.\n  gre        GRE tunnel interface.\n  gretap     GRETAP (Ethernet over GRE) tunnel interface.\n  lag        IEEE link aggregate interface.\n  loopback   Linux loopback interface.\n  other      Other interface, i.e., unknown.\n  veth       Linux virtual Ethernet pair.\n  vlan       Layer 2 Virtual LAN using 802.1Q.\n  vxlan      Virtual eXtensible LAN tunnel interface.\n  wifi       WiFi (802.11) interface\n  wireguard  WireGuard VPN tunnel interface.\n
        "},{"location":"iface/#enabledisable","title":"Enable/Disable","text":"

        An interface can be administratively disabled using the enabled setting. By default, interfaces are enabled (true).

        admin@example:/config/> edit interface eth0\nadmin@example:/config/interface/eth0/> set enabled false\nadmin@example:/config/interface/eth0/> leave\n

        The operational status can be inspected to see both administrative and actual link state:

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\neth0            ethernet   DISABLED    02:00:00:00:00:00\neth1            ethernet   UP          02:00:00:00:00:01\n...\n
        "},{"location":"iface/#description","title":"Description","text":"

        The description is a free-form text string (max 64 characters) saved as the Linux interface alias (ifalias). Use it to document an interface's purpose or add notes for remote debugging.

        admin@example:/config/> edit interface eth0\nadmin@example:/config/interface/eth0/> set description \"Uplink to core switch\"\nadmin@example:/config/interface/eth0/> leave\n

        The description is visible in the operational datastore and in show commands:

        admin@example:/> show interface eth0\nname                : eth0\ndescription         : Uplink to core switch\nindex               : 2\n...\n
        "},{"location":"iface/#custom-mac-address","title":"Custom MAC Address","text":"

        The custom-phys-address can be used to set an interface's MAC address. This is an extension to the ietf-interfaces YANG model, which defines phys-address as read-only1.

        Caution

        There is no validation or safety checks performed by the system when using custom-phys-address. In particular the offset variant can be dangerous to use -- pay attention to the meaning of bits in the upper-most octet: local bit, multicast/group, etc.

        "},{"location":"iface/#fixed-custom-mac","title":"Fixed custom MAC","text":"

        Use a fixed custom MAC address when the interface must present a specific, deterministic identity on the network. This option bypasses any chassis-derived logic and applies the configured address verbatim.

        admin@example:/config/> edit interface veth0a\nadmin@example:/config/interface/veth0a/> set custom-phys-address static 00:ab:00:11:22:33\n\n=> 00:ab:00:11:22:33\n
        "},{"location":"iface/#chassis-mac","title":"Chassis MAC","text":"

        Chassis MAC, sometimes also referred to as base MAC. In these two examples it is 00:53:00:c0:ff:ee.

        admin@example:/config/> edit interface veth0a\nadmin@example:/config/interface/veth0a/> set custom-phys-address chassis\n\n=> 00:53:00:c0:ff:ee\n
        "},{"location":"iface/#chassis-mac-with-offset","title":"Chassis MAC, with offset","text":"

        When constructing a derived address it is recommended to set the locally administered bit. Same chassis MAC as before.

        admin@example:/config/> edit interface veth0a\nadmin@example:/config/interface/veth0a/> set custom-phys-address chassis offset 02:00:00:00:00:02\n\n=> 02:53:00:c0:ff:f0\n
        1. A YANG deviation was previously used to make it possible to set phys-address, but this has been replaced with the more flexible custom-phys-address.\u00a0\u21a9

        "},{"location":"ip/","title":"IP Addressing","text":""},{"location":"ip/#ip-address-configuration","title":"IP Address Configuration","text":"

        This section details IP Addresses And Other Per-Interface IP settings.

        Infix support several network interface types, each can be assigned one or more IP addresses, both IPv4 and IPv6 are supported. (There is no concept of a \"primary\" address.)

        "},{"location":"ip/#ipv4-address-assignment","title":"IPv4 Address Assignment","text":"

        Multiple address assignment methods are available:

        Type Yang Model Description static ietf-ip Static assignment of IPv4 address, e.g., 10.0.1.1/24 link-local infix-ip Auto-assignment of IPv4 address in 169.254.x.x/16 range dhcp infix-dhcp-client Assignment of IPv4 address by DHCP server, e.g., 10.0.1.1/24

        Note

        The DHCP address method is only available for LAN interfaces (Ethernet, virtual Ethernet (veth), bridge, link aggregates, etc.)

        Supported DHCP (request) options, configurability (Cfg) and defaults, are listed below. Configurable options can be disabled on a per client interface basis, some options, like clientid and option 81, are possible to set the value of as well.

        Opt Name Cfg Description 1 netmask No Request IP address and netmask 3 router Yes Default route(s), see also option 121 and 249 6 dns-server Yes DNS server(s), static ones take precedence 12 hostname Yes DHCP cannot set hostname, only for informing server 15 domain Yes Default domain name, for name resolution 28 broadcast Yes Broadcast address, calculated if disabled 42 ntp-server Yes NTP server(s), static ones take precedence 50 address Yes Request (previously cached) address 61 client-id Yes Default MAC address (and option 12) 81 fqdn Yes Similar to option 12, request FQDN update in DNS 119 search Yes Request domain search list 121 classless-static-route Yes Classless static routes 249 ms-classless-static-route Yes Microsoft static route, same as option 121

        Default: router, dns-server, domain, broadcast, ntp-server, search, address, classless-static-route, ms-classless-static-route

        When configuring a DHCP client, ensure that the NTP client is enabled for the ntp-server DHCP option to be processed correctly. If the NTP client is not enabled, any NTP servers provided by the DHCP server will be ignored. For details on how to enable the NTP client, see the NTP Client Configuration section.

        Important

        Per RFC3442, if the DHCP server returns both a Classless Static Routes option (121) and Router option (3), the DHCP client must ignore the latter.

        "},{"location":"ip/#ipv6-address-assignment","title":"IPv6 Address Assignment","text":"

        Multiple address assignment methods are available:

        Type Yang Model Description static ietf-ip Static assignment of IPv6 address, e.g., 2001:db8:0:1::1/64 link-local ietf-ip1 (RFC4862) Auto-configured link-local IPv6 address (fe80::0 prefix + interface identifier, e.g., fe80::ccd2:82ff:fe52:728b/64) global auto-conf ietf-ip (RFC4862) Auto-configured (stateless) global IPv6 address (prefix from router + interface identifier, e.g., 2001:db8:0:1:ccd2:82ff:fe52:728b/64 dhcp infix-dhcpv6-client Assignment of IPv6 address by DHCPv6 server, e.g., 2001:db8::42/128

        Both for link-local and global auto-configuration, it is possible to auto-configure using a random suffix instead of the interface identifier.

        Note

        The DHCPv6 address method is only available for LAN interfaces (Ethernet, virtual Ethernet (veth), bridge, link aggregates, etc.)

        Supported DHCPv6 (request) options, configurability (Cfg) and defaults, are listed below. Configurable options can be disabled on a per client interface basis, some options, like client-id and client-fqdn, are possible to set the value of as well.

        Opt Name Cfg Description 1 client-id Yes Client identifier (DUID), auto-generated by default 2 server-id Yes Server identifier (DUID) 23 dns-server Yes DNS recursive name servers, static ones take precedence 24 domain-search Yes Domain search list 25 ia-pd Yes Prefix delegation for downstream networks 31 sntp-server Yes Simple Network Time Protocol servers 32 information-refresh-time Yes Refresh time for stateless DHCPv6 39 client-fqdn Yes Client FQDN, request DNS update from server 56 ntp-server Yes NTP time servers, static ones take precedence

        Default: dns-server, domain-search, ntp-server

        DHCPv6 supports both stateful (address assignment) and stateless (information-only) modes:

        • Stateful DHCPv6: The server assigns IPv6 addresses to clients. This is the default mode when enabling the DHCPv6 client.
        • Stateless DHCPv6: Used with SLAAC (Stateless Address Autoconfiguration) when only configuration information (DNS, NTP, etc.) is needed. Enable with the information-only setting.

        When configuring a DHCPv6 client, ensure that the NTP client is enabled for the ntp-server DHCPv6 option to be processed correctly. If the NTP client is not enabled, any NTP servers provided by the DHCPv6 server will be ignored. For details on how to enable the NTP client, see the NTP Client Configuration section.

        "},{"location":"ip/#examples","title":"Examples","text":"
        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        To illustrate IP address configuration, the examples below uses a switch with a single Ethernet interface (eth0) and a loopback interface (lo). As shown above, these examples assume eth0 has an IPv6 link-local address and lo has static IPv4 and IPv6 addresses by default.

        "},{"location":"ip/#static-and-link-local-ipv4-addresses","title":"Static and link-local IPv4 addresses","text":"
        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv4\nadmin@example:/config/interface/eth0/ipv4/> set address 10.0.1.1 prefix-length 24\nadmin@example:/config/interface/eth0/ipv4/> set autoconf\nadmin@example:/config/interface/eth0/ipv4/> diff\n+interfaces {\n+  interface eth0 {\n+    ipv4 {\n+      address 10.0.1.1 {\n+        prefix-length 24;\n+      }\n+      autoconf;\n+    }\n+  }\n+}\nadmin@example:/config/interface/eth0/ipv4/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv4                   169.254.1.3/16 (random)\n                ipv4                   10.0.1.1/24 (static)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        As shown, the link-local IPv4 address is configured with set autoconf. The presence of the autoconf container enables IPv4 link-local address assignment. The resulting address (169.254.1.3/16) is of type random (ietf-ip.yang).

        The IPv4LL client also supports a request-address setting which can be used to \"seed\" the client's starting address. If the address is free it will be used, otherwise it falls back to the default algorithm.

        admin@example:/config/interface/eth0/ipv4/> edit autoconf\nadmin@example:/config/interface/eth0/ipv4/autoconf/> set request-address 169.254.1.2\nadmin@example:/config/interface/eth0/ipv4/autoconf/> leave\n
        "},{"location":"ip/#use-of-dhcp-for-ipv4-address-assignment","title":"Use of DHCP for IPv4 address assignment","text":"
        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv4\nadmin@example:/config/interface/eth0/ipv4/> set dhcp\nadmin@example:/config/interface/eth0/ipv4/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv4                   10.1.2.100/24 (dhcp)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        The resulting address (10.1.2.100/24) is of type dhcp.

        To configure DHCP client options, such as sending a specific hostname to the server, you can specify options with values:

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv4 dhcp\nadmin@example:/config/interface/eth0/ipv4/dhcp/> set option hostname value myhost\nadmin@example:/config/interface/eth0/ipv4/dhcp/> show\noption hostname {\n  value myhost;\n}\nadmin@example:/config/interface/eth0/ipv4/dhcp/> leave\nadmin@example:/>\n

        Tip

        The special value auto can be used with the hostname option to automatically use the configured system hostname.

        Other useful DHCP options include:

        • client-id - Send a specific client identifier to the server
        • route-preference - Set the administrative distance for DHCP-learned routes (default: 5)

        For advanced usage with vendor-specific options, see the YANG model.

        "},{"location":"ip/#use-of-dhcpv6-for-ipv6-address-assignment","title":"Use of DHCPv6 for IPv6 address assignment","text":"
        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6\nadmin@example:/config/interface/eth0/ipv6/> set dhcp\nadmin@example:/config/interface/eth0/ipv6/> leave\nadmin@example:/> show interface\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   2001:db8::42/128 (dhcp)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        The resulting address (2001:db8::42/128) is of type dhcp.

        To configure DHCPv6 client options, such as requesting prefix delegation for downstream networks, you can specify options:

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6 dhcp\nadmin@example:/config/interface/eth0/ipv6/dhcp/> set option ia-pd\nadmin@example:/config/interface/eth0/ipv6/dhcp/> set option dns-server\nadmin@example:/config/interface/eth0/ipv6/dhcp/> show\noption dns-server;\noption ia-pd;\nadmin@example:/config/interface/eth0/ipv6/dhcp/> leave\nadmin@example:/>\n

        For stateless DHCPv6 (used with SLAAC to get only configuration information):

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6 dhcp\nadmin@example:/config/interface/eth0/ipv6/dhcp/> set information-only true\nadmin@example:/config/interface/eth0/ipv6/dhcp/> show\ninformation-only true;\noption dns-server;\noption domain-search;\nadmin@example:/config/interface/eth0/ipv6/dhcp/> leave\nadmin@example:/>\n

        Other useful DHCPv6 options include:

        • duid - Set a specific DHCPv6 Unique Identifier (auto-generated by default)
        • client-fqdn - Request the server to update DNS records with client's FQDN
        • route-preference - Set the administrative distance for DHCPv6-learned routes (default: 5)

        For advanced usage with vendor-specific options, see the YANG model.

        "},{"location":"ip/#disabling-ipv6-link-local-addresses","title":"Disabling IPv6 link-local address(es)","text":"

        The (only) way to disable IPv6 link-local addresses is by disabling IPv6 on the interface.

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6\nadmin@example:/config/interface/eth0/ipv6/> set enabled false\nadmin@example:/config/interface/eth0/ipv6/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n
        "},{"location":"ip/#static-ipv6-address","title":"Static IPv6 address","text":"
        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6\nadmin@example:/config/interface/eth0/ipv6/> set address 2001:db8::1 prefix-length 64\nadmin@example:/config/interface/eth0/ipv6/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   2001:db8::1/64 (static)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n
        "},{"location":"ip/#stateless-auto-configuration-of-global-ipv6-address","title":"Stateless Auto-configuration of Global IPv6 Address","text":"

        Stateless address auto-configuration of global addresses is enabled by default. The address is formed by concatenating the network prefix advertised by the router (here 2001:db8:0:1::0/64) and the interface identifier. The resulting address is of type link-layer, as it is formed based on the interface identifier (ietf-ip.yang).

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   2001:db8:0:1:0:ff:fe00:0/64 (link-layer)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        Disabling auto-configuration of global IPv6 addresses can be done as shown below.

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6\nadmin@example:/config/interface/eth0/ipv6/> set autoconf create-global-addresses false\nadmin@example:/config/interface/eth0/ipv6/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n
        "},{"location":"ip/#random-link-identifiers-for-ipv6-stateless-autoconfiguration","title":"Random Link Identifiers for IPv6 Stateless Autoconfiguration","text":"

        By default, the auto-configured link-local and global IPv6 addresses are formed from a link-identifier based on the MAC address.

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   2001:db8:0:1:0:ff:fe00:0/64 (link-layer)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        To avoid revealing identity information in the IPv6 address, it is possible to specify use of a random identifier (ietf-ip.yang and RFC8981).

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6\nadmin@example:/config/interface/eth0/ipv6/> set autoconf create-temporary-addresses true\nadmin@example:/config/interface/eth0/ipv6/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   2001:db8:0:1:b705:8374:638e:74a8/64 (random)\n                ipv6                   fe80::ad3d:b274:885a:9ffb/64 (random)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        Both the link-local address (fe80::) and the global address (2001:) have changed type to random.

        "},{"location":"ip/#ipv4-forwarding","title":"IPv4 forwarding","text":"

        To be able to route (static or dynamic) on the interface it is required to enable forwarding. This setting controls if packets received on this interface can be forwarded.

        admin@example:/config/> edit interface eth0\nadmin@example:/config/interface/eth0/> set ipv4 forwarding\nadmin@example:/config/interface/eth0/> leave\nadmin@example:/>\n
        "},{"location":"ip/#ipv6-forwarding","title":"IPv6 forwarding","text":"

        Forwarding must be enabled on an interface for it to route IPv6 traffic (static or dynamic). The setting is per-interface and works the same way as IPv4 forwarding.

        The following table shows the IPv6 features that the forwarding setting controls when it is Enabled or Disabled:

        IPv6 Feature Enabled Disabled IsRouter set in Neighbour Advertisements Yes No Transmit Router Solicitations No Yes Router Advertisements are ignored Yes Yes Accept Redirects No Yes
        admin@example:/config/> edit interface eth0\nadmin@example:/config/interface/eth0/> set ipv6 forwarding\nadmin@example:/config/interface/eth0/> leave\nadmin@example:/>\n
        1. Link-local IPv6 addresses are implicitly enabled when enabling IPv6. IPv6 can be enabled/disabled per interface in the ietf-ip YANG model.\u00a0\u21a9

        "},{"location":"keystore/","title":"Keystore","text":""},{"location":"keystore/#keystore","title":"Keystore","text":"

        The Infix keystore is a centralized storage system for cryptographic keys used throughout the system. It is based on the IETF standards RFC 9641 (Keystore) and RFC 9640 (Cryptographic Types), with Infix extensions for WiFi and WireGuard key formats.

        "},{"location":"keystore/#overview","title":"Overview","text":"

        The keystore supports two types of cryptographic keys:

        1. Asymmetric Keys \u2014 public/private key pairs used for:

          • SSH host authentication (RSA keys)
          • HTTPS/TLS certificates (X.509 keys)
          • WireGuard VPN tunnels (X25519 keys)
        2. Symmetric Keys \u2014 shared secrets used for:

          • WiFi authentication (WPA2/WPA3 pre-shared keys)
          • WireGuard VPN pre-shared keys

        All keys are stored under the ietf-keystore configuration path and can be managed via CLI, NETCONF, or RESTCONF.

        "},{"location":"keystore/#supported-formats","title":"Supported Formats","text":"Asymmetric Key Format Use Case Key Type rsa-private-key-format / ssh-public-key-format SSH host keys RSA rsa-private-key-format / x509-public-key-format TLS certificates RSA + X.509 x25519-private-key-format / x25519-public-key-format WireGuard VPN Curve25519 Symmetric Key Format Use Case passphrase-key-format Human-readable passphrases (WiFi) octet-string-key-format Raw symmetric keys (WireGuard)"},{"location":"keystore/#asymmetric-keys","title":"Asymmetric Keys","text":"

        Asymmetric keys consist of a public/private key pair. The public key can be shared freely, while the private key must be kept secure.

        "},{"location":"keystore/#ssh-host-keys","title":"SSH Host Keys","text":"

        SSH host keys identify the system during SSH and NETCONF connections. The default host key is automatically generated on first boot and stored in the keystore with the name genkey.

        See SSH Management for details on generating and importing custom SSH host keys.

        "},{"location":"keystore/#tls-certificates","title":"TLS Certificates","text":"

        TLS certificates are used by the web server (nginx) for HTTPS connections. The default certificate is a self-signed certificate automatically generated on first boot and stored in the keystore with the name gencert. Like SSH host keys, the certificate is regenerated on factory reset when its keys are empty.

        The web server's certificate leaf references which keystore entry to use:

        \"infix-services:web\": {\n    \"certificate\": \"gencert\",\n    \"enabled\": true\n}\n

        To use a custom (e.g., CA-signed) certificate, create a new asymmetric key entry with x509-public-key-format, populate it with your certificate and private key, then point the web certificate leaf to it:

        \"ietf-keystore:keystore\": {\n    \"asymmetric-keys\": {\n        \"asymmetric-key\": [\n            {\n                \"name\": \"my-cert\",\n                \"public-key-format\": \"infix-crypto-types:x509-public-key-format\",\n                \"public-key\": \"<base64-encoded-cert>\",\n                \"private-key-format\": \"infix-crypto-types:rsa-private-key-format\",\n                \"cleartext-private-key\": \"<base64-encoded-key>\",\n                \"certificates\": {\n                    \"certificate\": [\n                        { \"name\": \"ca-signed\", \"cert-data\": \"<base64-encoded-cert>\" }\n                    ]\n                }\n            }\n        ]\n    }\n}\n

        Note

        The public-key and cert-data fields contain base64-encoded PEM data with the -----BEGIN/END----- markers stripped. The system reconstructs the PEM files when writing them to disk for nginx.

        "},{"location":"keystore/#wireguard-keys","title":"WireGuard Keys","text":"

        WireGuard uses X25519 elliptic curve cryptography for key exchange. Each WireGuard interface requires a public/private key pair stored as an asymmetric key in the keystore. Key pairs can be generated directly from the CLI:

        admin@example:/> wireguard genkey\nPrivate: aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=\nPublic:  bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=\n

        See WireGuard VPN for key generation and configuration examples.

        "},{"location":"keystore/#symmetric-keys","title":"Symmetric Keys","text":"

        Symmetric keys are shared secrets where the same key must be configured on all systems that need to communicate.

        "},{"location":"keystore/#wifi-pre-shared-keys","title":"WiFi Pre-Shared Keys","text":"

        WiFi networks secured with WPA2 or WPA3 use pre-shared keys stored as symmetric keys in the keystore with passphrase-key-format. The passphrase must be 8-63 printable ASCII characters.

        Since symmetric keys are stored as binary (base64-encoded), the CLI provides the change command to enter passphrases interactively:

        admin@example:/config/keystore/\u2026/my-wifi-key/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\n

        See WiFi for complete configuration examples.

        "},{"location":"keystore/#wireguard-pre-shared-keys","title":"WireGuard Pre-Shared Keys","text":"

        WireGuard supports optional pre-shared keys (PSK) that add a layer of symmetric encryption alongside Curve25519. PSKs use the standard IETF octet-string-key-format (32 random bytes). This provides defense-in-depth against future quantum computers that might break elliptic curve cryptography. Note, however, that WireGuard\u2019s authentication and initial key agreement remain Curve25519-based, so PSKs only protect the session encryption, not the handshake itself.

        PSKs can be generated directly from the CLI:

        admin@example:/> wireguard genpsk\ncO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=\n

        See WireGuard VPN for PSK generation and usage examples.

        "},{"location":"keystore/#viewing-keys","title":"Viewing Keys","text":"

        The show keystore command in admin-exec mode gives an overview of all keys in the keystore. Passphrases (WiFi passwords) are decoded and shown in cleartext, while binary keys (WireGuard PSKs) are shown as base64:

        admin@example:/> show keystore\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nSymmetric Keys\nNAME                         FORMAT        VALUE                        \nmy-wifi-key                  passphrase    MySecretPassword\nwg-psk                       octet-string  zYr83O4Ykj9i1gN+/aaosJxQx...\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nAsymmetric Keys\nNAME                         TYPE    PUBLIC KEY                         \ngenkey                       rsa     MIIBCgKCAQEAnj0YinjhYDgYbEGuh7...\ngencert                      x509    MIIDXTCCAkWgAwIBAgIJAJC1HiIAZA...\nwg-tunnel                    x25519  bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1...\n

        To see the full (untruncated) details of a specific key, use the symmetric or asymmetric qualifier with the key name:

        admin@example:/> show keystore symmetric my-wifi-key\nname                : my-wifi-key\nformat              : passphrase\nvalue               : MySecretPassword\n\nadmin@example:/> show keystore asymmetric genkey\nname                : genkey\nalgorithm           : rsa\npublic key format   : ssh-public-key\npublic key          : MIIBCgKCAQEAnj0YinjhY...full key...IDAQAB\n

        Note

        The show keystore command is protected by NACM. Only users in the admin group can view keystore data. Operator-level users will see a message indicating that no keystore data is available.

        The full configuration-mode view (including private keys) is still available via configure and then show keystore:

        admin@example:/config/> show keystore\n

        Warning

        The configuration-mode show keystore displays private keys in cleartext. Be careful when viewing keys on shared screens or in logged sessions. The admin-exec show keystore command never displays private keys.

        "},{"location":"keystore/#deleting-keys","title":"Deleting Keys","text":"
        admin@example:/> configure\nadmin@example:/config/> delete keystore asymmetric-key mykey\nadmin@example:/config/> leave\n

        Caution

        Deleting a key that is referenced by a service (SSH, WireGuard, WiFi) will cause that service to fail. Verify the key is not in use before deletion.

        "},{"location":"keystore/#security-considerations","title":"Security Considerations","text":"

        The keystore is protected by NACM (Network Access Control Model) rules. Only users in the admin group can view or modify cryptographic keys. See NACM for details on access control.

        Private keys are stored in cleartext in the configuration database. Configuration files and backups containing the keystore should be treated as sensitive and protected accordingly.

        "},{"location":"keystore/#key-validation","title":"Key Validation","text":"

        Symmetric key values are stored as binary (base64-encoded). The system validates them based on their declared format:

        • passphrase-key-format: Used by WiFi, must decode to 8-63 ASCII characters
        • octet-string-key-format: Used by Wireguard, must decode to exactly 32 bytes (256 bits)
        "},{"location":"keystore/#references","title":"References","text":"
        • RFC 9641 - A YANG Data Model for a Keystore
        • RFC 9640 - YANG Data Types and Groupings for Cryptography
        • WiFi Documentation
        • WireGuard VPN Documentation
        • SSH Management
        • NACM Access Control
        "},{"location":"lag/","title":"Link Aggregation","text":""},{"location":"lag/#link-aggregation","title":"Link Aggregation","text":"

        A link aggregate, or lag, allows multiple physical interfaces to be combined into a single logical interface, providing increased bandwidth (in some cases) and redundancy (primarily). Two modes of qualifying lag member ports are available:

        1. static: Active members selected based on link status (carrier)
        2. lacp: IEEE 802.3ad Link Aggregation Control Protocol

        In LACP mode, LACPDUs are exchanged by the link partners to qualify each lag member, while in static mode only carrier is used. This additional exchange in LACP ensures traffic can be forwarded in both directions.

        Traffic distribution, for both modes, across the active lag member ports is determined by the hash policy1. It uses an XOR of the source, destination MAC addresses and the EtherType field. This, IEEE 802.3ad-compliant, algorithm will place all traffic to a particular network peer on the same link. Meaning there is no increased bandwidth for communication between two specific devices.

        Tip

        Similar to other interface types, naming your interface lagN, where N is a number, allows the CLI to automatically infer the interface type as LAG.

        "},{"location":"lag/#basic-configuration","title":"Basic Configuration","text":"

        Creating a link aggregate interface and adding member ports:

        admin@example:/> configure\nadmin@example:/config/> edit interface lag0\nadmin@example:/config/interface/lag0/> set lag mode static\nadmin@example:/config/interface/lag0/> end\nadmin@example:/config/> set interface eth7 lag-port lag lag0\nadmin@example:/config/> set interface eth8 lag-port lag lag0\nadmin@example:/config/> leave\n

        A static lag responds only to link (carrier) changes of member ports. E.g., in this example egressing traffic is continuously distributed over the two links until link down on one link is detected, triggering all traffic to be steered to the sole remaining link.

        "},{"location":"lag/#lacp-configuration","title":"LACP Configuration","text":"

        LACP mode provides dynamic negotiation of the link aggregate. Key settings include:

        admin@example:/> configure\nadmin@example:/config/> edit interface lag0\nadmin@example:/config/interface/lag0/> set lag mode lacp\nadmin@example:/config/interface/lag0/> set lag lacp mode passive\nadmin@example:/config/interface/lag0/> set lag lacp rate fast\nadmin@example:/config/interface/lag0/> set lag lacp system-priority 100\n

        LACP mode supports two operational modes:

        • active: Initiates negotiation by sending LACPDUs (default)
        • passive: Waits for peer to initiate negotiation

        Note

        At least one end of the link must be in active mode for negotiation to occur.

        The LACP rate setting controls protocol timing:

        • slow: LACPDUs sent every 30 seconds, with 90 second timeout (default)
        • fast: LACPDUs sent every second, with 3 second timeout
        "},{"location":"lag/#link-flapping","title":"Link Flapping","text":"

        To protect against link flapping, debounce timers can be configured to delay link qualification. Usually only the up delay is needed:

        admin@example:/config/interface/lag0/lag/link-monitor/> edit debounce\nadmin@example:/config/interface/lag0/lag/link-monitor/debounce/> set up 500\nadmin@example:/config/interface/lag0/lag/link-monitor/debounce/> set down 200\n
        "},{"location":"lag/#operational-status-overview","title":"Operational Status, Overview","text":"

        Like other interfaces, link aggregates are also available in the general interfaces overview in the CLI admin-exec context. Here is the above static mode aggregate:

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\n.\n.\n.\nlag0            lag        UP          static: balance-xor, hash: layer2\n\u2502               ethernet   UP          00:a0:85:00:02:00\n\u251c eth7          lag        ACTIVE\n\u2514 eth8          lag        ACTIVE\n

        Same aggregate, but in LACP mode:

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\n.\n.\n.\nlag0            lag        UP          lacp: active, rate: fast (1s), hash: layer2\n\u2502               ethernet   UP          00:a0:85:00:02:00\n\u251c eth7          lag        ACTIVE      active, short_timeout, aggregating, in_sync, collecting, distributing\n\u2514 eth8          lag        ACTIVE      active, short_timeout, aggregating, in_sync, collecting, distributing\n
        "},{"location":"lag/#operational-status-detail","title":"Operational Status, Detail","text":"

        In addition to basic status shown in the interface overview, detailed LAG status can be inspected:

        admin@example:/> show interface lag0\nname                : lag0\nindex               : 25\nmtu                 : 1500\noperational status  : up\nphysical address    : 00:a0:85:00:02:00\nlag mode            : static\nlag type            : balance-xor\nlag hash            : layer2\nlink debounce up    : 0 msec\nlink debounce down  : 0 msec\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 0\nout-octets          : 2142\n

        Same aggregate, but in LACP mode:

        admin@example:/> show interface lag0\nname                : lag0\nindex               : 24\nmtu                 : 1500\noperational status  : up\nphysical address    : 00:a0:85:00:02:00\nlag mode            : lacp\nlag hash            : layer2\nlacp mode           : active\nlacp rate           : fast (1s)\nlacp aggregate id   : 1\nlacp system priority: 65535\nlacp actor key      : 9\nlacp partner key    : 9\nlacp partner mac    : 00:a0:85:00:03:00\nlink debounce up    : 0 msec\nlink debounce down  : 0 msec\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 100892\nout-octets          : 111776\n

        Member ports provide additional status information:

        • Link failure counter: number of detected link failures
        • LACP state flags: various states of LACP negotiation:
        • active: port is actively sending LACPDUs
        • short_timeout: using fast rate (1s) vs. slow rate (30s)
        • aggregating: port is allowed to aggregate in this LAG
        • in_sync: port is synchronized with partner
        • collecting: port is allowed to receive traffic
        • distributing: port is allowed to send traffic
        • defaulted: using default partner info (partner not responding)
        • expired: partner info has expired (no LACPDUs received)
        • Aggregator ID: unique identifier for this LAG group
        • Actor state: LACP state flags for this port (local)
        • Partner state: LACP state flags from the remote port

        Example member port status:

        admin@example:/> show interface eth7\nname                : eth7\nindex               : 8\nmtu                 : 1500\noperational status  : up\nphysical address    : 00:a0:85:00:02:00\nlag member          : lag0\nlag member state    : active\nlacp aggregate id   : 1\nlacp actor state    : active, short_timeout, aggregating, in_sync, collecting, distributing\nlacp partner state  : active, short_timeout, aggregating, in_sync, collecting, distributing\nlink failure count  : 0\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 473244\nout-octets          : 499037\n
        "},{"location":"lag/#example-switch-uplink-with-lacp","title":"Example: Switch Uplink with LACP","text":"

        LACP mode provides the most robust operation, automatically negotiating the link aggregate and detecting configuration mismatches.

        A common use case is connecting a switch to an upstream device:

        admin@example:/> configure\nadmin@example:/config/> edit interface lag0\nadmin@example:/config/interface/lag0/> set lag mode lacp\n

        Enable fast LACP for quicker fail-over:

        admin@example:/config/interface/lag0/> set lag lacp rate fast\n

        Add uplink ports

        admin@example:/config/interface/lag0/> end\nadmin@example:/config/> set interface eth7 lag-port lag lag0\nadmin@example:/config/> set interface eth8 lag-port lag lag0\n

        Enable protection against \"link flapping\".

        admin@example:/config/interface/lag0/> edit lag link-monitor\nadmin@example:/config/interface/lag0/lag/link-monitor/> edit debounce\nadmin@example:/config/interface/lag0/lag/link-monitor/debounce/> set up 500\nadmin@example:/config/interface/lag0/lag/link-monitor/debounce/> set down 200\nadmin@example:/config/interface/lag0/lag/link-monitor/debounce/> top\n

        Add to bridge for switching

        admin@example:/config/interface/lag0/lag/link-monitor/debounce/> end\nadmin@example:/config/> set interface lag0 bridge-port bridge br0\nadmin@example:/config/> leave\n
        1. (source MAC XOR destination MAC XOR EtherType) MODULO num_links \u21a9

        "},{"location":"license/","title":"Origin & Licensing","text":""},{"location":"license/#origin-licensing","title":"Origin & Licensing","text":"

        Infix is entirely built on Open Source components (packages). Most of them, as well as the build system with its helper scripts and tools, is from Buildroot, which is distributed under the terms of the GNU General Public License (GPL). See the file COPYING for details.

        Some files in Buildroot may contain a different open source license statement. Those files are licensed under the license contained in the file itself.

        Buildroot and Infix also bundle patch files, which are applied to the sources of the various packages. Those patches are not covered by the license of Buildroot or Infix. Instead, they are covered by the license of the software to which the patches are applied. When said software is available under multiple licenses, the patches are only provided under the publicly accessible licenses.

        Infix releases include the license information covering all Open Source packages. This is extracted automatically at build time using the tool make legal-info. Any proprietary software built on top of Infix, or Buildroot, would need separate auditing to ensure it does not link with any GPL1 licensed library.

        1. Infix image builds use GNU libc (GLIBC) which is covered by the LGPL. The LGPL does allow proprietary software, as long as said software is linking dynamically, not statically, to GLIBC.\u00a0\u21a9

        "},{"location":"management/","title":"Management","text":""},{"location":"management/#system-management","title":"System Management","text":"

        The system utilizes YANG models for keeping configuration and operational data. These databases can be managed through different interfaces such as NETCONF, RESTCONF, and CLI via SSH or Console.

        "},{"location":"management/#ssh-management","title":"SSH Management","text":"

        An SSH server (SSHv2) is provided for remote management. It can be enabled/disabled as shown below.

        admin@example:/> configure\nadmin@example:/config/> edit ssh\nadmin@example:/config/ssh/> set enabled\nadmin@example:/config/ssh/>\n

        By default the SSH server accepts connections to port 22 on all its IP addresses, but this can be adjusted using the listen command. To make the server (only) listen for incoming connections to IP address 192.168.1.1 and port 12345 the following commands can be used.

        admin@example:/> configure\nadmin@example:/config/> edit ssh\nadmin@example:/config/ssh/> show\nenabled true;\nhostkey genkey;\nlisten ipv4 {\n  address 0.0.0.0;\n  port 22;\n}\nlisten ipv6 {\n  address ::;\n  port 22;\n}\nadmin@example:/config/ssh/> no listen ipv6\nadmin@example:/config/ssh/> edit listen ipv4\nadmin@example:/config/ssh/listen/ipv4/> set address 192.168.1.1\nadmin@example:/config/ssh/listen/ipv4/> set port 12345\nadmin@example:/config/ssh/listen/ipv4/>\n

        The default SSH hostkey is generated on first boot and is used in both SSH and NETCONF (SSH transport). Custom keys can be added to the configuration in ietf-keystore. The only supported hostkey type is RSA for now, thus the private key must be ietf-crypto-types:rsa-private-key-format and the public key ietf-crypto-types:ssh-public-key-format

        Tip

        For comprehensive information about the keystore, including key management, security considerations, and examples for different key types, see the Keystore documentation.

        "},{"location":"management/#use-your-own-ssh-hostkeys","title":"Use your own SSH hostkeys","text":"

        Hostkeys can be generated with OpenSSL:

        openssl genpkey -quiet -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -outform PEM > mykey\nopenssl rsa -RSAPublicKey_out < mykey > mykey.pub\n
        Store the keys in ietf-keystore without the header and footer information created by OpenSSL.

        After the key has been stored in the keystore and given the name mykey it can be added to SSH configuration:

        admin@example:/> configure\nadmin@example:/config/> edit ssh\nadmin@example:/config/ssh/> set hostkey mykey\n
        "},{"location":"management/#console-port","title":"Console Port","text":"

        For units with a console port, it is possible for users to login to shell/CLI with functionality similar to what is provided via SSH.

        The type and setup for your console port is product specific. For instance, it can be a USB-C port connected to the CPU serial port using a USB-to-serial converter. To connect you would need a USB-C cable connected to the console port of the device. The serial port is typically setup to run at 115200 baud, 8N1.

        Infix OS \u2014 Immutable.Friendly.Secure v24.11.1 (ttyS0)\nexample login: admin\nPassword:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-'\n\nRun the command 'cli' for interactive OAM\n\nadmin@example:~$\n

        The resize command can be used to update terminal settings to the size of your terminal window.

        admin@example:~$ resize\nCOLUMNS=115;LINES=59;export COLUMNS LINES;\nadmin@example:~$\n

        CLI can be entered from shell in the same way as for SSH.

        admin@example:~$ cli\n\nSee the 'help' command for an introduction to the system\n\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\ne1              ethernet   LOWER-DOWN  00:53:00:06:03:01\ne2              ethernet   LOWER-DOWN  00:53:00:06:03:02\n...\nadmin@example:/>\n
        "},{"location":"management/#web-web-console-and-restconf","title":"Web, Web-console and RESTCONF","text":"

        The system provides a set of Web services:

        • a rudimentary Web server, currently limited to an information page
        • a RESTCONF server with equivalent management capabilities as NETCONF
        • a Web console service, where the shell/CLI can be accessed via HTTPS, similar to connecting via a console port or SSH

        There is also a Netbrowse Web service presenting information about the unit's neighbors, collected via mDNS (see Discovery for more details).

        admin@example:/> configure\nadmin@example:/config/> edit web\nadmin@example:/config/web/> help\n  certificate                       Reference to asymmetric key in central keystore.\n  enabled                           Enable or disable on all web services.\n  console                           Web console interface.\n  netbrowse                         mDNS Network Browser.\n  restconf                          IETF RESTCONF Server.\nadmin@example:/config/web/>\n
        "},{"location":"management/#enabledisable-web-service-and-server","title":"Enable/disable Web Service and Server","text":"

        The Web service can be enabled as shown below.

        admin@example:/> configure\nadmin@example:/config/> edit web\nadmin@example:/config/web/> set enabled\nadmin@example:/config/web/>\n

        Enabling the Web service implies that a Web server is enabled. Currently this Web server provides generic Infix information, as well as a link to a Web console. The Web server uses HTTPS; any HTTP request is redirected to HTTPS.

        The enabled setting for the Web service acts as a global enable/disable setting for the other Web services (Web console, RESTCONF and Netbrowse).

        "},{"location":"management/#enabledisable-web-console","title":"Enable/disable Web Console","text":"

        The Web console service provides a terminal service similar to Console or SSH. The Web console is secured via HTTPS on port 7861.

        The Web console has its own enable/disable setting, but will only be activated if the Web service is enabled. The example below shows how to disable the Web console.

        admin@example:/config/web/> edit console\nadmin@example:/config/web/console/> no enabled\nadmin@example:/config/web/console/>\n
        "},{"location":"management/#enabledisable-restconf-service","title":"Enable/disable RESTCONF Service","text":"

        Alternatively, the system can be managed remotely using RESTCONF. Meaning you can curl it instead of using a dedicated NETCONF client.

        The RESTCONF service has its own enable/disable setting, but will only be activated if the Web service is enabled. The example below shows how to disable the RESTCONF service.

        admin@example:/config/web/> edit restconf\nadmin@example:/config/web/restconf/> no enabled\nadmin@example:/config/web/restconf/>\n
        "},{"location":"management/#https-certificate","title":"HTTPS Certificate","text":"

        The Web server uses a TLS certificate from the central keystore. By default it uses gencert, a self-signed certificate that is automatically generated on first boot.

        To use a different certificate, e.g., one signed by a CA, first add it to the keystore as an asymmetric key with x509-public-key-format, then point the web certificate leaf to it:

        admin@example:/config/web/> set certificate my-cert\nadmin@example:/config/web/>\n

        See Keystore for details on managing TLS certificates.

        "},{"location":"management/#system-upgrade","title":"System Upgrade","text":"

        See Upgrade & Boot Order for information on upgrading.

        "},{"location":"nacm/","title":"Access Control (NACM)","text":""},{"location":"nacm/#network-access-control-model-nacm","title":"Network Access Control Model (NACM)","text":"

        NETCONF Access Control Model (RFC 8341) provides fine-grained access control for YANG data models. NACM controls who can read, write, and execute operations on specific parts of the configuration and operational state.

        This document provides technical details about how NACM works, how rules are evaluated, and best practices for creating custom access control policies.

        Tip

        For a practical introduction to user management and the built-in user levels (admin, operator, guest), see the Multiple Users section in the System Configuration guide.

        "},{"location":"nacm/#tldr-the-factory-defaults-work","title":"TL;DR - The Factory Defaults Work","text":"

        You don't need to understand NACM to use the system securely.

        The factory configuration implements a \"permit by default, deny sensitive items\" policy that provides:

        • Immediate usability - Operators can configure the entire system without custom NACM rules
        • Automatic security - Passwords, cryptographic keys, and dangerous operations are protected out-of-the-box
        • Future-proof design - New features work immediately without NACM updates

        The three built-in user levels (admin, operator, guest) cover most use cases. Only read on if you need to create custom access control policies.

        "},{"location":"nacm/#overview","title":"Overview","text":"

        NACM provides three types of access control:

        • Data node access - Control read/write access to configuration and state
        • Operation access - Control execution of RPCs (remote procedure calls)
        • Notification access - Control subscription to event notifications (not covered here)

        Access is controlled through:

        1. Global defaults - Default permissions for read/write/exec
        2. YANG-level annotations - Security markers in YANG modules (see below)
        3. NACM rules - Explicit permit/deny rules organized in rule-lists
        "},{"location":"nacm/#rule-evaluation","title":"Rule Evaluation","text":"

        NACM rules are evaluated in a specific order:

        1. Rule-lists are processed sequentially in the order they appear in the configuration
        2. Within each rule-list, rules are evaluated sequentially
        3. First matching rule wins - no further rules are evaluated
        4. If no rule matches, global defaults apply (read-default, write-default, exec-default)
        5. YANG annotations override everything - nacm:default-deny-all in a YANG module requires an explicit permit rule regardless of global defaults
        "},{"location":"nacm/#example-rule-evaluation","title":"Example Rule Evaluation","text":"

        Given this configuration:

        {\n  \"read-default\": \"permit\",\n  \"write-default\": \"permit\",\n  \"rule-list\": [\n    {\n      \"name\": \"operator-acl\",\n      \"group\": [\"operator\"],\n      \"rule\": [\n        {\n          \"name\": \"permit-system-rpcs\",\n          \"module-name\": \"ietf-system\",\n          \"access-operations\": \"exec\",\n          \"action\": \"permit\"\n        }\n      ]\n    },\n    {\n      \"name\": \"default-deny-all\",\n      \"group\": [\"*\"],\n      \"rule\": [\n        {\n          \"name\": \"deny-passwords\",\n          \"path\": \"/ietf-system:system/authentication/user/password\",\n          \"access-operations\": \"*\",\n          \"action\": \"deny\"\n        }\n      ]\n    }\n  ]\n}\n

        When operator user \"jacky\" tries to:

        • Read password: Matches \"deny-passwords\" \u2192 DENIED
        • Write interface config: No match, uses write-default \u2192 PERMITTED (write-default=permit)
        • Write hostname: No match, uses write-default \u2192 PERMITTED (write-default=permit)
        • Reboot system: Matches \"permit-system-rpcs\" \u2192 PERMITTED (overrides nacm:default-deny-all)
        "},{"location":"nacm/#global-defaults","title":"Global Defaults","text":"

        NACM has three global defaults that apply when no rule matches:

        {\n  \"read-default\": \"permit\",\n  \"write-default\": \"permit\",\n  \"exec-default\": \"permit\"\n}\n

        Factory configuration defaults:

        • read-default: \"permit\" - Users can read configuration and state by default
        • write-default: \"permit\" - Users can modify configuration by default
        • exec-default: \"permit\" - Users can execute RPCs by default

        This permit-by-default approach, combined with targeted denials for sensitive items (passwords, cryptographic keys), provides a balance between usability and security. It also makes the system \"future proof\" - when new YANG modules are added, operators can immediately configure them without updating NACM rules.

        Important

        YANG modules with nacm:default-deny-all or nacm:default-deny-write annotations override these global defaults. You must create explicit permit rules for those operations.

        "},{"location":"nacm/#module-name-vs-path","title":"Module-Name vs Path","text":"

        NACM rules can match operations using either module-name or path. The following sub-sections provide detailed information and examples of both.

        "},{"location":"nacm/#module-name-matching","title":"Module-Name Matching","text":"

        Matches all nodes defined in a specific YANG module:

        {\n  \"name\": \"permit-keystore\",\n  \"module-name\": \"ietf-keystore\",\n  \"access-operations\": \"*\",\n  \"action\": \"permit\"\n}\n

        This permits all operations on data defined in ietf-keystore, but does NOT cover augments from other modules.

        Example: The /interfaces/interface/ipv4/address path:

        • Interface is defined in ietf-interfaces
        • IPv4 config is defined in ietf-ip (augments ietf-interfaces)
        • A rule with module-name: \"ietf-interfaces\" does NOT cover ipv4/address
        "},{"location":"nacm/#path-matching","title":"Path Matching","text":"

        Matches a specific data tree path and all nodes under it, including augments from other modules:

        {\n  \"name\": \"permit-network-config\",\n  \"path\": \"/ietf-interfaces:interfaces/interface\",\n  \"access-operations\": \"*\",\n  \"action\": \"permit\"\n}\n

        This permits operations on /interfaces/interface and all child nodes, including augments like:

        • /interfaces/interface/ipv4 (from ietf-ip)
        • /interfaces/interface/ipv6 (from ietf-ip)
        • /interfaces/interface/bridge-port (from infix-interfaces)

        Path syntax:

        • When used with module-name: Path is module-relative (no prefix)

          \"module-name\": \"ietf-system\",\n\"path\": \"/system/authentication/user/password\"\n
        • When used without module-name: Path must include module prefix

          \"path\": \"/ietf-system:system/authentication/user/password\"\n

        Use path-based rules

        When you want to permit/deny access to a configuration subtree including all augments, e.g., all IP settings below /ietf-interfaces:interfaces/. This is more flexible and requires less maintenance as new features are added.

        "},{"location":"nacm/#yang-level-annotations","title":"YANG-Level Annotations","text":"

        Many YANG modules include NACM annotations that provide baseline security:

        "},{"location":"nacm/#nacmdefault-deny-all","title":"nacm:default-deny-all","text":"

        Requires an explicit permit rule, regardless of global defaults:

        rpc system-restart {\n  nacm:default-deny-all;\n  description \"Restart the system\";\n}\n

        Even with exec-default: \"permit\", users need an explicit permit rule to execute system-restart.

        "},{"location":"nacm/#nacmdefault-deny-write","title":"nacm:default-deny-write","text":"

        Write operations require an explicit permit rule:

        container authentication {\n  nacm:default-deny-write;\n  description \"User authentication configuration\";\n}\n

        Even with write-default: \"permit\", users need an explicit permit rule to modify authentication settings.

        "},{"location":"nacm/#protected-operations","title":"Protected Operations","text":"

        The following are protected by YANG annotations and require explicit permits:

        RPC Operations:

        • ietf-system:system-restart (ietf-system)
        • ietf-system:system-shutdown (ietf-system)
        • ietf-system:set-current-datetime (ietf-system)
        • infix-factory-default:factory-default
        • ietf-factory-default:factory-reset (RFC 8808)
        • infix-system-software:install-bundle
        • infix-system-software:set-boot-order

        Data Containers:

        • /system/authentication (nacm:default-deny-write, ietf-system)
        • /nacm (nacm:default-deny-all, RFC 8341)
        • Routing protocol key chains (ietf-key-chain)
        • RADIUS shared secrets (ietf-system)
        • TLS client/server credentials (ietf-tls-client)

        This provides defense-in-depth - even if NACM rules are misconfigured, these critical operations remain protected.

        "},{"location":"nacm/#access-operations","title":"Access Operations","text":"

        NACM supports the following access operations:

        • create - Create new data nodes
        • read - Read existing data nodes
        • update - Modify existing data nodes
        • delete - Delete data nodes
        • exec - Execute RPC operations
        • * - All operations (wildcard)

        Common combinations:

        • \"create update delete\" - All write operations
        • \"*\" - Everything (read, write, execute)
        • \"read\" - Read-only access
        "},{"location":"nacm/#rule-list-groups","title":"Rule-List Groups","text":"

        Each rule-list applies to one or more user groups:

        {\n  \"name\": \"operator-acl\",\n  \"group\": [\"operator\"],\n  \"rule\": [...]\n}\n

        Special group names:

        • \"*\" - Matches all users (including those not in any NACM group)

        Evaluation: A user can be in multiple NACM groups. All rule-lists matching the user's groups are evaluated in order until a matching rule is found.

        "},{"location":"nacm/#example-factory-configuration","title":"Example: Factory Configuration","text":"

        The factory configuration uses a \"permit by default, deny sensitive items\" approach. This design is \"future proof\" - when new YANG modules are added, operators can immediately configure them without updating NACM rules.

        {\n  \"ietf-netconf-acm:nacm\": {\n    \"enable-nacm\": true,\n    \"read-default\": \"permit\",\n    \"write-default\": \"permit\",\n    \"exec-default\": \"permit\",\n    \"groups\": {\n      \"group\": [\n        {\"name\": \"admin\", \"user-name\": [\"admin\"]},\n        {\"name\": \"operator\", \"user-name\": []},\n        {\"name\": \"guest\", \"user-name\": []}\n      ]\n    },\n    \"rule-list\": [\n      {\n        \"name\": \"admin-acl\",\n        \"group\": [\"admin\"],\n        \"rule\": [\n          {\n            \"name\": \"permit-all\",\n            \"module-name\": \"*\",\n            \"access-operations\": \"*\",\n            \"action\": \"permit\",\n            \"comment\": \"Admin has full unrestricted access\"\n          }\n        ]\n      },\n      {\n        \"name\": \"operator-acl\",\n        \"group\": [\"operator\"],\n        \"rule\": [\n          {\n            \"name\": \"permit-system-rpcs\",\n            \"module-name\": \"ietf-system\",\n            \"rpc-name\": \"*\",\n            \"access-operations\": \"exec\",\n            \"action\": \"permit\",\n            \"comment\": \"Operators can reboot, shutdown, and set system time\"\n          }\n        ]\n      },\n      {\n        \"name\": \"guest-acl\",\n        \"group\": [\"guest\"],\n        \"rule\": [\n          {\n            \"name\": \"deny-all-write+exec\",\n            \"module-name\": \"*\",\n            \"access-operations\": \"create update delete exec\",\n            \"action\": \"deny\",\n            \"comment\": \"Guests can only read, not modify or execute\"\n          }\n        ]\n      },\n      {\n        \"name\": \"default-deny-all\",\n        \"group\": [\"*\"],\n        \"rule\": [\n          {\n            \"name\": \"deny-password-access\",\n            \"path\": \"/ietf-system:system/authentication/user/password\",\n            \"access-operations\": \"*\",\n            \"action\": \"deny\",\n            \"comment\": \"No user except admins can access password hashes\"\n          },\n          {\n            \"name\": \"deny-keystore-access\",\n            \"module-name\": \"ietf-keystore\",\n            \"access-operations\": \"*\",\n            \"action\": \"deny\",\n            \"comment\": \"No user except admins can access cryptographic keys\"\n          },\n          {\n            \"name\": \"deny-truststore-access\",\n            \"module-name\": \"ietf-truststore\",\n            \"access-operations\": \"*\",\n            \"action\": \"deny\",\n            \"comment\": \"No user except admins can access trust store\"\n          }\n        ]\n      }\n    ]\n  }\n}\n

        Key design decisions:

        1. Permit by default - write-default: permit allows operators to configure any module
        2. Minimal operator rules - Only one rule to permit system RPCs (reboot, set time)
        3. Future proof - New YANG modules automatically configurable by operators
        4. Targeted denials - Only sensitive items (passwords, keys) are explicitly denied
        5. Global denials - Password/keystore/truststore denied for everyone via group \"*\"
        6. YANG annotations - Sensitive operations (factory-reset, software upgrades) still protected by nacm:default-deny-all in YANG modules

        Effective permissions by group:

        Group Read Write Exec Exceptions admin All All All None operator All All All Cannot access passwords, keystore, truststore guest All None None Read-only access"},{"location":"nacm/#common-patterns","title":"Common Patterns","text":""},{"location":"nacm/#permit-by-default","title":"Permit-by-Default","text":"

        The factory default approach - allow everything except sensitive items:

        {\n  \"write-default\": \"permit\",\n  \"exec-default\": \"permit\",\n  \"rule-list\": [\n    {\n      \"name\": \"admin-acl\",\n      \"group\": [\"admin\"],\n      \"rule\": [\n        {\n          \"name\": \"permit-all\",\n          \"module-name\": \"*\",\n          \"access-operations\": \"*\",\n          \"action\": \"permit\"\n        }\n      ]\n    },\n    {\n      \"name\": \"global-denials\",\n      \"group\": [\"*\"],\n      \"rule\": [\n        {\n          \"name\": \"deny-passwords\",\n          \"path\": \"/ietf-system:system/authentication/user/password\",\n          \"access-operations\": \"*\",\n          \"action\": \"deny\"\n        }\n      ]\n    }\n  ]\n}\n

        This approach is \"future proof\" - new YANG modules are automatically accessible without rule updates. Admins bypass the global denials because their permit-all rule is evaluated first.

        "},{"location":"nacm/#deny-by-default","title":"Deny-by-Default","text":"

        More restrictive approach - deny everything except what is explicitly allowed:

        {\n  \"write-default\": \"deny\",\n  \"exec-default\": \"deny\",\n  \"rule-list\": [\n    {\n      \"group\": [\"limited-user\"],\n      \"rule\": [\n        {\n          \"name\": \"permit-interface-config\",\n          \"path\": \"/ietf-interfaces:interfaces/interface\",\n          \"access-operations\": \"create update delete\",\n          \"action\": \"permit\"\n        }\n      ]\n    }\n  ]\n}\n

        Note

        This requires updating NACM rules whenever new features are added.

        "},{"location":"nacm/#global-restrictions","title":"Global Restrictions","text":"

        Deny access to sensitive data for all users (except admins with permit-all):

        {\n  \"rule-list\": [\n    {\n      \"group\": [\"*\"],\n      \"rule\": [\n        {\n          \"name\": \"deny-passwords\",\n          \"path\": \"/ietf-system:system/authentication/user/password\",\n          \"access-operations\": \"*\",\n          \"action\": \"deny\"\n        }\n      ]\n    }\n  ]\n}\n
        "},{"location":"nacm/#debugging-nacm","title":"Debugging NACM","text":""},{"location":"nacm/#viewing-effective-permissions","title":"Viewing Effective Permissions","text":"

        Check what NACM groups a user belongs to:

        admin@example:/> show nacm\nenabled              : yes\ndefault read access  : permit\ndefault write access : permit\ndefault exec access  : permit\ndenied operations    : 0\ndenied data writes   : 0\ndenied notifications : 0\n\n          \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n          \u2502 GROUP    \u2502  READ   \u2502  WRITE  \u2502  EXEC   \u2502\n          \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n          \u2502 admin    \u2502    \u2713    \u2502    \u2713    \u2502    \u2713    \u2502\n          \u2502 operator \u2502    \u26a0    \u2502    \u26a0    \u2502    \u26a0    \u2502\n          \u2502 guest    \u2502    \u26a0    \u2502    \u2717    \u2502    \u2717    \u2502\n          \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n              \u2713 Full    \u26a0 Restricted    \u2717 Denied\n\nUSER                   SHELL   LOGIN                            \nadmin                  bash    password+key\njacky                  bash    password\nmonitor                false   key\n\nGROUP                  USERS                                    \nadmin                  admin\noperator               jacky\nguest                  monitor\n

        For details about a group's restrictions, use show nacm group <name>:

        admin@example:/> show nacm group operator\nmembers          : jacky\nread permission  : restricted\nwrite permission : restricted\nexec permission  : restricted\napplicable rules : 4\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\npermit-system-rpcs\n  action     : permit\n  operations : exec\n  target     : ietf-system (rpc: *)\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-password-access (via '*')\n  action     : deny\n  operations : *\n  target     : /ietf-system:system/authentication/user/password\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-keystore-access (via '*')\n  action     : deny\n  operations : *\n  target     : ietf-keystore\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-truststore-access (via '*')\n  action     : deny\n  operations : *\n  target     : ietf-truststore\n
        "},{"location":"nacm/#testing-access","title":"Testing Access","text":"

        The easiest way to test NACM permissions is to log in as the user and try the operation:

        $ ssh jacky@host\njacky@example:/> configure\njacky@example:/config/> edit system authentication user admin\njacky@example:/config/system/authentication/user/admin/> set authorized-key foo\nError: Access to the data model \"ietf-system\" is denied because \"jacky\" NACM authorization failed.\nError: Failed applying changes (2).\n
        "},{"location":"nacm/#nacm-statistics","title":"NACM Statistics","text":"

        NACM tracks denied operations. If you suspect permission issues, check the statistics:

        admin@example:/> show nacm\n...\n  denied operations      : 5\n  denied data writes     : 12\n...\n

        Increasing counters indicate permission denials are occurring.

        "},{"location":"nacm/#best-practices","title":"Best Practices","text":"
        1. Leverage permit-by-default - The factory configuration uses write-default: \"permit\" with targeted denials. This is \"future proof\" - new features work immediately without NACM updates.

        2. Protect sensitive items globally - Use group: [\"*\"] rule-list to deny access to passwords, cryptographic keys, and similar sensitive data. Admin's permit-all rule (evaluated first) bypasses these denials.

        3. Leverage YANG annotations - Many sensitive operations are already protected by nacm:default-deny-all in YANG modules (factory-reset, software upgrades, etc.). Only add explicit permit rules when needed.

        4. Order matters - Rule-lists are evaluated in order. Place admin's permit-all rule first so it bypasses global denials.

        5. Use path-based denials - For protecting specific data (like password hashes), use path rules. For protecting entire modules (like keystore), use module-name rules.

        6. Test thoroughly - Always test user permissions after changes. NACM errors can be subtle (nodes may be silently omitted from read operations).

        7. Keep it simple - The factory configuration uses only 6 rules for three user levels. Fewer rules are easier to understand and maintain.

        8. Document rules - Use the \"comment\" field to explain why specific permissions are granted or denied.

        "},{"location":"nacm/#references","title":"References","text":"
        • RFC 8341: Network Configuration Access Control Model (NACM)
        • RFC 7317: A YANG Data Model for System Management (ietf-system)
        • RFC 8808: A YANG Data Model for Factory Default Settings (ietf-factory-default)
        • RFC 8177: YANG Key Chain (ietf-key-chain)
        • System Configuration - Multiple Users
        "},{"location":"netboot/","title":"Netboot HowTo","text":""},{"location":"netboot/#netboot-howto","title":"Netboot HowTo","text":"

        This document describes how to set up network booting U-Boot devices on a LAN, e.g., when working with an evaluation board or other embedded system. The most secure way to do this is with a local LAN between a PC and the device.

        Instead of setting up everything in U-Boot to download the Linux Image, device tree, and initramfs, we will let U-Boot download a script with instructions to run. When you have multiple systems (boards) this quickly becomes a lot easier to manage.

        Note

        Instructions in this HowTo assume a Debian based development system, e.g., Ubuntu or Linux Mint.

        "},{"location":"netboot/#network-interface-setup","title":"Network Interface Setup","text":"

        For two dedicated network interfaces, here eth2 and eth3 (ymmv), we create an old-style interfaces config1 with the following content:

        # /etc/network/interfaces.d/gimli\nauto eth2\niface eth2 inet static\n        address 192.168.0.1\n        netmask 255.255.255.0\n\nauto eth3\niface eth3 inet manual\n

        Tip

        Use configuration file names in .d/ directories that make sense and can easily be remembered. Here we use the hostname of the PC.

        "},{"location":"netboot/#dhcptftp-server-setup","title":"DHCP/TFTP Server Setup","text":"

        The examples given here use dnsmasq, which provides both DHCP and TFTP server support. The same can be achieved with other implementations.

        Similar to interfaces.d, dnsmasq has an /etc/dnsmasq.d directory so we can use \"snippets\" instead of modifying /etc/dnsmasq.conf directly. Add a file called /etc/dnsmasq.d/gimli.

        Initial content:

        # Remember IP address handed out to BOOTP clients\nbootp-dynamic\n# Disable check-if-ip-address-is-already-used\nno-ping\n\n# Enable TFTP server, use /srv/ftp, same as any FTP server, useful\n# when using the same images for system upgrade as for netbooting.\nenable-tftp\ntftp-root=/srv/ftp\n

        Caution

        First of all, make sure you DO NOT accidentally set up dnsmasq so that it starts acting as a DHCP server also on your office LAN! Follow the instructions below for more details.

        If you have many interfaces used for lab equipment and only one office LAN interface, then use something like this:

        # Disable DHCP server on loopback and office LAN (eth0)\nexcept-interface=lo\nexcept-interface=eth0\n

        To further lock this down, we only run the DHCP server on each of the interfaces used for lab equipment, with a dedicated IP range as well:

        # Currently I have an imx8mp-evk on eth2, so on my system I have a\n# symlink bootfile-eth2 -> netboot.scr\ninterface=eth2\ndhcp-range=192.168.0.100,192.168.0.199,1h\ndhcp-boot=tag:eth2,bootfile-eth2\n
        "},{"location":"netboot/#bootfile-netbootscr","title":"Bootfile netboot.scr","text":"

        The bootfile U-Boot retrieves from the TFTP server is a script that looks like this, netboot.sh:

        setenv ramdisk_addr_r 0x58000000\nsetenv fdt_addr_r     0x50400000\n\nsetenv autoboot off\ntftp ${fdt_addr_r}     imx8mp-evk/imx8mp-evk.dtb\ntftp ${kernel_addr_r}  imx8mp-evk/Image\ntftp ${ramdisk_addr_r} imx8mp-evk/rootfs.squashfs\n\nsetenv bootargs console=ttymxc1,115200 root=/dev/ram0 brd.rd_size=500000 rauc.slot=net\nbooti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r}\n

        U-Boot cannot read script files directly, so we need to wrap it with a FIT format header, this is done by first converting it on the PC:

        $ mkimage -T script -d netboot.sh netboot.scr\n

        The output is netboot.scr which we symlink to above in the dnsmasq setup step.

        "},{"location":"netboot/#u-boot-commands","title":"U-Boot Commands","text":"

        U-Boot is a maze of environment variables, some with values, some wrap commands, and most are undocumented. We will use a prefix for our variables to ensure we do not overwrite anything you may want to use later.

        ==> setenv ixboot 'dhcp && source \\${fileaddr}'\n==> saveenv\n
        1. To prevent NetworkManager from automatically managing the interfaces.\u00a0\u21a9

        "},{"location":"networking/","title":"Overview","text":""},{"location":"networking/#network-configuration","title":"Network Configuration","text":"

        Infix aims to support all Linux Networking constructs. The YANG models used to describe the system are chosen to fit well and leverage the underlying Linux kernel's capabilities. The ietf-interfaces.yang model forms the base, extended with ietf-ip.yang and other layer-3 IETF models. The layer-2 bridge and aggregate models are defined by Infix to exploit the unique features not available in IEEE models.

        Important

        When issuing leave to activate your changes, remember to also save your settings, copy running-config startup-config. See the CLI Introduction for a background.

        "},{"location":"networking/#interface-lego","title":"Interface LEGO\u00ae","text":"

        The network building blocks available in Linux are akin to the popular LEGO\u00ae bricks.

        There are two types of relationships that can link two blocks together:

        1. Lower-to-upper: Visually represented by an extruding square connected upwards to a square socket. An interface can only have a single lower-to-upper relationship, i.e., it can be attached to a single upper interface like a bridge or a LAG. In iproute2 parlance, this corresponds to the interface's master setting
        2. Upper-to-lower: Visually represented by an extruding semicircle connected downwards to a semicircle socket. The lower interface in these relationships accepts multiple upper-to-lower relationships from different upper blocks. E.g., multiple VLANs and IP address blocks can be connected to the same lower interface

        An interface may simultaneously have a lower-to-upper relation to some other interface, and be the target of one or more upper-to-lower relationships. It is valid, for example, for a physical port to be attached to a bridge, but also have a VLAN interface stacked on top of it. In this example, traffic assigned to the VLAN in question would be diverted to the VLAN interface before entering the bridge, while all other traffic would be bridged as usual.

        Type Yang Model Description bridge infix-if-bridge SW implementation of an IEEE 802.1Q bridge ip ietf-ip, infix-ip IP address to the subordinate interface vlan infix-if-vlan Capture all traffic belonging to a specific 802.1Q VID lag infix-if-lag Link aggregation, static and IEEE 802.3ad (LACP) lo ietf-interfaces Software loopback interface eth ieee802-ethernet-interface Physical Ethernet device/port infix-ethernet-interface veth infix-if-veth Virtual Ethernet pair, typically one end is in a container common ietf-interfaces, Properties common to all interface types infix-interfaces"},{"location":"networking/#data-plane","title":"Data Plane","text":"

        The blocks you choose, and how you connect them, defines your data plane. Here we see an example of how to bridge a virtual port with a physical LAN.

        Depending on the (optional) VLAN filtering of the bridge, the container may have full or limited connectivity with outside ports, as well as the internal CPU.

        In fact the virtual port connected to the bridge can be member of several VLANs, with each VLAN being an interface with an IP address inside the container.

        Thanks to Linux, and technologies like switchdev, that allow you to split a switching fabric into unique (isolated) ports, the full separation and virtualization of all Ethernet layer properties are possible to share with a container. Meaning, all the building blocks used on the left hand side can also be used freely on the right hand side as well.

        "},{"location":"ntp/","title":"NTP Server","text":""},{"location":"ntp/#ntp-server","title":"NTP Server","text":"

        The NTP (Network Time Protocol) server provides accurate time synchronization for network clients. It supports both standalone operation with a local reference clock and hybrid mode where it synchronizes with upstream servers while serving time to downstream clients.

        Note

        The NTP server is mutually exclusive with the NTP client in system configuration context.

        "},{"location":"ntp/#standalone-mode","title":"Standalone Mode","text":"

        Configure a standalone NTP server using only a local reference clock:

        admin@example:/> configure\nadmin@example:/config/> edit ntp\nadmin@example:/config/ntp/> leave\n

        When setting up NTP via the CLI the system automatically configures a local reference clock. The default stratum is 16 (unsynchronized), which is suitable for isolated networks. For production use, configure a specific stratum level:

        admin@example:/config/> edit ntp\nadmin@example:/config/ntp/> set refclock-master master-stratum 10\nadmin@example:/config/ntp/> leave\n
        "},{"location":"ntp/#gps-reference-clock","title":"GPS Reference Clock","text":"

        A GPS/GNSS receiver can be used as an NTP reference clock source, providing stratum 1 time derived from the GPS satellite constellation. This requires a GPS hardware component to be configured first, see Hardware \u2014 GPS/GNSS Receivers.

        "},{"location":"ntp/#basic-setup","title":"Basic setup","text":"

        Add a GPS receiver as a reference clock source:

        admin@example:/config/> edit ntp\nadmin@example:/config/ntp/> edit refclock-master source gps0\nadmin@example:/config/ntp/refclock-master/source/gps0/> set poll 2\nadmin@example:/config/ntp/refclock-master/source/gps0/> set precision 0.1\nadmin@example:/config/ntp/refclock-master/source/gps0/> end\nadmin@example:/config/ntp/> leave\n

        Tunable parameters:

        Parameter Default Description poll 2 Polling interval in log2 seconds (2 = 4s) precision 0.1 Assumed precision in seconds (0.1 = 100ms) refid \"GPS\" Reference identifier (e.g., GPS, GNSS, GLO) prefer false Prefer this source over other reference clocks pps false Enable PPS for microsecond-level accuracy offset 0.0 Constant offset correction in seconds delay 0.0 Assumed maximum delay from the receiver"},{"location":"ntp/#pps-pulse-per-second","title":"PPS (Pulse Per Second)","text":"

        When the GPS receiver provides a PPS signal, enable the pps option for microsecond-level accuracy. With PPS, the GPS time provides the initial lock and the PPS edges discipline the clock:

        admin@example:/config/ntp/> edit refclock-master source gps0\nadmin@example:/config/ntp/refclock-master/source/gps0/> set pps true\nadmin@example:/config/ntp/refclock-master/source/gps0/> set precision 0.000001\nadmin@example:/config/ntp/refclock-master/source/gps0/> end\nadmin@example:/config/ntp/> leave\n
        "},{"location":"ntp/#monitoring","title":"Monitoring","text":"

        The show ntp command shows the GPS receiver as the reference clock source:

        admin@example:/> show ntp\nMode                : Server (GPS reference clock: gps0)\nPort                : 123\nStratum             : 1\nRef time (UTC)      : Sun Feb 08 19:44:36 2026\n

        Use show ntp source to see GPS reference clock details:

        admin@example:/> show ntp source\nReference Clock     : gps0 (u-blox)\nStatus              : selected\nFix Mode            : 3D\nSatellites          : 9/17 (used/visible)\n
        "},{"location":"ntp/#server-mode","title":"Server Mode","text":"

        Synchronize from upstream NTP servers while serving time to clients:

        admin@example:/config/> edit ntp\nadmin@example:/config/ntp/> edit unicast-configuration 0.pool.ntp.org type uc-server\nadmin@example:/config/ntp/\u2026/0.pool.ntp.org/type/uc-server/> set iburst true\nadmin@example:/config/ntp/\u2026/0.pool.ntp.org/type/uc-server/> end\nadmin@example:/config/ntp/> edit unicast-configuration 1.pool.ntp.org type uc-server\nadmin@example:/config/ntp/\u2026/1.pool.ntp.org/type/uc-server/> set iburst true\nadmin@example:/config/ntp/\u2026/1.pool.ntp.org/type/uc-server/> end\nadmin@example:/config/ntp/> leave\n

        The unicast-configuration uses a composite key with both address and type. Both hostnames and IP addresses are supported. The iburst option enables fast initial synchronization.

        "},{"location":"ntp/#peer-mode","title":"Peer Mode","text":"

        In peer mode, two NTP servers synchronize with each other bidirectionally. Each server acts as both client and server to the other:

        First peer:

        admin@peer1:/config/> edit ntp\nadmin@peer1:/config/ntp/> edit unicast-configuration 192.168.1.2 type uc-peer\nadmin@peer1:/config/ntp/\u2026/192.168.1.2/type/uc-peer/> end\nadmin@peer1:/config/ntp/> set refclock-master master-stratum 8\nadmin@peer1:/config/ntp/> leave\n

        Second peer:

        admin@peer2:/config/> edit ntp\nadmin@peer2:/config/ntp/> edit unicast-configuration 192.168.1.1 type uc-peer\nadmin@peer2:/config/ntp/\u2026/192.168.1.1/type/uc-peer/> end\nadmin@peer2:/config/ntp/> set refclock-master master-stratum 8\nadmin@peer2:/config/ntp/> leave\n

        This configuration provides mutual synchronization between peers. If one peer fails, the other continues to serve time to clients.

        Note

        The iburst and burst options are not supported in peer mode.

        "},{"location":"ntp/#peer-selection-in-symmetric-mode","title":"Peer Selection in Symmetric Mode","text":"

        When both peers have the same stratum (as in the example above where both are stratum 8), NTP's clock selection algorithm uses the Reference ID as the tie-breaker. The Reference ID is typically derived from the peer's IP address when using a local reference clock.

        This means the peer with the numerically lower IP address will be selected as the sync source by the other peer. In the example above:

        • peer1 (192.168.1.1) has a lower Reference ID
        • peer2 (192.168.1.2) will select peer1 as sync source

        This behavior is deterministic and ensures stable clock selection. If you need a specific peer to be selected, configure it with a lower stratum level than the other peer.

        "},{"location":"ntp/#timing-configuration","title":"Timing Configuration","text":""},{"location":"ntp/#poll-intervals","title":"Poll Intervals","text":"

        Control how often the NTP server polls upstream sources:

        admin@example:/config/ntp/> edit unicast-configuration 0.pool.ntp.org type uc-server\nadmin@example:/config/ntp/\u2026/0.pool.ntp.org/type/uc-server/> set minpoll 4\nadmin@example:/config/ntp/\u2026/0.pool.ntp.org/type/uc-server/> set maxpoll 10\nadmin@example:/config/ntp/\u2026/0.pool.ntp.org/type/uc-server/> end\n

        Poll intervals are specified as powers of 2: - minpoll 4 = poll every 2^4 = 16 seconds (minimum polling rate) - maxpoll 10 = poll every 2^10 = 1024 seconds (maximum polling rate) - Defaults: minpoll 6 (64 seconds), maxpoll 10 (1024 seconds)

        Use shorter intervals (minpoll 2-4) for faster convergence in test environments or peer configurations. Use defaults for production servers.

        "},{"location":"ntp/#fast-initial-synchronization","title":"Fast Initial Synchronization","text":"

        The makestep directive is automatically configured with safe defaults (1.0 seconds threshold, 3 updates limit) when creating an NTP server. This is critical for embedded systems without RTC that boot with epoch time.

        To customize the values:

        admin@example:/config/ntp/> edit makestep\nadmin@example:/config/ntp/makestep/> set threshold 2.0\nadmin@example:/config/ntp/makestep/> set limit 1\nadmin@example:/config/ntp/makestep/> end\n
        • threshold - If clock offset exceeds this (in seconds), step immediately instead of slewing slowly
        • limit - Number of updates during which stepping is allowed. After this, only gradual slewing is used for security

        With these defaults, a device booting at epoch time (1970-01-01) will sync to correct time within seconds instead of hours.

        "},{"location":"ntp/#monitoring_1","title":"Monitoring","text":"

        For a quick overview:

        To view the sources being used by the NTP client, run:

        admin@example:/> show ntp\nMode                : Client\nStratum             : 3\nRef time (UTC)      : Sat Jan 24 23:41:42 2026\n\nADDRESS         MODE    STATE     STRATUM  POLL\n147.78.228.41   server  outlier         2   64s\n192.168.0.1     server  unusable        0  128s\n176.126.86.247  server  selected        2   64s\n

        Check NTP source status:

        admin@example:/> show ntp source\nMS  Name/IP address  Stratum  Poll  Reach  LastRx          Last sample\n^+  147.78.228.41          2     6    007      15  -431us +/- 33.573ms\n^*  176.126.86.247         2     6    007      14   -389us +/- 4.307ms\n

        For detailed information about a specific source:

        admin@example:/> show ntp source 176.126.86.247\nAddress             : 176.126.86.247\nMode                : Server (client mode) [^]\nState               : Selected sync source [*]\nConfigured          : Yes\nStratum             : 2\nPoll interval       : 7 (2^7 seconds = 128s)\nReachability        : 377 (octal) = 11111111b\nLast RX             : 75s ago\nOffset              : +2.0us (+0.002000ms)\nDelay               : 4.270ms (0.004270s)\nDispersion          : 205.0us (0.205000ms)\n
        "},{"location":"ntp/#ntp-stratum-levels","title":"NTP Stratum Levels","text":"

        NTP uses a hierarchical system called stratum to indicate distance from authoritative time sources:

        • Stratum 0: Reference clocks (atomic clocks)
        • Stratum 1: Servers directly connected to stratum 0 (e.g., GPS receivers)
        • Stratum 2-15: Servers that sync from lower stratum (each hop adds one)
        • Stratum 16: Unsynchronized (invalid)

        The default stratum (16) is not suitable for distributing time in isolated networks, so when setting up an NTP server remember to adjust this value. Use, e.g., 10, this is a safe, low-priority value that ensures clients will prefer upstream-synchronized servers (stratum 1-9) while still having a fallback time source in isolated networks.

        "},{"location":"override-package/","title":"Developing with Buildroot","text":""},{"location":"override-package/#package-override","title":"Package Override","text":"

        This guide demonstrates how the local.mk file is utilized to override a Buildroot package, which can be very useful when fixing bugs, adding features, or evaluating new releases of packages. As an example we use tcpdump to illustrate this process.

        Tip

        For a comprehensive guide to utilizing Buildroot during development, including the <pkg>_OVERRIDE_SRCDIR mechanism, shown below, please see Using Buildroot during development in the official docs.

        "},{"location":"override-package/#setup","title":"Setup","text":"

        Since the output/ directory is often wiped for rebuilds, make sure you keep the file local.mk in the top directory and only symlink it to your build directory:

        ~$ cd infix/\n~/infix(main)$ touch local.mk\n~/infix/output(main)$ ln -s ../local.mk .\n~/infix/output(main)$ cd ..\n
        "},{"location":"override-package/#override","title":"Override","text":"

        Now edit the file:

        ~/infix(main)$ editor local.mk\n

        Add an override for tcpdump. The file is a Makefile snippet so you can add a lot of things. Comment out lines with the UNIX comment # character if needed:

        TCPDUMP_OVERRIDE_SRCDIR = /path/to/tcpdump/repo\n
        "},{"location":"override-package/#building","title":"Building","text":"

        The execution of make tcpdump-rebuild all triggers a process where Buildroot synchronizes the tcpdump source code from the specified override directory to output/build/tcpdump-custom, followed by the rebuilding of the entire project.

        ~/infix$(main)$ make tcpdump-rebuild all\n

        Buildroot follows a process of downloading and processing tarballs: extraction, configuration, compilation, and installation. The source for each package is extracted to a temporary build directory:

        output/build/<package>-<version>    # e.g., tcpdump-4.99.4\n

        Let's have a look at what we got:

        ~/infix$(main)$ ll /output/build/ | grep tcpdump\ndrwxr-xr-x   7 group user 20480 Nov 10 18:26 tcpdump-4.99.4/\ndrwxr-xr-x   7 group user 12288 Nov 10 18:28 tcpdump-custom/\n

        As long as your local override is in place, Buildroot will use your custom version.

        Note

        Remember, the build directory is ephemeral, so be careful to change any of the files therein. It can be useful though during debugging, but just make sure to learn the difference between the various Buildroot commands to build, clean, reconfigure, etc.

        "},{"location":"ptp/","title":"PTP (IEEE 1588/802.1AS)","text":""},{"location":"ptp/#ptp-precision-time-protocol","title":"PTP \u2014 Precision Time Protocol","text":"

        The Precision Time Protocol (PTP), defined in IEEE 1588-2019, synchronises clocks across a network to sub-microsecond accuracy. Where NTP (Network Time Protocol) aims at millisecond accuracy over wide-area networks, PTP is designed for local-area networks and relies on hardware timestamping in the network interface to eliminate software-induced jitter.

        PTP works by exchanging timestamped messages between devices. A grandmaster clock \u2014 elected by the Best TimeTransmitter Clock Algorithm (BTCA) based on priority, clock class, and accuracy \u2014 distributes time to the rest of the network. Each synchronising device measures the one-way message delay to its time-transmitter and continuously adjusts its local clock to compensate.

        Note

        The IEEE 1588g-2022 amendment to IEEE 1588-2019 introduced the terms timeTransmitter and timeReceiver as replacements for the former master and slave terminology, and Best TimeTransmitter Clock Algorithm (BTCA) in place of BMCA. This document uses the updated terms throughout. You may even see the short forms transmitter and receiver here and in online documentation.

        "},{"location":"ptp/#clock-roles","title":"Clock roles","text":"

        Every device in a PTP network takes one of the following roles:

        Role Description Grandmaster (GM) Network-wide time source; elected by BTCA Time-transmitter Sends Sync messages downstream on a port Time-receiver Synchronises to a time-transmitter on a port Boundary Clock (BC) Terminates PTP on each port; acts as time-receiver upstream and time-transmitter downstream Transparent Clock (TC) Passes PTP messages while correcting the residence-time delay accumulated in the device

        An Ordinary Clock (OC) has a single PTP port and is either a time-transmitter (acting as a grandmaster candidate) or a time-receiver (a leaf node synchronising to the network).

        "},{"location":"ptp/#ptp-profiles","title":"PTP profiles","text":"

        A PTP profile (as defined in IEEE 1588-2019 \u00a73.1) is a document that specifies a consistent set of required, permitted, and prohibited PTP options for a particular application domain \u2014 much like a dialect of the protocol. Examples from the standards world include profiles for power utilities (IEC/IEEE C37.238), telecom (ITU-T G.8265.1), and Time-Sensitive Networks.

        Each profile sets a unique value in the majorSdoId field of PTP message headers \u2014 a 4-bit identifier that lets devices distinguish traffic belonging to different profiles on the same link. Profile also determines the network transport (UDP or Ethernet) and the delay measurement mechanism.

        Currently, two profiles are supported via the profile leaf in default-ds:

        profile Standard majorSdoId Transport Delay ieee1588 (default) IEEE 1588-2019 0x0 UDP/IPv4 e2e or p2p ieee802-dot1as IEEE 802.1AS-2020 0x1 L2 p2p

        The gPTP (generalized Precision Time Protocol) profile from IEEE 802.1AS-2020 is used in TSN (Time-Sensitive Networking) and AVB (Audio/Video Bridging) applications. Setting profile ieee802-dot1as applies all protocol-mandatory settings automatically \u2014 Layer 2 transport, P2P delay measurement, 802.1AS multicast addressing, path trace, follow-up information, and neighbour propagation delay thresholds. The user still configures priority1, priority2, domain-number, time-receiver-only, and timer interval leaves.

        The ieee1588 profile leaves transport and delay mechanism user-configurable per port.

        "},{"location":"ptp/#delay-mechanisms","title":"Delay mechanisms","text":"

        PTP measures the link delay between neighbours using one of two mechanisms:

        • End-to-End (E2E): Each time-receiver measures the delay to the grandmaster by sending a DELAY_REQ message upstream. Simple to configure; works with any network topology.
        • Peer-to-Peer (P2P): Each port measures its delay to its immediate neighbour independently using PDELAY_REQ messages. Enables faster path-delay updates and is required by the gPTP profile.
        "},{"location":"ptp/#data-sets","title":"Data Sets","text":"

        IEEE 1588 organises protocol state into named Data Sets (DS) \u2014 each a collection of related attributes for one aspect of a PTP instance. You will encounter these directly in the CLI and in the show ptp output:

        Data Set CLI node Contents Default DS default-ds Instance identity, clock class, priority, domain number Current DS current-ds Live offset-from-GM, mean path delay, steps-removed Parent DS parent-ds Grandmaster identity and quality attributes Time Properties DS time-properties-ds UTC offset, leap-second flags, time source Port DS port-ds Per-port state, delay mechanism, message intervals"},{"location":"ptp/#domains","title":"Domains","text":"

        A PTP domain (0\u2013255) is a logical partition of the network. Devices only synchronise with others in the same domain. Running multiple instances on the same device \u2014 one per domain, or one per profile \u2014 is fully supported; each instance is independent.

        Each PTP instance is identified on the network by its (domain-number, profile) pair, which must be unique across all instances on a device.

        Note

        The show ptp offset values reflect PHC (PTP Hardware Clock) synchronisation only. A PHC is the hardware clock exposed by the network interface; it tracks the PTP grandmaster but is independent of the Linux system clock, which currently is not automatically adjusted.

        "},{"location":"ptp/#ordinary-clock-time-receiver","title":"Ordinary Clock (time-receiver)","text":"

        A typical time-receiver Ordinary Clock, synchronising on interface eth0 using the default IEEE 1588 profile:

        admin@example:/> configure\nadmin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> set default-ds time-receiver-only true\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> leave\n
        "},{"location":"ptp/#ordinary-clock-time-transmitter-grandmaster","title":"Ordinary Clock (time-transmitter / grandmaster)","text":"

        A grandmaster clock with high priority, domain 0:

        admin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> set default-ds priority1 1\nadmin@example:/config/ptp/instance/0/> set default-ds priority2 1\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> leave\n

        Lower priority1 values win in the BTCA. A clock with priority1 1 will be preferred over the default 128 in any compliant network.

        "},{"location":"ptp/#boundary-clock","title":"Boundary Clock","text":"

        A Boundary Clock terminates PTP on each port and re-originates it. Add one port per interface:

        admin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds instance-type bc\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> end\nadmin@example:/config/ptp/instance/0/> edit port 2\nadmin@example:/config/ptp/\u2026/0/port/2/> set underlying-interface eth1\nadmin@example:/config/ptp/\u2026/0/port/2/> leave\n

        Tip

        PTP port numbers are assigned sorted by port-index, so port-index 1 becomes PTP port 1, port-index 2 becomes PTP port 2, and so on.

        "},{"location":"ptp/#transparent-clock","title":"Transparent Clock","text":"

        Transparent Clocks correct timestamps end-to-end without terminating PTP. Use instance-type p2p-tc for a P2P TC (preferred in TSN networks) or instance-type e2e-tc for an E2E TC:

        admin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds instance-type p2p-tc\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> end\nadmin@example:/config/ptp/instance/0/> edit port 2\nadmin@example:/config/ptp/\u2026/0/port/2/> set underlying-interface eth1\nadmin@example:/config/ptp/\u2026/0/port/2/> leave\n

        Note

        For Transparent Clocks the delay mechanism is determined globally by the instance-type (p2p-tc \u2192 P2P, e2e-tc \u2192 E2E). Per-port delay-mechanism settings have no effect for TC instances.

        "},{"location":"ptp/#gptp-ieee-8021as","title":"gPTP / IEEE 802.1AS","text":"

        The gPTP profile is used in TSN and AVB applications. Setting profile ieee802-dot1as applies all protocol-mandatory options from IEEE 802.1AS-2020 automatically \u2014 Layer 2 transport, P2P delay measurement, 802.1AS multicast addressing, and related protocol features.

        admin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds profile ieee802-dot1as\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> set default-ds time-receiver-only true\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> leave\n

        Note

        The ieee802-dot1as profile enforces Layer 2 transport and P2P delay measurement globally, as required by IEEE 802.1AS-2020. Per-port delay-mechanism settings have no effect for 802.1AS instances.

        "},{"location":"ptp/#multiple-instances","title":"Multiple Instances","text":"

        Multiple PTP instances can run simultaneously, one per domain or profile combination. Each instance must have a unique (domain-number, profile) pair and an independent set of ports:

        admin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> set default-ds profile ieee1588\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> end\nadmin@example:/config/ptp/instance/0/> end\nadmin@example:/config/ptp/> edit instance 1\nadmin@example:/config/ptp/instance/1/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/1/> set default-ds profile ieee802-dot1as\nadmin@example:/config/ptp/instance/1/> edit port 1\nadmin@example:/config/ptp/\u2026/1/port/1/> set underlying-interface eth1\nadmin@example:/config/ptp/\u2026/1/port/1/> leave\n
        "},{"location":"ptp/#port-states","title":"Port states","text":"

        Each PTP port progresses through a state machine. The current state is shown in the show ptp port table:

        State Meaning initializing Port is starting up, not yet ready to exchange messages faulty A fault condition has been detected on this port disabled Port is administratively disabled listening Awaiting ANNOUNCE messages; BTCA has not yet resolved pre-time-transmitter Transitioning towards time-transmitter state time-transmitter Port is acting as time-transmitter on this link passive Another port on this device is already time-transmitter uncalibrated Receiving sync; local clock not yet locked to time-transmitter time-receiver Port is locked and tracking its time-transmitter

        A port in uncalibrated will typically transition to time-receiver within a few seconds once the clock servo has converged.

        "},{"location":"ptp/#monitoring","title":"Monitoring","text":"

        Use the ? key in the CLI

        The show ptp command has sub-commands \u2014 tap ? after show ptp to see them, or use Tab to complete.

        "},{"location":"ptp/#show-all-ptp-instances","title":"Show all PTP instances","text":"
        admin@example:/> show ptp\nPTP Instance 0                          Ordinary Clock \u00b7 domain 0\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  Clock identity          : AA-BB-CC-FF-FE-00-11-22\n  Grandmaster             : DD-EE-FF-FF-FE-33-44-55\n  Priority1/Priority2     : 128 / 128\n  GM Priority1/Priority2  : 1 / 1\n  Clock class             : cc-time-receiver-only\n  GM clock class          : cc-primary-sync\n  Time source             : gnss\n  PTP timescale           : yes\n  UTC offset              : 37 s\n  Time traceable          : yes\n  Freq. traceable         : yes\n  Offset from GM          : -42 ns\n  Mean path delay         : 1250 ns\n  Steps removed           : 1\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPorts\nPORT  INTERFACE          STATE                DELAY  LINK DELAY (ns)\n   1  eth0               time-receiver        E2E                  0\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nMessage Statistics  (\u25bc rx  \u25b2 tx)\nPORT  INTERFACE             SYNC \u25bc  SYNC \u25b2  ANN \u25bc  ANN \u25b2  PD \u25bc  PD \u25b2\n   1  eth0                      42       0     15      0     0     0\n\n

        Port state is colour-coded: green for time-transmitter and time-receiver (actively synchronising), yellow for transient states (listening, uncalibrated, pre-time-transmitter), and red for fault states (faulty, disabled). The Message Statistics section is omitted when no counts are available.

        "},{"location":"ptp/#show-a-specific-instance","title":"Show a specific instance","text":"
        admin@example:/> show ptp 0\n
        "},{"location":"ptp/#tuning-port-intervals","title":"Tuning port intervals","text":"

        Adjust announcement, sync, and delay-request intervals per port. Values are expressed as log\u2082 of the interval in seconds (e.g. -3 = 125 ms, 0 = 1 s, 1 = 2 s):

        admin@example:/config/ptp/\u2026/0/port/1/> set port-ds log-announce-interval 0\nadmin@example:/config/ptp/\u2026/0/port/1/> set port-ds log-sync-interval -3\nadmin@example:/config/ptp/\u2026/0/port/1/> set port-ds log-min-delay-req-interval 0\nadmin@example:/config/ptp/\u2026/0/port/1/> set announce-receipt-timeout 3\n

        announce-receipt-timeout is a count of announce intervals, not a duration in seconds. With log-announce-interval 0 (1 s) and announce-receipt-timeout 3, a port waits 3 s without receiving an ANNOUNCE before declaring the time-transmitter lost and returning to listening.

        "},{"location":"ptp/#message-exchange","title":"Message exchange","text":"

        PTP distributes time using a small set of messages, all of which carry hardware timestamps at the network interface:

        Message Timestamped Purpose ANNOUNCE No Advertises clock quality for BTCA election SYNC Yes Carries transmitter timestamp to receivers FOLLOW_UP No Carries precise t1 in two-step mode DELAY_REQ Yes Receiver-initiated E2E delay measurement DELAY_RESP No Time-transmitter reply to DELAY_REQ PDELAY_REQ Yes Initiates P2P neighbour-delay measurement PDELAY_RESP Yes Neighbour reply to PDELAY_REQ PDELAY_RESP_FOLLOW_UP No Carries precise PDELAY_RESP t3 in two-step mode

        In one-step mode the timestamp is embedded directly into each SYNC message as it leaves the wire, eliminating the need for FOLLOW_UP. In two-step mode the SYNC carries a placeholder and the precise transmit timestamp arrives in a subsequent FOLLOW_UP. Hardware timestamping gives high accuracy in both modes; one-step reduces message overhead at the cost of more demanding hardware support.

        "},{"location":"ptp/#message-format","title":"Message format","text":"

        Every PTP message begins with a common 34-octet header, regardless of type. The structure below follows the traditional IETF bit-field layout: each row is four octets wide, bit 7 (MSB) is on the left and bit 0 (LSB) on the right within each octet.

                 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  0-3   |trSpec |msgType|  rsv  |  ver  |         messageLength         |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  4-7   |  domainNumber |  minorSdoId   |             flags             |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n 8-15   |                                                               |\n        +                        correctionField                        +\n        |                                                               |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n16-19   |                      messageTypeSpecific                      |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n20-27   |                                                               |\n        +                         clockIdentity                         +\n        |                                                               |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n28-31   |          portNumber           |           sequenceId          |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n32-33   |  controlField | logMsgIntvl   |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n
        • trSpec (transportSpecific, bits 7\u20134 of octet 0): 4-bit profile identifier. 0x0 = IEEE 1588, 0x1 = gPTP (802.1AS). Set implicitly by the profile configuration leaf.
        • msgType (messageType, bits 3\u20130 of octet 0): 0x0 SYNC \u00b7 0x1 DELAY_REQ \u00b7 0x2 PDELAY_REQ \u00b7 0x3 PDELAY_RESP \u00b7 0x8 FOLLOW_UP \u00b7 0x9 DELAY_RESP \u00b7 0xA PDELAY_RESP_FOLLOW_UP \u00b7 0xB ANNOUNCE.
        • rsv (reserved, bits 7\u20134 of octet 1): Set to zero; ignored on receipt.
        • ver (versionPTP, bits 3\u20130 of octet 1): PTP version; 2 for IEEE 1588-2008 and IEEE 1588-2019.
        • messageLength (octets 2\u20133): Total message length in octets, including the header.
        • domainNumber (octet 4): PTP domain; receivers silently discard messages that do not match their configured domain.
        • minorSdoId (octet 5): Reserved in IEEE 1588-2008; carries a profile sub-identifier in IEEE 1588-2019.
        • flags (octets 6\u20137): Per-message flags \u2014 includes the two-step flag (set when a FOLLOW_UP will follow a SYNC), UTC offset valid, and leap-second indicators.
        • correctionField (octets 8\u201315): Accumulated path correction in nanoseconds \u00d7 2\u00b9\u2076. Transparent Clocks add their measured residence time and link delay here as they forward each message, so the final time-receiver can subtract the total accumulated delay.
        • messageTypeSpecific (octets 16\u201319): Reserved in IEEE 1588-2008; carries message-type-specific data in IEEE 1588-2019.
        • clockIdentity (octets 20\u201327): EUI-64 identity of the sending clock \u2014 the value shown as \"Clock identity\" in show ptp.
        • portNumber (octets 28\u201329): Port number of the sender within its clock; together with clockIdentity it forms the unique sourcePortIdentity.
        • sequenceId (octets 30\u201331): Increments with each message; used to match a DELAY_REQ to its DELAY_RESP.
        • controlField (octet 32): Deprecated in PTPv2; set to fixed values per message type for backward compatibility with PTPv1.
        • logMsgIntvl (logMessageInterval, octet 33): Log\u2082 of the expected interval between messages of this type; 0x7F means not applicable.

        The transportSpecific and domainNumber fields are the quickest way to verify on the wire that a device is using the profile and domain you configured.

        "},{"location":"ptp/#decoding-with-wireshark","title":"Decoding with Wireshark","text":"

        Wireshark decodes PTP messages automatically, expanding every header field and message-type-specific payload in the packet tree. PTP travels over two UDP ports \u2014 319 for event messages (SYNC, DELAY_REQ, PDELAY_REQ and their responses) and 320 for general messages (ANNOUNCE, FOLLOW_UP) \u2014 as well as directly over Ethernet (EtherType 0x88F7) when layer-2 transport is in use.

        Use the display filter ptp to isolate PTP traffic:

        ptp\n

        To narrow down to a specific domain or profile (exact field names can be verified in Wireshark via View \u2192 Internals \u2192 Supported Protocols, filtering for ptp):

        ptp.v2.domainnumber == 0\nptp.v2.transportspecific == 1\n

        This makes it straightforward to confirm which grandmaster a port is tracking, verify that correctionField is being updated by a Transparent Clock, or diagnose why the BTCA is not electing the expected grandmaster.

        "},{"location":"ptp/#glossary","title":"Glossary","text":"Abbreviation Expansion Notes AVB Audio/Video Bridging IEEE 802.1 precursor to TSN; real-time AV over Ethernet IETF Internet Engineering Task Force Standards body; defines RFC for layer-3 and up UDP User Datagram Protocol IP transport used by PTP; port 319 (event) and 320 (general) EUI-64 Extended Unique Identifier (64-bit) IEEE identifier format used as clockIdentity in PTP EtherType Ethernet frame type field 0x88F7 identifies PTP over layer-2 Ethernet BC Boundary Clock Terminates and re-originates PTP on each port BTCA Best TimeTransmitter Clock Algorithm Elects the GM; replaces BMCA from IEEE 1588-2008 CMLDS Common Mean Link Delay Service IEEE 1588-2019 \u00a716.6; shared delay service for multiple instances DS Data Set Named attribute collection in IEEE 1588 (default-ds, port-ds, \u2026) E2E End-to-End Delay mechanism: measures path from GM to time-receiver GM Grandmaster PTP network-wide time source, elected by BTCA gPTP generalized Precision Time Protocol IEEE 802.1AS profile; used in TSN and AVB NTP Network Time Protocol Millisecond-accuracy time protocol for wide-area use OC Ordinary Clock Single-port PTP clock; time-transmitter or time-receiver P2P Peer-to-Peer Delay mechanism: measures delay to immediate neighbour PHC PTP Hardware Clock Hardware clock in the NIC used for PTP timestamping PTP Precision Time Protocol IEEE 1588 sub-microsecond clock synchronisation protocol SDO Standards Development Organization Body that defines a PTP profile; encoded in sdo-id TC Transparent Clock Forwards PTP messages, correcting for residence-time delay TSN Time-Sensitive Networking IEEE 802.1 standard set for deterministic Ethernet"},{"location":"qos/","title":"Quality of Service","text":""},{"location":"qos/#quality-of-service","title":"Quality of Service","text":"

        On occasion, most networks will experience congestion due to some extraordinary load being placed upon it. If the load is transient, switches and routers may be able to absorb such bursts of traffic by queuing packets in internal memories. However, if the load is sustained over long periods of time, queues will fill up and packets will start to be dropped. When such situations arise, it is the job of the network's Quality of Service (QoS) policy to define which packets to drop and which ones to prioritize, such that critical services remain operational.

        "},{"location":"qos/#hardware-forwarded-traffic","title":"Hardware Forwarded Traffic","text":"

        The default QoS policy for flows which are offloaded to a switching ASIC is defined by the hardware defaults of the device in question.

        "},{"location":"qos/#marvell-linkstreet","title":"Marvell LinkStreet","text":"

        This family of devices, sometimes also referred to as SOHO, are managed by the mv88e6xxx driver in the Linux kernel. While older chips in this family where limited to 4 output queues per port, this documentation is only valid for newer generations with 8 output queues per port.

        The picture illustrates packets having their priority determined at ingress, here interface e1 and e3. In this example, both packets are forwarded to the same outgoing interface (e2), subject to output queueing. The sections below provides more information on these topics.

        "},{"location":"qos/#default-policy","title":"Default Policy","text":""},{"location":"qos/#queueing","title":"Queueing","text":"

        Both layer 2 (VLAN PCP) and layer 3 (IP DSCP) priority marks are considered when selecting the output queue of an incoming frame. PCP to queue mapping is done 1:1. For IP packets, the 3 most significant bits of the DSCP is used to select the queue:

        PCP DSCP \u21d2 Queue Weight 0 0-7 \u21d2 0 1 1 8-15 \u21d2 1 2 2 16-23 \u21d2 2 3 3 24-31 \u21d2 3 6 4 32-39 \u21d2 4 12 5 40-47 \u21d2 5 17 6 48-55 \u21d2 6 25 7 56-63 \u21d2 7 33

        For packets containing both a VLAN tag and an IP header, PCP priority takes precedence over DSCP priority. In cases where neither are available, packets are always assigned to queue 0.

        Each port's set of 8 egress queues operate on a Weighted Round Robin (WRR) schedule, using the weights listed in the table above. The sum of all weights adds up to 99, meaning that the weight of any given queue is roughly equivalent to the percentage of the available bandwidth reserved for it.

        "},{"location":"qos/#marking","title":"Marking","text":"

        Any priority marks available on ingress are left unmodified when the frame egresses an output port. In the case when an IP packet ingresses without a VLAN tag, and is to egress with a VLAN tag, its PCP is set to the 3 most significant bits of it. If no priority information is available in the frame on ingress (i.e. untagged non-IP), then packets will egress out of tagged ports with PCP set to 0.

        "},{"location":"qos/#software-forwarded-traffic","title":"Software Forwarded Traffic","text":"

        For packets which are processed by a CPU, i.e. typically routed traffic, and bridged traffic between interfaces that do not belong to the same hardware switching domain, an nftables container can be used to define a QoS policy.

        For VLAN interfaces, Infix provides support for mapping the Priority Code Point (PCP) to internal priority on ingress, and the reverse on egress.

        These ingress-qos and egress-qos settings are done per VLAN, both defaulting to '0'. The example below shows how to keep the PCP priority for packets being routed between two VLAN interfaces.

        admin@example:/config/> edit interface e1.10\nadmin@example:/config/interface/e1.10/> set vlan ingress-qos priority from-pcp\nadmin@example:/config/interface/e1.10/> up\nadmin@example:/config/> edit interface e1.20\nadmin@example:/config/interface/e1.20/> set vlan egress-qos pcp from-priority\nadmin@example:/config/interface/e1.20/> leave\nadmin@example:/>\n
        "},{"location":"qos/#a-complex-example","title":"A complex example","text":"

        The picture below shows a packet flow being subject both to software forwarding and hardware offloading.

        "},{"location":"routing/","title":"Routing","text":""},{"location":"routing/#routing","title":"Routing","text":"

        Currently supported YANG models:

        YANG Model Description ietf-routing Base model for all other models ietf-ipv4-unicast-routing Static IPv4 unicast routing ietf-ipv6-unicast-routing Static IPv6 unicast routing ietf-ospf OSPF routing ietf-rip RIP routing infix-routing Infix deviations and extensions

        The base model, ietf-routing, is where all the other models hook in. It is used to set configuration and read operational status (RIB tables) in the other models.

        Note

        The standard IETF routing models allows multiple instances, but Infix currently only support one instance per routing protocol! In the examples presented here, the instance name default is used.

        "},{"location":"routing/#ipv4-static-routes","title":"IPv4 Static routes","text":"

        The standard IETF model for static routes reside under the static control plane protocol. For our examples we use the instance name default, you can use any name.

        The most common case when using a static IP setup is adding a default route (i.e., the default gateway):

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol static name default ipv4\nadmin@example:/config/routing/\u2026/ipv4/> set route 0.0.0.0/0 next-hop next-hop-address 192.168.1.1\nadmin@example:/config/routing/\u2026/ipv4/> leave\nadmin@example:/>\n

        For a route with destination 192.168.200.0/24 via 192.168.1.1:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol static name default ipv4\nadmin@example:/config/routing/\u2026/ipv4/> set route 192.168.200.0/24 next-hop next-hop-address 192.168.1.1\nadmin@example:/config/routing/\u2026/ipv4/> leave\nadmin@example:/>\n

        For a \"floating\" static route with destination 10.0.0.0/16 via a backup router 192.168.1.1, using the highest possible distance:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol static name default ipv4\nadmin@example:/config/routing/\u2026/ipv4/> set route 10.0.0.0/16 next-hop next-hop-address 192.168.1.1 route-preference 254\nadmin@example:/config/routing/\u2026/ipv4/> leave\nadmin@example:/>\n

        Tip

        Remember to enable IPv4 forwarding for the interfaces you want to route between.

        "},{"location":"routing/#ipv6-static-routes","title":"IPv6 Static routes","text":"

        Default route via an IPv6 gateway:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol static name default ipv6\nadmin@example:/config/routing/\u2026/ipv6/> set route ::/0 next-hop next-hop-address 2001:db8:3c4d:1::1\nadmin@example:/config/routing/\u2026/ipv6/> leave\nadmin@example:/>\n

        For a route with destination 2001:db8:3c4d:200::/64 via 2001:db8:3c4d:1::1:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol static name default ipv6\nadmin@example:/config/routing/\u2026/ipv6/> set route 2001:db8:3c4d:200::/64 next-hop next-hop-address 2001:db8:3c4d:1::1\nadmin@example:/config/routing/\u2026/ipv6/> leave\nadmin@example:/>\n
        "},{"location":"routing/#ospfv2-routing","title":"OSPFv2 Routing","text":"

        The system supports OSPF dynamic routing for IPv4, i.e., OSPFv2. To enable OSPF and set one active interface in area 0:

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 enabled\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n

        Tip

        Remember to enable IPv4 forwarding for all the interfaces you want to route between.

        "},{"location":"routing/#ospf-area-types","title":"OSPF area types","text":"

        In addition to regular OSPF areas, area types NSSA and Stub are also supported. To configure an NSSA area with summary routes:

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.1 area-type nssa-area\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.1 summary true\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n
        "},{"location":"routing/#bidirectional-forwarding-detection-bfd","title":"Bidirectional Forwarding Detection (BFD)","text":"

        It is possible to enable BFD per OSPF interface to speed up detection of link loss.

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 bfd enabled true\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n
        "},{"location":"routing/#ospf-interface-settings","title":"OSPF interface settings","text":"

        We have already seen how to enable OSPF per interface (enabled true) and BFD for OSPF per interface (bfd enabled true). These and other OSPF interface settings are done in context of an OSFP area, e.g., area 0.0.0.0. Available commands can be listed using the ? mark.

        admin@example:/config/routing/\u2026/> edit ospf area 0.0.0.0\nadmin@example:/config/routing/\u2026/ospf/area/0.0.0.0/> edit interface e0\nadmin@example:/config/routing/\u2026/ospf/area/0.0.0.0/interface/e0/> set ?\n  bfd                  BFD interface configuration.\n  cost                 Interface's cost.\n  dead-interval        Interval after which a neighbor is declared down\n  enabled              Enables/disables the OSPF protocol on the interface.\n  hello-interval       Interval between Hello packets (seconds).  It must\n  interface-type       Interface type.\n  passive              Enables/disables a passive interface.  A passive\n  retransmit-interval  Interval between retransmitting unacknowledged Link\n  transmit-delay       Estimated time needed to transmit Link State Update\nadmin@example:/config/routing/\u2026/ospf/area/0.0.0.0/interface/e0/> set\n

        For example, setting the OSPF interface type to point-to-point for an Ethernet interface can be done as follows.

        admin@example:/config/routing/\u2026/ospf/area/0.0.0.0/interface/e0/> set interface-type point-to-point\nadmin@example:/config/routing/\u2026/ospf/area/0.0.0.0/interface/e0/>\n
        "},{"location":"routing/#point-to-multipoint","title":"Point-to-Multipoint","text":"

        Point-to-Multipoint (P2MP) is used when multiple OSPF routers share a common network segment but should form individual adjacencies rather than electing a Designated Router (DR). This is common in NBMA-like environments, DMVPN, or hub-and-spoke topologies.

        Infix supports two P2MP variants via the interface-type setting:

        Interface Type Behavior hybrid P2MP with multicast Hellos (broadcast-capable) point-to-multipoint P2MP with unicast Hellos (non-broadcast)"},{"location":"routing/#hybrid-broadcast-capable-p2mp","title":"Hybrid (broadcast-capable P2MP)","text":"

        Use hybrid when all neighbors on the segment can receive multicast. Hello packets are sent to the standard OSPF multicast address (224.0.0.5) and neighbors are discovered automatically \u2014 no manual neighbor configuration is needed.

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 interface-type hybrid\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n
        "},{"location":"routing/#non-broadcast-p2mp","title":"Non-broadcast P2MP","text":"

        Use point-to-multipoint when the network does not support multicast. Hello packets are sent as unicast directly to each configured neighbor. Since neighbors cannot be discovered automatically, they must be configured explicitly using static neighbors (see below).

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 interface-type point-to-multipoint\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n
        "},{"location":"routing/#static-neighbors","title":"Static Neighbors","text":"

        When using non-broadcast interface types (such as point-to-multipoint), OSPF cannot discover neighbors via multicast. Static neighbors must be configured so the router knows where to send unicast Hello packets.

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 static-neighbors neighbor 10.0.123.2\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 static-neighbors neighbor 10.0.123.3\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n

        Note

        Static neighbors are only needed for non-broadcast interface types. With hybrid (or broadcast), neighbors are discovered automatically via multicast.

        "},{"location":"routing/#ospf-global-settings","title":"OSPF global settings","text":"

        In addition to area and interface specific settings, OSPF provides global settings for route redistribution and OSPF router identifier.

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set ?\n  area                     List of OSPF areas.\n  default-route-advertise  Distribute default route to network\n  explicit-router-id       Defined in RFC 2328.  A 32-bit number\n  redistribute             Redistribute protocols into OSPF\nadmin@example:/config/routing/\u2026/ospf/> set\n
        • Explicit router ID: By default the router will pick an IP address from one of its OSPF interfaces as OSPF router ID. An explicit ID is used to get a deterministic behavior, e.g., set explicit-router-id 1.1.1.1.
        • Redistribution: set redistribute static and set redistribute connected can be used to include static or connected routes into the OSPF routing domain. These routes are redistributed as external type-2 (E2) routes.
        • Advertising default route: An OSPF router can be made to distribute a default route into the OSPF domain by command set default-route-advertise enabled. This route is distributed as long as the router itself has an active default route in its routing table. By adding command set default-route-advertise always the router will distribute a default route even when it lacks a default route. The default route will be distributed as an external type-2 (E2) route.
        "},{"location":"routing/#debug-ospfv2","title":"Debug OSPFv2","text":"

        Using NETCONF and the YANG model ietf-routing it is possible to read the OSPF routing table, neighbors and more, that may be useful for debugging the OSPFv2 setup. The CLI has various OSPF status commands such as show ospf neighbor, show ospf interface and show ospf routes.

        admin@example:/> show ospf neighbor\nNeighbor ID     Pri State           Up Time         Dead Time Address         Interface                        RXmtL RqstL DBsmL\n10.1.1.2          1 Full/-          3h46m59s          30.177s 10.1.1.2        e0:10.1.1.1                          0     0     0\n10.1.1.3          1 Full/-          3h46m55s          34.665s 10.1.1.3        e1:10.1.1.1                          0     0     0\nadmin@example:/>\n

        For more detailed troubleshooting, OSPF debug logging can be enabled to capture specific protocol events. Debug messages are written to the routing log file (/var/log/routing).

        Caution

        Debug logging significantly increases log output and may impact performance. Only enable debug categories needed for troubleshooting, and disable them when done.

        To enable specific OSPF debug categories:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf debug\nadmin@example:/config/routing/\u2026/ospf/debug/> set bfd true\nadmin@example:/config/routing/\u2026/ospf/debug/> set nsm true\nadmin@example:/config/routing/\u2026/ospf/debug/> leave\nadmin@example:/>\n

        Available debug categories include:

        • bfd: BFD (Bidirectional Forwarding Detection) events
        • packet: Detailed packet debugging (all OSPF packets)
        • ism: Interface State Machine events
        • nsm: Neighbor State Machine events
        • default-information: Default route origination
        • nssa: Not-So-Stubby Area events

        All debug options are disabled by default. Refer to the infix-routing YANG model for the complete list of available debug options.

        To view current debug settings:

        admin@example:/> show running-config routing control-plane-protocol\n

        To disable all debug logging, simply delete the debug settings or set all options back to false:

        admin@example:/> configure\nadmin@example:/config/> delete routing control-plane-protocol ospfv2 name default ospf debug\nadmin@example:/config/> leave\nadmin@example:/>\n
        "},{"location":"routing/#rip-routing","title":"RIP Routing","text":"

        The system supports RIP dynamic routing for IPv4, i.e., RIPv2. To enable RIP and set active interfaces:

        admin@example:/config/> edit routing control-plane-protocol ripv2 name default rip\nadmin@example:/config/routing/\u2026/rip/> set interfaces interface e0\nadmin@example:/config/routing/\u2026/rip/> set interfaces interface e1\nadmin@example:/config/routing/\u2026/rip/> leave\nadmin@example:/>\n

        Tip

        Remember to enable IPv4 forwarding for all the interfaces you want to route between.

        "},{"location":"routing/#rip-interface-settings","title":"RIP interface settings","text":"

        By default, interfaces send and receive RIPv2 packets. To control the RIP version per interface:

        admin@example:/config/routing/\u2026/rip/> edit interfaces interface e0\nadmin@example:/config/routing/\u2026/rip/interfaces/interface/e0/> set send-version 1\nadmin@example:/config/routing/\u2026/rip/interfaces/interface/e0/> set receive-version 1-2\nadmin@example:/config/routing/\u2026/rip/interfaces/interface/e0/> leave\nadmin@example:/>\n

        Valid version values are 1, 2, or 1-2 (both versions).

        To configure a passive interface (advertise network but don't send/receive RIP updates):

        admin@example:/config/routing/\u2026/rip/> edit interfaces interface e0\nadmin@example:/config/routing/\u2026/rip/interfaces/interface/e0/> set passive\nadmin@example:/config/routing/\u2026/rip/interfaces/interface/e0/> leave\nadmin@example:/>\n
        "},{"location":"routing/#rip-global-settings","title":"RIP global settings","text":"

        RIP supports redistribution of connected and static routes:

        admin@example:/config/routing/\u2026/rip/> set redistribute connected\nadmin@example:/config/routing/\u2026/rip/> set redistribute static\nadmin@example:/config/routing/\u2026/rip/> leave\nadmin@example:/>\n
        "},{"location":"routing/#debug-ripv2","title":"Debug RIPv2","text":"

        The CLI provides various RIP status commands:

        admin@example:/> show ip rip\nDefault version control: send version 2, receive version 2\n  Interface        Send  Recv   Key-chain                    \n  e0               2     2\n  e1               2     2\n\nRouting for Networks:\n  e0\n  e1\n\nRouting Information Sources:\n  Gateway          BadPackets BadRoutes  Distance Last Update\n  10.0.1.2                  0         0       120    00:00:16\nDistance: (default is 120)\n\nadmin@example:/> show ip rip neighbor\nADDRESS          BAD-PACKETS    BAD-ROUTES                   \n10.0.1.2         0              0\nadmin@example:/>\n

        For more detailed troubleshooting, RIP debug logging can be enabled to capture specific protocol events. Debug messages are written to the routing log file (/var/log/routing).

        Caution

        Debug logging significantly increases log output and may impact performance. Only enable debug categories needed for troubleshooting, and disable them when done.

        To enable specific RIP debug categories:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol ripv2 name default rip debug\nadmin@example:/config/routing/\u2026/rip/debug/> set events true\nadmin@example:/config/routing/\u2026/rip/debug/> set packet true\nadmin@example:/config/routing/\u2026/rip/debug/> leave\nadmin@example:/>\n

        Available debug categories include:

        • events: RIP events (sending/receiving packets, timers, interface changes)
        • packet: Detailed packet debugging (packet dumps with origin and port)
        • kernel: Kernel routing table updates (route add/delete, interface updates)

        All debug options are disabled by default. Refer to the infix-routing YANG model for the complete list of available debug options.

        To view current debug settings:

        admin@example:/> show running-config routing control-plane-protocol\n

        To disable all debug logging, simply delete the debug settings or set all options back to false:

        admin@example:/> configure\nadmin@example:/config/> delete routing control-plane-protocol ripv2 name default rip debug\nadmin@example:/config/> leave\nadmin@example:/>\n
        "},{"location":"routing/#view-routing-table","title":"View routing table","text":"

        The routing table can be inspected from the operational datastore, XPath /routing/ribs, using sysrepocfg, NETCONF/RESTCONF, or using the CLI.

        "},{"location":"routing/#ipv4-routing-table","title":"IPv4 routing table","text":"

        This CLI example shows the IPv4 routing table with a few connected routes and some routes learned from OSPF. See the next section for an explanation of route preferences (PREF).

        The > at the start of a line marks a selected route (in the IETF YANG model referred to as active), if there are more than one route with the same destination the * marks the next-hop used and installed in the kernel FIB (the YANG model refers to this as installed).

        admin@example:/> show ip route\n   DESTINATION            PREF NEXT-HOP         PROTO     UPTIME\n>* 0.0.0.0/0             110/2 10.0.23.1        ospfv2   4h2m43s\n>* 10.0.0.1/32        110/4000 10.0.13.1        ospfv2   4h2m43s\n   10.0.0.3/32           110/0 lo               ospfv2   4h2m57s\n>* 10.0.0.3/32             0/0 lo               direct   4h2m58s\n   10.0.13.0/30       110/2000 e5               ospfv2   4h2m57s\n>* 10.0.13.0/30            0/0 e5               direct   4h2m58s\n   10.0.23.0/30          110/1 e6               ospfv2   4h2m57s\n>* 10.0.23.0/30            0/0 e6               direct   4h2m58s\n   192.168.3.0/24        110/1 e2               ospfv2   4h2m57s\n>* 192.168.3.0/24          0/0 e2               direct   4h2m58s\nadmin@example:/>\n
        "},{"location":"routing/#ipv6-routing-table","title":"IPv6 routing table","text":"

        This CLI example show the IPv6 routing table.

        admin@example:/> show ipv6 route\n   DESTINATION                      PREF NEXT-HOP              PROTO     UPTIME\n>* ::/0                              1/0 2001:db8:3c4d:50::1   static   0h1m20s\n>* 2001:db8:3c4d:50::/64             0/0 e6                    direct   0h1m20s\n>* 2001:db8:3c4d:200::1/128          0/0 lo                    direct   0h1m20s\n * fe80::/64                         0/0 e7                    direct   0h1m20s\n * fe80::/64                         0/0 e6                    direct   0h1m20s\n * fe80::/64                         0/0 e5                    direct   0h1m20s\n * fe80::/64                         0/0 e4                    direct   0h1m20s\n * fe80::/64                         0/0 e3                    direct   0h1m20s\n * fe80::/64                         0/0 e2                    direct   0h1m20s\n>* fe80::/64                         0/0 e1                    direct   0h1m20s\nadmin@example:/>\n
        "},{"location":"routing/#route-preference","title":"Route Preference","text":"

        The operating system leverages FRRouting (Frr) as routing engine for both static and dynamic routing. Even routes injected from a DHCP client, and IPv4 link-local (IPv4) routes, are injected into Frr to let it weigh all routes before installing them into the kernel routing table (sometimes referred to as FIB).

        Routes have different weights made up from a distance and a metric. The kernel routing table only talks about metric, which unfortunately is not the same -- this is one of the reasons why the term route preference is used instead. It is recommended to use the CLI, or any of the other previously mentioned YANG based front-ends, to inspect the routing table.

        Default distances used (lower numeric value wins):

        Distance Protocol 0 Kernel routes, i.e., connected routes 1 Static routes 5 DHCP routes 110 OSPF 254 IPv4LL (ZeroConf) device routes 255 Route will not be used or redistributed

        Hence, a route learned from OSPF may be overridden by a static route set locally. By default, even a route to the same destination, but with a different next-hop, learned from a DHCP server wins over an OSPF route.

        The distance used for static routes and DHCP routes can be changed by setting a different routing preference value.

        Note

        The kernel metric is an unsigned 32-bit value, which is read by Frr as (upper) 8 bits distance and 24 bits metric. But it does not write it back to the kernel FIB this way, only selected routes are candidates to be installed in the FIB by Frr.

        "},{"location":"routing/#source-protocol","title":"Source protocol","text":"

        The source protocol describes the origin of the route.

        Protocol Description kernel Added when setting a subnet address on an interface static User created, learned from DHCP, or IPv4LL ospfv2 Routes learned from OSPFv2

        The YANG model ietf-routing support multiple ribs but only two are currently supported, namely ipv4 and ipv6.

        "},{"location":"scripting-netconf/","title":"NETCONF Scripting","text":""},{"location":"scripting-netconf/#scripting-with-netconf","title":"Scripting with NETCONF","text":"

        NETCONF (Network Configuration Protocol) provides a standardized mechanism for managing network devices using XML-based RPC operations over SSH (port 830). This guide shows practical examples for interacting with Infix using NETCONF.

        NETCONF offers robust capabilities for network automation:

        • Transactional operations: Validate before commit, rollback on error
        • Fine-grained locking: Prevent concurrent configuration conflicts
        • Structured data: XML with YANG schema validation
        • Standardized operations: Get, edit-config, copy-config, etc.
        "},{"location":"scripting-netconf/#netconf-vs-restconf","title":"NETCONF vs RESTCONF","text":"

        Both protocols use the same YANG data models, but differ in approach:

        Feature NETCONF RESTCONF Transport SSH HTTPS Encoding XML JSON/XML Operations RPC-based REST/HTTP methods Best for Automation scripts Web integration, simple queries

        Choose NETCONF when you need:

        • Transactional configuration changes
        • Configuration validation before commit
        • Locking to prevent concurrent changes
        • Integration with existing NETCONF tooling

        Choose RESTCONF for:

        • Simple queries and updates
        • Web-based applications
        • When you prefer JSON over XML
        • RESTful API patterns
        "},{"location":"scripting-netconf/#quick-start-with-netopeer2-cli","title":"Quick Start with netopeer2-cli","text":"

        netopeer2-cli is an interactive NETCONF client, useful for learning and testing. Install it:

        ~$ sudo apt install netopeer2-cli\n

        Connect to your Infix device:

        ~$ netopeer2-cli\n> connect --host example.local --login admin\nadmin@example.local password:\n> status\nCurrent NETCONF session:\n  ID          : 1\n  Host        : example.local\n  Port        : 830\n  Transport   : SSH\n  Capabilities: 35\n
        "},{"location":"scripting-netconf/#basic-operations-in-netopeer2-cli","title":"Basic Operations in netopeer2-cli","text":"

        Get entire configuration:

        > get-config --source running\n

        Get specific subtree (hostname):

        > get-config --source running --filter-xpath /system/hostname\n<data xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n    <hostname>example</hostname>\n  </system>\n</data>\n

        Get operational state:

        > get --filter-xpath /interfaces\n

        Edit configuration:

        > edit-config --target candidate --config=/tmp/config.xml\n> commit\n

        Disconnect:

        > disconnect\n> quit\n
        "},{"location":"scripting-netconf/#discovery-common-patterns","title":"Discovery & Common Patterns","text":"

        Before working with specific configuration items, you often need to discover what exists on the system. This section shows common discovery patterns and practical workflows.

        "},{"location":"scripting-netconf/#discovering-available-interfaces","title":"Discovering Available Interfaces","text":"

        List all interface names:

        <rpc message-id=\"1\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <get>\n    <filter type=\"subtree\">\n      <interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">\n        <interface>\n          <name/>\n        </interface>\n      </interfaces>\n    </filter>\n  </get>\n</rpc>\n

        Using netopeer2-cli:

        > get --filter-xpath /interfaces/interface/name\n

        This returns all interface names, useful for iterating through interfaces in scripts.

        "},{"location":"scripting-netconf/#get-all-yang-capabilities","title":"Get All YANG Capabilities","text":"

        Discover which YANG modules and features are available:

        > status\n

        Or programmatically via the <hello> message capabilities received during connection establishment.

        "},{"location":"scripting-netconf/#get-entire-running-configuration","title":"Get Entire Running Configuration","text":"

        Useful for exploration or backup:

        <rpc message-id=\"2\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <get-config>\n    <source>\n      <running/>\n    </source>\n  </get-config>\n</rpc>\n

        Using netopeer2-cli:

        > get-config --source running\n
        "},{"location":"scripting-netconf/#common-workflow-patterns","title":"Common Workflow Patterns","text":""},{"location":"scripting-netconf/#pattern-1-find-interface-by-ip-address","title":"Pattern 1: Find interface by IP address","text":"

        Get all interfaces with their IPs, then filter:

        > get --filter-xpath /interfaces\n

        Parse the XML output to find which interface has the desired IP.

        "},{"location":"scripting-netconf/#pattern-2-check-which-interfaces-are-down","title":"Pattern 2: Check which interfaces are down","text":"
        > get --filter-xpath /interfaces/interface/oper-status\n

        Look for interfaces with <oper-status>down</oper-status>.

        "},{"location":"scripting-netconf/#pattern-3-get-interface-statistics-for-monitoring","title":"Pattern 3: Get interface statistics for monitoring","text":"
        <rpc message-id=\"3\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <get>\n    <filter type=\"subtree\">\n      <interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">\n        <interface>\n          <statistics/>\n        </interface>\n      </interfaces>\n    </filter>\n  </get>\n</rpc>\n

        Returns in-octets, out-octets, in-errors, out-errors for all interfaces.

        "},{"location":"scripting-netconf/#scripting-with-netconf-client","title":"Scripting with netconf-client","text":"

        netconf-client is a lightweight Python-based NETCONF client designed for scripting and automation.

        "},{"location":"scripting-netconf/#installation","title":"Installation","text":"
        ~$ pip install netconf-client\n

        Or clone and install from source:

        ~$ git clone https://github.com/wires-se/netconf-client.git\n~$ cd netconf-client\n~$ pip install .\n
        "},{"location":"scripting-netconf/#basic-usage","title":"Basic Usage","text":"

        The client provides a simple command-line interface:

        ~$ netconf-client --host example.local --user admin --password admin <operation>\n

        Common operations:

        • get-config - Retrieve configuration
        • edit-config - Modify configuration
        • get - Retrieve operational state
        • copy-config - Copy between datastores
        • lock/unlock - Lock datastores
        • commit - Commit candidate configuration
        "},{"location":"scripting-netconf/#python-api","title":"Python API","text":"

        You can also use netconf-client as a Python library:

        from netconf_client.client import NetconfClient\n\n# Connect\nclient = NetconfClient(\n    host='example.local',\n    username='admin',\n    password='admin'\n)\n\n# Get configuration\nconfig = client.get_config(source='running')\nprint(config)\n\n# Edit configuration\nxml_config = \"\"\"\n<config>\n  <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n    <hostname>newhostname</hostname>\n  </system>\n</config>\n\"\"\"\nclient.edit_config(target='candidate', config=xml_config)\nclient.commit()\n\n# Close connection\nclient.close()\n
        "},{"location":"scripting-netconf/#configuration-examples","title":"Configuration Examples","text":""},{"location":"scripting-netconf/#read-hostname","title":"Read Hostname","text":"

        XML request:

        <rpc message-id=\"1\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <get-config>\n    <source>\n      <running/>\n    </source>\n    <filter type=\"subtree\">\n      <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n        <hostname/>\n      </system>\n    </filter>\n  </get-config>\n</rpc>\n

        Using netopeer2-cli:

        > get-config --source running --filter-xpath /system/hostname\n

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     get-config --source running --xpath /system/hostname\n

        Response:

        <rpc-reply message-id=\"1\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <data>\n    <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n      <hostname>example</hostname>\n    </system>\n  </data>\n</rpc-reply>\n
        "},{"location":"scripting-netconf/#set-hostname","title":"Set Hostname","text":"

        XML request:

        <rpc message-id=\"2\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <edit-config>\n    <target>\n      <candidate/>\n    </target>\n    <config>\n      <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n        <hostname>newhostname</hostname>\n      </system>\n    </config>\n  </edit-config>\n</rpc>\n

        Using netopeer2-cli:

        Save the config to /tmp/hostname.xml:

        <config>\n  <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n    <hostname>newhostname</hostname>\n  </system>\n</config>\n

        Then apply:

        > edit-config --target candidate --config=/tmp/hostname.xml\n> commit\n

        Using netconf-client:

        ~$ cat > hostname.xml <<EOF\n<config>\n  <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n    <hostname>newhostname</hostname>\n  </system>\n</config>\nEOF\n\n~$ netconf-client --host example.local --user admin \\\n     edit-config --target candidate --config hostname.xml\n\n~$ netconf-client --host example.local --user admin commit\n
        "},{"location":"scripting-netconf/#add-ip-address-to-interface","title":"Add IP Address to Interface","text":"

        Save the config to ip-config.xml:

        <config>\n  <interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">\n    <interface>\n      <name>eth0</name>\n      <ipv4 xmlns=\"urn:ietf:params:xml:ns:yang:ietf-ip\">\n        <address>\n          <ip>192.168.1.100</ip>\n          <prefix-length>24</prefix-length>\n        </address>\n      </ipv4>\n    </interface>\n  </interfaces>\n</config>\n

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     edit-config --target candidate --config ip-config.xml\n~$ netconf-client --host example.local --user admin commit\n
        "},{"location":"scripting-netconf/#copy-running-to-startup","title":"Copy Running to Startup","text":"

        XML request:

        <rpc message-id=\"3\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <copy-config>\n    <source>\n      <running/>\n    </source>\n    <target>\n      <startup/>\n    </target>\n  </copy-config>\n</rpc>\n

        Using netopeer2-cli:

        > copy-config --source running --target startup\n

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     copy-config --source running --target startup\n
        "},{"location":"scripting-netconf/#operational-data-examples","title":"Operational Data Examples","text":""},{"location":"scripting-netconf/#read-interface-state","title":"Read Interface State","text":"

        XML request:

        <rpc message-id=\"4\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <get>\n    <filter type=\"subtree\">\n      <interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">\n        <interface>\n          <name>eth0</name>\n        </interface>\n      </interfaces>\n    </filter>\n  </get>\n</rpc>\n

        Using netopeer2-cli:

        > get --filter-xpath /interfaces/interface[name='eth0']\n

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     get --xpath \"/interfaces/interface[name='eth0']\"\n

        This returns operational state including admin/oper status, statistics, MAC address, MTU, and IP addresses.

        "},{"location":"scripting-netconf/#read-all-interfaces","title":"Read All Interfaces","text":"

        Using netopeer2-cli:

        > get --filter-xpath /interfaces\n

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     get --xpath /interfaces\n
        "},{"location":"scripting-netconf/#read-routing-table","title":"Read Routing Table","text":"

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     get --xpath \"/routing/ribs/rib[name='ipv4-default']\"\n
        "},{"location":"scripting-netconf/#read-ospf-neighbors","title":"Read OSPF Neighbors","text":"

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     get --xpath \"/routing/control-plane-protocols/control-plane-protocol[type='ietf-ospf:ospfv2'][name='default']/ietf-ospf:ospf\"\n
        "},{"location":"scripting-netconf/#advanced-scripting","title":"Advanced Scripting","text":""},{"location":"scripting-netconf/#python-script-backup-configuration","title":"Python Script: Backup Configuration","text":"
        #!/usr/bin/env python3\nfrom netconf_client.client import NetconfClient\nfrom datetime import datetime\nimport sys\n\ndef backup_config(host, user, password, output_file):\n    try:\n        # Connect\n        client = NetconfClient(host=host, username=user, password=password)\n\n        # Get running config\n        config = client.get_config(source='running')\n\n        # Save to file with timestamp\n        timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')\n        filename = f\"{output_file}-{timestamp}.xml\"\n\n        with open(filename, 'w') as f:\n            f.write(config)\n\n        print(f\"Configuration backed up to {filename}\")\n\n        client.close()\n        return 0\n\n    except Exception as e:\n        print(f\"Error: {e}\", file=sys.stderr)\n        return 1\n\nif __name__ == '__main__':\n    if len(sys.argv) != 5:\n        print(f\"Usage: {sys.argv[0]} <host> <user> <password> <output_prefix>\")\n        sys.exit(1)\n\n    sys.exit(backup_config(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]))\n

        Usage:

        ~$ chmod +x backup.py\n~$ ./backup.py example.local admin admin config-backup\nConfiguration backed up to config-backup-20250102-143022.xml\n
        "},{"location":"scripting-netconf/#python-script-monitor-interface-statistics","title":"Python Script: Monitor Interface Statistics","text":"
        #!/usr/bin/env python3\nfrom netconf_client.client import NetconfClient\nimport xml.etree.ElementTree as ET\nimport time\nimport sys\n\ndef get_interface_stats(client, interface):\n    \"\"\"Get interface statistics\"\"\"\n    xpath = f\"/interfaces/interface[name='{interface}']/statistics\"\n    data = client.get(filter_xpath=xpath)\n\n    # Parse XML to extract counters\n    root = ET.fromstring(data)\n    ns = {'if': 'urn:ietf:params:xml:ns:yang:ietf-interfaces'}\n\n    stats = {}\n    for stat in root.findall('.//if:statistics/*', ns):\n        stats[stat.tag.split('}')[1]] = int(stat.text)\n\n    return stats\n\ndef monitor_interface(host, user, password, interface, interval=5):\n    \"\"\"Monitor interface statistics\"\"\"\n    client = NetconfClient(host=host, username=user, password=password)\n\n    print(f\"Monitoring {interface} on {host} (Ctrl-C to stop)\")\n    print(f\"{'Time':<20} {'RX Packets':<15} {'TX Packets':<15} {'RX Bytes':<15} {'TX Bytes':<15}\")\n    print(\"-\" * 80)\n\n    try:\n        while True:\n            stats = get_interface_stats(client, interface)\n            timestamp = time.strftime('%Y-%m-%d %H:%M:%S')\n\n            print(f\"{timestamp:<20} \"\n                  f\"{stats.get('in-unicast-pkts', 0):<15} \"\n                  f\"{stats.get('out-unicast-pkts', 0):<15} \"\n                  f\"{stats.get('in-octets', 0):<15} \"\n                  f\"{stats.get('out-octets', 0):<15}\")\n\n            time.sleep(interval)\n\n    except KeyboardInterrupt:\n        print(\"\\nMonitoring stopped\")\n    finally:\n        client.close()\n\nif __name__ == '__main__':\n    if len(sys.argv) < 5:\n        print(f\"Usage: {sys.argv[0]} <host> <user> <password> <interface> [interval]\")\n        sys.exit(1)\n\n    interval = int(sys.argv[5]) if len(sys.argv) > 5 else 5\n    monitor_interface(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], interval)\n

        Usage:

        ~$ chmod +x monitor.py\n~$ ./monitor.py example.local admin admin eth0 2\nMonitoring eth0 on example.local (Ctrl-C to stop)\nTime                 RX Packets      TX Packets      RX Bytes        TX Bytes\n--------------------------------------------------------------------------------\n2025-01-02 14:35:10  12453          8932           1847392        892341\n2025-01-02 14:35:12  12489          8967           1851204        895673\n...\n
        "},{"location":"scripting-netconf/#shell-script-batch-configuration","title":"Shell Script: Batch Configuration","text":"
        #!/bin/bash\n# Apply configuration to multiple devices\n\nDEVICES=\"device1.local device2.local device3.local\"\nUSER=\"admin\"\nPASSWORD=\"admin\"\nCONFIG_FILE=\"$1\"\n\nif [ -z \"$CONFIG_FILE\" ]; then\n    echo \"Usage: $0 <config.xml>\"\n    exit 1\nfi\n\nfor device in $DEVICES; do\n    echo \"Configuring $device...\"\n\n    # Edit candidate\n    netconf-client --host \"$device\" --user \"$USER\" --password \"$PASSWORD\" \\\n        edit-config --target candidate --config \"$CONFIG_FILE\"\n\n    if [ $? -eq 0 ]; then\n        # Commit if edit succeeded\n        netconf-client --host \"$device\" --user \"$USER\" --password \"$PASSWORD\" commit\n        echo \"  \u2713 $device configured successfully\"\n    else\n        echo \"  \u2717 $device configuration failed\"\n    fi\ndone\n
        "},{"location":"scripting-netconf/#other-netconf-tools","title":"Other NETCONF Tools","text":""},{"location":"scripting-netconf/#ncclient-python","title":"ncclient (Python)","text":"

        Popular Python library for NETCONF:

        ~$ pip install ncclient\n

        Example:

        from ncclient import manager\n\nwith manager.connect(host='example.local', port=830,\n                     username='admin', password='admin',\n                     hostkey_verify=False) as m:\n    # Get config\n    c = m.get_config(source='running')\n    print(c)\n
        "},{"location":"scripting-netconf/#ansible","title":"Ansible","text":"

        Ansible includes NETCONF modules for automation:

        - name: Get interface config\n  netconf_get:\n    source: running\n    filter: <interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\"/>\n
        "},{"location":"scripting-netconf/#cisco-tools","title":"Cisco Tools","text":"
        • NSO (Network Services Orchestrator): Enterprise orchestration platform
        • YANG Suite: Web-based YANG exploration and testing tool
        • Cisco pyATS: Network test automation
        "},{"location":"scripting-netconf/#troubleshooting","title":"Troubleshooting","text":""},{"location":"scripting-netconf/#enable-netconf-debugging","title":"Enable NETCONF Debugging","text":"

        For netconf-client, use verbose mode:

        ~$ netconf-client --host example.local --user admin --verbose get-config\n

        For netopeer2-cli, enable debug output:

        > debug 1\n> get-config --source running\n
        "},{"location":"scripting-netconf/#common-issues","title":"Common Issues","text":"

        Connection refused:

        • Verify SSH is running on port 830: ssh -p 830 admin@example.local
        • Check firewall rules

        Authentication failed:

        • Verify credentials
        • Check user has NETCONF access permissions

        Operation not supported:

        • Verify NETCONF capability: netopeer2-cli \u2192 status \u2192 check capabilities
        • Some operations require specific YANG modules
        "},{"location":"scripting-netconf/#references","title":"References","text":"
        • NETCONF Protocol (RFC 6241)
        • netconf-client on GitHub
        • netopeer2 Documentation
        • YANG Data Modeling Language (RFC 7950)
        • ietf-interfaces YANG module
        • ietf-system YANG module
        • ncclient Documentation
        "},{"location":"scripting-prod/","title":"Production Testing","text":""},{"location":"scripting-prod/#scripting-for-production-tests","title":"Scripting for Production Tests","text":"

        This document shows how to set up and remotely script devices with a focus on production testing.

        "},{"location":"scripting-prod/#vlan-snake","title":"VLAN Snake","text":"

        As part of production tests, verification of Ethernet ports is usually expected. A common way for devices with multiple bridged Ethernet ports is to connect a test PC to two ports and send a ping traversing all ports. This can be achieved by using VLANs, on the switch, as described in this section. The resulting configuration file can be applied to the running configuration of the produced unit, e.g, use config file restore as described previously.

        In this example we assume a 10 port switch, with ports e1-e10. The following VLAN configuration and cable connections will be used:

        VLAN & Ports Connect VLAN 10: e1 & e2 e2 <=> e3 VLAN 20: e3 & e4 e4 <=> e5 VLAN 30: e5 & e6 e6 <=> e7 VLAN 40: e7 & e8 e8 <=> e9 VLAN 50: e9 & e10

        The test PC is connected to e1 and e10 via different interfaces (alternatively, two different PCs are used).

        Tip

        Configuration here is done via console. When configuring remotely over SSH, remember to keep one IP address (the one used for the SSH connection)! I.e., set a static IP address first, then perform the VLAN configuration step.

        "},{"location":"scripting-prod/#configuration-at-start","title":"Configuration at Start","text":"

        Starting out, we assume a configuration where all ports are network interfaces (possibly with IPv6 enabled).

        admin@example:/> show interfaces\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\ne1              ethernet   LOWER-DOWN  00:53:00:06:11:01\ne2              ethernet   LOWER-DOWN  00:53:00:06:11:02\ne3              ethernet   LOWER-DOWN  00:53:00:06:11:03\ne4              ethernet   LOWER-DOWN  00:53:00:06:11:04\ne5              ethernet   LOWER-DOWN  00:53:00:06:11:05\ne6              ethernet   LOWER-DOWN  00:53:00:06:11:06\ne7              ethernet   LOWER-DOWN  00:53:00:06:11:07\ne8              ethernet   LOWER-DOWN  00:53:00:06:11:08\ne9              ethernet   LOWER-DOWN  00:53:00:06:11:09\ne10             ethernet   UP          00:53:00:06:11:0a\n                ipv6                   fe80::0053:00ff:fe06:110a/64 (link-layer)\nadmin@example:/>\n
        "},{"location":"scripting-prod/#creating-bridge-and-adding-ports","title":"Creating Bridge and Adding Ports","text":"

        The following example creates a bridge and adds all Ethernet ports to it. On a device with layer-2 offloading (switch fabric), this sets all ports in the same VLAN. The next section sets up VLAN isolation.

        admin@example:/> configure\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> end\nadmin@example:/config/> set interface e1 bridge-port bridge br0\nadmin@example:/config/> set interface e2 bridge-port bridge br0\nadmin@example:/config/> set interface e3 bridge-port bridge br0\nadmin@example:/config/> set interface e4 bridge-port bridge br0\nadmin@example:/config/> set interface e5 bridge-port bridge br0\nadmin@example:/config/> set interface e6 bridge-port bridge br0\nadmin@example:/config/> set interface e7 bridge-port bridge br0\nadmin@example:/config/> set interface e8 bridge-port bridge br0\nadmin@example:/config/> set interface e9 bridge-port bridge br0\nadmin@example:/config/> set interface e10 bridge-port bridge br0\nadmin@example:/config/>\n

        The interface status can be viewed using show interfaces after leaving configuration context. When configuring via SSH, first assign an IP address to br0 before leaving configuration context, e.g.

        admin@example:/config/> set interface br0 ipv6 enabled\n

        This enables IPv6 SLAAC, auto-configured address. Or skip leave and stay in configuration context until you have completed all the device setup, including setting IP address.

        admin@example:/config/> leave\nadmin@example:/>\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\nbr0             bridge\n\u2502               ethernet   UP          00:53:00:06:11:01\n\u251c e1            bridge     LOWER-DOWN\n\u251c e2            bridge     LOWER-DOWN\n\u251c e3            bridge     LOWER-DOWN\n\u251c e4            bridge     LOWER-DOWN\n\u251c e5            bridge     LOWER-DOWN\n\u251c e6            bridge     LOWER-DOWN\n\u251c e7            bridge     LOWER-DOWN\n\u251c e8            bridge     LOWER-DOWN\n\u251c e9            bridge     LOWER-DOWN\n\u2514 e10           bridge     FORWARDING\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n
        "},{"location":"scripting-prod/#assign-vlans-to-ports","title":"Assign VLANs to Ports","text":"

        Now, configure VLANs as outlined previously: default VID for ingress (PVID), which is done per port, and egress mode (untagged), which is done at the bridge level. See the VLAN bridges section for more information.

        admin@example:/>\nadmin@example:/> configure\nadmin@example:/config/> set interface e1 bridge-port pvid 10\nadmin@example:/config/> set interface e2 bridge-port pvid 10\nadmin@example:/config/> set interface e3 bridge-port pvid 20\nadmin@example:/config/> set interface e4 bridge-port pvid 20\nadmin@example:/config/> set interface e5 bridge-port pvid 30\nadmin@example:/config/> set interface e6 bridge-port pvid 30\nadmin@example:/config/> set interface e7 bridge-port pvid 40\nadmin@example:/config/> set interface e8 bridge-port pvid 40\nadmin@example:/config/> set interface e9 bridge-port pvid 50\nadmin@example:/config/> set interface e10 bridge-port pvid 50\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> edit bridge vlans\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 10 untagged e1\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 10 untagged e2\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 20 untagged e3\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 20 untagged e4\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 30 untagged e5\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 30 untagged e6\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 40 untagged e7\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 40 untagged e8\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 50 untagged e9\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 50 untagged e10\nadmin@example:/config/interface/br0/bridge/vlans/> leave\nadmin@example:/>\n

        Interface status would now should something like the following

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\nbr0             bridge\n\u2502               ethernet   UP          00:53:00:06:11:01\n\u251c e1            bridge     LOWER-DOWN  vlan:10u pvid:10\n\u251c e2            bridge     LOWER-DOWN  vlan:10u pvid:10\n\u251c e3            bridge     LOWER-DOWN  vlan:20u pvid:20\n\u251c e4            bridge     LOWER-DOWN  vlan:20u pvid:20\n\u251c e5            bridge     LOWER-DOWN  vlan:30u pvid:30\n\u251c e6            bridge     LOWER-DOWN  vlan:30u pvid:30\n\u251c e7            bridge     LOWER-DOWN  vlan:40u pvid:40\n\u251c e8            bridge     LOWER-DOWN  vlan:40u pvid:40\n\u251c e9            bridge     LOWER-DOWN  vlan:50u pvid:50\n\u2514 e10           bridge     FORWARDING  vlan:50u pvid:50\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n
        "},{"location":"scripting-prod/#connect-cables-and-test","title":"Connect Cables and Test","text":"

        We can now connect the PC to e1 and e10, while the other ports are patched according to above. We should see link up and FORWARDING on all ports in the bridge.

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\nbr0             bridge\n\u2502               ethernet   UP          00:53:00:06:11:01\n\u251c e1            bridge     FORWARDING  vlan:10u pvid:10\n\u251c e2            bridge     FORWARDING  vlan:10u pvid:10\n\u251c e3            bridge     FORWARDING  vlan:20u pvid:20\n\u251c e4            bridge     FORWARDING  vlan:20u pvid:20\n\u251c e5            bridge     FORWARDING  vlan:30u pvid:30\n\u251c e6            bridge     FORWARDING  vlan:30u pvid:30\n\u251c e7            bridge     FORWARDING  vlan:40u pvid:40\n\u251c e8            bridge     FORWARDING  vlan:40u pvid:40\n\u251c e9            bridge     FORWARDING  vlan:50u pvid:50\n\u2514 e10           bridge     FORWARDING  vlan:50u pvid:50\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        Here we use IPv6 ping all hosts (ff02::1) on PC interface eth1 to check reachability to the other interface of the PC.

        Tip

        We recommend using network namespaces (Linux only) on the PC to ensure that traffic is actually sent out to the switch, rather than being looped back internally. Alternatively, use two separate PCs.

        ~ $ ping -L ff02::1%eth1\nPING ff02::1%eth1(ff02::1%eth1) 56 data bytes\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=1 ttl=64 time=0.496 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=2 ttl=64 time=0.514 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=3 ttl=64 time=0.473 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=4 ttl=64 time=0.736 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=5 ttl=64 time=0.563 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=6 ttl=64 time=0.507 ms\n^C\n--- ff02::1%eth1 ping statistics ---\n6 packets transmitted, 6 received, 0% packet loss, time 5108ms\nrtt min/avg/max/mdev = 0.473/0.548/0.736/0.088 ms\n~ $\n

        We can verify that traffic goes through the switch by disconnecting one of the patch cables, e.g., between e4 and e5

        ~ $ ping -L ff02::1%eth1\nPING ff02::1%eth1(ff02::1%eth1) 56 data bytes\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=1 ttl=64 time=0.510 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=2 ttl=64 time=0.448 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=3 ttl=64 time=0.583 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=4 ttl=64 time=0.515 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=5 ttl=64 time=0.521 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=6 ttl=64 time=0.495 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=7 ttl=64 time=0.743 ms\n... Disconnecting patch cable, thus losing packets\n... and reconnecting again. Connectivity resumes.\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=16 ttl=64 time=0.961 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=17 ttl=64 time=0.513 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=18 ttl=64 time=0.794 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=19 ttl=64 time=0.755 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=20 ttl=64 time=0.779 ms\n^C\n--- ff02::1%eth1 ping statistics ---\n20 packets transmitted, 12 received, 40% packet loss, time 19432ms\nrtt min/avg/max/mdev = 0.448/0.634/0.961/0.156 ms\n~ $\n
        "},{"location":"scripting-prod/#set-ip-address","title":"Set IP Address","text":"

        The configuration so far does not provide a means to connect to the switch management via SSH or NETCONF, as the switch has no IP address. The example below shows how to add the switch to VLAN 10 (as used for ports e1 and e2) and enables IPv6.

        admin@example:/config/> edit interface vlan10\nadmin@example:/config/interface/vlan10/> set vlan lower-layer-if br0\nadmin@example:/config/interface/vlan10/> set ipv6 enabled\nadmin@example:/config/interface/vlan10/> show\ntype vlan;\nipv6 {\n  enabled true;\n}\nvlan {\n  tag-type c-vlan;\n  id 10;\n  lower-layer-if br0;\n}\nadmin@example:/config/interface/vlan10/>\nadmin@example:/config/interface/vlan10/> end\nadmin@example:/config/> edit interface br0 bridge vlans\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 10 tagged br0\nadmin@example:/config/interface/br0/bridge/vlans/> leave\nadmin@example:/>\n

        Interface vlan10 with an auto-configured IPv6 address should appear.

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\nbr0             bridge                 vlan:10t\n\u2502               ethernet   UP          00:53:00:06:11:01\n\u251c e1            bridge     FORWARDING  vlan:10u pvid:10\n\u251c e2            bridge     FORWARDING  vlan:10u pvid:10\n\u251c e3            bridge     FORWARDING  vlan:20u pvid:20\n\u251c e4            bridge     FORWARDING  vlan:20u pvid:20\n\u251c e5            bridge     FORWARDING  vlan:30u pvid:30\n\u251c e6            bridge     FORWARDING  vlan:30u pvid:30\n\u251c e7            bridge     FORWARDING  vlan:40u pvid:40\n\u251c e8            bridge     FORWARDING  vlan:40u pvid:40\n\u251c e9            bridge     FORWARDING  vlan:50u pvid:50\n\u2514 e10           bridge     FORWARDING  vlan:50u pvid:50\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nvlan10          ethernet   UP          00:53:00:06:11:01\n\u2502               ipv6                   fe80::0053:00ff:fe06:1101/64 (link-layer)\n\u2514 br0           ethernet   UP          00:53:00:06:11:01\nadmin@example:/>\n

        When pinging \"IPv6 all hosts\" from the PC, there should be two responses for every ping, one from the switch and one from the PC attached to e10.

        ~ $ ping -L ff02::1%eth1\nPING ff02::1%eth1(ff02::1%eth1) 56 data bytes\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=1 ttl=64 time=0.508 ms\n64 bytes from fe80::0053:00ff:fe06:1101%eth1: icmp_seq=1 ttl=64 time=0.968 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=2 ttl=64 time=0.866 ms\n64 bytes from fe80::0053:00ff:fe06:1101%eth1: icmp_seq=2 ttl=64 time=0.867 ms\n64 bytes from fe80::0053:00ff:fe06:1101%eth1: icmp_seq=3 ttl=64 time=0.467 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=3 ttl=64 time=0.469 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=4 ttl=64 time=0.452 ms\n64 bytes from fe80::0053:00ff:fe06:1101%eth1: icmp_seq=4 ttl=64 time=0.453 ms\n^C\n--- ff02::1%eth1 ping statistics ---\n4 packets transmitted, 4 received, +4 duplicates, 0% packet loss, time 3031ms\nrtt min/avg/max/mdev = 0.452/0.631/0.968/0.211 ms\n~ $\n

        It should now be possible to access the switch from the PC via SSH (or NETCONF).

        ~ $ ssh admin@fe80::0053:00ff:fe06:1101%eth1\nadmin@fe80::0053:00ff:fe06:1101%eth1's password:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-'\n\nRun the command 'cli' for interactive OAM\n\nadmin@example:~$ exit\n~ $\n

        See previous sections on backup and restore of the created configuration.

        "},{"location":"scripting-restconf/","title":"RESTCONF Scripting","text":""},{"location":"scripting-restconf/#scripting-with-restconf","title":"Scripting with RESTCONF","text":"

        RESTCONF provides a programmatic interface to both configuration and operational data over HTTPS. This guide shows practical examples using curl to interact with the RESTCONF API.

        All examples use the following conventions:

        • Host: example.local (replace with your device hostname/IP)
        • Credentials: admin:admin (default username:password)
        • HTTPS: Self-signed certificates require -k flag in curl
        "},{"location":"scripting-restconf/#helper-script","title":"Helper Script","text":"

        To simplify RESTCONF operations, create a curl.sh wrapper script:

        #!/bin/sh\n# RESTCONF CLI wrapper for curl\n\nHOST=${HOST:-infix.local}\nDATASTORE=running\nAUTH=admin:admin\n\nusage()\n{\n    cat <<EOF >&2\nUsage: $0 [-v] [-h HOST] [-d DATASTORE] [-u USER:PASS] METHOD XPATH [CURL_ARGS...]\n\nOptions:\n  -h HOST    Target host (default: infix.local)\n  -d DS      Datastore: running, operational, startup (default: running)\n  -u CREDS   Credentials as user:pass (default: admin:admin)\n  -v         Verbose mode, use it to display variables and curl command\n\nMethods: GET, POST, PUT, PATCH, DELETE\nEOF\n    exit \"$1\"\n}\n\ncheck()\n{\n    hint=\"Note: URL may be missing a module prefix (e.g., ietf-system:system)\"\n    resp=\"$1\"\n    url=\"$2\"\n\n    if ! command -v jq >/dev/null 2>&1; then\n        printf \"%s\\n\" \"$resp\"\n        return\n    fi\n\n    case \"$resp\" in\n        *\"Syntax error\"*)\n            path_only=\"${url#*//}\"\n\n            # Check for common URL error(s), e.g., missing module prefix\n            case \"$path_only\" in\n                *\":\"*)\n                    printf \"%s\\n\" \"$resp\" | jq .\n                    ;;\n                *)\n                    printf \"%s\\n\" \"$resp\" | jq --arg hint \"$hint\" \\\n                           '.[\"ietf-restconf:errors\"].error[] |= . + {comment: $hint}'\n                    ;;\n            esac\n            ;;\n        *)\n            printf \"%s\\n\" \"$resp\" | jq .\n            ;;\n    esac\n}\n\nwhile getopts \"h:d:u:v\" opt; do\n    case $opt in\n        h) HOST=\"$OPTARG\"      ;;\n        d) DATASTORE=\"$OPTARG\" ;;\n        u) AUTH=\"$OPTARG\"      ;;\n        v) VERBOSE=1           ;;\n        *) usage 1 ;;\n    esac\ndone\nshift $((OPTIND - 1))\n\nif [ $# -lt 2 ]; then\n    echo \"Error: METHOD and XPATH are required\" >&2\n    usage 1\nfi\n\nMETHOD=$1\nXPATH=$2\nshift 2\n\n# Ensure XPATH starts with /\ncase \"$XPATH\" in\n    /*) ;;\n    *) XPATH=\"/$XPATH\" ;;\nesac\n\ncase \"$DATASTORE\" in\n    running|startup)\n        URL=\"https://${HOST}/restconf/data${XPATH}\"\n        ;;\n    operational)\n        URL=\"https://${HOST}/restconf/data${XPATH}\"\n        ;;\n    *)\n        echo \"Error: Invalid datastore '$DATASTORE'. Use: running, operational, or startup\" >&2\n        exit 1\n        ;;\nesac\n\nif [ \"$VERBOSE\" ]; then\n    echo \"DS     : $DATASTORE\"\n    echo \"OP     : $METHOD\"\n    echo \"XPATH  : $XPATH\"\n    echo \"AUTH   : $AUTH\"\n    echo \"=> URL : $URL\"\n    set -x\nfi\n\nRESP=$(/usr/bin/curl --silent                           \\\n    --insecure                                          \\\n    --user \"${AUTH}\"                                    \\\n    --request \"${METHOD}\"                               \\\n    --header \"Content-Type: application/yang-data+json\" \\\n    --header \"Accept: application/yang-data+json\"       \\\n    \"$@\"                                                \\\n    \"${URL}\")\n\ncheck \"$RESP\" \"$URL\"\n

        Make it executable:

        ~$ chmod +x curl.sh\n

        This wrapper handles authentication, headers, SSL certificates, and URL construction, making commands much cleaner. You can override defaults with command-line options or environment variables:

        # Using command-line options\n~$ ./curl.sh -h 192.168.1.10 -d operational -u admin:secret GET /ietf-interfaces:interfaces\n\n# Using environment variables\n~$ HOST=192.168.1.10 ./curl.sh GET /ietf-system:system\n

        The examples below show both raw curl commands and the equivalent using curl.sh where applicable.

        "},{"location":"scripting-restconf/#http-methods-in-restconf","title":"HTTP Methods in RESTCONF","text":"

        RESTCONF uses standard HTTP methods to perform different operations on configuration and operational data. Understanding when to use each method is crucial for correct and safe operations.

        "},{"location":"scripting-restconf/#get-read-data","title":"GET - Read Data","text":"

        Retrieve configuration or operational data without making changes.

        When to use:

        • Reading current configuration
        • Querying operational state (interface statistics, routing tables, etc.)
        • Discovering what exists before making changes

        Characteristics:

        • Safe operation (no side effects)
        • Can be repeated without consequences
        • Works on both configuration and operational datastores

        Example:

        ~$ ./curl.sh -h example.local GET /ietf-interfaces:interfaces\n

        "},{"location":"scripting-restconf/#put-replace-resource","title":"PUT - Replace Resource","text":"

        Heads-up!

        PUT replaces everything - missing fields will be deleted!

        Replace an entire resource with new content.

        When to use:

        • Replacing entire datastore (backup/restore scenarios)
        • Complete reconfiguration of a container
        • When you want to ensure the resource matches exactly what you provide

        Characteristics:

        • Replaces all content at the target path
        • Any existing data not in the PUT request is removed
        • Requires complete, valid configuration
        • Dangerous if you don't include all necessary fields

        Example:

        ~$ ./curl.sh -h example.local PUT /ietf-system:system \\\n     -d '{\"ietf-system:system\":{\"hostname\":\"newhost\",\"contact\":\"admin@example.com\"}}'\n

        "},{"location":"scripting-restconf/#patch-mergeupdate-resource","title":"PATCH - Merge/Update Resource","text":"

        Partially update a resource by merging changes with existing data.

        When to use:

        • Modifying specific fields while preserving others
        • Working with path-based NACM permissions
        • Incremental configuration changes
        • Safer alternative to PUT for most use cases

        Characteristics:

        • Merges changes with existing configuration
        • Only specified fields are modified
        • Unspecified fields remain unchanged
        • Works with partial data structures
        • NACM-friendly (respects path-based permissions)

        Example:

        ~$ ./curl.sh -h example.local PATCH /ietf-interfaces:interfaces \\\n     -d '{\"ietf-interfaces:interfaces\":{\"interface\":[{\"name\":\"e0\",\"description\":\"WAN\"}]}}'\n

        Best practice

        Use PATCH instead of PUT for most configuration updates.

        "},{"location":"scripting-restconf/#post-create-new-resource","title":"POST - Create New Resource","text":"

        Create a new resource within a collection or invoke an RPC.

        When to use:

        • Adding new list entries (interfaces, users, routes, etc.)
        • Creating resources at specific paths
        • Invoking RPC operations (reboot, factory-reset, etc.)

        Characteristics:

        • Creates new resources
        • For lists: adds a new element
        • For RPCs: executes the operation
        • Returns error if resource already exists (for configuration)

        Example - Add IP address:

        ~$ ./curl.sh -h example.local POST \\\n     /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254 \\\n     -d '{\"prefix-length\": 32}'\n

        Example - Invoke RPC:

        ~$ curl -kX POST -u admin:admin \\\n     -H \"Content-Type: application/yang-data+json\" \\\n     https://example.local/restconf/operations/ietf-system:system-restart\n

        "},{"location":"scripting-restconf/#delete-remove-resource","title":"DELETE - Remove Resource","text":"

        Delete a resource or configuration element.

        When to use:

        • Removing interfaces, routes, users, etc.
        • Deleting specific configuration items
        • Cleaning up unwanted configuration

        Characteristics:

        • Removes the resource completely
        • Cannot be undone (except by reconfiguration)
        • Returns error if resource doesn't exist

        Example:

        ~$ ./curl.sh -h example.local DELETE \\\n     /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254\n

        "},{"location":"scripting-restconf/#quick-reference","title":"Quick Reference","text":"Method Use Case Safe? Idempotent? NACM Impact GET Read data \u2713 \u2713 Read permissions PUT Replace entire resource \u2717 \u2713 Full write access needed PATCH Merge/update fields \u2713 \u2713 Path-specific write POST Create new/invoke RPC \u2717 \u2717 Create/exec permissions DELETE Remove resource \u2717 \u2713 Delete permissions

        Key takeaways:

        • Use PATCH for updates - Safer than PUT, works with NACM
        • Use PUT sparingly - Only when you need complete replacement
        • GET is always safe - Read as much as you need
        • POST for creation/RPCs - Creating new items or executing operations
        • DELETE with care - Cannot be undone
        "},{"location":"scripting-restconf/#discovery-common-patterns","title":"Discovery & Common Patterns","text":"

        Before working with specific configuration items, you often need to discover what exists on the system. This section shows common discovery patterns and practical workflows.

        "},{"location":"scripting-restconf/#discovering-available-interfaces","title":"Discovering Available Interfaces","text":"

        List all interface names:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces 2>/dev/null | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][].name'\nlo\ne0\ne1\n

        This is essential for automation - interface names vary by platform (eth0, e1, enp0s3, etc.), so scripts should discover them rather than hardcode.

        "},{"location":"scripting-restconf/#get-api-capabilities","title":"Get API Capabilities","text":"

        Discover what YANG modules are available:

        ~$ curl -kX GET -u admin:admin \\\n        -H 'Accept: application/yang-data+json' \\\n        https://example.local/restconf/data/ietf-yang-library:yang-library\n

        This returns all supported YANG modules, revisions, and features.

        "},{"location":"scripting-restconf/#get-entire-running-configuration","title":"Get Entire Running Configuration","text":"

        Useful for exploration or backup:

        ~$ ./curl.sh -h example.local GET / -o backup.json\n
        "},{"location":"scripting-restconf/#common-workflow-patterns","title":"Common Workflow Patterns","text":""},{"location":"scripting-restconf/#pattern-1-find-interface-by-ip-address","title":"Pattern 1: Find interface by IP address","text":"

        Get all interfaces with IPs and search:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces 2>/dev/null \\\n     | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][] | select(.[\"ietf-ip:ipv4\"][\"address\"][]?.ip == \"192.168.1.100\") | .name'\n
        "},{"location":"scripting-restconf/#pattern-2-list-all-interfaces-that-are-down","title":"Pattern 2: List all interfaces that are down","text":"
        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces 2>/dev/null \\\n     | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][] | select(.[\"oper-status\"] == \"down\") | .name'\n
        "},{"location":"scripting-restconf/#pattern-3-get-statistics-for-all-interfaces","title":"Pattern 3: Get statistics for all interfaces","text":"
        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces 2>/dev/null \\\n     | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][] | \"\\(.name): RX \\(.statistics[\"in-octets\"]) TX \\(.statistics[\"out-octets\"])\"'\n

        Output:

        lo: RX 29320 TX 29320\ne0: RX 1847392 TX 892341\ne1: RX 0 TX 0\n
        "},{"location":"scripting-restconf/#pattern-4-check-if-interface-exists-before-configuring","title":"Pattern 4: Check if interface exists before configuring","text":"
        ~$ if ./curl.sh -h example.local GET /ietf-interfaces:interfaces/interface=eth0 2>/dev/null | grep -q \"ietf-interfaces:interface\"; then\n     echo \"Interface eth0 exists\"\n   else\n     echo \"Interface eth0 not found\"\n   fi\n
        "},{"location":"scripting-restconf/#configuration-operations","title":"Configuration Operations","text":""},{"location":"scripting-restconf/#read-hostname","title":"Read Hostname","text":"

        Example of fetching JSON configuration data:

        Using curl directly:

        ~$ curl -kX GET -u admin:admin \\\n        -H 'Accept: application/yang-data+json' \\\n        https://example.local/restconf/data/ietf-system:system/hostname\n{\n  \"ietf-system:system\": {\n    \"hostname\": \"foo\"\n  }\n}\n

        Using curl.sh:

        ~$ ./curl.sh -h example.local GET /ietf-system:system/hostname\n{\n  \"ietf-system:system\": {\n    \"hostname\": \"foo\"\n  }\n}\n
        "},{"location":"scripting-restconf/#set-hostname","title":"Set Hostname","text":"

        Example of updating configuration with inline JSON data:

        Using curl directly:

        ~$ curl -kX PATCH -u admin:admin \\\n     -H 'Content-Type: application/yang-data+json' \\\n     -d '{\"ietf-system:system\":{\"hostname\":\"bar\"}}' \\\n     https://example.local/restconf/data/ietf-system:system\n

        Using curl.sh:

        ~$ ./curl.sh -h example.local PATCH /ietf-system:system \\\n     -d '{\"ietf-system:system\":{\"hostname\":\"bar\"}}'\n
        "},{"location":"scripting-restconf/#update-interface-description","title":"Update Interface Description","text":"

        PATCH allows you to modify specific parts of the configuration without replacing the entire container. This is particularly useful when working with NACM (Network Access Control Model) permissions that grant access to specific paths.

        Why use PATCH instead of PUT:

        • Partial updates: Only changes specified fields, preserves others
        • NACM-friendly: Works with path-based access control rules
        • Safer: Reduces risk of accidentally removing unrelated configuration

        Example - Modify an interface description:

        ~$ curl -kX PATCH -u admin:admin \\\n     -H 'Content-Type: application/yang-data+json' \\\n     -d '{\"ietf-interfaces:interfaces\":{\"interface\":[{\"name\":\"e0\",\"description\":\"WAN Port\"}]}}' \\\n     https://example.local/restconf/data/ietf-interfaces:interfaces\n

        Using curl.sh:

        ~$ ./curl.sh -h example.local PATCH /ietf-interfaces:interfaces \\\n     -d '{\"ietf-interfaces:interfaces\":{\"interface\":[{\"name\":\"e0\",\"description\":\"WAN Port\"}]}}'\n

        Formatted for readability:

        ~$ curl -kX PATCH -u admin:admin \\\n     -H 'Content-Type: application/yang-data+json' \\\n     -d '{\n       \"ietf-interfaces:interfaces\": {\n         \"interface\": [\n           {\n             \"name\": \"e0\",\n             \"description\": \"WAN Port\"\n           }\n         ]\n       }\n     }' \\\n     https://example.local/restconf/data/ietf-interfaces:interfaces\n

        Key points:

        • PATCH URL targets the container (/interfaces), not a specific interface
        • JSON body includes the full structure with the interface array
        • Only specified fields are modified; other interface settings remain unchanged
        • The name field identifies which interface to update

        Verify the change:

        ~$ ./curl.sh -h example.local GET /ietf-interfaces:interfaces/interface=e0 2>/dev/null \\\n     | jq '.[\"ietf-interfaces:interface\"][0].description'\n\"WAN Port\"\n
        "},{"location":"scripting-restconf/#add-ip-address-to-interface","title":"Add IP Address to Interface","text":"

        Add an IP address to the loopback interface:

        ~$ ./curl.sh -h example.local POST \\\n     /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254 \\\n     -d '{ \"prefix-length\": 32 }'\n
        "},{"location":"scripting-restconf/#delete-ip-address-from-interface","title":"Delete IP Address from Interface","text":"

        Remove an IP address from the loopback interface:

        ~$ ./curl.sh -h example.local DELETE \\\n     /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254\n
        "},{"location":"scripting-restconf/#copy-running-to-startup","title":"Copy Running to Startup","text":"

        No copy command available yet to copy between datastores, and the Rousette back-end also does not support \"write-through\" to the startup datastore.

        To save running-config to startup-config, fetch running to a local file and then update startup with it:

        Using curl directly:

        ~$ curl -kX GET -u admin:admin -o running-config.json \\\n        -H 'Accept: application/yang-data+json'       \\\n         https://example.local/restconf/ds/ietf-datastores:running\n\n~$ curl -kX PUT -u admin:admin -d @running-config.json \\\n        -H 'Content-Type: application/yang-data+json'  \\\n        https://example.local/restconf/ds/ietf-datastores:startup\n

        Using curl.sh:

        ~$ ./curl.sh -h example.local GET / -o running-config.json\n~$ ./curl.sh -h example.local -d startup PUT / -d @running-config.json\n
        "},{"location":"scripting-restconf/#operational-data","title":"Operational Data","text":""},{"location":"scripting-restconf/#read-interface-configuration","title":"Read Interface Configuration","text":"

        Get the running configuration for the loopback interface:

        ~$ ./curl.sh -h example.local GET /ietf-interfaces:interfaces/interface=lo\n
        "},{"location":"scripting-restconf/#read-interface-operational-state","title":"Read Interface Operational State","text":"

        Get operational data (state, statistics, etc.) for an interface:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces/interface=lo\n

        This includes administrative and operational state, MAC address, MTU, and statistics counters.

        "},{"location":"scripting-restconf/#read-interface-statistics","title":"Read Interface Statistics","text":"

        Extract specific statistics using jq:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces/interface=eth0 2>/dev/null \\\n     | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][0][\"statistics\"][\"in-octets\"]'\n
        "},{"location":"scripting-restconf/#list-all-interfaces","title":"List All Interfaces","text":"

        Get operational data for all interfaces:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces\n
        "},{"location":"scripting-restconf/#read-routing-table","title":"Read Routing Table","text":"

        Get the IPv4 routing table:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-routing:routing/ribs/rib=ipv4-default\n
        "},{"location":"scripting-restconf/#read-ospf-state","title":"Read OSPF State","text":"

        Get OSPF operational data (neighbors, routes, etc.):

        ~$ ./curl.sh -h example.local -d operational GET /ietf-routing:routing/control-plane-protocols/control-plane-protocol=ietf-ospf:ospfv2,default\n

        Or get just the neighbor information:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-routing:routing/control-plane-protocols/control-plane-protocol=ietf-ospf:ospfv2,default/ietf-ospf:ospf/areas/area=0.0.0.0/interfaces\n
        "},{"location":"scripting-restconf/#system-operations-rpcs","title":"System Operations (RPCs)","text":""},{"location":"scripting-restconf/#factory-reset","title":"Factory Reset","text":"

        Reset the system to factory defaults:

        ~$ curl -kX POST -u admin:admin \\\n        -H \"Content-Type: application/yang-data+json\" \\\n        https://example.local/restconf/operations/ietf-factory-default:factory-reset\ncurl: (56) OpenSSL SSL_read: error:0A000126:SSL routines::unexpected eof while reading, errno 0\n

        Note: The connection error is expected - the device resets immediately.

        "},{"location":"scripting-restconf/#system-reboot","title":"System Reboot","text":"

        Reboot the system:

        ~$ curl -kX POST -u admin:admin \\\n        -H \"Content-Type: application/yang-data+json\" \\\n        https://example.local/restconf/operations/ietf-system:system-restart\n
        "},{"location":"scripting-restconf/#set-date-and-time","title":"Set Date and Time","text":"

        Example of an RPC that takes input/arguments:

        ~$ curl -kX POST -u admin:admin \\\n        -H \"Content-Type: application/yang-data+json\" \\\n        -d '{\"ietf-system:input\": {\"current-datetime\": \"2024-04-17T13:48:02-01:00\"}}' \\\n        https://example.local/restconf/operations/ietf-system:set-current-datetime\n

        Verify the change with SSH:

        ~$ ssh admin@example.local 'date'\nWed Apr 17 14:48:12 UTC 2024\n
        "},{"location":"scripting-restconf/#advanced-examples","title":"Advanced Examples","text":""},{"location":"scripting-restconf/#makefile-for-common-operations","title":"Makefile for Common Operations","text":"

        Create a Makefile to simplify common operations:

        HOST ?= infix.local\n\nlo-running:\n    ./curl.sh -h $(HOST) GET /ietf-interfaces:interfaces/interface=lo\n\nlo-operational:\n    ./curl.sh -h $(HOST) -d operational GET /ietf-interfaces:interfaces/interface=lo\n\nlo-add-ip:\n    ./curl.sh -h $(HOST) POST \\\n        /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254 \\\n        -d '{ \"prefix-length\": 32 }'\n\nlo-del-ip:\n    ./curl.sh -h $(HOST) DELETE \\\n        /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254\n\n%-stats:\n    @./curl.sh -h $(HOST) -d operational GET /ietf-interfaces:interfaces/interface=$* 2>/dev/null \\\n        | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][0][\"statistics\"][\"in-octets\"]'\n\n%-monitor:\n    while sleep 0.2; do make -s HOST=$(HOST) $*-stats; done \\\n        | ttyplot -t \"$(HOST):$* in-octets\" -r\n

        Usage examples:

        # Get loopback operational state\n~$ make lo-operational\n\n# Add IP to loopback\n~$ make lo-add-ip\n\n# Get eth0 statistics\n~$ make eth0-stats\n\n# Monitor eth0 traffic in real-time (requires ttyplot)\n~$ make eth0-monitor\n

        You can override the host:

        ~$ make HOST=192.168.1.10 lo-operational\n
        "},{"location":"scripting-restconf/#monitoring-interface-traffic","title":"Monitoring Interface Traffic","text":"

        The %-monitor target demonstrates real-time monitoring by polling interface statistics and piping to ttyplot for visualization. Install ttyplot with:

        ~$ sudo apt install ttyplot\n

        Then monitor any interface:

        ~$ make eth0-monitor\n

        This creates a live ASCII graph of incoming octets on eth0.

        "},{"location":"scripting-restconf/#references","title":"References","text":"
        • RESTCONF Protocol (RFC 8040)
        • YANG Data Modeling Language (RFC 7950)
        • ietf-interfaces YANG module
        • ietf-routing YANG module
        • ietf-system YANG module
        "},{"location":"scripting-sysrepocfg/","title":"Legacy Scripting","text":"

        Deprecated - Use RESTCONF Instead

        This legacy interface requires elevated privileges (sudo sysrepocfg) even for admin users and is no longer intended for production use. All scripting should use RESTCONF as the standard management interface. There is however an NACM compliant replacement copy that can be used without sudo.

        "},{"location":"scripting-sysrepocfg/#legacy-scripting","title":"Legacy Scripting","text":"

        Although not the primary interface for Infix, it is possible to interact with the system using raw sysrepocfg commands. This way you get to interact directly with the YANG models when logged in to Infix. Thus, a set config, read config, read status and an RPC can be conducted using sysrepocfg for any supported YANG model.

        See sysrepocfg for more information. Examples below will utilize:

        • sysrepocfg -I FILE -fjson -d DATABASE to import/write a JSON formatted configuration file to the specified database.
        • sysrepocfg -E FILE -fjson -d DATABASE to edit/merge JSON formatted configuration in FILE with the specified database.
        • sysrepocfg -R FILE -fjson to execute remote procedure call (RPC) defined in FILE (JSON formatted).
        • sysrepocfg -X -fjson -d DATABASE -x xpath to read configuration or status from specified database.

        For importing (-I) and editing (-E), -d running is typically used in examples below. Specify -d startup to apply changes to startup configuration. Exporting (-X) could operate on configuration (e.g., -d running) or status (-d operational).

        Some commands require a file as input. In the examples below we assume it has been transferred to the device in advance, e.g. using scp:

        ~$ cat file.json\n{\n   \"ietf-factory-default:factory-reset\": {\n   }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$\n
        "},{"location":"scripting-sysrepocfg/#factory-reset","title":"Factory Reset","text":"
        ~$ cat file.json\n{\n   \"ietf-factory-default:factory-reset\": {\n   }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -fjson -R /tmp/file.json'\n^C\n~$\n

        See Factory Reset for another (simpler) alternative.

        If it is only wished to copy factory config to running config the following RPC is available

        ~$ cat file.json\n{\n   \"infix-factory-default:factory-default\": {\n   }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -fjson -R /tmp/file.json'\n^C\n~$\n
        "},{"location":"scripting-sysrepocfg/#system-reboot","title":"System Reboot","text":"
        ~$ cat /tmp/file.json\n{\n   \"ietf-system:system-restart\": {\n   }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -fjson -R /tmp/file.json'\n~$\n

        See System Reboot for another (simpler) alternative.

        "},{"location":"scripting-sysrepocfg/#set-date-and-time","title":"Set Date and Time","text":"
        ~$ ssh admin@example.local 'date'\nSun Nov 20 10:20:23 UTC 2005\n~$ cat file.json\n{\n   \"ietf-system:set-current-datetime\": {\n      \"current-datetime\": \"2024-04-17T13:48:02-01:00\"\n   }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -fjson -R /tmp/file.json'\n~$ ssh admin@example.local 'date'\nWed Apr 17 14:48:12 UTC 2024\n~$\n

        See Set Date and Time for another (simpler) alternative.

        "},{"location":"scripting-sysrepocfg/#remote-control-of-ethernet-ports","title":"Remote Control of Ethernet Ports","text":"

        Reading administrative status of interface e0 of running configuration.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d running -e report-all \\\n       -x \\\"/ietf-interfaces:interfaces/interface[name='e0']/enabled\\\"'\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\",\n        \"enabled\": true\n      }\n    ]\n  }\n}\n~$\n

        Note

        Without -e report-all argument the line \"enabled: true would not be shown as true is default.

        ~$ ssh admin@example.local \"sysrepocfg -X -fjson -d running \\\n       -x \\\"/ietf-interfaces:interfaces/interface[name='e0']/enabled\\\"\"\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\"\n      }\n    ]\n  }\n}\n~$\n

        Setting the administrative status of interface e0 of running configuration.

        $ cat file.json\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\",\n        \"enabled\": false\n      }\n    ]\n  }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -E /tmp/file.json -fjson -d running'\n~$\n
        "},{"location":"scripting-sysrepocfg/#enabledisable-dhcpv4-client","title":"Enable/Disable DHCPv4 client","text":"

        Enabling DHCPv4 client on interface e0, with current default options.

        ~$ cat /tmp/file.json\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\",\n        \"ietf-ip:ipv4\": {\n          \"infix-dhcp-client:dhcp\": {}\n        }\n      }\n    ]\n  }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -E /tmp/file.json -fjson -d running'\n~$\n

        Disabling DHCPv4 client on interface e0 (remove the dhcp container).

        ~$ cat /tmp/file.json\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\",\n        \"ietf-ip:ipv4\": {}\n      }\n    ]\n  }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -E /tmp/file.json -fjson -d running'\n~$\n

        To fully remove the DHCPv4 client configuration, remove the infix-dhcp-client:dhcp container from the interface's ipv4 configuration.

        "},{"location":"scripting-sysrepocfg/#enabledisable-ipv6","title":"Enable/Disable IPv6","text":"

        IPv6 is typically enabled on all interfaces by default. The example below shows IPv4 and IPv6 addresses assigned on e0.

        ~$ ssh admin@example.local 'ip addr show dev e0'\n2: e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000\n    link/ether 02:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff\n    inet 10.0.2.15/24 scope global proto dhcp e0\n       valid_lft forever preferred_lft forever\n    inet6 fec0::ff:fe00:0/64 scope site dynamic mngtmpaddr proto kernel_ra\n       valid_lft 86380sec preferred_lft 14380sec\n    inet6 fe80::ff:fe00:0/64 scope link proto kernel_ll\n       valid_lft forever preferred_lft forever\n~$\n

        IPv6 is enabled/disabled per interface. The example below disables IPv6 on interface e0.

        ~$ cat /tmp/file.json\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\",\n        \"ietf-ip:ipv6\": {\n          \"enabled\": false\n        }\n      }\n    ]\n  }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -E /tmp/file.json -fjson -d running'\n~$ ssh admin@example.local 'ip addr show dev e0'\n2: e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000\n    link/ether 02:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff\n    inet 10.0.2.15/24 scope global proto dhcp e0\n       valid_lft forever preferred_lft forever\n~$\n
        "},{"location":"scripting-sysrepocfg/#change-a-binary-setting","title":"Change a Binary Setting","text":"

        A YANG binary type setting is Base64 encoded and requires a little bit more tricks. We take the opportunity to showcase a shell script helper: /usr/bin/text-editor, which works just like the text-editor command in the CLI, but this one takes an XPath argument to the binary leaf to edit.

        Stripped down, it looks something like this:

        if tmp=$(sysrepocfg -G \"$xpath\"); then\n    file=$(mktemp)\n\n    echo \"$tmp\" | base64 -d > \"$file\"\n    if edit \"$file\"; then\n        tmp=$(base64 -w0 < \"$file\")\n        sysrepocfg -S \"$xpath\" -u \"$tmp\"\n    fi\n\n    rm -f \"$file\"\nelse\n    echo \"Failed to retrieve value for $xpath\"\n    exit 1\nfi\n

        An example container configuration, with an embedded file that is mounted to /var/www/index.html can look like this:

          \"infix-containers:containers\": {\n    \"container\": [\n      {\n        \"name\": \"web\",\n        \"image\": \"oci-archive:/lib/oci/curios-httpd-latest.tar.gz\",\n        \"hostname\": \"web\",\n        \"network\": {\n          \"interface\": [\n            {\n              \"name\": \"veth-sys0\"\n            }\n          ]\n        },\n        \"mount\": [\n          {\n            \"name\": \"index.html\",\n            \"content\": \"PCFET0NUWVBFIGh0bWwjibberish.shortened.down==\",\n            \"target\": \"/var/www/index.html\"\n          }\n        ]\n      }\n    ]\n  }\n

        The command to edit this file, and restart the container with the new contents, look like this:

        admin@infix:~$ cfg edit \"/infix-containers:containers/container[name='web']/mount[name='index.html']/content\"\n
        "},{"location":"scripting-sysrepocfg/#backup-configuration","title":"Backup Configuration","text":"

        Displaying running or startup configuration is possible with sysrepocfg -X, as shown below.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d running'\n{\n  \"ieee802-dot1ab-lldp:lldp\": {\n    \"infix-lldp:enabled\": true\n...\n~$\n

        An example for backing up startup configuration from remote PC.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d startup > /tmp/backup.json'\n~$ scp admin@example.local:/tmp/backup.json .\n~$\n

        Or possibly skip intermediate storage of file

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d startup' > backup.json\n~$\n

        A final example is to only use scp. This is simpler, but only works to backup the startup configuration (not running).

        ~$ scp admin@example.local:/cfg/startup-config.cfg backup.json\n~$\n
        "},{"location":"scripting-sysrepocfg/#restore-configuration","title":"Restore Configuration","text":"

        To restore a backup configuration to startup, the simplest way is to use scp and reboot as shown below

        ~$ scp admin@example.local:/cfg/startup-config.cfg backup.json\n~$ ssh admin@example.local 'reboot'\nConnection to switch.local closed by remote host.\n~$\n

        An alternative method to restore a backup configuration is to use the sysrepocfg -I FILE (import) command.

        The example below imports the backup configuration to startup, and reboots the unit.

        ~$ scp backup.json admin@example.local:/tmp/\n~$ ssh admin@example.local 'sudo sysrepocfg -I /tmp/backup.json -fjson -d startup'\n~$ ssh admin@example.local 'reboot'\nConnection to switch.local closed by remote host.\n~$\n

        Note

        The login credentials (hash) for the admin user are stored as part of the configuration file. When replacing a switch and applying the backed up configuration from the former switch, the password on the replacement unit will also change.

        "},{"location":"scripting-sysrepocfg/#copy-running-to-startup","title":"Copy Running to Startup","text":"

        The following command reads out the running config via sysrepocfg -X and writes the result to the startup configuration.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d running > /cfg/startup-config.cfg'\n~$\n

        An alternative is to write it to a temporary file, and use sysrepocfg -I to import it to startup.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d running > /tmp/running.json'\n~$ ssh admin@example.local 'sysrepocfg -I /tmp/running.json -fjson -d startup'\n~$\n
        "},{"location":"scripting-sysrepocfg/#read-hardware-information","title":"Read Hardware Information","text":"

        The IETF Hardware YANG model has been augmented for ONIE formatted production data stored in EEPROMs, if available. For details, see the VPD documentation and the ietf-hardware and infix-hardware YANG models.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d operational -x /ietf-hardware:hardware'\n{\n  \"ietf-hardware:hardware\": {\n    \"component\": [\n      {\n        \"name\": \"product\",\n        \"class\": \"infix-hardware:vpd\",\n        \"serial-num\": \"12345\",\n        \"model-name\": \"Switch2010\",\n        \"mfg-date\": \"2024-01-30T16:42:37+00:00\",\n        \"infix-hardware:vpd-data\": {\n          \"product-name\": \"Switch2010\",\n          \"part-number\": \"ABC123-001\",\n          \"serial-number\": \"007\",\n          \"mac-address\": \"00:53:00:01:23:45\",\n          \"manufacture-date\": \"01/30/2024 16:42:37\",\n          \"num-macs\": 11,\n          \"manufacturer\": \"ACME Production\",\n          \"vendor\": \"SanFran Networks\"\n        }\n      },\n      {\n        \"name\": \"USB\",\n        \"class\": \"infix-hardware:usb\",\n        \"state\": {\n          \"admin-state\": \"unlocked\",\n          \"oper-state\": \"enabled\"\n        }\n      }\n    ]\n  }\n}\n~$\n
        "},{"location":"scripting/","title":"Introduction","text":""},{"location":"scripting/#scripting-infix","title":"Scripting Infix","text":"

        In some situations a user cannot, or does not want to, use the NETCONF API for interacting with Infix. Examples include production tasks and simpler remote scripting jobs to one or more remote devices.

        This document assumes you have the password for the admin user, and that you can connect to the device. Please see Finding my Device for help on locating it.

        Furthermore, the example commands shown here that are execute from a PC to a remote device over SSH, use Linux/UNIX. With advances lately in both Windows and macOS, many of the user friendly tools previously only available in Linux are now available there too.

        The shell prompt for the PC laptop side:

        ~$\n

        The shell prompt when logged in to an Infix device:

        admin@example:~$\n

        Note

        The shell script commands used here are the raw variants which the CLI usually wraps in a warm and snugly blanket. Meaning they may change over time, while the CLI wrappers do not. That being said, please let us know if you find any inconsistencies.

        "},{"location":"scripting/#tips","title":"Tips","text":"
        • Ensure the admin user does not have clish as login shell
        • Enable SSH key authentication
        • Deploy same SSH public key to many Infix devices
        • Secure your private SSH key using, e.g., ssh-agent

        The ssh-keygen command, used to create the private/public key pair, asks for a passphrase and although this is technically optional it is highly recommended to set one. For ease of use, in particular when scripting, use ssh-agent to avoid retyping the passphrase for every command.

        Useful links on SSH, keys, and using ssh-agent:

        • https://en.wikipedia.org/wiki/Ssh-agent
        • https://www.cyberciti.biz/faq/how-to-use-ssh-agent-for-authentication-on-linux-unix/
        • https://goteleport.com/blog/how-to-use-ssh-agent-safely/
        "},{"location":"scripting/#admin-user-not-authorized","title":"Admin User Not Authorized","text":"

        All system services and critical configuration files are owned by the locked-down root user. It is not possible to activate the root user account for remote logins. Instead, use admin user and the sudo command prefix.

        Here we are logged in to an example device:

        admin@example:~$ cp /cfg/startup-config.cfg /cfg/backup-config.cfg\ncp: can't create '/cfg/backup-config.cfg': Permission denied\nadmin@example:~$ sudo cp /cfg/startup-config.cfg /cfg/backup-config.cfg\n
        "},{"location":"scripting/#examples","title":"Examples","text":"

        The following example commands are run from the PC over SSH. The following is a very brief introduction.

        The notation is ssh username@address, where address can be an IPv4 or IPv6 address, a DNS name, or an mDNS name, e.g. infix.local. In the case of IPv6: address%interface, where interface differs between operating systems. On Linux and macOS the interface name is used, but on Windows the interface index1 is used.

        "},{"location":"scripting/#logging-in-to-a-device","title":"Logging in to a device","text":"
        ~$ ssh admin@fe80::ff:fe00:0%eth0\nThe authenticity of host 'fe80::ff:fe00:0%eth0 (fe80::ff:fe00:0%eth0)' can't be established.\nED25519 key fingerprint is SHA256:5/9mw64jhmYyD8MD+SwrsG3RXMBbP48pDe2T8bg14RQ.\nThis key is not known by any other names\nAre you sure you want to continue connecting (yes/no/[fingerprint])? yes\nWarning: Permanently added 'fe80::ff:fe00:0%eth0' (ED25519) to the list of known hosts.\nadmin@fe80::ff:fe00:0%eth0's password: *****\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-'\n\nRun the command 'cli' for interactive OAM\n\nadmin@infix-c0-ff-ee:/>\n
        "},{"location":"scripting/#running-a-remote-command","title":"Running a remote command","text":"
        ~$ ssh admin@fe80::ff:fe00:0%eth0 echo hej\nadmin@fe80::ff:fe00:0%eth0's password: *****\nhej\n
        "},{"location":"scripting/#made-easy-with-mdns","title":"Made Easy with mDNS","text":"

        Connecting to networked devices using IP addresses is the way many people are used to. The above example with IPv6 tend to scare off people, so for the rest of this document we'll use the mDNS name instead:

        ~$ ssh admin@infix.local%eth0\nThe authenticity of host 'infix.local%eth0' can't be established.\nAre you sure you want to continue connecting (yes/no/[fingerprint])? yes\n...\nadmin@infix:/>\n
        "},{"location":"scripting/#factory-reset","title":"Factory Reset","text":"

        The command option -y disables any \"are you sure?\" interaction and immediately triggers a factory reset and reboot of the device. It is when the device boots up it erases all writable storage.

        ~$ ssh admin@infix.local%eth0 factory -y\nadmin@infix.local%eth0's password: *****\n
        "},{"location":"scripting/#system-reboot","title":"System Reboot","text":"
        ~$ ssh admin@infix.local%eth0 reboot\nadmin@infix.local%eth0's password: *****\n
        "},{"location":"scripting/#set-date-and-time","title":"Set Date and Time","text":"

        Devices running Infix may have their system time completely off and this can cause problems for upgrading and when accessing the web interface over HTTPS (certificate validation looks at start and end dates).

        To set the device's system time, and sync that with the RTC: use the PCs current time as argument:

        ~$ ssh admin@infix.local%eth0 \"sudo date -s '2024-03-20 18:14+01:00' && \\\n                               sudo hwclock -w -u\"\nadmin@infix.local%eth0's password: *****\n

        Tip

        The -u option ensures saving system time to the RTC in UTC time.

        Verify that the change took:

        ~$ ssh admin@infix.local%eth0 date\nadmin@infix.local%eth0's password: *****\nWed Mar 20 17:14:47 UTC 2024\n
        "},{"location":"scripting/#remote-control-of-ethernet-ports","title":"Remote Control of Ethernet Ports","text":"

        There are two ways to do it:

        1. Change the configuration without saving it to startup-config
        2. Change the operational state

        The first involves sending a NETCONF command/config in XML. The second we will cover here. We start by querying available interfaces (ports) on the remote system:

        ~$ ssh admin@infix.local%qtap0 ip -br a\nadmin@infix.local%qtap0's password:\nlo               UP             127.0.0.1/8 ::1/128\ne0               UP             fe80::ff:fe00:0/64\ne1               UP\ne2               UP\ne3               UP\ne4               UP\ne5               UP             fe80::ff:fe00:5/64\ne6               UP             fe80::ff:fe00:6/64\ne7               UP             fe80::ff:fe00:7/64\ne8               UP             fe80::ff:fe00:8/64\ne9               UP             192.168.2.200/24 fe80::ff:fe00:9/64\nbr0              UP\n

        Here we see a loopback interface (lo), ten Ethernet ports (e0-e9) and a bridge (br0). From this quick glance we can guess that the ports e1-e4 are bridged (you can verify this with the remote command bridge link) because they do not have a link-local IPv6 address.

        I know it's port e6 that I want to take down:

        ~$ ssh admin@infix.local%qtap0 ip link set e6 down\nadmin@infix.local%qtap0's password:\nRTNETLINK answers: Operation not permitted\n~$ ssh admin@infix.local%qtap0 sudo ip link set e6 down\nadmin@infix.local%qtap0's password:\n

        Changing the operational link state of a port is a privileged command, so we have to prefix our command with sudo.

        Inspecting the link state again show the port is now down:

        ~$ ssh admin@infix.local%qtap0 ip -br a\nadmin@infix.local%qtap0's password:\nlo               UP             127.0.0.1/8 ::1/128\ne0               UP             fe80::ff:fe00:0/64\ne1               UP\ne2               UP\ne3               UP\ne4               UP\ne5               UP             fe80::ff:fe00:5/64\ne6               DOWN\ne7               UP             fe80::ff:fe00:7/64\ne8               UP             fe80::ff:fe00:8/64\ne9               UP             192.168.2.200/24 fe80::ff:fe00:9/64\nbr0              UP\n
        "},{"location":"scripting/#check-network-connectivity","title":"Check Network Connectivity","text":"

        Say you want to perform a [System Upgrade][#system-upgrade] and it just does not work, a good place to start troubleshooting is ensuring the device can actually reach the upgrade server.

        We run the following ping command remotely on the device:

        ~$ ssh admin@infix.local%eth0 ping -c 3 server.local\nadmin@infix.local%qtap0's password: *****\nPING server.local (192.168.2.42) 56(84) bytes of data.\n64 bytes from server.local: icmp_seq=1 ttl=64 time=0.201 ms\n64 bytes from server.local: icmp_seq=2 ttl=64 time=0.432 ms\n64 bytes from server.local: icmp_seq=3 ttl=64 time=0.427 ms\n\n--- server.local ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2050ms\nrtt min/avg/max/mdev = 0.201/0.353/0.432/0.107 ms\n

        Here we get a reply, so whatever is the issue with the upgrade was not hiding behind a connectivity issue at least.

        "},{"location":"scripting/#system-upgrade","title":"System Upgrade","text":"

        The underlying software that handles upgrades is called RAUC. To trigger an upgrade you (currently) need an FTP/TFTP or HTTP/HTTPS server where RAUC can fetch the upgrade from. In this example we use an FTP server to upgrade the currently inactive \"slot\":

        ~$ ssh admin@infix.local%eth0 rauc install ftp://server.local/infix-aarch64-24.06.0.pkg\nadmin@infix.local%eth0's password: *****\ninstalling\n  0% Installing\n  0% Determining slot states\n 20% Determining slot states done.\n 20% Checking bundle\n 20% Verifying signature\n 40% Verifying signature done.\n 40% Checking bundle done.\n 40% Checking manifest contents\n 60% Checking manifest contents done.\n 60% Determining target install group\n 80% Determining target install group done.\n 80% Updating slots\n 80% Checking slot rootfs.1\n 90% Checking slot rootfs.1 done.\n 90% Copying image to rootfs.1\n 99% Copying image to rootfs.1 done.\n 99% Updating slots done.\n100% Installing done.\nidle\nInstalling `ftp://server.local/infix-aarch64-24.06.0.pkg` succeeded\n~$\n

        The inactive slot is now marked active and will be used on the next boot. To upgrade the partition we booted from, we must first reboot.

        For more information, see the Boot Procedure document.

        Alternative:

        If you know your device has sufficient storage, eMMC or RAM disk (check with the remote command df -h), you can also copy the .pkg file to the device instead of having to set up an FTP/TFTP or HTTP/HTTPS server.

        Create an upload directory where admin has write permission:

        ~$ ssh admin@infix.local%eth0 \"sudo mkdir /var/tmp/upload; \\\n                               sudo chown admin /var/tmp/upload\"\nadmin@infix.local%eth0's password:\n

        Copy the file with secure copy, first we show the nasty IPv6 version of the command:

        ~$ scp infix-aarch64-24.06.0.pkg admin@\\[fe80::ff:fe00:0%eth0\\]:/var/tmp/upload/\nadmin@fe80::ff:fe00:0%eth0's password:\ninfix-aarch64-24.06.0.pkg                              100%  296   601.4KB/s   00:00\n

        And the upgrade command itself:

        ~$ ssh admin@infix.local%eth0 rauc install /var/tmp/upload/infix-aarch64-24.06.0.pkg\nadmin@infix.local%eth0's password: *****\n.\n.\n.\n

        Remember to remove the file from the upload directory when you are done, this can be done before or after the reboot to activate the upgrade. If you want to upgrade both \"slots\", then you can of course keep the file until you are done (provided the upload directory was created on persistent storage).

        ~$ ssh admin@infix.local%eth0 rm /var/tmp/upload/infix-aarch64-24.06.0.pkg\nadmin@infix.local%eth0's password: *****\n~$\n
        "},{"location":"scripting/#controlling-leds","title":"Controlling LEDs","text":"

        As part of production testing, LED verification is often expected to be performed. Infix uses standard Linux support for LED management, where LEDs appear in the file system under /sys/class/leds and can be controlled using echo command. sudo privileges are required.

        When interacting with LEDs this way, first disable the Infix iitod daemon to avoid conflicting LED control.

        ~$ ssh admin@example.local 'initctl stop iitod'\n

        Then run the test, e.g., visually control that a red LED labeled 'LAN' is working.

        ~$ ssh admin@example.local 'echo none | sudo tee /sys/class/leds/red\\:lan/trigger'\n~$ ssh admin@example.local 'echo 1    | sudo tee /sys/class/leds/red\\:lan/brightness'\n

        To turn off the same LED, run the following commands.

        ~$ ssh admin@example.local 'echo none | sudo tee /sys/class/leds/red\\:lan/trigger'\n~$ ssh admin@example.local 'echo 0    | sudo tee /sys/class/leds/red\\:lan/brightness'\n

        When done with LED testing, enable Infix iitod daemon again.

        ~$ ssh admin@example.local 'initctl start iitod'\n
        "},{"location":"scripting/#reading-power-feed-status","title":"Reading Power Feed Status","text":"

        As part of production tests, verification of Power Feed sensors is often expected to be performed. Infix uses standard Linux support for Power management, where power sources appear in the file system under /sys/class/power_supply. The following example reads status of two power supplies named pwr1 and pwr2.

        ~$ ssh admin@example 'cat /sys/class/power_supply/pwr1/online'\n1\n~$ ssh admin@example 'cat /sys/class/power_supply/pwr2/online'\n0\n~$\n

        Here, only pwr1 have power.

        1. Press Win-r to bring up the Run dialog, enter cmd.exe and press enter. Then type in ipconfig /all to list all interfaces, their status, as well as interface index.\u00a0\u21a9

        "},{"location":"support/","title":"Support Data","text":""},{"location":"support/#support-data-collection","title":"Support Data Collection","text":"

        When troubleshooting issues or seeking support, the support command provides a convenient way to collect comprehensive system diagnostics. This command gathers configuration files, logs, network state, and other system information into a single compressed archive.

        Note

        The support collect command should be run with sudo to collect complete system information (kernel logs, hardware details, etc.). Use the --unprivileged option to run as a regular user in degraded data collection mode.

        "},{"location":"support/#collecting-support-data","title":"Collecting Support Data","text":"

        To collect support data and save it to a file:

        admin@host:~$ sudo support collect > support-data.tar.gz\nStarting support data collection from host...\nCollecting to: /var/lib/support\nThis may take up to a minute. Please wait...\nTailing /var/log/messages for 30 seconds (please wait)...\nLog tail complete.\nCollection complete. Creating archive...\nadmin@host:~$ ls -l support-data.tar.gz\n-rw-rw-r-- 1 admin admin 508362 nov 30 13:05 support-data.tar.gz\n

        The command can also be run remotely via SSH from your workstation:

        $ ssh admin@host 'sudo support collect' > support-data.tar.gz\n...\n

        The collection process may take up to a minute depending on system load and the amount of logging data. Progress messages are shown during the collection process.

        "},{"location":"support/#encrypted-collection","title":"Encrypted Collection","text":"

        For secure transmission of support data, the archive can be encrypted with GPG using a password:

        admin@host:~$ sudo support collect -p mypassword > support-data.tar.gz.gpg\nStarting support data collection from host...\nCollecting to: /var/lib/support\nThis may take up to a minute. Please wait...\n...\nCollection complete. Creating archive...\nEncrypting with GPG...\n

        The support collect command even supports omitting mypassword and will then prompt interactively for the password. This works over SSH too, but the local ssh client may then echo the password.

        Tip

        To hide the encryption password for an SSH session, the script supports reading from stdin: echo \"$MYSECRET\" | ssh user@device 'sudo support collect -p' > file.tar.gz.gpg

        After transferring the resulting file to your workstation, decrypt it with the password:

        $ gpg -d support-data.tar.gz.gpg > support-data.tar.gz\n$ tar xzf support-data.tar.gz\n...\n

        or

        $ gpg -d support-data.tar.gz.gpg | tar xz\n...\n

        Important

        Make sure to share mypassword out-of-band from the encrypted data with the recipient of the data. I.e., avoid sending both in the same plain-text email for example.

        "},{"location":"support/#what-is-collected","title":"What is Collected","text":"

        The support archive includes:

        • System identification (hostname, uptime, kernel version)
        • Running and operational configuration (sysrepo datastores)
        • System logs (/var/log directory and live tail of messages log)
        • Network configuration and state (interfaces, routes, neighbors, bridges)
        • FRRouting information (OSPF, BFD status)
        • Container information (podman containers and their configuration)
        • System resource usage (CPU, memory, disk, processes)
        • Hardware information (PCI, USB devices, network interfaces)
        "},{"location":"syslog/","title":"Syslog Support","text":""},{"location":"syslog/#syslog-support","title":"Syslog Support","text":"

        The system comes with native logging to internal storage, /var/log/, or /log for a shortcut. Depending on the device, this may be a RAM disk, meaning logs are not retained across reboots. This document details how to log to external media or remote syslog servers.

        It is also possible to set up the device to act as a syslog server (log sink), this is covered briefly at the very end of this document.

        Note

        The default logging setup in the system cannot be modified, only the log file rotation. Please see the dir admin-exec command for a listing of existing log files.

        "},{"location":"syslog/#log-to-file","title":"Log to File","text":"

        Logging to a local file is useful when combined with an external media. E.g., a USB stick with a log partition (named/labeled: \"log\"). Below is an example.

        For a list of available log facilities, see the table in a later section.

        admin@example:/> configure\nadmin@example:/config/> edit syslog actions log-file file:/media/log/mylog\nadmin@example:/config/syslog/\u2026/file:/media/log/mylog/> set facility-list\n      all    audit     auth authpriv  console     cron    cron2   daemon      ftp     kern\n   local0   local1   local2   local3   local4   local5   local6   local7      lpr     mail\n     news      ntp   syslog     user     uucp\nadmin@example:/config/syslog/\u2026/file:/media/log/mylog/> set facility-list all severity\n     alert       all  critical     debug emergency     error      info      none    notice   warning\nadmin@example:/config/syslog/\u2026/file:/media/log/mylog/> set facility-list all severity critical\nadmin@example:/config/syslog/\u2026/file:/media/log/mylog/> set facility-list mail severity warning\nadmin@example:/config/syslog/\u2026/file:/media/log/mylog/> leave\nadmin@example:/>\n

        Important

        The log-file syntax requires the leading prefix file:. If the path is not absolute, e.g., file:mylog, the file is saved to the system default path, i.e., /log/mylog. In this case, verify that the filename is not already in use.

        "},{"location":"syslog/#log-rotation","title":"Log Rotation","text":"

        By default log files are allowed to grow to a size of 1 MiB after which they are \"rotated\". The whole reason for this is to not fill up the disk with outdated logs. A rotated file is saved in stages and older ones are also compressed (using gzip). Use the show log command in admin-exec context to start the log file viewer:

        admin@example:/config/syslog/> do show log\nlog  log.0  log.1.gz  log.2.gz  log.3.gz  log.4.gz  log.5.gz\nadmin@example:/config/syslog/> do show log log.1.gz\n

        Tip

        Use the Tab key on your keyboard list available log files. The do prefix is also very useful in configure context to access commands from admin-exec context.

        By default 10 compressed older files are saved. Here the oldest is log.5.gz and the most recently rotated one is log.0.

        Log file rotation can be configured both globally and per log file. Here we show the global settings, the set up is the same for per log file, which if unset inherit the global settings:

        admin@example:/> configure\nadmin@example:/config/> edit syslog file-rotation\nadmin@example:/config/syslog/file-rotation/> show\nadmin@example:/config/syslog/file-rotation/>\n

        The defaults are not shown. We can inspect them by asking the YANG model for the help texts:

        admin@example:/config/syslog/file-rotation/> help\n   max-file-size    number-of-files\nadmin@example:/config/syslog/file-rotation/> help max-file-size\nNAME\n        max-file-size kilobytes\n\nDESCRIPTION\n        Maximum log file size (kiB), before rotation.\n\nDEFAULT\n        1024\nadmin@example:/config/syslog/file-rotation/> help number-of-files\nNAME\n        number-of-files [0..4294967295]\n\nDESCRIPTION\n        Maximum number of log files retained.\n\nDEFAULT\n        10\n

        To change the defaults to something smaller, 512 kiB and 20 (remember everything after .0 is compressed, and text compresses well):

        admin@example:/config/syslog/file-rotation/> set max-file-size 512\nadmin@example:/config/syslog/file-rotation/> set number-of-files 20\nadmin@example:/config/syslog/file-rotation/> show\nnumber-of-files 20;\nmax-file-size 512;\nadmin@example:/config/syslog/file-rotation/> leave\nadmin@example:/>\n
        "},{"location":"syslog/#log-format","title":"Log Format","text":"

        There are three major syslog log formats, the default is RFC3164 for log files and BSD for remote logging. Depending on time synchronization and remote log server capabilities, or policies, the RFC5424 format is often preferred since it not only has better time resolution but also supports structured logging:

        BSD     : myproc[8710]: Kilroy was here.\nRFC3164 : Aug 24 05:14:15 192.0.2.1 myproc[8710]: Kilroy was here.\nRFC5424 : 2003-08-24T05:14:15.000003-07:00 192.0.2.1 myproc 8710 - - Kilroy was here.\n

        The BSD format is only applicable to remote logging. It remains the default for compatibility reasons, and is recommended since the device may not have proper time, making it better for the remote log server to perform time stamping at the time of arrival.

        Configuring the log format is the same for log files and remotes:

        admin@example:/config/> edit syslog actions log-file file:foobar\nadmin@example:/config/syslog/\u2026/file:foobar/> set log-format\n                  bsd               rfc3164              rfc5424\nadmin@example:/config/syslog/\u2026/file:foobar/> set log-format rfc5424\nadmin@example:/config/syslog/\u2026/file:foobar/> leave\nadmin@example:/>\n
        "},{"location":"syslog/#log-to-remote-server","title":"Log to Remote Server","text":"

        Logging to a remote syslog server is the recommended way of supervising the system. This way all login attempts (console, SSH, or web) and any configuration changes can be traced, even in cases of a remote attacker tries to cover their traces by deleting logs.

        The recommended setup involves using a fixed IP address, default BSD log format, and the default Internet port (514). This is the most reliable, because your device may not have DNS set up or even available, and some remote syslog servers do not support receiving time stamped log messages -- this is of course entirely dependent on how the remote server is set up, as well as local policy.

        admin@example:/config/> edit syslog\n       actions file-rotation        server\nadmin@example:/config/> edit syslog actions destination moon\nadmin@example:/config/syslog/\u2026/moon/> set\n facility-list    log-format           udp\nadmin@example:/config/syslog/\u2026/moon/> set udp\n address    port\nadmin@example:/config/syslog/\u2026/moon/> set udp address 192.168.0.12\nadmin@example:/config/syslog/\u2026/moon/> set facility-list container severity all\nadmin@example:/config/syslog/\u2026/moon/> leave\nadmin@example:/>\n

        Tip

        The alternatives shown below each prompt in the example above can be found by tapping the Tab key.

        "},{"location":"syslog/#acting-as-a-log-server","title":"Acting as a Log Server","text":"

        The syslog server can act as a log sink for other devices on a LAN. For this to work you need a static IP address, here we use 10.0.0.1/24.

        admin@example:/> configure\nadmin@example:/config/> edit syslog server\nadmin@example:/config/syslog/server/> set enabled true\nadmin@example:/config/syslog/server/> set listen udp 514 address 10.0.0.1\nadmin@example:/config/syslog/server/> leave\nadmin@example:/>\n

        See the above Log to File section on how to set up filtering of received logs to local files. Advanced filtering based on hostname and message properties is also available, see the next section for details.

        "},{"location":"syslog/#advanced-filtering","title":"Advanced Filtering","text":"

        The syslog subsystem supports several advanced filtering options that allow fine-grained control over which messages are logged. These can be combined with facility and severity filters to create sophisticated logging rules.

        "},{"location":"syslog/#pattern-matching","title":"Pattern Matching","text":"

        Messages can be filtered using regular expressions (POSIX extended regex) on the message content. This is useful when you want to log only messages containing specific keywords or patterns:

        admin@example:/config/> edit syslog actions log-file file:errors\nadmin@example:/config/syslog/\u2026/file:errors/> set pattern-match \"ERROR|CRITICAL|FATAL\"\nadmin@example:/config/syslog/\u2026/file:errors/> set facility-list all severity info\nadmin@example:/config/syslog/\u2026/file:errors/> leave\nadmin@example:/>\n

        This will log all messages containing ERROR, CRITICAL, or FATAL.

        "},{"location":"syslog/#advanced-severity-comparison","title":"Advanced Severity Comparison","text":"

        By default, severity filtering uses \"equals-or-higher\" comparison, meaning a severity of error will match error, critical, alert, and emergency messages. You can change this behavior:

        admin@example:/config/> edit syslog actions log-file file:daemon-errors\nadmin@example:/config/syslog/\u2026/file:daemon-errors/> set facility-list daemon\nadmin@example:/config/syslog/\u2026/daemon/> set severity error\nadmin@example:/config/syslog/\u2026/daemon/> set advanced-compare compare equals\nadmin@example:/config/syslog/\u2026/daemon/> leave\nadmin@example:/>\n

        This will log only error severity messages, not higher severities.

        You can also block specific severities:

        admin@example:/config/syslog/\u2026/daemon/> set advanced-compare action block\n

        This will exclude error messages from the log.

        "},{"location":"syslog/#hostname-filtering","title":"Hostname Filtering","text":"

        When acting as a log server, you can filter messages by hostname. This is useful for directing logs from different devices to separate files:

        admin@example:/config/> edit syslog actions log-file file:router1\nadmin@example:/config/syslog/\u2026/file:router1/> set hostname-filter router1\nadmin@example:/config/syslog/\u2026/file:router1/> set facility-list all severity info\nadmin@example:/config/syslog/\u2026/file:router1/> leave\nadmin@example:/>\n

        Multiple hostnames can be added to the filter list.

        "},{"location":"syslog/#property-based-filtering","title":"Property-Based Filtering","text":"

        For more advanced filtering, you can match on specific message properties using various comparison operators:

        admin@example:/config/> edit syslog actions log-file file:myapp\nadmin@example:/config/syslog/\u2026/file:myapp/> edit property-filter\nadmin@example:/config/syslog/\u2026/property-filter/> set property programname\nadmin@example:/config/syslog/\u2026/property-filter/> set operator isequal\nadmin@example:/config/syslog/\u2026/property-filter/> set value myapp\nadmin@example:/config/syslog/\u2026/property-filter/> leave\nadmin@example:/>\n

        Available properties: - msg: Message body - msgid: RFC5424 message identifier - programname: Program/tag name - hostname: Source hostname - source: Alias for hostname - data: RFC5424 structured data

        Available operators: - contains: Substring match - isequal: Exact equality - startswith: Prefix match - regex: Basic regular expression - ereregex: Extended regular expression (POSIX ERE)

        The comparison can be made case-insensitive:

        admin@example:/config/syslog/\u2026/property-filter/> set case-insensitive true\n

        Or negated to exclude matching messages:

        admin@example:/config/syslog/\u2026/property-filter/> set negate true\n
        "},{"location":"syslog/#facilities","title":"Facilities","text":"Code Facility Description 0 kern Kernel log messages 1 user User-level messages 2 mail Mail system 3 daemon General system daemons 4 auth Security/authorization messages 5 syslog Messages generated by syslogd 6 lpr Line printer subsystem 7 news Network news subsystem 8 uucp UNIX-to-UNIX copy 9 cron Clock/cron daemon (BSD, Linux) 10 authpriv Security/authorization messages (private) 11 ftp FTP daemon 12 ntp NTP subsystem 13 audit Log audit (security) 14 console Log alert 15 cron2 Clock/cron daemon (Solaris) 16 rauc* local0, reserved for RAUC 17 container* local1, reserved for containers 18 local2 Currently unused 19 local3 Currently unused 20 local4 Currently unused 21 local5 Currently unused 22 reserved* local6, reserved for industrial Ethernet 23 web* local7, reserved for nginx web server

        Facilities marked * are local augments to the model.

        "},{"location":"system/","title":"Configuration","text":""},{"location":"system/#system-configuration","title":"System Configuration","text":"

        System settings in Infix are provided by the ietf-system YANG model, augmented with Linux specific extensions in infix-system, like Message of the Day (login message) and user login shell. More on this later on in this document.

        For the sake of brevity, the hostname in the following examples has been shortened to example. The default hostname is composed from a product specific string followed by the last three octets of the system base MAC address, e.g., switch-12-34-56. An example of how to change the hostname is included below.

        Note

        When issuing leave to activate your changes, remember to also save your settings, copy running-config startup-config. See the CLI Introduction for a background.

        "},{"location":"system/#changing-password","title":"Changing Password","text":"

        User management, including passwords, SSH keys, remote authentication is available in the system authentication configuration context.

        admin@example:/config/> edit system authentication user admin\nadmin@example:/config/system/\u2026/user/admin/> change password\nNew password:\nRetype password:\nadmin@example:/config/system/\u2026/user/admin/> leave\n

        The change password command starts an interactive dialogue that asks for the new password, with a confirmation, and then salts and encrypts the password with sha512crypt.

        It is also possible to use the set password ... command. This allows setting an already hashed password. To manually hash a password, use the do password encrypt command. This launches the admin-exec command to hash, and optionally salt, your password. This encrypted string can then be used with set password ....

        Tip

        If you are having trouble thinking of a password, there is a nifty password generate command in admin-exec context which generates random passwords using the UNIX command pwgen. Use the do prefix when inside any configuration context to access admin-exec commands.

        "},{"location":"system/#ssh-authorized-key","title":"SSH Authorized Key","text":"

        Logging in remotely with SSH is possible by adding a public key to a user. Here we add the authorized key to the admin user, multiple keys are supported.

        With SSH keys in place it is possible to disable password login, just remember to verify SSH login and network connectivity before doing so.

        admin@example:/config/> edit system authentication user admin\nadmin@example:/config/system/\u2026/user/admin/> edit authorized-key admin@example\nadmin@example:/config/system/\u2026/example@host/> set algorithm ssh-rsa\nadmin@example:/config/system/\u2026/example@host/> set key-data AAAAB3NzaC1yc2EAAAADAQABAAABgQC8iBL42yeMBioFay7lty1C4ZDTHcHyo739gc91rTTH8SKvAE4g8Rr97KOz/8PFtOObBrE9G21K7d6UBuPqmd0RUF2CkXXN/eN2PBSHJ50YprRFt/z/304bsBYkDdflKlPDjuSmZ/+OMp4pTsq0R0eNFlX9wcwxEzooIb7VPEdvWE7AYoBRUdf41u3KBHuvjGd1M6QYJtbFLQMMTiVe5IUfyVSZ1RCxEyAB9fR9CBhtVheTVsY3iG0fZc9eCEo89ErDgtGUTJK4Hxt5yCNwI88YaVmkE85cNtw8YwubWQL3/tGZHfbbQ0fynfB4kWNloyRHFr7E1kDxuX5+pbv26EqRdcOVGucNn7hnGU6C1+ejLWdBD7vgsoilFrEaBWF41elJEPKDzpszEijQ9gTrrWeYOQ+x++lvmOdssDu4KvGmj2K/MQTL2jJYrMJ7GDzsUu3XikChRL7zNfS2jYYQLzovboUCgqfPUsVba9hqeX3U67GsJo+hy5MG9RSry4+ucHs=\nadmin@example:/config/system/\u2026/example@host/> show\nalgorithm ssh-rsa;\nkey-data AAAAB3NzaC1yc2EAAAADAQABAAABgQC8iBL42yeMBioFay7lty1C4ZDTHcHyo739gc91rTTH8SKvAE4g8Rr97KOz/8PFtOObBrE9G21K7d6UBuPqmd0RUF2CkXXN/eN2PBSHJ50YprRFt/z/304bsBYkDdflKlPDjuSmZ/+OMp4pTsq0R0eNFlX9wcwxEzooIb7VPEdvWE7AYoBRUdf41u3KBHuvjGd1M6QYJtbFLQMMTiVe5IUfyVSZ1RCxEyAB9fR9CBhtVheTVsY3iG0fZc9eCEo89ErDgtGUTJK4Hxt5yCNwI88YaVmkE85cNtw8YwubWQL3/tGZHfbbQ0fynfB4kWNloyRHFr7E1kDxuX5+pbv26EqRdcOVGucNn7hnGU6C1+ejLWdBD7vgsoilFrEaBWF41elJEPKDzpszEijQ9gTrrWeYOQ+x++lvmOdssDu4KvGmj2K/MQTL2jJYrMJ7GDzsUu3XikChRL7zNfS2jYYQLzovboUCgqfPUsVba9hqeX3U67GsJo+hy5MG9RSry4+ucHs=;\nadmin@example:/config/system/\u2026/example@host/> leave\n

        Note

        The ssh-keygen program already base64 encodes the public key data, so there is no need to use the text-editor command, set does the job.

        "},{"location":"system/#multiple-users","title":"Multiple Users","text":"

        The factory configuration provides three hierarchical user group levels by default: guest \u2282 operator \u2282 admin. These levels work out-of-the-box with sensible permissions - operators can configure the system immediately, while sensitive items (passwords, cryptographic keys) remain protected.

        The default levels provide different access to system resources and configuration:

        • Admin: Full system access - can manage users, upgrade software, restart the system, and modify all configuration including network settings, routing, and firewall rules.

        • Operator: Configuration access - can modify most system settings including network interfaces, routing, firewall, hostname, and more. Cannot access password hashes, cryptographic keys, or perform sensitive operations (factory reset, software upgrade).

        • Guest: Read-only access - can view operational state and configuration but cannot modify anything or execute operations.

        System access control is handled by the ietf-netconf-acm YANG model, usually referred to as NACM, which provides granular access to configuration, data, and RPC commands. The hierarchical levels in the system are determined by:

        1. NACM permissions - what the user can access
        2. Shell setting - which command-line interface the user can use

        By default the system ships with a single user, admin, in the admin group. There are no restrictions on the number of users with admin privileges, nor is the admin user reserved or protected -- it can be removed from the configuration. However, it is strongly recommended to keep at least one user with administrator privileges, otherwise the only way to regain full access is to perform a factory reset.

        For an overview of users and groups on the system, there is an admin-exec command:

        admin@example:/> show nacm\nenabled              : yes\ndefault read access  : permit\ndefault write access : permit\ndefault exec access  : permit\ndenied operations    : 0\ndenied data writes   : 0\ndenied notifications : 0\n\n          \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n          \u2502 GROUP    \u2502  READ   \u2502  WRITE  \u2502  EXEC   \u2502\n          \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n          \u2502 admin    \u2502    \u2713    \u2502    \u2713    \u2502    \u2713    \u2502\n          \u2502 operator \u2502    \u26a0    \u2502    \u26a0    \u2502    \u26a0    \u2502\n          \u2502 guest    \u2502    \u26a0    \u2502    \u2717    \u2502    \u2717    \u2502\n          \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n              \u2713 Full    \u26a0 Restricted    \u2717 Denied\n\nUSER                   SHELL   LOGIN                            \nadmin                  bash    password+key\njacky                  clish   password\nmonitor                false   key\n\nGROUP                  USERS                                    \nadmin                  admin\noperator               jacky\nguest                  monitor\n

        The permissions matrix shows effective access for each NACM group:

        • \u2713 Full (green) - unrestricted access
        • \u26a0 Restricted (yellow) - access with exceptions, use show nacm group for details
        • \u2717 Denied (red) - no access

        For detailed information about a specific group's rules:

        admin@example:/> show nacm group operator\nmembers          : jacky\nread permission  : restricted\nwrite permission : restricted\nexec permission  : restricted\napplicable rules : 4\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\npermit-system-rpcs\n  action     : permit\n  operations : exec\n  target     : ietf-system (rpc: *)\n  comment    : Operators can reboot, shutdown, and set system time.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-password-access (via '*')\n  action     : deny\n  operations : *\n  target     : /ietf-system:system/authentication/user/password\n  comment    : No user except admins can access password hashes.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-keystore-access (via '*')\n  action     : deny\n  operations : *\n  target     : ietf-keystore\n  comment    : No user except admins can access cryptographic keys.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-truststore-access (via '*')\n  action     : deny\n  operations : *\n  target     : ietf-truststore\n  comment    : No user except admins can access trust store.\n

        For user details:

        admin@example:/> show nacm user jacky\nshell            : clish\nlogin            : password\nnacm group       : operator\nread permission  : restricted\nwrite permission : restricted\nexec permission  : restricted\n\nFor detailed rules, use: show nacm group <name>\n
        "},{"location":"system/#adding-a-user","title":"Adding a User","text":"

        Creating a new user starts with defining the user account in the system:

        admin@example:/config/> edit system authentication user jacky\nadmin@example:/config/system/\u2026/user/jacky/> change password\nNew password:\nRetype password:\nadmin@example:/config/system/\u2026/user/jacky/> leave\n

        Tip

        It is also possible to use set password ... if you have the fully crypted and salted string ready. This can be created offline with the mkpasswd(1) tool, or the built-in CLI version do password encrypt [OPTS]. The do prefix is handy for reaching all top-level commands when in configure context.

        An authorized SSH key can be added the same way as described in the previous sections.

        By default, shell access is disabled (shell false). To allow CLI/SSH access, set the shell:

        admin@example:/config/> edit system authentication user jacky\nadmin@example:/config/system/\u2026/user/jacky/> set shell clish\nadmin@example:/config/system/\u2026/user/jacky/> leave\n

        Available shells:

        • bash - Full Bourne-again shell (recommended for admins only)
        • sh - POSIX shell (recommended for admins only)
        • clish - Limited CLI-only shell (recommended for operators and guests)
        • false - No shell access (default)

        Security Notice

        For security reasons, it is strongly recommended to limit non-admin users to the clish shell, which provides CLI access without exposing the underlying UNIX system. Reserve bash and sh for administrators who need full system access for debugging and maintenance.

        Note that shell and CLI access is not always necessary - the system supports NETCONF and RESTCONF for remote management and automation. Setting shell false for users who only need programmatic access minimizes the attack surface and improves overall system security.

        "},{"location":"system/#adding-a-user-to-a-group","title":"Adding a User to a Group","text":"

        To assign a user to a specific privilege level, add them to the corresponding NACM group:

        Operator user:

        admin@example:/config/> edit nacm group operator\nadmin@example:/config/nacm/group/operator/> set user-name jacky\nadmin@example:/config/nacm/group/operator/> leave\n

        Adding another admin:

        admin@example:/config/> edit nacm group admin\nadmin@example:/config/nacm/group/admin/> set user-name alice\nadmin@example:/config/nacm/group/admin/> leave\n

        Guest user:

        admin@example:/config/> edit nacm group guest\nadmin@example:/config/nacm/group/guest/> set user-name monitor\nadmin@example:/config/nacm/group/guest/> leave\n

        Tip

        For technical details about NACM rule evaluation, module-name vs path matching, and creating custom access control policies, see the NACM Technical Guide.

        "},{"location":"system/#access-control-matrix","title":"Access Control Matrix","text":"

        The following table shows what each user level can do based on the NACM rules and shell access configured for each user:

        • Admin: bash \u2014 full system access
        • Operator: clish \u2014 CLI-only access without UNIX system exposure
        • Guest: false \u2014 no shell access
        Feature Admin Operator Guest Network interfaces \u2713 \u2713 Read only Routing (FRR) \u2713 \u2713 Read only Firewall rules \u2713 \u2713 Read only VLANs/bridges \u2713 \u2713 Read only Containers \u2713 \u2713 Read only Hostname/system config \u2713 \u2713 Read only CLI/SSH access \u2713 \u2713 \u2717 System restart \u2713 \u2713 \u2717 Set date/time \u2713 \u2713 \u2717 System reboot \u2713 \u2713 \u2717 System shutdown \u2713 \u2713 \u2717 User management \u2713 \u2717 Read only Keystore (certs/keys) \u2713 \u2717 \u2717 Truststore \u2713 \u2717 \u2717 Read passwords/secrets \u2713 \u2717 \u2717 NACM rules \u2713 \u2717 \u2717 Factory reset \u2713 \u2717 \u2717 Software upgrade \u2713 \u2717 \u2717"},{"location":"system/#security-aspects","title":"Security Aspects","text":"

        The three default user levels are implemented through a combination of NACM rules and UNIX group membership. Access control is permission-based, not name-based - the system detects user levels by examining their NACM permissions and shell settings.

        Admin users have unrestricted NACM access with the following rule:

           \"module-name\": \"*\",\n   \"access-operations\": \"*\",\n   \"action\": \"permit\"\n

        Admin users are automatically added to the UNIX wheel and frrvty groups, granting them sudo privileges and access to FRR routing protocols. This makes it possible to use all the underlying UNIX tooling, which can be very useful for debugging, but please use with care -- the system is designed to be managed through the CLI and NETCONF, not directly via shell commands.

        Operator users use the permit-by-default NACM model (write-default: \"permit\"), which means they can configure most system settings without explicit permit rules. This design is \"future proof\" - when new features are added, operators can immediately use them.

        The following are explicitly denied to operators through global NACM rules:

        • Password hashes (/ietf-system:system/authentication/user/password)
        • Cryptographic keys (ietf-keystore module)
        • Trust store certificates (ietf-truststore module)

        Additionally, sensitive operations like factory reset, software upgrades, and system shutdown are protected by YANG-level nacm:default-deny-all annotations and remain restricted to administrators.

        Operators are automatically added to the UNIX operator and frrvty groups, granting them sudo privileges for network operations and FRR access.

        Guest users have read-only NACM access through an explicit deny rule that blocks all write and exec operations (create update delete exec), while read-default: \"permit\" allows viewing configuration and state. Guests receive no special UNIX group memberships. The shell setting determines whether guests can access the CLI (clish) or are restricted from shell access entirely (false).

        All users, regardless of level, are denied access to password hashes and cryptographic key material through global NACM rules.

        "},{"location":"system/#changing-hostname","title":"Changing Hostname","text":"

        Notice how the hostname in the prompt does not change until the change is committed by issuing the leave command.

        admin@example:/config/> edit system\nadmin@example:/config/system/> set hostname myrouter\nadmin@example:/config/system/> leave\nadmin@myrouter:/>\n

        The hostname is advertised over mDNS-SD in the .local domain. If another device already has claimed the myrouter.local CNAME, in our case, mDNS will advertise a \"uniqified\" variant, usually suffixing with an index, e.g., myrouter-1.local. Use an mDNS browser to scan for available devices on your LAN.

        In some cases you may want to set the device's domain name as well. This is handled the same way:

        admin@example:/config/> edit system\nadmin@example:/config/system/> set hostname foo.example.com\nadmin@example:/config/system/> leave\nadmin@foo:/>\n

        Both host and domain name are stored in the system files /etc/hosts and /etc/hostname. The latter is exclusively for the host name. The domain may be used by the system DHCP server when handing out leases to clients, it is up to the clients to request the domain name option.

        Note

        Critical services like syslog, mDNS, LLDP, and similar that advertise the hostname, are restarted when the hostname is changed.

        "},{"location":"system/#changing-login-banner","title":"Changing Login Banner","text":"

        The motd-banner setting is an Infix augment and an example of a binary type setting that can be changed interactively with the built-in text-editor command.

        Tip

        See the next section for how to change the editor used to something you may be more familiar with.

        admin@example:/config/> edit system\nadmin@example:/config/system/> text-editor motd-banner\nadmin@example:/config/system/> leave\nadmin@example:/>\n

        Log out and log back in again to inspect the changes.

        "},{"location":"system/#changing-the-editor","title":"Changing the Editor","text":"

        The system has three different built-in editors that can be used as the text-editor command:

        • emacs (Micro Emacs)
        • nano (GNU Nano)
        • vi (Visual Editor)

        To change the editor to GNU Nano:

        admin@example:/> configure\nadmin@example:/config/> edit system\nadmin@example:/config/system/> set text-editor nano\nadmin@example:/config/system/> leave\nadmin@example:/>\n

        Important

        Configuration changes only take effect after issuing the leave command. I.e., you must change the editor first, and then re-enter configure context to use your editor of choice.

        "},{"location":"system/#dns-resolver-configuration","title":"DNS Resolver Configuration","text":"

        The system supports both static and dynamic (DHCP) DNS setup. The locally configured (static) server is preferred over any acquired from a DHCP client.

        admin@example:/> configure\nadmin@example:/config/> edit system dns-resolver\nadmin@example:/config/system/dns-resolver/> set server google udp-and-tcp address 8.8.8.8\nadmin@example:/config/system/dns-resolver/> show\nserver google {\n  udp-and-tcp {\n    address 8.8.8.8;\n  }\n}\nadmin@example:/config/system/dns-resolver/> leave\n

        It is also possible to configure resolver options like timeout and retry attempts. See the YANG model for details, or use the built-in help system in the CLI.

        Note

        When acting as a DHCP server and DNS proxy for other devices, any local DNS server configured here is automatically used as upstream DNS server.

        "},{"location":"system/#ntp-client-configuration","title":"NTP Client Configuration","text":"

        Below is an example configuration for enabling NTP with a specific server and the iburst option for faster initial synchronization.

        admin@example:/> configure\nadmin@example:/config/> edit system ntp\nadmin@example:/config/system/ntp/> set enabled\nadmin@example:/config/system/ntp/> set server ntp-pool\nadmin@example:/config/system/ntp/> set server ntp-pool udp address pool.ntp.org\nadmin@example:/config/system/ntp/> set server ntp-pool iburst\nadmin@example:/config/system/ntp/> set server ntp-pool prefer\nadmin@example:/config/system/ntp/> leave\n

        This configuration enables the NTP client and sets the NTP server to pool.ntp.org with the iburst and prefer options. The iburst option ensures faster initial synchronization, and the prefer option designates this server as preferred.

        • prefer false: The NTP client will choose the best available source based on several factors, such as network delay, stratum, and other metrics (default config).
        • prefer true: The NTP client will try to use the preferred server as the primary source unless it becomes unreachable or unusable.
        "},{"location":"system/#show-ntp-sources","title":"Show NTP Sources","text":"

        The status for NTP sources is availble in YANG and accessable with CLI/NETCONF/RESTCONF.

        To view the sources being used by the NTP client, run:

        admin@example:/> show ntp\nMode                : Client\nStratum             : 3\nRef time (UTC)      : Sat Jan 24 23:41:42 2026\n\nADDRESS         MODE    STATE     STRATUM  POLL\n147.78.228.41   server  outlier         2   64s\n192.168.0.1     server  unusable        0  128s\n176.126.86.247  server  selected        2   64s\n
        "},{"location":"system/#show-ntp-status","title":"Show NTP Status","text":"

        To check the status of NTP synchronization (only availble in CLI), use the following command:

        admin@example:/> show ntp tracking\nReference ID        : 176.126.86.247\nStratum             : 3\nRef time (UTC)      : Sat Jan 24 23:41:42 2026\nSystem time         : 0.000000 seconds slow of NTP time\nLast offset         : -454779.375000000 seconds\nRMS offset          : 454779.375000000 seconds\nFrequency           : 0.000 ppm slow\nResidual freq       : -26.383 ppm\nSkew                : 1000000.000 ppm\nRoot delay          : 0.007395 seconds\nRoot dispersion     : 39.181149 seconds\nUpdate interval     : 0.0 seconds\nLeap status         : Normal\n

        This output provides detailed information about the NTP status, including reference ID, stratum, time offsets, frequency, and root delay.

        Tip

        The system uses chronyd Network Time Protocol (NTP) daemon. The output shown here is best explained in the Chrony documentation.

        "},{"location":"test-arch/","title":"Test System Architecture","text":""},{"location":"test-arch/#test-system-architecture","title":"Test System Architecture","text":""},{"location":"test-arch/#tenets","title":"Tenets","text":"
        • Keep overhead to a minimum. Tests should be fast to both write and run. Ideally, the developer should want to add tests early in the development cycle because they instinctively feel that that is the quickest route to arrive at a correct and robust implementation.

        • Both physical and virtual hardware matters. Infix is primarily deployed on physical hardware, so being able to run the test suite on real devices is crucial to guarantee a high quality product. At the same time, there is much value in running the same suite on virtual hardware, as it makes it easy to catch regressions early. It is also much more practical and economical to build large virtual networks than physical ones.

        • Avoid CLI scripting & scraping. Reliably interacting with a DUT over a serial line in a robust way is very hard to get right. Given that we have a proper API (RESTCONF), we should leverage that when testing. Front-ends can be tested by other means.

        "},{"location":"test-arch/#overview","title":"Overview","text":"

        The test system is made up of several independent components, which are typically used in concert to run a full test suite.

        "},{"location":"test-arch/#test-cases","title":"Test Cases","text":"

        A test case is an executable, receiving the physical topology as a positional argument, which produces TAP compliant output on its stdout. I.e., it is executed in the following manner:

        test-case [OPTS] <physical-topology>\n

        Test cases are typically written in Python, using the Infamy library. Ultimately though, it can be implemented in any language, as long as it matches the calling convention above.

        "},{"location":"test-arch/#infamy","title":"Infamy","text":"

        Rather than having each test case come up with its own implementation of how to map topologies, how to push NETCONF data to a device, etc., we provide a library of functions to take care of all that, dubbed \"Infamy\". When adding a new test case, ask yourself if any parts of it might belong in Infamy as a generalized component that can be reused by other tests.

        Some of the core functions provided by Infamy are:

        • Mapping a logical topology to a physical one
        • Finding and attaching to a device over an Ethernet interface, using NETCONF
        • Pushing/pulling NETCONF data to/from a device
        • Generating TAP compliant output
        "},{"location":"test-arch/#9pm","title":"9PM","text":"

        To run multiple tests, we employ 9PM. It let's us define test suites as simple YAML files. Suites can also be hierarchically structured, with a suite being made up of other suites, etc.

        It also validates the TAP output, making sure to catch early exits from a case, and produces a nice summary report.

        "},{"location":"test-arch/#testenv","title":"/test/env","text":"

        A good way to ensure that nobody ever runs the test suite is to make it really hard to do so. /test/env's job is instead to make it very easy to create a reproducible environment in which tests can be executed.

        Several technologies are leveraged to accomplish this:

        • Containers: The entire execution is optionally done inside a standardized docker container environment. This ensures that the software needed to run the test suite is always available, no matter which distribution the user is running on their machine.

        • Python Virtual Environments: To make sure that the expected versions of all Python packages are available, the execution is wrapped inside a venv. This is true for containerized executions, where the container comes with a pre-installed environment, but it can also be sourced from the host system when running outside of the container.

        • Virtual Test Topology: Using Qeneth, the environment can optionally be started with a virtual topology of DUTs to run the tests on.

        docker is the only supported container environment when running tests in the host's network namespace. When running on a virtual Qeneth topology, podman may also be used by installing the podman-docker package from your host system's distro.

        "},{"location":"test-arch/#physical-and-logical-topologies","title":"Physical and Logical Topologies","text":"

        Imagine that we want to create a test with three DUTs; one acting as a DHCP server, and the other two as DHCP clients - with all three having a management connection to the host PC running the test. In other words, the test requires a logical topology like the one below.

        graph \"dhcp-client-server\" {\n    host [\n        label=\"host | { <c1> c1 | <srv> srv | <c2> c2 }\",\n        kind=\"controller\",\n    ];\n\n    server [\n        label=\"{ <mgmt> mgmt } | server | { <c1> c1 | <c2> c2 }\",\n        kind=\"infix\",\n    ];\n    client1 [\n        label=\"{ <mgmt> mgmt } | client1 | { <srv> srv }\",\n        kind=\"infix\",\n    ];\n    client2 [\n        label=\"{ <mgmt> mgmt } | client2 | { <srv> srv }\",\n        kind=\"infix\",\n    ];\n\n    host:srv -- server:mgmt\n    host:c1  -- client1:mgmt\n    host:c2  -- client2:mgmt\n\n    server:c1 -- client1:srv;\n    server:c2 -- client2:srv;\n}\n

        When running in a virtualized environment, one could simply create a setup that matches the test's logical topology. But in scenarios when devices are physical systems, connected by real copper cables, this is not possible (unless you have some wicked L1 relay matrix thingy).

        Instead, the test implementation does not concern itself with the exact nodes used to run the test, only that the logical topology can be mapped to some subset of the physical topology. In mathematical terms, the physical topology must contain a subgraph that is isomorphic to the logical topology.

        Standing on the shoulders of giants (i.e. people with mathematics degrees), we can deploy well-known algorithms to find such subgraphs. Continuing our example, let's say we want to run our DHCP test on the physical topology below.

        graph \"quad-ring\" {\n    host [\n        label=\"host | { <d1a> d1a | <d1b> d1b | <d1c> d1c | <d2a> d2a | <d2b> d2b | <d2c> d2c | <d3a> d3a | <d3b> d3b | <d3c> d3c | <d4a> d4a | <d4b> d4b | <d4c> d4c }\",\n        kind=\"controller\",\n    ];\n\n    dut1 [\n        label=\"{ <e1> e1 | <e2> e2 | <e3> e3 } | dut1 | { <e4> e4 | <e5> e5 }\",\n        kind=\"infix\",\n    ];\n    dut2 [\n        label=\"{ <e1> e1 | <e2> e2 | <e3> e3 } | dut2 | { <e4> e4 | <e5> e5 }\",\n        kind=\"infix\",\n    ];\n    dut3 [\n        label=\"{ <e1> e1 | <e2> e2 | <e3> e3 } | dut3 | { <e4> e4 | <e5> e5 }\",\n        kind=\"infix\",\n    ];\n    dut4 [\n        label=\"{ <e1> e1 | <e2> e2 | <e3> e3 } | dut4 | { <e4> e4 | <e5> e5 }\",\n        kind=\"infix\",\n    ];\n\n    host:d1a -- dut1:e1\n    host:d1b -- dut1:e2\n    host:d1c -- dut1:e3\n\n    host:d2a -- dut2:e1\n    host:d2b -- dut2:e2\n    host:d2c -- dut2:e3\n\n    host:d3a -- dut3:e1\n    host:d3b -- dut3:e2\n    host:d3c -- dut3:e3\n\n    host:d4a -- dut4:e1\n    host:d4b -- dut4:e2\n    host:d4c -- dut4:e3\n\n    dut1:e5 -- dut2:e4\n    dut2:e5 -- dut3:e4\n    dut3:e5 -- dut4:e4\n    dut4:e5 -- dut1:e4\n}\n

        Our test (in fact, all tests) receives the physical topology as an input parameter, and then maps the desired logical topology onto it, producing a mapping from logical nodes and ports to their physical counterparts.

        {\n  \"client1\": \"dut1\",\n  \"client1:mgmt\": \"dut1:e1\",\n  \"client1:srv\": \"dut1:e4\",\n  \"client2\": \"dut3\",\n  \"client2:mgmt\": \"dut3:e2\",\n  \"client2:srv\": \"dut3:e5\",\n  \"host\": \"host\",\n  \"host:c1\": \"host:d1a\",\n  \"host:c2\": \"host:d3b\",\n  \"host:srv\": \"host:d4c\",\n  \"server\": \"dut4\",\n  \"server:c1\": \"dut4:e5\",\n  \"server:c2\": \"dut4:e4\",\n  \"server:mgmt\": \"dut4:e3\"\n}\n

        With this information, the test knows that, in this particular environment, the server should be managed via the port called d4c on the node called host; that the port connected to the server on client1 is e4 on dut1, etc. Thereby separating the implementation of the test from any specific physical setup.

        Testcases are not required to use a logical topology; they may choose to accept whatever physical topology its given, and dynamically determine the DUTs to use for testing. As an example, an STP test could accept an arbitrary physical topology, run the STP algorithm on it offline, enable STP on all DUTs, and then verify that the resulting spanning tree matches the expected one.

        "},{"location":"test-arch/#integration-to-infix","title":"Integration to Infix","text":""},{"location":"test-arch/#test-mode","title":"Test Mode","text":"

        Infix supports a test mode that makes devices safe and predictable to test against. When active, each boot starts from a known-good test-config (equivalent to the failure/secure config) instead of the stored startup or factory configuration. This prevents any persistent configuration from interfering with tests and avoids L2 loops that a factory config might create. It also enables two extra RPCs used by Infamy:

        • infix-test:test/reset \u2014 reload the test-config into the running datastore (called by env.attach() at the start of each test)
        • infix-test:test/override-startup \u2014 boot from the startup datastore once, instead of the test-config (used by tests that verify save-and-reboot behaviour)

        Test mode is activated by the presence of an empty file named test-mode on the device's aux partition (/mnt/aux/test-mode).

        "},{"location":"test-arch/#test-mode-on-virtual-devices","title":"Test Mode on Virtual Devices","text":"

        When the test environment is started with Qeneth, the inject-test-mode script (test/inject-test-mode) is run automatically. It copies the base disk image and writes the test-mode marker into the aux partition of that copy, which is then used as the Qeneth backing image.

        "},{"location":"test-arch/#test-mode-on-physical-devices","title":"Test Mode on Physical Devices","text":"

        For a physical device, log in as root (or admin) and create the marker file on the aux partition, then reboot:

        # touch /mnt/aux/test-mode\n# reboot\n

        Alternatively, if you want to prepare the disk image before flashing, use the inject-test-mode script:

        $ test/inject-test-mode -b infix-<board>.img -o infix-<board>-test.img\n

        Flash infix-<board>-test.img to the device instead of the plain image. The device will boot into test mode on first power-on.

        Note

        Test mode persists across reboots until the marker file is removed (rm /mnt/aux/test-mode). A device in test mode always starts from the test-config, so any configuration changes are lost on reboot unless startup_override() is called first (or the marker is removed and a normal startup config is saved).

        "},{"location":"testing/","title":"Regression Testing","text":""},{"location":"testing/#regression-testing-with-infamy","title":"Regression Testing with Infamy","text":"

        Infix comes with a test suite that is intended to provide end-to-end verification of supported features. Generally speaking, this means that one or more DUTs are configured over NETCONF or RESTCONF; the resulting network is then black-box tested by injecting and inspecting network traffic at various points.

        This document is intended to be a practical guide on how to run, develop and debug tests. There is a separate document describing the Test System Architecture.

        "},{"location":"testing/#modes-of-testing","title":"Modes of Testing","text":""},{"location":"testing/#virtual-devices","title":"Virtual Devices","text":"

        By default, tests are run on a topology made up of virtual Infix nodes using Qeneth. To run the full regression test suite: build Infix for x86_64:

        $ make x86_64_defconfig\n...\n$ make\n...\n$ make test\n...\n
        "},{"location":"testing/#physical-devices","title":"Physical Devices","text":"

        To run the tests on a preexisting topology from the host's network namespace, specify the host TEST_MODE:

        $ make TEST_MODE=host test\n...\n

        This is typically used when testing on physical hardware. By default the topology will be sourced from /etc/infamy.dot, but this can be overwritten by setting the TOPOLOGY variable:

        $ make TEST_MODE=host TOPOLOGY=~/my-topology.dot test\n...\n

        Important

        Physical devices must be put into test mode before running tests. See Test Mode in the architecture document for details. The short version: log in to the device and run touch /mnt/aux/test-mode && reboot.

        "},{"location":"testing/#make-run-devices","title":"make run Devices","text":"

        Some tests only require a single DUT. These can therefore be run against an Infix image started from make run. This requires that the instance is configured to use TAP networking.

        When the instance is running, you can open a separate terminal and run the subset of the test suite that can be mapped to it:

        $ make TEST_MODE=run test\n...\n
        "},{"location":"testing/#interactive-usage","title":"Interactive Usage","text":"

        When developing and debugging tests, the overhead of repeatedly setting up and tearing down the test environment can quickly start to weigh you down. In these situation, you can start an interactive test environment:

        $ make test-sh\nInfo: Generating topology\nInfo: Generating node YAML\nInfo: Generating executables\nInfo: Launching dut1\nInfo: Launching dut2\nInfo: Launching dut3\nInfo: Launching dut4\n11:42:52 infamy0:test #\n

        The example above uses the default mode (qeneth), but the host and run modes also support the test-sh target.

        It takes a little while to start up, but then we have a shell prompt inside the container running Infamy. It's a very limited environment, but it has enough to easily run single tests, connect to the virtual devices, and step your code.

        "},{"location":"testing/#running-subsets-of-tests","title":"Running Subsets of Tests","text":"

        Each test case is a separate executable, which can be run without arguments:

        11:42:53 infamy0:test # ./case/dhcp/dhcp_basic.py\n

        To run a suite of tests, e.g., only the DHCP client tests, pass the suite as an argument to 9PM:

        11:42:53 infamy0:test # ./9pm/9pm.py case/dhcp/dhcp.yaml\n

        To run the suite of all tests:

        11:42:53 infamy0:test # ./9pm/9pm.py case/all.yaml\n
        "},{"location":"testing/#connecting-to-infamy","title":"Connecting to Infamy","text":"

        The test system runs in a Docker container, so to get a shell prompt in another terminal you need to connect to that container. Infamy comes with a helper script for this:

        $ ./test/shell\n11:42:53 infamy0:test #\n

        By default it connect to the latest started Infamy instance. If you for some reason run multiple instances of Infamy the shell script takes an optional argument \"system\", which is the hostname of the container you want to connect to:

        $ ./test/shell infamy2\n11:42:53 infamy2:test #\n
        "},{"location":"testing/#connecting-to-a-dut","title":"Connecting to a DUT","text":"

        Tip

        The console script uses telnet to connect to a port forwarded to localhost by the Docker container. To exit Telnet, use Ctrl-] and then 'q' followed by enter. This can be customized in ~/.telnetrc

        All DUTs in a virtual Infamy topology are emulated in Qemu instances managed by qeneth. If you want to watch what's happening on one of the target systems, e.g., tail a log file or run tcpdump during the test, there is another helper script in Infamy for this:

        $ ./test/console 1\nTrying 127.0.0.1...\nConnected to 127.0.0.1.\n\nInfix OS \u2014 Immutable.Friendly.Secure v23.11.0-226-g0c144da (console)\ninfix-00-00-00 login: admin\nPassword:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-'\n\nRun the command 'cli' for interactive OAM\n\nadmin@infix-00-00-00:~$\n

        From here we can observe dut1 freely while running tests.

        Like the shell script, console takes an optional \"system\" argument in case you run multiple instances of Infamy:

        $ ./test/console 1 infamy2\n...\n

        You can also connect to the console of a DUT from within a shell:

        $ ./test/shell\n11:42:54 infamy0:test # qeneth status\n11:42:54 infamy0:test # qeneth console dut1\nlogin: admin\npassword: *****\nadmin@infix-00-00-00:~$\n

        Tip

        Disconnect from the qeneth console by bringing up the old Telnet \"menu\" using Ctrl-], compared to standard Telnet this is the BusyBox version so you press 'e' + enter instead of 'q' to quit.

        "},{"location":"testing/#attaching-to-macvlans","title":"Attaching to MACVLANs","text":"

        To fully isolate the host's interfaces from one another, many tests will stack a MACVLAN on an interface, which is then placed in a separate network namespace.

        It is often useful to attach to those namespaces, so that you can interactively inject traffic into the test setup.

        First, connect to the Infamy instance in question:

        $ ./test/shell\n11:43:19 infamy0:test #\n

        Then, attach to the MACVLAN namespace by running the nsenter helper script from the test directory, supplying the base interface name as the first argument:

        11:43:19 infamy0:test # ./nsenter d1b\n11:43:20 infamy0(d1b):test #\n

        By default, an interactive shell is started, but you can also supply another command:

        11:43:19 infamy0:test # ./nsenter d1b ip -br addr\nlo               UNKNOWN        127.0.0.1/8 ::1/128\niface@if7        UP             10.0.0.1/24 fe80::38d0:88ff:fe77:b7cd/64\n

        You can now freely debug the network activity of your test and the responses from the DUT.

        "},{"location":"testing/#using-the-python-debugger","title":"Using the Python Debugger","text":"

        The built in breakpoint() function in Python is very useful when you want to run a test case to a certain point at which you might want to interactively inspect either the test's or the device's state.

        Simply insert a call to breakpoint() at the point of interest in your test and run it as normal. Once Python executes the call, it will drop you into the Python debugger:

        11:42:58 infamy0:test # ./case/dhcp/dhcp_basic.py\n# Starting (2024-02-10 11:42:59)\n# Probing dut1 on port d1a for IPv6LL mgmt address ...\n# Connecting to mgmt IP fe80::ff:fe00:0%d1a:830 ...\nok 1 - Initialize\n> /home/jocke/src/infix/test/case/dhcp/dhcp_basic.py(44)<module>()\n(Pdb)\n

        At this point you have full access to the test's state, but it is also an opportunity to inspect the state of the DUTs (e.g. via their console or over SSH).

        It is also possible to run a test under Pdb from the get-go, if you want to setup breakpoints without modifying the source, or simply step through the code:

        11:42:58 infamy0:test # python -m pdb case/dhcp/dhcp_basic.py\n
        "},{"location":"testing/#deterministic-topology-mappings","title":"Deterministic Topology Mappings","text":"

        By default, mappings from logical to physical topologies are not stable across test case executions. This can be very frustrating when debugging a failing test, since logical nodes are suffled around between phyical nodes. In such cases, supplying a PYTHONHASHSEED variable (set to any 32-bit unsigned integer) when launching the test environment will make sure that topology mappings are deterministic:

        $ make PYTHONHASHSEED=0 test-sh\n

        If a seed is not supplied, a random value is chosen. This seed is logged by the meta/reproducible.py test case when running a test suite:

        $ make test\nInfo: Generating topology\nInfo: Generating node YAML\nInfo: Generating executables\nInfo: Launching dut1\nInfo: Launching dut2\nInfo: Launching dut3\nInfo: Launching dut4\n9PM - Simplicity is the ultimate sophistication\n\nStarting test 0002-reproducible.py\n2024-05-03 10:40:30 # Starting (2024-05-03 10:40:30)\n2024-05-03 10:40:30 # Specify PYTHONHASHSEED=3773822171 to reproduce this test environment\n2024-05-03 10:40:30 ok 1 - $PYTHONHASHSEED is set\n2024-05-03 10:40:30 # Exiting (2024-05-03 10:40:30)\n2024-05-03 10:40:30 1..1\n...\n

        This is useful because this value can then be used to rerun a test (or the whole suite) with identical topology mappings:

        $ make PYTHONHASHSEED=3773822171 TESTS=case/system/hostname.py test\n
        "},{"location":"testing/#deterministic-transport-protocol","title":"Deterministic Transport Protocol","text":"

        By default, the communication transport protocol (NETCONF/RESTCONF) is chosen randomly. If you supply a PYTHONHASHSEED as described above, you get the same protocol used for that hash. But if you want to choose the protocol, add extra arguments to Infamy:

        $ make INFAMY_EXTRA_ARGS=\"--transport=restconf\" TESTS=case/system/hostname.py test\n

        or, when running interactively:

        $ make test-sh\n09:08:17 infamy0:test # ./9pm/9pm.py -o\"--transport=restconf\" case/system/hostname.py\n
        "},{"location":"testing/#test-specification","title":"Test specification","text":"

        The test specification is automaticly generated from the test cases, this puts some constraints on the test cases:

        All test cases must be written so that it can be understandable with only the steps so the result can be reproduced manually. E.g., instead of set ip address on the interface it should be more human readable, for example:

        test.step(\"Set IPv4 address on the interface dut:cross\")\ntest.step(\"Verify the IP address is set on dut:cross\")\n

        where dut, cross and the IPv4 address could be found inside the logical topology for the test.

        Each test case must begin with the test name followed by a empty line and the description of the test. For example:

        \"\"\"\nSet hostname\n\nVerify that it it possible to change hostname\n\"\"\"\n

        The test specifaction can be genererated with:

        $ make test-spec\n...\n
        "},{"location":"testing/#node-and-link-capabilities","title":"Node and Link Capabilities","text":"

        Logical topology files (topology.dot) declare what each node and link requires; physical topology files declare what each node and link provides. When mapping a logical topology to physical hardware, infamy only assigns a physical node to a logical node when the physical node's provides set is a superset of the logical node's requires set. Tests are skipped if no matching physical topology can be found.

        "},{"location":"testing/#declaring-requirements-logical-topology","title":"Declaring requirements (logical topology)","text":"
        dut [\n    requires=\"infix\",\n];\n\nhost:data -- dut:data [requires=\"ptp-hwts\"]\n
        "},{"location":"testing/#declaring-capabilities-physical-topology","title":"Declaring capabilities (physical topology)","text":"
        switch1 [\n    provides=\"infix\",\n];\n\nswitch1:eth0 -- switch2:eth0 [provides=\"ptp-hwts\"]\n
        "},{"location":"testing/#node-capabilities","title":"Node capabilities","text":"Capability Meaning controller Reserved for the host/controller node; never assigned to a DUT infix Node runs Infix OS \u2014 required by virtually all DUT nodes gps Node has a GPS receiver available as a time reference watchdog Node has a hardware watchdog device"},{"location":"testing/#link-capabilities","title":"Link capabilities","text":"Capability Meaning mgmt Link is a management path (typically coloured grey in diagrams) ieee-mc Link carries IEEE multicast traffic (required by LAG and some L2 tests) link-ctrl copper Link supports copper speed/duplex control ptp-hwts Both ends of this link support PTP hardware timestamping (PHC); required for sub-microsecond accuracy"},{"location":"testing/#test-development","title":"Test Development","text":"

        For adding a new test to the automated regression test suite, it's best to start by reviewing an existing test case.

        All tests are located in the infix/test/case repository and are grouped by the features they verify. For example, infix/test/case/services contains tests for various Infix services, such as LLDP and mDNS.

        While test grouping is flexible, each test should be placed in a logically relevant category.

        When creating a new test group, add it to infix/test/case/all.yaml, to enable it to run as a subset of the test suite:

        - name:  infix-services\n  suite: services/services.yaml\n

        A new test (e.g., lldp_enable_disable) should be added to the corresponding test group .yaml file, such as infix/test/cases/services.yaml:

        - name: lldp_enable_disable\n  case: lldp_enable_disable/test.py\n

        It is necessary to include the test in infix/test/case/services/Readme.adoc to ensure proper test specification generation:

        include::lldp_enable_disable/Readme.adoc[]\n

        Each test case should have its own directory under, infix/test/case/services, containing:

        • test.py - the test script
        • topology.dot - the logical topology definition.

        When the test specification is generated, topology.svg and Readme.adoc should also be created.

        "},{"location":"tunnels/","title":"Tunneling (L2/L3)","text":""},{"location":"tunnels/#tunnel-configuration","title":"Tunnel Configuration","text":"

        Infix supports multiple tunnel encapsulation protocols for connecting remote networks or devices across an IP backbone. Tunnels encapsulate packets within IP datagrams, allowing traffic to traverse intermediate networks transparently.

        Important

        When issuing leave to activate your changes, remember to also save your settings, copy running-config startup-config. See the CLI Introduction for a background.

        "},{"location":"tunnels/#generic-routing-encapsulation-gre","title":"Generic Routing Encapsulation (GRE)","text":"

        GRE tunnels provide a simple and efficient method to encapsulate various network layer protocols over IP networks. Infix supports both IPv4 and IPv6 tunnels in two modes:

        • GRE (Layer 3): Point-to-point IP tunnel for routing protocols and routed traffic
        • GRETAP (Layer 2): Ethernet tunnel for bridging Layer 2 networks

        Tip

        If you name your tunnel interface greN or gretapN, where N is a number, the CLI infers the interface type automatically.

        "},{"location":"tunnels/#basic-gre-configuration","title":"Basic GRE Configuration","text":"

        A basic GRE tunnel for routing between two sites:

        admin@example:/> configure\nadmin@example:/config/> edit interface gre0\nadmin@example:/config/interface/gre0/> set gre local 192.168.3.1 remote 192.168.3.2\nadmin@example:/config/interface/gre0/> set ipv4 address 10.255.0.1 prefix-length 30\nadmin@example:/config/interface/gre0/> leave\nadmin@example:/>\n

        This creates a Layer 3 tunnel between 192.168.3.1 and 192.168.3.2 using the outer IP addresses, with the tunnel itself using 10.255.0.0/30 for the inner IP addressing.

        "},{"location":"tunnels/#gretap-configuration","title":"GRETAP Configuration","text":"

        GRETAP tunnels operate at Layer 2, allowing bridging across the tunnel:

        admin@example:/> configure\nadmin@example:/config/> edit interface gretap0\nadmin@example:/config/interface/gretap0/> set type gretap\nadmin@example:/config/interface/gretap0/> set gre local 192.168.3.1 remote 192.168.3.2\nadmin@example:/config/interface/gretap0/> leave\nadmin@example:/>\n

        GRETAP interfaces can be added to a bridge, bridging local and remote Ethernet segments. See the Bridge Configuration for more on bridges.

        "},{"location":"tunnels/#ospf-over-gre","title":"OSPF over GRE","text":"

        GRE tunnels are commonly used to carry dynamic routing protocols like OSPF across networks that don't support multicast or where you want to create a virtual topology different from the physical network.

        Example topology: Two sites connected via a GRE tunnel, running OSPF to exchange routes.

        Site A configuration:

        admin@siteA:/> configure\nadmin@siteA:/config/> edit interface gre0\nadmin@siteA:/config/interface/gre0/> set gre local 203.0.113.1 remote 203.0.113.2\nadmin@siteA:/config/interface/gre0/> set ipv4 address 10.255.0.1 prefix-length 30\nadmin@siteA:/config/interface/gre0/> set ipv4 forwarding\nadmin@siteA:/config/interface/gre0/> end\nadmin@siteA:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@siteA:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface gre0\nadmin@siteA:/config/routing/\u2026/ospf/> leave\nadmin@siteA:/>\n

        Site B configuration:

        admin@siteB:/> configure\nadmin@siteB:/config/> edit interface gre0\nadmin@siteB:/config/interface/gre0/> set gre local 203.0.113.2 remote 203.0.113.1\nadmin@siteB:/config/interface/gre0/> set ipv4 address 10.255.0.2 prefix-length 30\nadmin@siteB:/config/interface/gre0/> set ipv4 forwarding\nadmin@siteB:/config/interface/gre0/> end\nadmin@siteB:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@siteB:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface gre0\nadmin@siteB:/config/routing/\u2026/ospf/> leave\nadmin@siteB:/>\n

        Once configured, OSPF will establish a neighbor relationship through the tunnel and exchange routes between the sites. For more info on OSPF configuration, see OSPFv2 Routing.

        Note

        Consider adjusting MTU on the tunnel interface to account for GRE overhead (typically 24 bytes for IPv4, 44 bytes for IPv6) to avoid fragmentation issues.

        "},{"location":"tunnels/#advanced-tunnel-settings","title":"Advanced Tunnel Settings","text":"

        All tunnel types support common parameters for controlling tunnel behavior and performance.

        "},{"location":"tunnels/#time-to-live-ttl","title":"Time To Live (TTL)","text":"

        The TTL setting controls the Time To Live value for the outer tunnel packets. By default, tunnels use a fixed TTL of 64, which allows packets to traverse multiple hops between tunnel endpoints.

        admin@example:/config/> edit interface gre0\nadmin@example:/config/interface/gre0/> set gre ttl 255\nadmin@example:/config/interface/gre0/> leave\n

        Valid values are 1-255, or the special value inherit which copies the TTL from the encapsulated packet.

        Important

        The inherit mode can cause problems with routing protocols like OSPF that use TTL=1 for their packets. For tunnels carrying routing protocols, always use a fixed TTL value (typically 64 or 255).

        "},{"location":"tunnels/#type-of-service-tos","title":"Type of Service (ToS)","text":"

        The ToS setting controls QoS marking for tunnel traffic:

        admin@example:/config/> edit interface gre0\nadmin@example:/config/interface/gre0/> set gre tos 0x10\nadmin@example:/config/interface/gre0/> leave\n

        Valid values are 0-255 for fixed ToS/DSCP marking, or inherit (default) to copy the ToS value from the encapsulated packet.

        "},{"location":"tunnels/#path-mtu-discovery-gre-only","title":"Path MTU Discovery (GRE only)","text":"

        The pmtu-discovery setting can be used to control the Path MTU Discovery on GRE tunnels. When enabled (default), the tunnel respects the Don't Fragment (DF) bit and performs PMTU discovery:

        admin@example:/config/> edit interface gre0\nadmin@example:/config/interface/gre0/> set gre pmtudisc false\nadmin@example:/config/interface/gre0/> leave\n

        Disabling PMTU discovery may be necessary in networks with broken ICMP filtering but can lead to suboptimal performance and fragmentation.

        "},{"location":"tunnels/#virtual-extensible-local-area-network-vxlan","title":"Virtual eXtensible Local Area Network (VXLAN)","text":"

        VXLAN is a network virtualization technology that encapsulates Layer 2 Ethernet frames within Layer 4 UDP datagrams. It uses a 24-bit segment ID, termed VXLAN Network Identifier (VNI), allowing up to 16 million isolated networks.

        Infix supports both IPv4 and IPv6 for VXLAN tunnel endpoints.

        "},{"location":"tunnels/#basic-vxlan-configuration","title":"Basic VXLAN Configuration","text":"

        Tip

        If you name your VXLAN interface vxlanN, where N is a number, the CLI infers the interface type automatically.

        admin@example:/> configure\nadmin@example:/config/> edit interface vxlan100\nadmin@example:/config/interface/vxlan100/> set vxlan local 192.168.3.1\nadmin@example:/config/interface/vxlan100/> set vxlan remote 192.168.3.2\nadmin@example:/config/interface/vxlan100/> set vxlan vni 100\nadmin@example:/config/interface/vxlan100/> leave\nadmin@example:/>\n

        The VNI uniquely identifies the VXLAN segment and must match on both tunnel endpoints.

        "},{"location":"tunnels/#vxlan-with-custom-udp-port","title":"VXLAN with Custom UDP Port","text":"

        The default VXLAN UDP destination port is 4789 (IANA assigned). In some cases you may need to use a different port:

        admin@example:/> configure\nadmin@example:/config/> edit interface vxlan100\nadmin@example:/config/interface/vxlan100/> set vxlan local 192.168.3.1\nadmin@example:/config/interface/vxlan100/> set vxlan remote 192.168.3.2\nadmin@example:/config/interface/vxlan100/> set vxlan vni 100\nadmin@example:/config/interface/vxlan100/> set vxlan remote-port 8472\nadmin@example:/config/interface/vxlan100/> leave\nadmin@example:/>\n

        The remote-port setting allows interoperability with systems using non-standard VXLAN ports.

        Note

        VXLAN tunnels also support the ttl and tos settings described in the Advanced Tunnel Settings section above.

        "},{"location":"upgrade/","title":"Upgrade","text":""},{"location":"upgrade/#upgrading-boot-order","title":"Upgrading & Boot Order","text":"

        For resilience purposes, Infix maintains two software images referred to as the primary and secondary partition image. In addition, some bootloaders support netbooting.

        The boot order defines which image is tried first, and is listed with the CLI show software command. It also shows Infix version installed per partition, and which image was used when booting (STATE booted).

        admin@example:/> show software\nBoot order : primary secondary net\n\nNAME       STATE     VERSION                DATE                     \nprimary    booted    v25.01.0               2025-04-25T10:15:00+00:00\nsecondary  inactive  v25.01.0               2025-04-25T10:07:20+00:00\nadmin@example:/>\n

        YANG support for upgrading Infix, inspecting and modifying the boot-order, is defined in infix-system-software.

        "},{"location":"upgrade/#changing-boot-order","title":"Changing Boot Order","text":"

        The boot order can be manually changed using the set boot-order command. This is useful for rolling back to a previous version or changing the preferred boot source.

        The command accepts one to three boot targets as separate arguments, in the desired boot order. Valid boot targets are:

        • primary - The primary partition
        • secondary - The secondary partition
        • net - Network boot (if supported by bootloader)

        The CLI provides tab-completion for boot targets, making it easy to enter valid values.

        Example: View current boot order and change it:

        admin@example:/> show boot-order\nprimary secondary net\nadmin@example:/> set boot-order secondary primary net\nadmin@example:/> show boot-order\nsecondary primary net\nadmin@example:/>\n

        Example: Set boot order to only try primary partition:

        admin@example:/> show boot-order\nsecondary primary net\nadmin@example:/> set boot-order primary\nadmin@example:/> show boot-order\nprimary\nadmin@example:/>\n

        Example: Using tab-completion (press TAB to see available options):

        admin@example:/> set boot-order TAB\nnet        primary    secondary\nadmin@example:/> set boot-order secondary TAB\nnet        primary    secondary\nadmin@example:/> set boot-order secondary primary\nadmin@example:/> show boot-order\nsecondary primary\nadmin@example:/>\n

        The new boot order takes effect on the next reboot and can be verified with show boot-order or show software:

        admin@example:/> show software\nBoot order : secondary primary\n\nNAME       STATE     VERSION                DATE                     \nprimary    booted    v25.01.0               2025-04-25T10:15:00+00:00\nsecondary  inactive  v25.01.0               2025-04-25T10:07:20+00:00\nadmin@example:/>\n

        Note

        The boot order is automatically updated when performing an upgrade. The newly installed image will be set as the first boot target.

        Duplicate boot targets are not allowed. The CLI will reject attempts to specify the same target multiple times.

        "},{"location":"upgrade/#upgrading","title":"Upgrading","text":"

        Upgrading Infix is done one partition at a time. If the system has booted from one partition, an upgrade will apply to the other (inactive) partition.

        1. Download and unpack the release to install. Make the image pkg bundle available at some URL2
        2. (Optional) Backup the startup configuration
        3. Assume the unit has booted the primary image. Then running the upgrade command installs a new image on the secondary partition
        4. As part of a successful upgrade, the boot-order is implictly changed to boot the newly installed image
        5. Reboot the unit
        6. The unit now runs the new image. To upgrade the remaining partition (primary), run the same upgrade command again, and (optionally) reboot to verify the upgrade

        Caution

        During boot (step 5), the unit may migrate the startup configuration for any syntax changes. It is therefore important that you make sure to upgrade the other partition as well after reboot, of course after having verified your setup.

        The CLI example below shows steps 2-5.

        Backup startup configuration: It is recommended to backup the startup configuration before performing an upgrade. The backup is useful if the upgrade fails, and makes a later downgrade a smoother process.

        admin@example:/> dir /cfg\n/cfg directory\nbackup/             ssl/                startup-config.cfg\n\nadmin@example:/> copy /cfg/startup-config.cfg /cfg/v25.01.0-startup-config.cfg\nadmin@example:/> dir /cfg\n/cfg directory\nbackup/             ssl/                startup-config.cfg           v25.01.0-startup-config.cfg\n\nadmin@example:/>\n

        Upgrade: Here the image pkg bundle was made available via TFTP.

        admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.03.1.pkg\ninstalling\n  0% Installing\n  0% Determining slot states\n 10% Determining slot states done.\n...\n 40% Checking slot rootfs.1 (secondary)\n 46% Checking slot rootfs.1 (secondary) done.\n...\n 98% Copying image to rootfs.1\n 99% Copying image to rootfs.1\n 99% Copying image to rootfs.1 done.\n 99% Updating slots done.\n100% Installing done.\nInstalling `tftp://198.18.117.1/infix-aarch64-25.03.1.pkg` succeeded\nadmin@example:/>\n

        Reboot: The unit will boot on the other partition, with the newly installed image. The Loading startup-config step conducts migration of startup configuration if applicable.

        admin@example:/> reboot\n[ OK ] Stopping Static routing daemon\n[ OK ] Stopping Zebra routing daemon\n...\n[ OK ] Loading startup-config\n[ OK ] Verifying self-signed https certificate\n[ OK ] Update DNS configuration\n[ OK ] Starting Status daemon\n\nInfix OS \u2014 Immutable.Friendly.Secure v25.03.1 (ttyS0)\nexample login: admin\nPassword:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-'\n\nRun the command 'cli' for interactive OAM\n\nadmin@example:~$ cli\n\nSee the 'help' command for an introduction to the system\n\nadmin@example:/> show software\nBoot order : secondary primary net\n\nNAME       STATE     VERSION                DATE                     \nprimary    inactive  v25.01.0               2025-04-25T10:15:00+00:00\nsecondary  booted    v25.03.1               2025-04-25T10:24:31+00:00\nadmin@example:/>\n

        As shown, the boot order has been updated, so that secondary is now the preferred boot source.

        To upgrade the remaining partition (primary), run the upgrade URL command again, and (optionally) reboot.

        "},{"location":"upgrade/#configuration-migration","title":"Configuration Migration","text":"

        The example above illustrated an upgrade from Infix v25.01.0 to v25.03.1. Inbetween these versions, YANG configuration definitions changed slightly (more details given below).

        During boot, Infix inspects the version meta information within the startup configuration file to determine if configuration migration is needed. In this specific case, the configuration file has version 1.4 while the booted software expects version 1.5 (the configuration version numbering differs from the Infix image version numbering). The startup configuration is migrated to 1.5 definitions and stored, while a backup previous startup configuration is stored in directory /cfg/backup/.

        admin@example:/> dir /cfg/backup/\n/cfg/backup/ directory\nstartup-config-1.4.cfg\n\nadmin@example:/>\n

        The modifications made to the startup configuration can be viewed by comparing the files from the shell. An example is shown below.

        admin@example:/> exit\nadmin@example:~$ diff /cfg/backup/startup-config-1.4.cfg /cfg/startup-config.cfg\n--- /cfg/backup/startup-config-1.4.cfg\n+++ /cfg/startup-config.cfg\n...\n-          \"public-key-format\": \"ietf-crypto-types:ssh-public-key-format\",\n+          \"public-key-format\": \"infix-crypto-types:ssh-public-key-format\",\n...\n-          \"private-key-format\": \"ietf-crypto-types:rsa-private-key-format\",\n+          \"private-key-format\": \"infix-crypto-types:rsa-private-key-format\",\n...\n-    \"version\": \"1.4\"\n+    \"version\": \"1.5\"\n...\nadmin@example:~$\n
        "},{"location":"upgrade/#downgrading","title":"Downgrading","text":"

        Downgrading to an earlier version is possible, however, downgrading is not guaranteed to work smoothly. In particular, when the unit boots up with the downgraded version, it may fail to apply the startup config, and instead apply its failure config.

        We consider two cases: downgrading with and without applying a backup startup configuration before rebooting.

        In both cases we start out with a unit running Infix v25.03.1, and wish to downgrade to v25.01.0.

        admin@example:/> show software\nBoot order : primary secondary net\n\nNAME       STATE     VERSION                DATE                     \nprimary    booted    v25.03.1               2025-04-25T11:36:26+00:00\nsecondary  inactive  v25.03.1               2025-04-25T10:24:31+00:00\nadmin@example:/>\n
        "},{"location":"upgrade/#with-backup-startup-config","title":"With Backup startup-config","text":"

        This is the recommended approach to downgrade, given that you have a backup configuration available. The objective is to avoid ending up with the unit in failure config.

        1. Find the backup configuration file
        2. Run upgrade URL to install Infix image to downgrade to
        3. Copy backup startup configuration to current startup configuration (from shell)
        4. Reboot

        Find the backup configuration file:

        Assume you have a backup startup config for the Infix version to downgrade to (here Infix v25.01.0, config version 1.4).

        The preferred approach is to use a startup configuration backed up when running Infix v25.01.0 on the unit. See section Upgrading above for more information. In the following example, there is a backup file available named v25.01.0-startup-config.cfg:

        admin@example:/> dir /cfg\n/cfg directory\nbackup/       ssl/       startup-config.cfg    v25.01.0-startup-config.cfg\n\nadmin@example:/>\n

        The alternative is to use a startup config implicitly backed up by the system as part of Configuration Migration.

        admin@example:/> dir /cfg/backup/\n/cfg/backup/ directory\nstartup-config-1.4.cfg\n\nadmin@example:/>\n

        Caution

        Using a backup configuration file stored when the unit was running the old version (e.g., v25.01.0-startup-config.cfg) is preferred. Although backup files stored due to configuration migration (e.g., startup-config-1.4.cfg) usually works too if the configuration file version (1.4) matches, there are situations when the system may fail to apply it as described below.

        The configuration file version (1.4) is only incremented when changes in YANG configuration syntax mandates it to handle upgrading. Say the next Infix version includes a new feature setting, it can still have version 1.4, as upgrading to it would not need migration. If a user then enables the new feature setting, the new configuration will no longer be compatible with the previous Infix version. A downgrade after enabling new features risks ending up with the unit in failure config.

        Use upgrade command to downgrade:

        admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.01.0.pkg\ninstalling\n  0% Installing\n  0% Determining slot states\n 10% Determining slot states done.\n ...\n 99% Copying image to rootfs.1 done.\n 99% Updating slots done.\n100% Installing done.\nInstalling `tftp://198.18.117.1/infix-aarch64-25.01.0.pkg` succeeded\nadmin@example:/>\n

        Apply the backup configuration file:

        It is recommended to use a backup configuration file for the Infix version to downgrade to, if there is one available.

        admin@example:/> copy /cfg/v25.01.0-startup-config.cfg /cfg/startup-config.cfg\nOverwrite existing file /cfg/startup-config.cfg (y/N)? y\nadmin@example:/>\n

        An alternative is to use a backup file stored when the system conducted a configuration migration. See the caution note above.

        admin@example:/> copy /cfg/backup/startup-config-1.4.cfg /cfg/startup-config.cfg\nOverwrite existing file /cfg/startup-config.cfg (y/N)? y\nadmin@example:/>\n

        Reboot:

        The unit will come up with the applied backup configuration.

        admin@example:/> reboot\n[ OK ] Saving system clock to file\n[ OK ] Stopping Software update service\n[ OK ] Stopping Status daemon\n...\n[ OK ] Bootstrapping YANG datastore\n[ OK ] Starting Configuration daemon\n[ OK ] Loading startup-config\n[ OK ] Update DNS configuration\n[ OK ] Verifying self-signed https certificate\n[ OK ] Starting Status daemon\n\nInfix OS \u2014 Immutable.Friendly.Secure v25.01.0 (ttyS0)\nexample login:\n

        Note

        If the unit despite these measures ends up in failure config, see the next section for more information on how to recover.

        "},{"location":"upgrade/#without-a-backup-startup-config","title":"Without a Backup startup-config","text":"

        This procedure assumes you have access to the unit's console port and its default login credentials1.

        1. Downgrade
        2. Reboot
        3. Login with unit's default credentials
        4. Conduct factory reset
        5. (Then go on configure the unit as you wish)

        Use upgrade command to downgrade:

        admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.01.0.pkg\ninstalling\n  0% Installing\n  0% Determining slot states\n 10% Determining slot states done.\n ...\n 99% Copying image to rootfs.1 done.\n 99% Updating slots done.\n100% Installing done.\nInstalling `tftp://198.18.117.1/infix-aarch64-25.01.0.pkg` succeeded\nadmin@example:/>\n

        Reboot:

        Conduct a reboot. During boot, the unit fails to apply the existing startup configuration (config version 1.5 while software expects version 1.4 or earlier), and instead applies its failure config. This is what is seen on the console when this situation occurs. Note that the login prompt displays failed as part of the hostname.

        admin@example:/> reboot\n[ OK ] Saving system clock to file\n[ OK ] Stopping Software update service\n[ OK ] Stopping Status daemon\n...\n[ OK ] Verifying SSH host keys\n[ OK ] Bootstrapping YANG datastore\n[ OK ] Starting Configuration daemon\n[FAIL] Loading startup-config\n[ OK ] Loading failure-config\n[ OK ] Verifying self-signed https certificate\n[ OK ] Starting Status daemon\n\nInfix OS \u2014 Immutable.Friendly.Secure v25.01.0 (ttyS0)\n\nERROR: Corrupt startup-config, system has reverted to default login credentials\nfailed-00-00-00 login:\n

        To remedy a situation like this, you can login with the unit's default login credentials, preferrably via a console port. The unit's default credentials are typically printed on a sticker on the unit.

        failed-00-00-00 login: admin\nPassword:\n\nRun the command 'cli' for interactive OAM\n\nadmin@failed-00-00-00:~$\n

        When it is safe from a network operations perspective, you can conduct a factory reset and reboot. It is recommended to remove the unit from any production network before doing this, as a factory reset may enable undesired connectivity between the unit's ports.

        admin@failed-00-00-00:~$ factory\nFactory reset device (y/N)? y\nfactory: scheduled factory reset on next boot.\nReboot now to perform reset, (y/N)? y\n[ OK ] Saving system time (UTC) to RTC\n[ OK ] Stopping mDNS alias advertiser\n...\n[ OK ] Starting Configuration daemon\n[ OK ] Loading startup-config\n[ OK ] Update DNS configuration\n[ OK ] Verifying self-signed https certificate\n[ OK ] Starting Status daemon\n[ OK ] Starting Status daemon\n\n\nPlease press Enter to activate this console.\n\nInfix OS \u2014 Immutable.Friendly.Secure v25.01.0 (ttyS0)\nexample login:\n

        Continued configuration is done as with any unit after factory reset.

        1. In failure config, Infix puts all Ethernet ports as individual interfaces. With direct access, one can connect with e.g., SSH, using link local IPv6 addresses. This as an alternative to connecting via a console port.\u00a0\u21a9

        2. Set up an FTP/TFTP/SFTP or HTTP/HTTPS server on the same LAN.\u00a0\u21a9

        "},{"location":"virtual/","title":"Virtual Environments","text":""},{"location":"virtual/#virtual-environments","title":"Virtual Environments","text":"

        Infix primarily targets real hardware, deployment to the cloud is not a priority at the moment. However, for development and testing purposes there is an x86_64 build that runs in Qemu.

        These images also work with the Graphical Network Simulator (GNS3), which is a very user-friendly tool for playing around with simple to complex network setups, verifying interoperability between vendors, etc.

        "},{"location":"virtual/#qemu","title":"QEMU","text":"

        Tip

        Installation for Debian/Ubuntu based systems can be done by \"simply\": sudo apt install virt-manager -- dependencies ensure the relevant Qemu packages are pulled in as well. This trick, installing virt-manager, helps set up Qemu networking on your system.

        A virtualized Infix x86_64 instance can be launched from a Linux system, with Qemu installed, by issuing:

        $ ./qemu/run.sh\n...\n

        from an unpacked release tarball. From a built source tree of Infix the same functionality is bundled as:

        $ make run\n...\n

        To change settings, e.g. networking, make run-menuconfig, or from a pre-built Infix release tarball, using ./qemu/run.sh -c

        The Infix test suite is built around Qemu and Qeneth, see:

        • Regression Testing with Infamy
        • Infamy Docker Image
        "},{"location":"virtual/#gns3","title":"GNS3","text":"

        Download the latest build of the x86_64, or aarch64 if your host machine is Arm. Unpack the tarball in a dedicated directory and use \"Import Appliance\" to install the .gns3a file into GNS3.

        Infix is in the \"Router\" category, it comes with 10 interfaces available by default for use as switch ports or routing.

        "},{"location":"vpd/","title":"Vital Product Data (VPD)","text":""},{"location":"vpd/#vital-product-data","title":"Vital Product Data","text":"

        Infix can source Vital Product Data (VPD) from EEPROMs containing an ONIE TLV. The load bearing information from VPDs, used by Infix by default, is:

        1. Base (chassis) MAC address of the system
        2. Factory default admin password hash

        Additionally, every VPD is listed as a separate component in the ietf-hardware model, in which Infix exports all available attributes to the user. This is useful to read out exact hardware revisions of each board making up a system, figure out if a system belongs to a particular production batch, etc.

        "},{"location":"vpd/#json-encoding","title":"JSON Encoding","text":"

        To make EEPROM binary generation less cumbersome, Infix defines a JSON encoding of an ONIE TLV, and provides a tool, onieprom, to translate from one encoding to another. I.e. a JSON object can be translated to its binary equivalent, and vice versa.

        A mapping between TLV attribute IDs and JSON keys is defined, using the Linux kernel driver as a reference:

        TLV ID Description Key Type 0x21 Product Name \"product-name\" String 0x22 Part Number \"part-number\" String 0x23 Serial Number \"serial-number\" String 0x24 MAC #1 Base \"mac-address\" String (\"c0:ff:ee:00:00:00\") 0x25 Manufacture Date \"manufacture-date\" String (\"MM/DD/YYYY HH:NN:SS\") 0x26 Device Version \"device-version\" Integer (u8) 0x27 Label Revision \"label-revision\" String 0x28 Platform Name \"platform-name\" String 0x29 ONIE Version \"onie-version\" String 0x2a Num MACs \"num-macs\" Integer (u16) 0x2b Manufacturer \"manufacturer\" String 0x2c Country Code \"country-code\" String (ISO 3166-1 2-byte code) 0x2d Vendor \"vendor\" String 0x2e Diag Version \"diag-version\" String 0x2f Service Tag \"service-tag\" String 0xfd Vendor Extension \"vendor-extension\" List of extensions (see below)

        The JSON encoding of a TLV is a single JSON object with an arbitrary subset of the attributes from the list above.

        Example:

        {\n    \"product-name\": \"Wacky Widget\",\n    \"serial-number\": \"#1\",\n    \"manufacture-date\": \"02/13/2024 11:29:52\"\n}\n

        "},{"location":"vpd/#vendor-extensions","title":"Vendor Extensions","text":"

        As the specification explicitly mentions the option of supplying multiple vendor extensions, possibly multiple ones of the same type, the JSON chosen encoding is a list, where each element is itself a list of exactly two elements:

        Element # Type 1 Integer (u32, IANA enterprise number) 2 String (UTF-8 encoded extension data)

        The format of the extension data is defined by the entity indicated by the IANA enterprise number. Restricting extension data to UTF-8 encoded strings is a decision imposed by Infix; the ONIE specification allows for arbitrary binary data.

        Example:

        {\n    ...\n    \"vendor-extension\": [\n        [ 12345, \"my extension data\" ]\n    ]\n}\n
        "},{"location":"vpd/#infix-specific-extensions","title":"Infix Specific Extensions","text":"

        Kernelkit's IANA enterprise number is 61046, under which any extensions required by Infix are stored. The only valid extension data that may be stored under this number is documented in this section. If other device specific data needs to be stored in a VPD, you must associate that with an enterprise number under our control.

        Every Kernelkit extension must be a valid JSON object containing an arbitrary subset of the following attributes:

        Key Value Description \"pwhash\" String (output of mkpasswd(1)) Factory default password for admin

        Since the extension is itself stored in a JSON document, it has to be appropriately quoted.

        Example:

        {\n    ...\n    \"vendor-extension\": [\n        [ 61046, \"{\\\"pwhash\\\":\\\"$6$9rufAxdqCrxrwfQR$G0l9cTVlu/vOhxgo/uMKfRDOmZRd5XWF3vKr5da6qYoxuTJBS/Pl9K.5lrabWoWFFc.71yFMaSlZz0O8FtAtl.\\\"}\" ]\n    ]\n}\n
        "},{"location":"vpd/#creating-and-parsing-onie-eeprom-binaries","title":"Creating and Parsing ONIE EEPROM Binaries","text":"
        usage: onieprom [-h] [-e] [-d] [infile] [outfile]\n\npositional arguments:\n  infile\n  outfile\n\noptions:\n  -h, --help    show this help message and exit\n  -e, --encode  Encode JSON input to binary output\n  -d, --decode  Decode binary input to JSON output\n

        To convert a compatible JSON document (using the first example above) to its binary equivalent, we ask onieprom to encode it for us:

        ~$ onieprom -e example.json >example.bin\n~$ hexdump -C example.bin\n00000000  54 6c 76 49 6e 66 6f 00  01 00 2d 25 13 30 32 2f  |TlvInfo...-%.02/|\n00000010  31 33 2f 32 30 32 34 20  31 31 3a 32 39 3a 35 32  |13/2024 11:29:52|\n00000020  21 0c 57 61 63 6b 79 20  57 69 64 67 65 74 23 02  |!.Wacky Widget#.|\n00000030  23 31 fe 04 dd 69 88 97                           |#1...i..|\n00000038\n

        We can also run the process in reverse, to inspect the contents of a binary in its equivalent JSON representation:

        ~$ onieprom -d example.bin >example-again.json\n~$ jq . example-again.json\n{\n  \"manufacture-date\": \"02/13/2024 11:29:52\",\n  \"product-name\": \"Wacky Widget\",\n  \"serial-number\": \"#1\"\n}\n
        "},{"location":"vpn-wireguard/","title":"WireGuard","text":""},{"location":"vpn-wireguard/#wireguard-vpn","title":"WireGuard VPN","text":"

        Note

        For a general introduction to VPN concepts and deployment models, see VPN Configuration.

        WireGuard is a modern, high-performance VPN protocol that uses state-of-the-art cryptography. It is significantly simpler and faster than traditional VPN solutions like IPsec or OpenVPN, while maintaining strong security guarantees.

        Key features of WireGuard:

        • Simple Configuration: Minimal settings required compared to IPsec
        • High Performance: Runs in kernel space with efficient cryptography
        • Strong Cryptography: Uses Curve25519, ChaCha20, Poly1305, and BLAKE2
        • Roaming Support: Seamlessly handles endpoint IP address changes
        • Dual-Stack: Supports IPv4 and IPv6 for both tunnel endpoints and traffic

        Important

        When issuing leave to activate your changes, remember to also save your settings, copy running-config startup-config. See the CLI Introduction for a background.

        "},{"location":"vpn-wireguard/#key-management","title":"Key Management","text":"

        WireGuard uses public-key cryptography similar to SSH. Each WireGuard interface requires a private key, and each peer is identified by its public key.

        Import the key pair into the keystore:

        admin@example:/> configure\nadmin@example:/config/> edit keystore asymmetric-key wg-site-a\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> do wireguard genkey\nPrivate: aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=\nPublic:  bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> set public-key-format x25519-public-key-format\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> set private-key-format x25519-private-key-format\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> set public-key bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> set cleartext-private-key aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> leave\nadmin@example:/>\n

        Tip

        The do prefix allows running admin-exec commands from configure context. Use the base for wireguard genkey when in admin-exec context.

        Import peer public keys into the truststore:

        admin@example:/> configure\nadmin@example:/config/> edit truststore public-key-bag wg-peers public-key peer-b\nadmin@example:/config/truststore/\u2026/peer-b/> set public-key-format x25519-public-key-format\nadmin@example:/config/truststore/\u2026/peer-b/> set public-key PEER_PUBLIC_KEY_HERE\nadmin@example:/config/truststore/\u2026/peer-b/> leave\nadmin@example:/>\n

        Important

        Keep private keys secure! Never share your private key. Only exchange public keys with peers.

        "},{"location":"vpn-wireguard/#point-to-point-configuration","title":"Point-to-Point Configuration","text":"

        Tip

        If you name your WireGuard interface wgN, where N is a number, the CLI infers the interface type automatically.

        A basic WireGuard tunnel between two sites:

        Site A configuration:

        admin@siteA:/> configure\nadmin@siteA:/config/> edit interface wg0\nadmin@siteA:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@siteA:/config/interface/wg0/> set wireguard private-key wg-site-a\nadmin@siteA:/config/interface/wg0/> set ipv4 address 10.0.0.1 prefix-length 24\nadmin@siteA:/config/interface/wg0/> edit wireguard peer wg-peers peer-b\nadmin@siteA:/config/interface/\u2026/wg-peers/peer/peer-b/> set endpoint 203.0.113.2\nadmin@siteA:/config/interface/\u2026/wg-peers/peer/peer-b/> set endpoint-port 51820\nadmin@siteA:/config/interface/\u2026/wg-peers/peer/peer-b/> set allowed-ips 10.0.0.2/32\nadmin@siteA:/config/interface/\u2026/wg-peers/peer/peer-b/> set persistent-keepalive 25\nadmin@siteA:/config/interface/\u2026/wg-peers/peer/peer-b/> leave\nadmin@siteA:/>\n

        Site B configuration:

        admin@siteB:/> configure\nadmin@siteB:/config/> edit interface wg0\nadmin@siteB:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@siteB:/config/interface/wg0/> set wireguard private-key wg-site-b\nadmin@siteB:/config/interface/wg0/> set ipv4 address 10.0.0.2 prefix-length 24\nadmin@siteB:/config/interface/wg0/> edit wireguard peer wg-peers peer-a\nadmin@siteB:/config/interface/\u2026/wg-peers/peer/peer-a/> set endpoint 203.0.113.1\nadmin@siteB:/config/interface/\u2026/wg-peers/peer/peer-a/> set endpoint-port 51820\nadmin@siteB:/config/interface/\u2026/wg-peers/peer/peer-a/> set allowed-ips 10.0.0.1/32\nadmin@siteB:/config/interface/\u2026/wg-peers/peer/peer-a/> set persistent-keepalive 25\nadmin@siteB:/config/interface/\u2026/wg-peers/peer/peer-a/> leave\nadmin@siteB:/>\n

        This creates an encrypted tunnel with Site A at 10.0.0.1 and Site B at 10.0.0.2.

        "},{"location":"vpn-wireguard/#understanding-allowed-ips","title":"Understanding Allowed IPs","text":"

        The allowed-ips setting in WireGuard serves two critical purposes:

        1. Ingress Filtering: Only packets with source IPs in the allowed list are accepted from the peer
        2. Cryptokey Routing: Determines which peer receives outbound packets for a given destination

        Think of allowed-ips as a combination of firewall rules and routing table.

        Figure: WireGuard key exchange and allowed-ips configuration between two peers

        For a simple point-to-point tunnel, you typically allow only the peer's tunnel IP address (e.g., 10.0.0.2/32). For site-to-site VPNs connecting entire networks, include the remote network prefixes:

        admin@example:/config/interface/\u2026/wg-peers/peer/peer-a/> set allowed-ips 10.0.0.2/32\nadmin@example:/config/interface/\u2026/wg-peers/peer/peer-a/> set allowed-ips 192.168.2.0/24\n

        This allows traffic to/from the peer at 10.0.0.2 and routes traffic destined for 192.168.2.0/24 through this peer.

        Note

        When routing traffic to networks behind WireGuard peers, you also need to configure static routes pointing to the WireGuard interface. See Static Routes for more information.

        "},{"location":"vpn-wireguard/#peer-configuration-and-key-bags","title":"Peer Configuration and Key Bags","text":"

        WireGuard peer configuration supports a two-level hierarchy that allows efficient management of multiple peers with shared settings.

        Public Key Bags group related peers together (e.g., all mobile clients, all branch offices) and allow you to configure default settings that apply to all peers in the bag. Individual peers can then override these defaults when needed.

        Settings that support bag-level defaults and per-peer overrides:

        • endpoint - Remote peer's IP address
        • endpoint-port - Remote peer's UDP port (defaults to 51820 at bag level)
        • persistent-keepalive - Keepalive interval in seconds
        • preshared-key - Optional pre-shared key for additional quantum resistance
        • allowed-ips - IP addresses allowed to/from this peer

        Important

        Key Bag Configuration Rules:

        • Single key in bag: You can use bag-level settings (endpoint, allowed-ips, etc.) without specifying individual peer configurations. All settings apply to that one peer.

        • Multiple keys in bag: You MUST provide individual peer configuration for each key in the bag. Settings like endpoint and allowed-ips must be unique per peer and cannot be shared at the bag level when multiple peers exist.

        This prevents configuration errors where multiple peers would incorrectly share the same endpoint address or allowed-ips ranges.

        Example with bag-level defaults:

        admin@example:/> configure\nadmin@example:/config/> edit interface wg0\nadmin@example:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@example:/config/interface/wg0/> set wireguard private-key wg-key\nadmin@example:/config/interface/wg0/> set ipv4 address 10.0.0.1 prefix-length 24\n\n# Configure defaults for all peers in the 'branch-offices' bag\nadmin@example:/config/interface/wg0/> edit wireguard peers branch-offices\nadmin@example:/config/interface/\u2026/wireguard/peers/branch-offices/> set endpoint-port 51820\nadmin@example:/config/interface/\u2026/wireguard/peers/branch-offices/> set persistent-keepalive 25\nadmin@example:/config/interface/\u2026/wireguard/peers/branch-offices/> end\n\n# Configure peer-specific settings (inherits endpoint-port and keepalive from bag)\nadmin@example:/config/interface/\u2026/wireguard/peers/branch-offices/> edit peer office-east\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-east/> set endpoint 203.0.113.10\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-east/> set allowed-ips 10.0.0.10/32\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-east/> set allowed-ips 192.168.10.0/24\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-east/> end\n\n# Another peer with an override for persistent-keepalive\nadmin@example:/config/interface/\u2026/wireguard/peers/branch-offices/> edit peer office-west\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-west/> set endpoint 203.0.113.20\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-west/> set allowed-ips 10.0.0.20/32\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-west/> set allowed-ips 192.168.20.0/24\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-west/> set persistent-keepalive 10\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-west/> leave\nadmin@example:/>\n

        In this example: - Both peers inherit endpoint-port 51820 and persistent-keepalive 25 from the bag - office-west overrides the keepalive to 10 seconds while office-east uses the default 25 - Each peer has its own endpoint and allowed-ips configuration

        This approach simplifies management when you have many peers with similar configurations - set the common defaults once at the bag level, then only specify per-peer differences.

        "},{"location":"vpn-wireguard/#site-to-site-vpn","title":"Site-to-Site VPN","text":"

        Figure: Site-to-Site VPN connecting two office networks

        A site-to-site VPN connects entire networks across locations, creating a unified private network over the internet. This allows devices in one location to seamlessly access resources in another as if they were on the same local network.

        This is the point-to-point configuration shown earlier, extended with routing to allow access to networks behind each peer. Configure the WireGuard tunnel as shown in Point-to-Point Configuration, then add the remote network to allowed-ips and configure static routes.

        "},{"location":"vpn-wireguard/#road-warrior-vpn","title":"Road Warrior VPN","text":"

        Figure: Mobile clients connecting to corporate network

        For mobile clients or peers without fixed IPs, omit the endpoint setting. WireGuard learns the peer's endpoint from authenticated incoming packets:

        admin@hub:/> configure\nadmin@hub:/config/> edit interface wg0 wireguard peers wg-peers peer mobile-client\nadmin@hub:/config/interface/\u2026/wg-peers/peer/mobile-client/> set allowed-ips 10.0.0.10/32\nadmin@hub:/config/interface/\u2026/wg-peers/peer/mobile-client/> leave\nadmin@hub:/>\n

        The mobile client configures the hub's endpoint normally. The hub learns and tracks the mobile client's changing IP address automatically.

        "},{"location":"vpn-wireguard/#hub-and-spoke-topology","title":"Hub-and-Spoke Topology","text":"

        Figure: Hub-and-Spoke topology with central hub routing traffic between spokes

        WireGuard excels at hub-and-spoke (star) topologies where multiple remote sites connect to a central hub.

        Hub configuration:

        admin@hub:/> configure\nadmin@hub:/config/> edit interface wg0\nadmin@hub:/config/interface/wg0/> set ipv4 address 10.0.0.1 prefix-length 24\nadmin@hub:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@hub:/config/interface/wg0/> set wireguard private-key wg-hub\nadmin@hub:/config/interface/wg0/> edit wireguard peers wg-peers\n\n# Spoke 1\nadmin@hub:/config/interface/\u2026/wireguard/peers/wg-peers/> edit peer spoke1\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke1/> set allowed-ips 10.0.0.2/32\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke1/> set allowed-ips 192.168.1.0/24\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke1/> end\n\n# Spoke 2\nadmin@hub:/config/interface/\u2026/wireguard/peers/wg-peers/> edit peer spoke2\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke2/> set allowed-ips 10.0.0.3/32\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke2/> set allowed-ips 192.168.2.0/24\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke2/> leave\n\n# Add routes for spoke networks\nadmin@hub:/> configure\nadmin@hub:/config/> edit routing control-plane-protocol static name default\nadmin@hub:/config/routing/\u2026/static/name/default/> set ipv4 route 192.168.1.0/24 wg0\nadmin@hub:/config/routing/\u2026/static/name/default/> set ipv4 route 192.168.2.0/24 wg0\nadmin@hub:/config/routing/\u2026/static/name/default/> leave\nadmin@hub:/>\n

        Spoke 1 configuration:

        admin@spoke1:/> configure\nadmin@spoke1:/config/> edit interface wg0\nadmin@spoke1:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@spoke1:/config/interface/wg0/> set wireguard private-key wg-spoke1\nadmin@spoke1:/config/interface/wg0/> set ipv4 address 10.0.0.2 prefix-length 24\nadmin@spoke1:/config/interface/wg0/> edit wireguard peers wg-peers peer hub\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set endpoint 203.0.113.1\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set endpoint-port 51820\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set allowed-ips 10.0.0.1/32\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set allowed-ips 10.0.0.3/32\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set allowed-ips 192.168.0.0/24\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set allowed-ips 192.168.2.0/24\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set persistent-keepalive 25\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> leave\nadmin@spoke1:/> configure\nadmin@spoke1:/config/> edit routing control-plane-protocol static name default\nadmin@spoke1:/config/routing/\u2026/static/name/default/> set ipv4 route 192.168.0.0/24 wg0\nadmin@spoke1:/config/routing/\u2026/static/name/default/> set ipv4 route 192.168.2.0/24 wg0\nadmin@spoke1:/config/routing/\u2026/static/name/default/> leave\nadmin@spoke1:/>\n

        This configuration allows Spoke 1 to reach both the hub network (192.168.0.0/24) and Spoke 2's network (192.168.2.0/24) via the hub, enabling spoke-to-spoke communication through the central hub.

        "},{"location":"vpn-wireguard/#persistent-keepalive","title":"Persistent Keepalive","text":"

        The persistent-keepalive setting sends periodic packets to keep the tunnel active through NAT devices and firewalls:

        admin@example:/config/interface/\u2026/wg-peers/peer/hub/> set persistent-keepalive 25\n

        This is particularly important when:

        • The peer is behind NAT
        • Intermediate firewalls have connection timeouts
        • You need the tunnel to remain ready for bidirectional traffic

        A value of 25 seconds is recommended for most scenarios. Omit this setting for peers with public static IPs that initiate connections.

        Note

        Only the peer behind NAT needs persistent-keepalive configured. The peer with a public IP learns the NAT endpoint from incoming packets.

        "},{"location":"vpn-wireguard/#ipv6-endpoints","title":"IPv6 Endpoints","text":"

        WireGuard fully supports IPv6 for tunnel endpoints:

        admin@example:/> configure\nadmin@example:/config/> edit interface wg0\nadmin@example:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@example:/config/interface/wg0/> set wireguard private-key wg-key\nadmin@example:/config/interface/wg0/> set ipv4 address 10.0.0.1 prefix-length 24\nadmin@example:/config/interface/wg0/> set ipv6 address fd00::1 prefix-length 64\nadmin@example:/config/interface/wg0/> edit wireguard peers wg-peers peer remote\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> set endpoint 2001:db8::2\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> set endpoint-port 51820\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> set allowed-ips 10.0.0.2/32\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> set allowed-ips fd00::2/128\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> leave\nadmin@example:/>\n

        WireGuard can carry both IPv4 and IPv6 traffic regardless of whether the tunnel endpoints use IPv4 or IPv6.

        "},{"location":"vpn-wireguard/#monitoring-wireguard-status","title":"Monitoring WireGuard Status","text":"

        Check WireGuard interface status and peer connections:

        admin@example:/> show interfaces\nwg0             wireguard  UP          2 peers (1 up)\n                ipv4                   10.0.0.1/24 (static)\n                ipv6                   fd00::1/64 (static)\n\nadmin@example:/> show interfaces wg0\nname                : wg0\ntype                : wireguard\nindex               : 12\noperational status  : up\npeers               : 2\n\n  Peer 1:\n    status            : UP\n    endpoint          : 203.0.113.2:51820\n    latest handshake  : 2025-12-09T10:23:45+0000\n    transfer tx       : 125648 bytes\n    transfer rx       : 98432 bytes\n\n  Peer 2:\n    status            : DOWN\n    endpoint          : 203.0.113.3:51820\n    latest handshake  : 2025-12-09T09:15:22+0000\n    transfer tx       : 45120 bytes\n    transfer rx       : 32768 bytes\n

        The connection status shows UP if a handshake occurred within the last 3 minutes, indicating an active tunnel. The latest handshake timestamp shows when the peers last successfully authenticated and exchanged keys.

        "},{"location":"vpn-wireguard/#post-quantum-security-preshared-keys","title":"Post-Quantum Security (Preshared Keys)","text":"

        WireGuard supports optional preshared keys (PSK) that add an extra layer of symmetric encryption alongside Curve25519. This provides defense-in-depth against future quantum computers that might break elliptic curve cryptography.

        PSKs protect your data from \"harvest now, decrypt later\" attacks - adversaries recording traffic today would still need the PSK even if they break Curve25519 later. However, peer authentication still relies on Curve25519, so PSKs don't provide complete post-quantum security.

        Generate a preshared key:

        admin@example:/config/> do wireguard genpsk\ncO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=\n

        Import the preshared key into the keystore:

        admin@example:/config/> edit keystore symmetric-key wg-psk\nadmin@example:/config/keystore/symmetric-key/wg-psk/> set key-format octet-string-key-format\nadmin@example:/config/keystore/symmetric-key/wg-psk/> set cleartext-symmetric-key cO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=\nadmin@example:/config/keystore/symmetric-key/wg-psk/> end\nadmin@example:/config/interface/wg0/> edit wireguard peers wg-peers peer remote\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> set preshared-key wg-psk\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> leave\nadmin@example:/>\n

        The preshared key must be securely shared between both peers and configured on both sides.

        Important

        Preshared keys must be kept secret and exchanged through a secure channel, just like passwords.

        "},{"location":"vpn/","title":"Overview","text":""},{"location":"vpn/#vpn-configuration","title":"VPN Configuration","text":"

        A Virtual Private Network (VPN) creates encrypted tunnels over public networks, enabling secure communication between remote locations or users. Unlike plain tunnels (GRE, VXLAN) that only provide encapsulation, VPNs add authentication and encryption to protect data confidentiality and integrity.

        "},{"location":"vpn/#configuring-vpn","title":"Configuring VPN","text":"

        For detailed configuration instructions and examples, see:

        • WireGuard VPN - Complete guide to configuring WireGuard tunnels, including site-to-site, road warrior, and hub-and-spoke topologies.
        "},{"location":"vpn/#understanding-vpn-tunnels","title":"Understanding VPN Tunnels","text":"

        VPN tunnels establish secure connections across untrusted networks by:

        • Authentication: Verifying the identity of tunnel endpoints using cryptographic keys or certificates
        • Encryption: Protecting data confidentiality with strong ciphers
        • Integrity: Detecting tampering through message authentication codes

        This makes VPNs essential for connecting sites over the internet, enabling remote access for mobile users, and securing traffic in untrusted environments.

        "},{"location":"vpn/#vpn-deployment-models","title":"VPN Deployment Models","text":"

        VPNs are typically deployed in one of several models:

        Site-to-Site VPN

        Figure: Site-to-Site VPN connecting two office networks

        Connects entire networks across locations, creating a unified private network over the internet. Routers or firewalls at each site maintain persistent tunnels, allowing seamless access between locations.

        • Use case: Connecting branch offices to headquarters
        • Characteristics: Always-on, connects networks not individual devices
        • Example: Main office (192.168.1.0/24) \u2194 Branch office (192.168.2.0/24)

        Remote Access VPN (Road Warrior)

        Figure: Mobile clients connecting to corporate network

        Enables individual users to securely access a private network from remote locations. Clients initiate connections as needed from dynamic IP addresses.

        • Use case: Remote employees accessing corporate resources
        • Characteristics: On-demand, handles dynamic endpoints and roaming
        • Example: Mobile laptop \u2194 Corporate network

        Hub-and-Spoke VPN

        Figure: Hub-and-Spoke topology with central hub routing traffic between spokes

        A central hub connects to multiple remote sites (spokes), routing traffic between them. Spokes don't connect directly to each other but communicate through the hub.

        • Use case: Central office connecting multiple remote locations
        • Characteristics: Centralized control, simplified management
        • Example: HQ \u2194 (Branch A, Branch B, Branch C)
        "},{"location":"vpn/#vpn-protocol-comparison","title":"VPN Protocol Comparison","text":"

        Different VPN protocols offer varying trade-offs between security, performance, and complexity:

        Protocol Complexity Performance Use Case WireGuard Simple Very High Modern deployments, all models IPsec Complex High Legacy systems, compliance reqs OpenVPN Moderate Moderate Maximum compatibility

        Infix supports WireGuard as its primary VPN solution, offering the best balance of simplicity, security, and performance for modern networks.

        "},{"location":"wifi/","title":"Wireless LAN (WiFi)","text":""},{"location":"wifi/#wi-fi-wireless-lan","title":"Wi-Fi (Wireless LAN)","text":"

        Infix includes comprehensive Wi-Fi support for both client (Station) and Access Point modes. When a compatible Wi-Fi adapter is detected, the system automatically creates a WiFi radio (PHY) in factory-config, that can host virtual interfaces.

        "},{"location":"wifi/#architecture-overview","title":"Architecture Overview","text":"

        Infix uses a two-layer WiFi architecture:

        1. WiFi Radio (PHY layer): Represents the physical wireless hardware

          • Configured via ietf-hardware module
          • Controls channel, transmit power, regulatory domain
          • One radio can host multiple virtual interfaces
        2. WiFi Interface (Network layer): Virtual interface on a radio

          • Configured via infix-interfaces module
          • Can operate in Station (client) or Access Point mode
          • Each interface references a parent radio
        "},{"location":"wifi/#naming-conventions","title":"Naming Conventions","text":"

        Like other interface types in Infix, WiFi components follow naming conventions that allow the CLI to automatically infer types:

        Name Pattern Type Description radioN WiFi Radio Hardware component for WiFi PHY wifiN WiFi Interface Virtual WiFi interface on a radio

        Where N is a number (0, 1, 2, ...).

        Tip

        Using these naming conventions simplifies configuration since type/class settings are automatically inferred. For example, creating a hardware component named radio0 automatically sets its class to wifi, enabling the wifi-radio configuration container.

        Note: This inference only works with the CLI. Configuring WiFi over NETCONF or RESTCONF requires setting the class/type explicitly.

        "},{"location":"wifi/#current-limitations","title":"Current Limitations","text":"
        • USB hotplug is not supported - adapters must be present at boot
        • Interface naming may be inconsistent with multiple USB Wi-Fi adapters
        • AP and Station modes cannot be mixed on the same radio
        "},{"location":"wifi/#supported-wi-fi-adapters","title":"Supported Wi-Fi Adapters","text":"

        Wi-Fi support is primarily tested with Realtek chipset-based adapters.

        "},{"location":"wifi/#known-working-chipsets","title":"Known Working Chipsets","text":"
        • Built-in Wi-Fi on Banana Pi BPi-R3
        • Built-in Wi-Fi on Raspberry Pi 4/CM4
        • Realtek:
          • RTL8188CU
          • RTL8188FU
          • RTL8821CU

        Other Realtek chipsets may work but are not tested.

        Note

        Some Realtek chipsets require proprietary drivers not included in the standard kernel.

        • Firmware requirements vary by chipset
        • Check kernel logs if your adapter is not detected
        "},{"location":"wifi/#usb-wifi-dongles","title":"USB WiFi Dongles","text":"

        USB WiFi dongles may be slow to initialize at boot due to firmware loading. If your USB dongle is not detected reliably, configure a probe-timeout on the radio to wait for the PHY:

        admin@example:/> configure\nadmin@example:/config/> edit hardware component radio0 wifi-radio\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set probe-timeout 30\nadmin@example:/config/hardware/component/radio0/wifi-radio/> leave\n

        This waits up to 30 seconds for the radio PHY to appear before creating WiFi interfaces. If the PHY is not detected within the timeout, a dummy interface is created as a placeholder, allowing IP configuration to proceed. Reboot when the radio becomes available.

        "},{"location":"wifi/#radio-configuration","title":"Radio Configuration","text":"

        Before configuring WiFi interfaces, you must first configure the WiFi radio. Radios are automatically discovered and named radio0, radio1, etc.

        "},{"location":"wifi/#country-code","title":"Country Code \u26a0","text":"

        The radio defaults to \"00\" for World domain, but some systems may ship with a factory default country code (typically \"DE\" for the BPi-R3).

        Legal notice!

        The country-code setting is legally required and determines which WiFi channels and power levels are permitted in your location. Using an incorrect country code may violate local wireless regulations.

        Common country codes, see ISO 3166-1 alpha-2 for the complete list:

        • Europe:
          • DE: Germany
          • SE: Sweden
          • GB: UK
          • FR: France
          • ES: Spain
        • Americas:
          • US: United States
          • CA: Canada
          • BR: Brazil
        • Asia-Pacific:
          • JP: Japan
          • AU: Australia
          • CN: China
        "},{"location":"wifi/#basic-radio-setup","title":"Basic Radio Setup","text":"

        Configure the radio with channel, power, and regulatory domain.

        For Station (client) mode:

        admin@example:/> configure\nadmin@example:/config/> edit hardware component radio0 wifi-radio\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE\nadmin@example:/config/hardware/component/radio0/wifi-radio/> leave\n

        For Access Point mode:

        admin@example:/> configure\nadmin@example:/config/> edit hardware component radio0 wifi-radio\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set band 5GHz\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set channel 36\nadmin@example:/config/hardware/component/radio0/wifi-radio/> leave\n

        Key radio parameters:

        • country-code: Two-letter ISO 3166-1 alpha-2 code, determines allowed channels and maximum power. Examples: US, DE, GB, SE, FR, JP. \u26a0 Must match your physical location for legal compliance! \u26a0
        • band: 2.4GHz, 5GHz, or 6GHz (required for AP mode). Automatically enables appropriate WiFi standards:
          • 2.4GHz: 802.11n/ax
          • 5GHz: 802.11n/ac/ax
          • 6GHz: 802.11ax
        • channel: Channel number (1-196) or \"auto\". When set to \"auto\", defaults to channel 6 for 2.4GHz, channel 36 for 5GHz, or channel 109 for 6GHz
        • probe-timeout: Seconds to wait for PHY detection at boot (default: 0). Set to a non-zero value (e.g., 30) for USB WiFi dongles that are slow to initialize due to firmware loading

        Note

        TX power and channel width are automatically determined by the driver based on regulatory constraints, PHY mode, and hardware capabilities.

        "},{"location":"wifi/#wifi-6-support","title":"WiFi 6 Support","text":"

        WiFi 6 (802.11ax) is always enabled in AP mode on all bands, providing improved performance through features like OFDMA, BSS Coloring, and beamforming.

        WiFi 6 Features (always enabled):

        • OFDMA: Better multi-user efficiency in dense environments
        • BSS Coloring: Reduced interference from neighboring networks
        • Beamforming: Improved signal quality and range

        Requirements:

        • Hardware must support 802.11ax
        • Client devices must support WiFi 6 for full benefits
        • Older WiFi 5/4 clients can still connect but won't use WiFi 6 features
        "},{"location":"wifi/#discovering-available-networks","title":"Discovering Available Networks","text":"

        Before connecting to a WiFi network, you need to discover which networks are available. Infix automatically scans for networks when a WiFi interface is created with a radio reference.

        "},{"location":"wifi/#enable-background-scanning","title":"Enable Background Scanning","text":"

        To enable scanning without connecting, configure the radio and create a WiFi interface referencing it:

        Step 1: Configure the radio

        admin@example:/> configure\nadmin@example:/config/> edit hardware component radio0 wifi-radio\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE\nadmin@example:/config/hardware/component/radio0/wifi-radio/> leave\n

        Step 2: Create WiFi interface with radio reference only

        admin@example:/> configure\nadmin@example:/config/> edit interface wifi0\nadmin@example:/config/interface/wifi0/> set wifi radio radio0\nadmin@example:/config/interface/wifi0/> leave\n

        The system will now start scanning in the background. The interface will operate in scan-only mode until you configure a specific mode (station or access-point).

        "},{"location":"wifi/#view-available-networks","title":"View Available Networks","text":"

        Use show interface to see discovered networks and their signal strength:

        admin@example:/> show interface wifi0\nname                : wifi0\ntype                : wifi\nindex               : 3\nmtu                 : 1500\noperational status  : up\nip forwarding       : enabled\nphysical address    : f0:09:0d:36:5f:86\nipv4 addresses      : 192.168.1.100/24 (dhcp)\nipv6 addresses      :\nin-octets           : 148388\nout-octets          : 24555\nmode                : station\nssid                : MyNetwork\nsignal              : -45 dBm (good)\nrx bitrate          : 72.2 Mbps\ntx bitrate          : 86.6 Mbps\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nAvailable Networks\nSSID                 BSSID              SECURITY       SIGNAL  CHANNEL\nMyNetwork            b4:fb:e4:17:b6:a7  WPA2-Personal  good          6\nGuestWiFi            c8:3a:35:12:34:56  WPA2-Personal  fair         11\nCoffeeShop           00:1a:2b:3c:4d:5e  Open           bad           1\n

        In the CLI, signal strength is reported as: excellent, good, fair or bad. For precise signal strength values in dBm, use NETCONF or RESTCONF to access the signal-strength leaf in the operational datastore.

        "},{"location":"wifi/#passphrase-requirements","title":"Passphrase Requirements","text":"

        To ensure your connection is secure and compatible with all network hardware, your passphrase must meet the following criteria:

        • Length: Between 8 and 63 characters
        • Characters: Use only standard English keyboard characters
          • Allowed: Letters (A-Z, a-z), numbers (0-9), and common symbols (e.g., ! @ # $ % ^ & * ( ) _ + - = [ ] { } | ; : ' \" , . < > / ? ~)
          • Spaces: Spaces are allowed, but not at the very beginning or very end of the passphrase
          • Prohibited: Emojis, accented characters (like \u00e1 or \u00f1), and special \"control\" characters

        Why the limit?

        Standard WiFi security (WPA2/WPA3) requires a minimum of 8 characters to prevent \"brute-force\" hacking. The character limit ensures your password works on older routers and various operating systems. Tips for password strength, see XKCD #936.

        "},{"location":"wifi/#station-mode-client","title":"Station Mode (Client)","text":"

        Station mode connects to an existing Wi-Fi network. Before configuring station mode, follow the \"Discovering Available Networks (Scanning)\" section above to scan for available networks and identify the SSID you want to connect to.

        "},{"location":"wifi/#step-1-configure-password","title":"Step 1: Configure Password","text":"

        Create a keystore entry for your WiFi password (8-63 characters):

        admin@example:/> configure\nadmin@example:/config/> edit keystore symmetric-key my-wifi-key\nadmin@example:/config/keystore/\u2026/my-wifi-key/> set key-format passphrase-key-format\nadmin@example:/config/keystore/\u2026/my-wifi-key/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\nadmin@example:/config/keystore/\u2026/my-wifi-key/> leave\n

        The change command prompts for the passphrase interactively and handles the base64 encoding required by the keystore automatically.

        "},{"location":"wifi/#step-2-connect-to-network","title":"Step 2: Connect to Network","text":"

        Configure station mode with the SSID and password to connect:

        admin@example:/> configure\nadmin@example:/config/> edit interface wifi0\nadmin@example:/config/interface/wifi0/> set wifi station ssid MyHomeNetwork\nadmin@example:/config/interface/wifi0/> set wifi station security secret my-wifi-key\nadmin@example:/config/interface/wifi0/> leave\n

        The connection attempt will start immediately. You can verify the connection status:

        admin@example:/> show interface wifi0\nname                : wifi0\ntype                : wifi\noperational status  : up\nphysical address    : f0:09:0d:36:5f:86\nmode                : station\nssid                : MyHomeNetwork\nsignal              : -52 dBm (good)\n

        Station configuration parameters:

        • radio: Reference to the WiFi radio (mandatory) - already set during scanning
        • station ssid: Network name to connect to (mandatory)
        • station security mode:
          • auto: default, WPA2/WPA3 auto-negotiation
          • disabled: open network
        • station security secret: Reference to keystore entry, required unless mode is disabled

        Note

        The auto security mode automatically selects WPA3-SAE or WPA2-PSK based on what the access point supports, prioritizing WPA3 for better security. Certificate-based authentication (802.1X/EAP) is not yet supported.

        "},{"location":"wifi/#access-point-mode","title":"Access Point Mode","text":"

        Access Point (AP) mode allows your device to create a WiFi network that other devices can connect to. APs are configured as virtual interfaces on a WiFi radio.

        "},{"location":"wifi/#basic-ap-configuration","title":"Basic AP Configuration","text":"

        First, ensure the radio is configured (see Radio Configuration above). Then create a keystore entry for your WiFi password and configure the AP interface:

        Step 1: Create keystore entry for the WiFi password

        admin@example:/> configure\nadmin@example:/config/> edit keystore symmetric-key my-wifi-secret\nadmin@example:/config/keystore/\u2026/my-wifi-secret/> set key-format passphrase-key-format\nadmin@example:/config/keystore/\u2026/my-wifi-secret/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\nadmin@example:/config/keystore/\u2026/my-wifi-secret/> end\n

        Step 2: Create the AP interface

        admin@example:/config/> edit interface wifi0\nadmin@example:/config/interface/wifi0/> set wifi radio radio0\nadmin@example:/config/interface/wifi0/> set wifi access-point ssid MyNetwork\nadmin@example:/config/interface/wifi0/> set wifi access-point security mode wpa2-personal\nadmin@example:/config/interface/wifi0/> set wifi access-point security secret my-wifi-secret\nadmin@example:/config/interface/wifi0/> leave\n

        Note

        Using wifiN as the interface name automatically sets the type to WiFi. Alternatively, you can use any name and explicitly set type wifi.

        Access Point configuration parameters:

        • radio: Reference to the WiFi radio (mandatory)
        • access-point ssid: Network name (SSID) to broadcast
        • access-point hidden: Set to true to hide SSID (optional, default: false)
        • access-point security mode: Security mode (see below)
        • access-point security secret: Reference to keystore entry (for secured networks)

        Security modes:

        • open: No encryption (not recommended)
        • wpa2-personal: WPA2-PSK (most compatible)
        • wpa3-personal: WPA3-SAE (more secure, requires WPA3-capable clients)
        • wpa2-wpa3-personal: Mixed mode (maximum compatibility)
        "},{"location":"wifi/#ssid-hiding","title":"SSID Hiding","text":"

        To create a hidden network that doesn't broadcast its SSID:

        admin@example:/config/interface/wifi0/> set wifi access-point hidden true\n
        "},{"location":"wifi/#multi-ssid-configuration","title":"Multi-SSID Configuration","text":"

        Multiple AP interfaces on the same radio allow broadcasting multiple SSIDs, each with independent security settings. This is useful for guest networks, IoT devices, or segregating traffic into different VLANs.

        Step 1: Configure the radio (shared by all APs)

        admin@example:/> configure\nadmin@example:/config/> edit hardware component radio0 wifi-radio\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set band 5GHz\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set channel 36\nadmin@example:/config/hardware/component/radio0/wifi-radio/> leave\n

        Step 2: Configure keystore secrets

        admin@example:/> configure\nadmin@example:/config/> edit keystore symmetric-key main-secret\nadmin@example:/config/keystore/\u2026/main-secret/> set key-format passphrase-key-format\nadmin@example:/config/keystore/\u2026/main-secret/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\nadmin@example:/config/> edit keystore symmetric-key guest-secret\nadmin@example:/config/keystore/\u2026/guest-secret/> set key-format passphrase-key-format\nadmin@example:/config/keystore/\u2026/guest-secret/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\nadmin@example:/config/> edit keystore symmetric-key iot-secret\nadmin@example:/config/keystore/\u2026/iot-secret/> set key-format passphrase-key-format\nadmin@example:/config/keystore/\u2026/iot-secret/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\nadmin@example:/config/keystore/\u2026/iot-secret/> leave\n

        Step 3: Create multiple AP interfaces (all on radio0)

        admin@example:/> configure\n# Primary AP - Main network (WPA3 for maximum security)\nadmin@example:/config/> edit interface wifi0\nadmin@example:/config/interface/wifi0/> set wifi radio radio0\nadmin@example:/config/interface/wifi0/> set wifi access-point ssid MainNetwork\nadmin@example:/config/interface/wifi0/> set wifi access-point security mode wpa3-personal\nadmin@example:/config/interface/wifi0/> set wifi access-point security secret main-secret\n\n# Guest AP - Guest network (WPA2/WPA3 mixed for compatibility)\nadmin@example:/config/> edit interface wifi1\nadmin@example:/config/interface/wifi1/> set wifi radio radio0\nadmin@example:/config/interface/wifi1/> set wifi access-point ssid GuestNetwork\nadmin@example:/config/interface/wifi1/> set wifi access-point security mode wpa2-wpa3-personal\nadmin@example:/config/interface/wifi1/> set wifi access-point security secret guest-secret\nadmin@example:/config/interface/wifi1/> set custom-phys-address static 00:0c:43:26:60:01\n\n# IoT AP - IoT devices (WPA2 for older device compatibility)\nadmin@example:/config/> edit interface wifi2\nadmin@example:/config/interface/wifi2/> set wifi radio radio0\nadmin@example:/config/interface/wifi2/> set wifi access-point ssid IoT-Devices\nadmin@example:/config/interface/wifi2/> set wifi access-point security mode wpa2-personal\nadmin@example:/config/interface/wifi2/> set wifi access-point security secret iot-secret\nadmin@example:/config/interface/wifi2/> set custom-phys-address static 00:0c:43:26:60:02\nadmin@example:/config/interface/wifi2/> leave\n

        Important

        MAC Address Requirement for Multi-SSID: When creating multiple AP interfaces on the same radio, you must configure a unique MAC address for each secondary interface (wifi1, wifi2, etc.) using set custom-phys-address static <MAC>. All interfaces on the same radio inherit the radio's hardware MAC address by default, which causes network conflicts. Only the primary interface (alphabetically first, e.g., wifi0) should use the default hardware MAC address.

        Choose MAC addresses from the same locally-administered range:

        • Primary (wifi0): Uses hardware MAC (e.g., 00:0c:43:26:60:00)
        • Secondary (wifi1): 00:0c:43:26:60:01 (increment last octet)
        • Tertiary (wifi2): 00:0c:43:26:60:02 (increment last octet)

        Result: Three SSIDs broadcasting simultaneously on radio0:

        • MainNetwork (WPA3, most secure)
        • GuestNetwork (WPA2/WPA3 mixed mode)
        • IoT-Devices (WPA2 for compatibility)

        All APs on the same radio share the same channel and physical layer settings (configured at the radio level). Each AP can have its own:

        • SSID (network name)
        • Security mode and passphrase
        • Hidden/visible SSID setting
        • Bridge membership

        You can verify the configuration with show hardware component radio0 to see radio settings, and show interface to see all active AP interfaces.

        Important

        AP and Station modes cannot be mixed on the same radio. All virtual interfaces on a radio must be the same mode (all APs or all Stations).

        "},{"location":"wifi/#ap-as-bridge-port","title":"AP as Bridge Port","text":"

        WiFi AP interfaces can be added to bridges to integrate wireless devices into your LAN:

        admin@example:/> configure\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> set type bridge\n\nadmin@example:/config/> edit interface wifi0\nadmin@example:/config/interface/wifi0/> set bridge-port bridge br0\nadmin@example:/config/interface/wifi0/> leave\n
        "},{"location":"wifi/#troubleshooting","title":"Troubleshooting","text":"

        Use show interface wifi0 to verify signal strength and connection status. If issues arise, try the following troubleshooting steps:

        1. Verify signal strength: Check that the target network shows \"good\" or \"excellent\" signal in scan results
        2. Check credentials: Use show keystore symmetric <name> to verify the passphrase matches the network password
        3. Review logs: Check system logs with show log for Wi-Fi related errors
        4. Regulatory compliance: Ensure the country-code on the radio matches your location
        5. Hardware detection: Confirm the WiFi radio appears in show hardware

        If issues persist, check the system log for specific error messages that can help identify the root cause.

        "},{"location":"cli/configure/","title":"Configuration","text":""},{"location":"cli/configure/#cli-configure-context","title":"CLI Configure Context","text":"

        Enter the configure context from admin-exec by typing configure followed by Enter. Available commands, press ? at the prompt:

        admin@host:/> configure\nadmin@host:/config/>\n  abort        Abandon candidate\n  change       Interactively change setting, e.g. password\n  check        Validate candidate\n  commit       Commit current candidate to running-config\n  delete       Delete configuration setting(s)\n  diff         Summarize uncommitted changes\n  do           Execute operational mode command\n  edit         Descend to the specified configuration node\n  end          Alias to up, leave this subsection/node\n  exit         Ascend to the parent configuration node, or abort (from top)\n  leave        Finalize candidate and apply to running-config\n  no           Alias for delete\n  rollback     Restore candidate to running-config\n  set          Set configuration setting\n  show         Show configuration\n  text-editor  Modify binary content in a text editor\n  top          Ascend to the configuration root\n  up           Ascend to the parent configuration node\n

        The edit command lets you change to a sub-configure context, e.g.:

        admin@host:/config/> edit interface eth0\nadmin@host:/config/interface/eth0/>\n

        Use up to go up one level.

        admin@host:/config/interface/eth0/> up\nadmin@host:/config/>\n

        Note

        Commands in configure context are automatically generated from the system's YANG models, hence different products likely have a different set of commands. However, both the ietf-interfaces.yang and ietf-ip.yang models, for instance, that provide the networking support are common to all systems.

        "},{"location":"cli/configure/#set-ip-address-on-an-interface","title":"Set IP Address on an Interface","text":"
        admin@host:/config/> edit interface eth0\nadmin@host:/config/interface/eth0/>\nadmin@host:/config/interface/eth0/> set ipv4 address 192.168.2.200 prefix-length 24\n

        From anywhere in configure context you can see the changes you have made by typing diff:

        admin@host:/config/interface/eth0/> diff\ninterfaces {\n  interface eth0 {\n+    ipv4 {\n+      address 192.168.2.200 {\n+        prefix-length 24;\n+      }\n+    }\n  }\n}\n
        "},{"location":"cli/configure/#saving-changes","title":"Saving Changes","text":"

        Apply the changes (from candidate to running-config):

        admin@host:/config/interface/eth0/> leave\nadmin@host:/> show running-config\n...\ninterfaces {\n  interface eth0 {\n    type ethernetCsmacd;\n    ipv4 {\n      address 192.168.2.200 {\n        prefix-length 24;\n      }\n    }\n  }\n...\n

        Since we did not get any warnings we can save the running (RAM only) configuration to startup, so the changes are made persistent across reboots:

        admin@host:/> copy running-config startup-config\n

        The startup-config can also be inspected with the show command to verify the changes are saved.

        Important

        All commands need to be spelled out, no short forms are allowed in the CLI. Use the TAB key to make your life easier.

        "},{"location":"cli/configure/#changing-hostname","title":"Changing Hostname","text":"

        Settings like hostname are located in the ietf-system.yang model. Here is how it can be set.

        admin@host:/config/> edit system\nadmin@host:/config/system/> set hostname example\nadmin@host:/config/system/> leave\nadmin@example:/> \n

        Notice how the hostname in the prompt does not change until the change is committed.

        Note

        Critical services like syslog, mDNS, LLDP, and similar that advertise the hostname, are restarted when the hostname is changed.

        "},{"location":"cli/configure/#changing-password","title":"Changing Password","text":"

        User management, including passwords, is also a part of ietf-system.

        admin@host:/config/> edit system authentication user admin\nadmin@host:/config/system/authentication/user/admin/> change password\nNew password:\nRetype password:\nadmin@host:/config/system/authentication/user/admin/> leave\n

        The change password command starts an interactive dialogue that asks for the new password, with a confirmation, and then salts and encrypts the password with sha512crypt.

        It is also possible to use the set password ... command. This allows setting an already hashed password. To manually hash a password, use the do password encrypt command. This launches the admin-exec command to hash, and optionally salt, your password. This encrypted string can then be used with set password ....

        Tip

        If you are having trouble thinking of a password, there is also do password generate, which generates random but readable strings using the UNIX command pwgen.

        "},{"location":"cli/configure/#ssh-authorized-key","title":"SSH Authorized Key","text":"

        Logging in remotely with SSH is possible by adding a public key to a user. Here we add the authorized key to the admin user, multiple keys are supported.

        With SSH keys in place it is possible to disable password login, just remember to verify SSH login and network connectivity before doing so.

        admin@host:/config/> edit system authentication user admin \nadmin@host:/config/system/authentication/user/admin/> edit authorized-key example@host\nadmin@host:/config/system/authentication/user/admin/authorized-key/example@host/> set algorithm ssh-rsa\nadmin@host:/config/system/authentication/user/admin/authorized-key/example@host/> set key-data AAAAB3NzaC1yc2EAAAADAQABAAABgQC8iBL42yeMBioFay7lty1C4ZDTHcHyo739gc91rTTH8SKvAE4g8Rr97KOz/8PFtOObBrE9G21K7d6UBuPqmd0RUF2CkXXN/eN2PBSHJ50YprRFt/z/304bsBYkDdflKlPDjuSmZ/+OMp4pTsq0R0eNFlX9wcwxEzooIb7VPEdvWE7AYoBRUdf41u3KBHuvjGd1M6QYJtbFLQMMTiVe5IUfyVSZ1RCxEyAB9fR9CBhtVheTVsY3iG0fZc9eCEo89ErDgtGUTJK4Hxt5yCNwI88YaVmkE85cNtw8YwubWQL3/tGZHfbbQ0fynfB4kWNloyRHFr7E1kDxuX5+pbv26EqRdcOVGucNn7hnGU6C1+ejLWdBD7vgsoilFrEaBWF41elJEPKDzpszEijQ9gTrrWeYOQ+x++lvmOdssDu4KvGmj2K/MQTL2jJYrMJ7GDzsUu3XikChRL7zNfS2jYYQLzovboUCgqfPUsVba9hqeX3U67GsJo+hy5MG9RSry4+ucHs=\nadmin@host:/config/system/authentication/user/admin/authorized-key/example@host/> show\nalgorithm ssh-rsa;\nkey-data AAAAB3NzaC1yc2EAAAADAQABAAABgQC8iBL42yeMBioFay7lty1C4ZDTHcHyo739gc91rTTH8SKvAE4g8Rr97KOz/8PFtOObBrE9G21K7d6UBuPqmd0RUF2CkXXN/eN2PBSHJ50YprRFt/z/304bsBYkDdflKlPDjuSmZ/+OMp4pTsq0R0eNFlX9wcwxEzooIb7VPEdvWE7AYoBRUdf41u3KBHuvjGd1M6QYJtbFLQMMTiVe5IUfyVSZ1RCxEyAB9fR9CBhtVheTVsY3iG0fZc9eCEo89ErDgtGUTJK4Hxt5yCNwI88YaVmkE85cNtw8YwubWQL3/tGZHfbbQ0fynfB4kWNloyRHFr7E1kDxuX5+pbv26EqRdcOVGucNn7hnGU6C1+ejLWdBD7vgsoilFrEaBWF41elJEPKDzpszEijQ9gTrrWeYOQ+x++lvmOdssDu4KvGmj2K/MQTL2jJYrMJ7GDzsUu3XikChRL7zNfS2jYYQLzovboUCgqfPUsVba9hqeX3U67GsJo+hy5MG9RSry4+ucHs=;\nadmin@host:/config/system/authentication/user/admin/authorized-key/example@host/> leave\n

        Note

        The ssh-keygen program already base64 encodes the public key data, so there is no need to use the text-editor command, set does the job.

        "},{"location":"cli/configure/#creating-a-veth-pair","title":"Creating a VETH Pair","text":"

        The following example creates a veth0a <--> veth0b virtual Ethernet pair which is useful for connecting, e.g., a container to the physical world. Here we also add an IPv4 address to one end of the pair.

        admin@host:/config/> edit interface veth0a\nadmin@host:/config/interface/veth0a/> set veth peer veth0b\nadmin@host:/config/interface/veth0a/> set ipv4 address 192.168.0.1 prefix-length 24\nadmin@host:/config/interface/veth0a/> up\nadmin@host:/config/> diff\ninterfaces {\n+  interface veth0a {\n+    type veth;\n+    ipv4 {\n+      address 192.168.0.1 {\n+        prefix-length 24;\n+      }\n+    }\n+    veth {\n+      peer veth0b;\n+    }\n+  }\n+  interface veth0b {\n+    type veth;\n+    veth {\n+      peer veth0a;\n+    }\n+  }\n}\nadmin@host:/config/> leave\n

        See the bridging example below for more.

        Tip

        In the CLI you do not have to create the veth0b interface. The system infers this for you. This does not apply when setting up a VETH pair using NETCONF or RESTCONF, then you must submit a complete configuration.

        "},{"location":"cli/configure/#creating-a-bridge","title":"Creating a Bridge","text":"

        Building on the previous example, we now create a non-VLAN filtering bridge (br0) that forwards any, normally link-local, LLDP traffic between both its bridge ports: eth0 and vet0b.

        admin@host:/> configure\nadmin@host:/config/> edit interface br0\nadmin@host:/config/interface/br0/> set bridge ieee-group-forward lldp\nadmin@host:/config/interface/br0/> up\nadmin@host:/config/> set interface eth0 bridge-port bridge br0\nadmin@host:/config/> set interface veth0b bridge-port bridge br0\nadmin@host:/config/> diff\ninterfaces {\n+  interface br0 {\n+    type bridge;\n+    bridge {\n+      ieee-group-forward lldp;\n+    }\n+  }\n  interface eth0 {\n+    bridge-port {\n+      bridge br0;\n+    }\n  }\n+  interface veth0a {\n+    type veth;\n+    ipv4 {\n+      address 192.168.0.1 {\n+        prefix-length 24;\n+      }\n+    }\n+    veth {\n+      peer veth0b;\n+    }\n+  }\n+  interface veth0b {\n+    type veth;\n+    veth {\n+      peer veth0a;\n+    }\n+    bridge-port {\n+      bridge br0;\n+    }\n+  }\n}\n

        Both a physical port eth0 and a virtual port veth0b (bridge side of the VETH pair from the previous example) are now bridged. Any traffic ingressing one port will egress the other. Only reserved IEEE multicast is filtered, except LLDP frames as shown above.

        Important

        The bridge can be named anything, provided the interface name is not already taken. However, for any name outside the pattern br[0-9]+, you have to set the interface type manually to bridge.

        "},{"location":"cli/introduction/","title":"Introduction","text":""},{"location":"cli/introduction/#cli-introduction","title":"CLI Introduction","text":"

        The command line interface (CLI, see-ell-aye) implements a CISCO-like, or Juniper Networks JunOS-like, CLI. It is the traditional way of interacting with single network equipment like switches and routers. Today more advanced graphical NETCONF-based tools are available that allows for managing entire fleets of installed equipment.

        Nevertheless, when it comes to initial deployment and debugging, it is very useful to know how to navigate and use the CLI. This very short guide intends to help you with that.

        New users usually get the CLI as the default \"shell\" when logging in, but the default admin user logs in to bash. To access the CLI, type:

        admin@host-12-34-56:~$ cli\n\nSee the 'help' command for an introduction to the system\n\nadmin@host-12-34-56:/>\n

        The prompt (beginning of the line) changes slightly. Key commands available in any context are:

        admin@host-12-34-56:/> help                   # Try: Tab or ?\n...\nadmin@host-12-34-56:/> show                   # Try: Tab or ?\nadmin@host-12-34-56:/>                        # Try: Tab or ?\n

        Tip

        Even on an empty command line, you can tap the Tab or ? keys. See help keybindings for more tips!

        "},{"location":"cli/introduction/#key-concepts","title":"Key Concepts","text":"

        The two modes in the CLI are the admin-exec and the configure context.

        The top-level context after logging in and starting the CLI is the admin-exec or \"main\" context. It is used for querying system status, managing configuration files/profiles and doing advanced debugging.

        Available commands can be seen by pressing ? at the prompt:

        admin@host:/>\n  configure      Create new candidate-config based on running-config\n  copy           Copy file or configuration, e.g., copy running-config startup-config\n  dir            List available configuration files\n  exit           Exit from CLI (log out)\n  factory-reset  Restore the system to factory default state\n  follow         Monitor a log file, use Ctrl-C to abort\n  help           Help system (try also the '?' key)\n  logout         Alias to exit\n  netcalc        IP subnet calculator, with subnetting\n  password       Password tools\n  ping           Ping a network host or multicast group\n  poweroff       Poweroff system (system policy may yield reboot)\n  reboot         Reboot system\n  remove         Remove a configuration file\n  set            Set operations, e.g., current date/time\n  show           Show system status and configuration files\n  tcpdump        Capture network traffic\n  upgrade        Install a software update bundle from remote or local file\n

        The system has three main datastores (or files): factory, startup, and running that can be managed and inspected using the copy, show, and configure commands. The traditional names used in the CLI for these are listed below:

        • factory-config the default configuration from factory for the device, i.e., what the system returns to after a factory-reset
        • startup-config created from factory-config at first boot after factory reset. Loaded as the system configuration on each boot
        • running-config what is actively running on the system. If no changes have been made since boot, it is the same as startup-config
        • candidate-config is created from running-config when entering the configure context. Any changes made here can be discarded (abort, rollback) or committed (commit, leave) to running-config

        Edit the running configuration using the configure command. This copies running to candidate, a temporary datastore, where changes are made:

        admin@host-12-34-56:/> configure\nadmin@host-12-34-56:/config/> ...             # Try: Tab or ?\nadmin@host-12-34-56:/config/> leave\n

        The leave command activates the changes by issuing a transaction to, essentially, copy the candidate back to running. Depending on the changes made, this can take a few seconds. If the changes are invalid, i.e., not correct according to the underlying YANG models, a warning is shown and the session remains in configure context. Use the abort command to cancel your changes, or investigate further with the diff command (see more below).

        To save configuration changes made to the running-config to persistent store, so the system will use them for consecutive reboots, use the copy command:

        admin@host-12-34-56:/> copy running-config startup-config\n

        In configure context the following commands are available:

        Command Description set foo bar val Set bar leaf node in foo subcontext to val no foo bar Clear/delete configuration made to bar in foo edit foo baz Enter baz sub-sub-context in foo subcontext change password Start password dialogue to change a user's password text-editor foo Open a text editor to edit binary setting foo abort Abort changes in configuration, return to admin-exec exit Exit one level sub-context, or abort from top-level leave Save changes to running-config, return to admin-exec show [foo] Show configured values (optionally in subcontext) diff [foo] Show uncommitted changes in candidate do command Call admin-exec command: do show log commit"},{"location":"cli/introduction/#example-session","title":"Example Session","text":"

        Tip

        Remember to use the TAB and ? keys to speed up your navigation. See help keybindings for more tips!

        In this example we enter configure context to add an IPv4 address to interface eth0, then we apply the changes using the leave command.

        We inspect the system status to ensure the change took effect. Then we save the changes for the next reboot.

        admin@host-12-34-56:/> configure\nadmin@host-12-34-56:/config/> edit interface eth0\nadmin@host-12-34-56:/config/interface/eth0/> set ipv4 <TAB>\n      address     autoconf      bind-ni-name     dhcp \n      enabled     forwarding    mtu              neighbor\nadmin@host-12-34-56:/config/interface/eth0/> set ipv4 address 192.168.2.200 prefix-length 24\nadmin@host-12-34-56:/config/interface/eth0/> show\ntype ethernetCsmacd;\nipv4 address 192.168.2.200 prefix-length 24;\nipv6 enabled true;\nadmin@host-12-34-56:/config/interface/eth0/> diff\ninterfaces {\n  interface eth0 {\n+    ipv4 {\n+      address 192.168.2.200 {\n+        prefix-length 24;\n+      }\n+    }\n  }\n}\nadmin@host-12-34-56:/config/interface/eth0/> leave\nadmin@host-12-34-56:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\nlo              loopback   UNKNOWN     00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\neth0            ethernet   UP          52:54:00:12:34:56\n                ipv4                   192.168.2.200/24 (static)\n                ipv6                   fe80::5054:ff:fe12:3456/64 (link-layer)\nadmin@host-12-34-56:/> show ip brief\nlo               UNKNOWN        127.0.0.1/8 ::1/128\neth0             UP             192.168.2.200/24 fe80::5054:ff:fe12:3456/64\nadmin@host-12-34-56:/> copy running-config startup-config\n

        One of the ideas behind a separate running and startup configuration is to be able to verify a configuration change. In case of an inadvertent change that, e.g., breaks networking, it is trivial to revert back by:

        admin@host-12-34-56:/> copy startup-config running-config\n

        Or restart the device, for example if the change to the configuration caused you to lose contact with the system (it happens to the best of us). The system will start up from the last \"save game\".

        Tip: Restoring Backups

        When restoring a backup of a configuration, or having manually edited a config file, you can validate it using the system's YANG models, it is not applied if validation is successful:

        copy /media/backup/old.cfg running-config validate

        "},{"location":"cli/keybindings/","title":"Keybindings","text":""},{"location":"cli/keybindings/#cli-keybindings","title":"CLI Keybindings","text":"

        Writing CLI commands by hand is very tedious. To make things easier the CLI has several keybindings, most significant first:

        Key Cmd/Key Description TAB Complete current command, see below for example ? Show available commands, or arguments, with help Ctrl-c Cancel everything on current line Ctrl-d abort/exit Delete character, or abort/exit on empty line Ctrl-z leave Leave and activate changes in configure context Ctrl-f Right arrow Move cursor forward one character Ctrl-b Left arrow Move cursor back one character Meta-f Ctrl-Right Move cursor forward one word Meta-b Ctrl-Left Move cursor back one word Ctrl-e End Move cursor to end of line Ctrl-a Home Move cursor to beginning of line Ctrl-@ Ctrl-Space Mark current position for region operations Meta w Copy region to kill buffer without deleting Ctrl-k Kill (cut) text from cursor to end of line Ctrl-u Delete (cut) entire line Ctrl-y Yank (paste) from kill buffer to cursor Meta-. Yank (paste) last argument from previous line Ctrl-w Kill region if mark set, else kill word backward Meta-Backspace Delete (cut) word to the left Meta-Delete Delete (cut) word to the right Ctrl-l Clear screen and refresh current line Ctrl-p Up arrow History, previous command Ctrl-n Down arrow History, next command Ctrl-q Ctrl-v Insert next character literally Ctrl-r History, reversed interactive search (i-search) Ctrl-t Transpose/Swap characters before and at cursor Meta-# Alt-Shift-3 Prepend # to current line and submit to history"},{"location":"cli/keybindings/#what-is-meta","title":"What is Meta","text":"

        The Meta key is called Alt on most modern keyboards. If you have neither, first tap the Esc key instead of holding down Alt/Meta.

        "},{"location":"cli/keybindings/#usage","title":"Usage","text":"

        Complete a word. Start by typing a few characters, then tap the TAB key on your keyboard:

        conf<TAB> --> configure\n

        See possible arguments, with brief help text, to a command:

        show ?\nbridge          Show bridge (ports/fdb/mdb/vlans)\ndatetime        Show current date and time, default RFC2822 format\n...\n

        Type the command, then tap the ? key.

        "},{"location":"cli/netcalc/","title":"Network Calculator","text":""},{"location":"cli/netcalc/#network-calculator","title":"Network Calculator","text":"
        netcalc <ADDRESS/LEN | NETWORK NETMASK> [split <1-32 | 64-128>]\n
        "},{"location":"cli/netcalc/#description","title":"Description","text":"

        netcalc is a network calculator that takes an IP address and a subnet mask, or an IP address and prefix mask in CIDR notation, and then returns information about the subnet. Both IPv4 and IPv6 is supported.

        A subnet can be entered in two ways:

        • 192.168.2.0 255.255.255.0: traditional IPv4 'address netmask' style
        • 192.168.2.0/24: modern prefix length, same also for IPv6

        An optional split LEN can be given as argument, the new length value must be bigger than the current prefix length. See example below.

        "},{"location":"cli/netcalc/#examples","title":"Examples","text":"

        Its most commonly used features are to understand how many addresses an IP subnet has, what the broadcast address is, first and last usable address.

        admin@example:/> netcalc 192.168.2.0/24\nAddress  : 192.168.2.0          11000000.10101000.00000010. 00000000\nNetmask  : 255.255.255.0 = 24   11111111.11111111.11111111. 00000000\nWildcard : 0.0.0.255            00000000.00000000.00000000. 11111111\n=>\nNetwork  : 192.168.2.0/24       11000000.10101000.00000010. 00000000\nHostMin  : 192.168.2.1          11000000.10101000.00000010. 00000001\nHostMax  : 192.168.2.254        11000000.10101000.00000010. 11111110\nBroadcast: 192.168.2.255        11000000.10101000.00000010. 11111111\nHosts/Net: 254                   Class C, Private network (RFC1918)\n

        Another common use-case is for IP subnetting, i.e., using only as many addresses for an IP subnet as needed. Example, to split the above /24 in four:

        admin@example:/> netcalc 192.168.2.0/24 split 26\nAddress  : 192.168.2.0          11000000.10101000.00000010. 00000000\nNetmask  : 255.255.255.0 = 24   11111111.11111111.11111111. 00000000\nWildcard : 0.0.0.255            00000000.00000000.00000000. 11111111\n=>\nNetwork  : 192.168.2.0/24       11000000.10101000.00000010. 00000000\nHostMin  : 192.168.2.1          11000000.10101000.00000010. 00000001\nHostMax  : 192.168.2.254        11000000.10101000.00000010. 11111110\nBroadcast: 192.168.2.255        11000000.10101000.00000010. 11111111\nHosts/Net: 254                   Class C, Private network (RFC1918)\n\n[Split network/26]\nNetwork  : 192.168.2.0   - 192.168.2.63     Netmask  : 255.255.255.192\nNetwork  : 192.168.2.64  - 192.168.2.127    Netmask  : 255.255.255.192\nNetwork  : 192.168.2.128 - 192.168.2.191    Netmask  : 255.255.255.192\nNetwork  : 192.168.2.192 - 192.168.2.255    Netmask  : 255.255.255.192\n
        "},{"location":"cli/quick/","title":"Quickstart Guide","text":""},{"location":"cli/quick/#cli-quickstart-guide","title":"CLI Quickstart Guide","text":"

        The question mark ? key along with the Tab key are your best friends in the command line interface. They provide context help and completion of commands you input. See the table below for a handful of examples.

        Command Description help Overview of topics/settings in the current context ? Lists commands + brief help in the current context <Tab> Lists commands available in the current context partial<Tab> Completes a partial command name command <Tab> Lists keywords/arguments associated with a command command keyword ? Lists arguments associated with a keyword + help

        Explore the following topics for more information. Note, the keybindings are really useful to learn!

        Command Description help introduction An introduction to the CLI help configure How to use configure context help text-editor Help with the built-in text-editor command help keybindings Lists available keybindings & other helpful tricks

        Tip: Online Help

        In configure context the help <setting> command shows the YANG description text for each node and container. To reach the admin exec help from configure context, e.g., do help text-editor

        "},{"location":"cli/tcpdump/","title":"Network Monitoring","text":""},{"location":"cli/tcpdump/#network-monitoring","title":"Network Monitoring","text":"

        tcpdump is useful tool for analyzing and diagnosing network problems. This document presents the limited feature set that exposed is in the CLI. Administrator level users with shell access can use the full feature set, and is not described here.

        The following section is useful for understanding how to use the tool. A section called Examples follows that, which may be what you want to scroll down to.

        "},{"location":"cli/tcpdump/#hardware-overview","title":"Hardware Overview","text":"

        Using tcpdump effectively requires an understanding of how the underlying hardware works. For a standard PC, or common single-board computers (SBC), the network cards (NICs) are usually connected directly to the CPU.

                              .---------.\n                      |         +-- eth0\n                      |   CPU   |\n                      |         +-- eth1\n                      '---------'\n

        In this setup it is evident that traffic coming in on either eth0 or eth1 reach the CPU, i.e., running tcpdump eth0 captures all traffic.

        However, on other types of networking hardware, e.g., dedicated switch core setups depicted below, the flow of network traffic will likely not pass through the CPU. This depends of course on how the switch is set up, for instance if routing between all ports is enabled, each flow will reach the CPU, but in a plain switching setup it will not.

                              .---------.\n                      |         |\n                      |   CPU   |\n                      |         |\n                      '----+----'\n                           |\n                           |\n                .----------+---------.\n                |                    |\n             ---+                    +---\n            E1  |         SC         |  E3\n             ---+                    +---\n            E2  |                    |  E4\n                '--+--+--+--+--+--+--'\n                   |  |  |  |  |  |\n                  E5 E6 E7 E8 E9 E10\n

        So, running tcpdump e1 in a switching setup, inside the CPU, the only traffic that will be captured is traffic ingressing port E1 destined for the CPU itself. To analyze traffic going through the switch, you need something called port mirroring, or port monitoring, i.e., setting up the switch core to mirror traffic ingressing and/or egressing a set of ports to another port. On this monitor port you can then run your tcpdump, which means you need an external device (laptop).

        A planned feature is to support mirroring traffic to the CPU port, which would be an effective way to log and monitor traffic over a longer period of time. Highly effective for diagnosing intermittent and other rare network issues.

        If only \"proof of life\" is required, then sometimes port counters, also called RMON counters, can be very useful too. Seeing counters of a particular type increment means traffic is ingressing or egressing.

        "},{"location":"cli/tcpdump/#examples","title":"Examples","text":"

        Listen to all traffic on an interface:

        admin@example:/> tcpdump e1\n

        Listen to only ping traffic:

        admin@example:/> tcpdump e1 expression icmp\n

        Listen to traffic on a given port:

        admin@example:/> tcpdump e1 expression \"port 80\"\n

        Wait for one ping only:

        admin@example:/> tcpdump e1 count 1 expression icmp\n

        Very verbose output:

        admin@example:/> tcpdump e1 verbose\n
        "},{"location":"cli/text-editor/","title":"Text Editor","text":""},{"location":"cli/text-editor/#text-editor","title":"Text Editor","text":"

        The CLI built-in text-editor command can be used to edit type binary settings in configure context.

        The default editor is a Micro Emacs clone. Users not familiar with terminal based editors may benefit from this introduction.

        "},{"location":"cli/text-editor/#escape-metaalt-control-shift","title":"Escape Meta/Alt Control Shift","text":"

        When starting up, the editor status field at the bottom shows the following shorthand:

        C-h q  quick help | C-h t  tutorial | C-h b  key bindings | C = Ctrl | M = Alt\n

        Key combinations with a - (dash) mean holding down the modifier key. Combinations without a - (dash) mean without any modifier key.

        "},{"location":"cli/text-editor/#quick-help-c-h-q","title":"Quick help C-h q","text":"
        • hold down the Ctrl key on
        • tap the h key
        • release Ctrl
        • tap the q key

        The bottom part of the terminal now shows a \"buffer\" called *quick*:

        FILE              BUFFER           WINDOW            MARK/KILL        MISC\nC-x C-c exit      C-x b   switch   C-x 0 only other  C-space mark     C-_ undo\nC-x C-f find      C-x k   close    C-x 1 only this   C-w     kill-rg  C-s search\nC-x C-s save      C-x C-b list     C-x 2 split two   C-k     kill-ln  C-r r-search\nC-x s   save-all  C-x h   mark     C-x ^ enlarge     C-y     yank     M-% replace\nC-x i   insert    C-x g   goto-ln  C-x o other win   C-x C-x swap     M-q reformat\n
        "},{"location":"cli/text-editor/#save-exit-c-x-c-c","title":"Save & Exit C-x C-c","text":"
        • Hold down the Ctrl key
        • tap X
        • tap c
        • release Ctrl

        The status field at the bottom asks if you are really sure, and/or if you want to add a final Enter/newline to the file. For binary content that final newline may be important.

        "},{"location":"cli/text-editor/#changing-the-editor","title":"Changing the Editor","text":"

        The system has three different built-in editors:

        • emacs (Micro Emacs)
        • nano (GNU Nano)
        • vi (Visual Editor)

        Changing editor is done in configure context, in the system container:

        admin@host:/> configure\nadmin@host:/config/> edit system\nadmin@host:/config/system/> set text-editor <TAB>\nemacs   nano    vi\nadmin@host:/config/system/> set text-editor nano\nadmin@host:/config/system/> leave\nadmin@host:/>\n
        "},{"location":"cli/upgrade/","title":"Upgrading","text":""},{"location":"cli/upgrade/#upgrading-the-system","title":"Upgrading the System","text":"

        The admin-exec command upgrade can be used to install software images, or bundles. A bundle is a signed and self-contained package that carries all the information necessary to determine if it holds a bootloader, a Linux image, or even both.

        To install a new software image to the currently inactive partition1, we use the upgrade command and a URI to a ftp/tftp/sftp or http/https server that hosts the file:

        admin@example:/> upgrade tftp://192.168.122.1/firmware-x86_64-v23.11.pkg\ninstalling\n  0% Installing\n  0% Determining slot states\n 20% Determining slot states done.\n 20% Checking bundle\n 20% Verifying signature\n 40% Verifying signature done.\n 40% Checking bundle done.\n 40% Checking manifest contents\n 60% Checking manifest contents done.\n 60% Determining target install group\n 80% Determining target install group done.\n 80% Updating slots\n 80% Checking slot rootfs.1\n 90% Checking slot rootfs.1 done.\n 90% Copying image to rootfs.1\n 99% Copying image to rootfs.1 done.\n 99% Updating slots done.\n100% Installing done.\nInstalling `tftp://192.168.122.1/firmware-x86_64-v23.11.pkg` succeeded\nadmin@example:/>\n

        The secondary partition (rootfs.1) has now been upgraded and will be used as the active partition on the next boot. Leaving the primary partition, with the version we are currently running, intact in case of trouble.

        See Upgrade & Boot Order for more information on upgrading.

        1. It is not possible to upgrade the partition we booted from. Thankfully the underlying \"rauc\" subsystem keeps track of this. Hence, to upgrade both partitions you must reboot to the new version (to verify it works) and then repeat the same command.\u00a0\u21a9

        "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Introduction","text":""},{"location":"#introduction","title":"Introduction","text":"

        Welcome to Infix, your immutable, friendly, and secure operating system! On these pages you can find both user and developer documentation.

        Most topics on configuring the system include CLI examples, but every setting, as well as status read-back from the operational datastore, is also possible to perform using NETCONF or RESTCONF. In fact, the Infix regression test system solely relies on NETCONF and RESTCONF.

        Tip

        The CLI documentation is also available from inside the CLI itself using the help command in admin-exec mode.

        This document provides an introduction of key concepts, details how the system boots, including failure modes, and provides links to other documents for further study.

        "},{"location":"#project-links","title":"Project Links","text":"Topic URL Home page & blog https://www.kernelkit.org GitHub https://github.com/kernelkit/infix Latest release https://github.com/kernelkit/infix/releases/latest"},{"location":"#command-line-interface","title":"Command Line Interface","text":"

        The command line interface (CLI, see-ell-i) is the traditional way of interacting with single network equipment like switches and routers. Today users have come to expect more advanced graphical GUIs, like a web interface, to manage a device or NETCONF-based tools that allow for managing entire fleets of installed equipment.

        Nevertheless, when it comes to initial deployment and debugging, it is very useful to know how to navigate and use the CLI.

        Info

        For more information, see the CLI Introduction and the CLI Configuration Tutorial.

        "},{"location":"#key-concepts","title":"Key Concepts","text":"

        The two modes in the CLI are the admin-exec and the configure context.

        However, when logging in to the system, from the console port or SSH, you land in a standard UNIX shell, Bash. This is for advanced users and remote scripting purposes (production equipment):

            Run the command 'cli' for interactive OAM\n\n    admin@example:~$\n

        To enter the CLI, follow the instructions, for interactive Operations, Administration, and Management (OAM), type:

            admin@example:~$ cli\n    admin@example:/>\n

        The prompt, constructed from your username and the device's hostname, changes slightly. You are now in the admin-exec context of the CLI. Here you can inspect system status and do operations to debug networking issues, e.g. ping. You can also enter configure context by typing: configure followed by commands to set, edit, apply changes using leave, or abort and return to admin-exec.

        Tip

        If you haven't already, the CLI Introduction would be useful to skim through at this point.

        "},{"location":"#datastores","title":"Datastores","text":"

        The system has several datastores (or files):

        • factory-config consists of a set of default configurations, some static and others generated per-device, e.g., a unique hostname and number of ports/interfaces. This file is generated at boot.
        • failure-config is also generated at boot, from the same YANG models as factory-config, and holds the system Fail Secure Mode
        • startup-config is created from factory-config at boot if it does not exist. It is loaded as the system configuration on each boot.
        • running-config is what is actively running on the system. If no changes have been made since the system booted, it is the same as startup-config.
        • candidate-config is created from running-config when entering the configure context. Any changes made here can be discarded (abort, rollback) or committed (commit, leave) to running-config.

        Tip

        Please see the Branding & Releases document for more in-depth information on how factory-config and failure-config can be adapted to different customer requirements. Including how you can override the generated versions of these files with plain per-product ones -- this may even protect against some of the failure modes below.

        "},{"location":"boot/","title":"Boot Procedure","text":""},{"location":"boot/#boot-procedure","title":"Boot Procedure","text":"

        Systems running Infix will typically boot in multiple phases, forming a boot chain. Each link in the chain has three main responsibilities:

        1. Ensuring the integrity of the next link before passing control to it. This avoids silent failures stemming from data corruption.
        2. Ensuring the authenticity of the next link before passing control to it, commonly referred to as Secure Boot. This protects against malicious attempts to modify a system's firmware.
        3. Preparing the system state according to the requirements of the next link. E.g. the Linux kernel requires the system's RAM to be operational.

        A typical chain consists of four stages:

            .---------.\n    |   ROM   >---.   Determine the location of and load the SPL\n    '---------'   |\n.-----------------'\n|   .---------.\n'--->   SPL   >---.   Perform DDR training and load the TPL\n    '---------'   |\n.-----------------'\n|   .---------.\n'--->   TPL   >---.   Load Linux kernel, device tree, and root filesystem\n    '---------'   |\n.-----------------'\n|   .---------.\n'--->  Infix  |       Get down to business\n    '---------'\n

        After a reset, hardware will pass control to a program (ROM) which is almost always programmed into the SoC by the vendor. This program will determine the location of the Secondary Program Loader (SPL), typically by reading a set of Sample at Reset (SaR) pins.

        The SPL is sometimes provided by the SoC vendor in binary form, and is sometimes built as a part of the Tertiary Program Loader (TPL) build. Its main responsibility is usually to set up the system's memory controller and perform DDR training, if required, before loading the TPL.

        Commonly referred to as the system's bootloader, the TPL is responsible for preparing the execution environment required by the Linux kernel.

        This document's focus is to describe the final two phases of the boot chain, as the initial phases are very hardware dependent, better described by existing documentation provided by the SoC vendor.

        "},{"location":"boot/#bootloader","title":"Bootloader","text":""},{"location":"boot/#configuration","title":"Configuration","text":"

        To mitigate the risk of a malicious user being able to circumvent the bootloader's validation procedure, user configuration is kept to a minimum. Two settings are available:

        Boot order: Since Infix maintains two copies of its software image, and as some bootloaders support netbooting, the order in which boot sources are considered can be configured. To select the active source, use RAUC:

        root@example:~# rauc status mark-active <slot>\n...\n

        Where <slot> is one of:

        <slot> Source rootfs.0 Primary partition rootfs.1 Secondary partition net.0 Netboot (where supported)

        Debug: By default, the kernel will only output errors to the console during boot. Optionally, this can be altered such that all enabled messages are logged.

        On systems using U-Boot, this can be enabled by running fw_setenv DEBUG 1. To restore the default behavior, run fw_setenv DEBUG.

        On systems running GRUB, this can be enabled by running:

        root@example:~# grub-editenv /mnt/aux/grub/grubenv set DEBUG=1\n

        To restore the default behavior, run:

        root@example:~# grub-editenv /mnt/aux/grub/grubenv unset DEBUG\n
        "},{"location":"boot/#u-boot","title":"U-Boot","text":"

        Used on aarch64 based systems. It is able to verify both the integrity and authenticity of an Infix image. As such, it can be used as a part of a Secure Boot chain, given that the preceding links are able to do the same.

        Supports booting Infix from a block device using the Disk Image layout. Currently, Virtio and MMC disks are supported.

        An FIT Framed Squash Image can be used to boot Infix over the network. DHCP is used to configure the network and TFTP to transfer the image to the system's RAM.

        Access to U-Boot's shell is disabled to prevent side-loading of malicious software. To configure the active boot partition, refer to the Bootloader Configuration section.

        "},{"location":"boot/#grub","title":"GRUB","text":"

        Used on x86_64 based systems. Neither the integrity nor the authenticity of the Infix image is verified. It is only intended to provide a way of booting a Disk Image, such that a standard System Upgrade can be performed on virtualized instances.

        Access to the GRUB shell is not limited in any way, and the boot partition can be selected interactively at boot using the arrow keys. It is also possible to permanently configure the default partition from Infix using the Bootloader Configuration.

        "},{"location":"boot/#system-boot","title":"System Boot","text":"

        After the system firmware (BIOS or and boot loader start Linux the following happens. The various failure modes, e.g., missing password in VPD, are detailed later in this section.

        1. Before mounting /cfg and /var partitions, hosting read-writable data like startup-config and container images, the system first checks if a factory reset has been requested by the user, if so it wipes the contents of these partitions
        2. Linux boots with a device tree which is used for detecting generic make and model of the device, e.g., number of interfaces. It may also reference an EEPROM with Vital Product Data. That is where the base MAC address and per-device password hash is stored. (Generic builds use the same MAC address and password)
        3. On every boot the system's factory-config and failure-config are generated from the YANG2 models of the current firmware version. This ensures that a factory reset device can always boot, and that there is a working fail safe, or rather fail secure, mode
        4. On first power-on, and after a factory reset, the system does not have a startup-config, in which case factory-config is copied to startup-config -- if a per-product specific version exists it is preferred over the generated one
        5. Provided the integrity of the startup-config is OK, a system service loads and activates the configuration
        "},{"location":"boot/#failure-modes","title":"Failure Modes","text":"

        So, what happens if any of the steps above fail?

        "},{"location":"boot/#vpd-fail","title":"VPD Fail","text":"

        The per-device password cannot be read, or is corrupt, so the system factory-config and failure-config are not generated:

        1. First boot, or after factory reset: startup-config cannot be created or loaded, and failure-config cannot be loaded. The system ends up in an unrecoverable state, i.e., RMA3 Mode
        2. The system has booted (at least) once with correct VPD and password and already has a startup-config. Provided the startup-config is OK (see below), it is loaded and system boots successfully

        In both cases, external factory reset modes/button will not help, and in the second case will cause the device to fail on the next boot.

        Note

        The second case does not yet have any warning or event that can be detected from the outside. This is planned for a later release.

        "},{"location":"boot/#broken-startup-config","title":"Broken startup-config","text":"

        If loading startup-config fails for some reason, e.g., invalid JSON syntax, failed validation against the system's YANG model, or a bug in the system's confd service, the Fail Secure Mode is triggered and failure-config is loaded (unless VPD Failure, see above).

        Tip

        Please see the Branding & Releases document for how to provide per-product failure-config, or factory-config to suit your product's preferences.

        Fail Secure Mode is a fail-safe mode provided for debugging the system. The default4 creates a setup of isolated interfaces with communication only to the management CPU, SSH and console login using the device's factory reset password, IP connectivity only using IPv6 link-local, and device discovery protocols: LLDP, mDNS-SD. The login and shell prompt are set to failure-c0-ff-ee, the last three octets of the device's base MAC address.

        "},{"location":"boot/#system-upgrade","title":"System Upgrade","text":"

        Much of the minutiae of software upgrades is delegated to RAUC, which offers lots of benefits out-of-the-box:

        • Upgrade Bundles are always signed, such that their authenticity can be verified by the running operating system, before the new one is installed.
        • The bureaucracy of interfacing with different bootloaders, manage the boot order, is a simple matter of providing a compatible configuration.
        • Updates can be sourced from the local filesystem (including external media like USB sticks or SD-cards) and from remote servers using FTP or HTTP(S).

        To initiate a system upgrade from the shell1, run:

        root@example:~# rauc install <file|url>\n...\n

        Where the file or URL points to a RAUC Upgrade Bundle.

        This will upgrade the partition not currently running. After a successful upgrade is completed, you can reboot your system, which will then boot from the newly installed image. Since the partition from which you were originally running is now inactive, running the same upgrade command again will bring both partitions into sync.

        "},{"location":"boot/#image-formats","title":"Image Formats","text":""},{"location":"boot/#squashfs-image","title":"SquashFS Image","text":"

        Canonical Name: rootfs.squashfs

        The central read-only filesystem image containing Infix's Linux kernel, device trees, and root filesystem. All other images bundle this image, or is dependent on it, in one way or another.

        On its own, it can be used as an initrd to efficiently boot a virtual instance of Infix.

        "},{"location":"boot/#fit-framed-squash-image","title":"FIT Framed Squash Image","text":"

        Canonical Name: rootfs.itb

        As the name suggests, this is essentially the Squash FS Image with a Flattened Image Tree (FIT) header. Being a native format to U-Boot, using this framing allows us to verify the integrity and authenticity of the SquashFS image using standard U-Boot primitives.

        In contrast to most FIT images, the kernel and device trees are not stored as separate binaries in the image tree. Instead, Infix follows the standard Linux layout where the kernel and related files are stored in the /boot directory of the filesystem.

        On disk, this image is then stored broken up into its two components; the FIT header (rootfs.itbh) and the SquashFS image. The header is stored on the Auxiliary Data partition of the Disk Image, while the SquashFS image is stored in one of the Root Filesystem partitions.

        When the system boots, U-Boot will concatenate the two parts to validate the SquashFS's contents. This path was chosen because:

        • Having a separate raw SquashFS means Linux can directly mount it as the root filesystem.

        • It decouples Infix from U-Boot. If a better way of validating our image is introduced, we can switch to it without major changes to Infix's boot process, as we can still use a regular SquashFS as the root filesystem.

        • It lets us use standard interfaces to boot Linux, like SYSLINUX. It also plays well with traditional bootloaders, like GRUB.

        In its full form, it can be used to netboot Infix, as it contains all the information needed by U-Boot in a single file.

        "},{"location":"boot/#rauc-upgrade-bundle","title":"RAUC Upgrade Bundle","text":"

        Canonical Name: infix-${ARCH}.pkg

        Itself a SquashFS image, this bundle (sometimes referred to package) contains the Infix SquashFS Image along with the header of the FIT Framed Squash Image, and some supporting files to let RAUC know how install it on the target system.

        When performing a System Upgrade, this is the format to use.

        "},{"location":"boot/#disk-image","title":"Disk Image","text":"

        Canonical Name: disk.img

        Infix runs from a block device (e.g. eMMC or virtio disk) with the following layout. The disk is expected to use the GPT partitioning scheme. Partitions marked with an asterisk are optional.

        .-----------.\n| GPT Table |\n:-----------:\n|    boot*  |\n:-----------:\n|    aux    |\n:-----------:\n|           |\n|  primary  |\n|           |\n:-----------:\n|           |\n| secondary |\n|           |\n:-----------:\n|    cfg    |\n:-----------:\n|           |\n|    var*   |\n|           |\n'-----------'\n
        "},{"location":"boot/#boot-bootloader","title":"boot - Bootloader","text":"Parameter Value Required No Size 4 MiB Format Raw binary, as dictated by the hardware

        Optional partition containing the system's bootloader. May also reside in a separate storage device, e.g. a serial FLASH.

        On x86_64, this partition holds the EFI system partition, containing the GRUB bootloader.

        "},{"location":"boot/#aux-auxiliary-data","title":"aux - Auxiliary Data","text":"Parameter Value Required Yes Size 4 MiB Format EXT4 filesystem

        Holds information that is shared between Infix and its bootloader, such as image signatures required to validate the chain of trust, bootloader configuration etc.

        Typical layout when using U-Boot bootloader:

        /\n\u251c primary.itbh\n\u251c secondary.itbh\n\u2514 uboot.env\n

        During boot, an ITB header along with the corresponding root filesystem image are concatenated in memory, by U-Boot, to form a valid FIT image that is used to verify its integrity and origin before any files are extracted from it.

        Note that the bootloader's primary environment is bundled in the binary - uboot.env is only used to import a few settings that is required to configure the boot order.

        "},{"location":"boot/#primarysecondary-root-filesystems","title":"primary/secondary - Root Filesystems","text":"Parameter Value Required Yes Size >= 256 MiB Format Squash filesystem

        Holds the SquashFS Image. Two copies exist so that an incomplete upgrade does not brick the system, and to allow fast rollbacks when upgrading to a new version.

        "},{"location":"boot/#cfg-configuration-data","title":"cfg - Configuration Data","text":"Parameter Value Required Yes Size >= 16 MiB Format EXT4 filesystem

        Non-volatile storage of the system configuration and user data. Concretely, user data is everything stored under /root and /home.

        "},{"location":"boot/#var-variable-data","title":"var - Variable Data","text":"Parameter Value Required No Size >= 16 MiB Format EXT4 filesystem

        Persistent storage for everything under /var. This is maintained as a separate filesystem from the data in cfg, because while the system can funtion reasonably well without a persistent /var, loosing /cfg or /etc is much more difficult.

        If var is not available, Infix will still persist /var/lib using cfg as the backing storage.

        1. See Upgrade & Boot Order for more information.\u00a0\u21a9

        2. YANG is a modeling language from IETF, replacing that used for SNMP (MIB), used to describe the subsystems and properties of the system.\u00a0\u21a9

        3. Return Merchandise Authorization (RMA), i.e., broken beyond repair by end-user and eligible for return to manufacturer.\u00a0\u21a9

        4. Customer specific builds can define their own failure-config. It may be the same as factory-config, with the hostname set to failure, or a dedicated configuration that isolates interfaces, or even disables ports, to ensure that the device does not cause any security problems on the network. E.g., start forwarding traffic between previously isolated VLANs.\u00a0\u21a9

        "},{"location":"branding/","title":"Branding & Releases","text":""},{"location":"branding/#branding-releases","title":"Branding & Releases","text":"

        This document is for projects using Infix as a br2-external, i.e., OEMs that want to create their own \"Spin\" of Infix.

        "},{"location":"branding/#branding","title":"Branding","text":"

        Branding is done in menuconfig, there are several settings affecting it, most are in the Infix external subsection called \"Branding\", but there is also BR2_TARGET_GENERIC_HOSTNAME, which deserves a special mention.

        The hostname is used for the system default /etc/hostname, which is the base name for the \"unique:ified\" hostname + the last three octets of the base MAC1 address, e.g., infix-c0-ff-ee. This in turn is the hostname that is set at first boot and also advertised by device discovery protocols like mDNS-SD and LLDP.

        See the help texts for the Infix Branding settings to understand which ones are mandatory and which are optional, menuconfig does not check this for you and you may end up with odd results.

        Verify the result after a build by inspecting:

        • output/images/*: names, missing prefix, etc.
        • output/target/etc/os-release: this file is sourced by other build scripts, e.g., mkgns3a.sh. For reference, see os-release(5)

        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 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.

        "},{"location":"branding/#factory-failure-config","title":"Factory & Failure Config","text":"

        To support booting the same image (CPU architecture) on multiple boards, Infix by default generates the device's initial configuration every time at boot. This also ensures the device can always be restored to a known state after a factory reset, since the factory-config is guaranteed to be compatible with the YANG models for the given software version. (For more information on how the system boots, please see the section Key Concepts in the Introduction document.)

        However, for custom builds of Infix it is possible to override this with a single static /etc/factory-config.cfg (and failure-config) in your rootfs overlay -- with a VPD you can even support several!

        "},{"location":"branding/#variables-format-specifiers","title":"Variables & Format Specifiers","text":"

        Parts of the configuration you likely always want to generated, like the SSH hostkey used by SSH server and NETCONF, a unique hostname, or the admin user's unique (per-device with a VPD) password hash. This section lists the available keywords, see the next section for examples of how to use them:

        • Default password hash: $factory$ (from VPD, .dtb, or built-in) XPath: /ietf-system:system/authentication/user/password
        • Default SSH and NETCONF hostkey: genkey (regenerated at factory reset) XPath: /ietf-keystore:keystore/asymmetric-keys/asymmetric-key[name='genkey']
        • Default HTTPS certificate: gencert (self-signed, regenerated at factory reset) XPath: /ietf-keystore:keystore/asymmetric-keys/asymmetric-key[name='gencert']
        • Hostname format specifiers: XPath: /ietf-system:system/hostname
        • %i: OS ID, from /etc/os-release, from Menuconfig branding
        • %h: Default hostname, from /etc/os-release, from branding
        • %m: NIC specific part of base MAC, e.g., to c0-ff-ee
        • %%: Literal %
        "},{"location":"branding/#static-files","title":"Static Files","text":"

        Caution

        Maintaining a static factory-config and failure-config may seem like an obvious choice, but as YANG models evolve (even the IETF models get upgraded), you may need to upgrade your static files.

        First, for one-off builds (one image per product), the simplest way is to override the location where the system looks for the files, /etc already at build time. This can be done using a Buildroot rootfs overlay providing, e.g., /etc/factory-config.cfg. Example: NanoPi R2S in ${INFIX}/board/aarch64/r2s/rootfs/etc/factory-config.cfg.

        Second, to support multiple products in a single image, we can employ another method to install a /etc/factory-config.cfg override -- at runtime. This relies on the very early system probe that detects the specific product from VPD data.

        The probe consists of several sequential steps that currently run from ${INFIX}/board/common/rootfs/usr/libexec/infix/init.d/. One of them check if /usr/share/product/<PRODUCT> exists, and if so attempts to copy the entire contents to /. Here, <PRODUCT> is determined from the VPD, which is available in /run/system.json as \"product-name\", after 00-probe has run. The lower case version of the string is used.

        I.e., create a rootfs overlay that provides any combination of:

        • /usr/share/product/<PRODUCT>/etc/factory-config.cfg
        • /usr/share/product/<PRODUCT>/etc/failure-config.cfg
        "},{"location":"branding/#dynamically-generated","title":"Dynamically Generated","text":"

        The generated factory-config and failure-config files consist of both static JSON files and part generated files at runtime for each device. The resulting files are written to the RAM disk in /run:

        • /run/confd/factory-config.gen
        • /run/confd/failure-config.gen

        Provided no custom overrides (see above) have been installed already, these files are then copied to:

        • /etc/factory-config.cfg
        • /etc/failure-config.cfg

        ... where the bootstrap process expects them to be in the next step.

        Examples of generated contents are the SSH hostkey and hostname. The latter is constructed from the file /etc/hostname, appended with the last three octets of the system's base MAC address. To override the base hostname, set BR2_TARGET_GENERIC_HOSTNAME in your defconfig.

        The static files are installed by Infix confd in /usr/share/confd/ at build time. It contains two subdirectories:

        /usr/share/confd/\n |- factory.d/\n |  |- 10-foo.json\n |  |- 10-bar.json\n |  `- 10-qux.json\n `- failure.d/\n    |- 10-xyzzy.json\n    `- 10-garply.json\n

        To override, or extend, these files in you br2-external, set up a rootfs overlay and add it last in BR2_ROOTFS_OVERLAY. Your overlay can look something like this:

        ./board/common/rootfs/\n  |- etc/\n  |  |- confdrc             # See below\n  |  `- confdrc.local\n  `- usr/\n     `- share/\n        `- confd/\n           |- 10-foo.json   # Override Infix foo\n           |- 30-bar.json   # Extend, probably 10-bar.json\n           `- 30-fred.json  # Extend, your own defaults\n

        Using the same filename in your overlay, here 10-foo.json, completely replaces the contents of the same file provided by Infix. If you just want to extend, or replace parts of an Infix default, use 30-....json. Here the file 30-bar.json is just a helpful hit to maintainers of your br2-external that it probably extends Infix' 10-bar.json.

        The reason for the jump in numbers is that 20 is reserved for files generated by Infix' gen-function scripts. Your br2-external can provide a few custom ones that the bootstrap knows about, e.g., gen-ifs-custom that overrides 20-interfaces.json. See the bootstrap script for more help, and up-to-date information.

        Tip

        You may not need to provide your own /etc/confdrc for your spin. The one installed by confd is usually enough. However, if you want to adjust the behavior of bootstrap you may want to override it. There is also confdrc.local, which usually is enough to change arguments to scripts like gen-interfaces, e.g., to create a bridge by default, you may want to look into GEN_IFACE_OPTS.

        "},{"location":"branding/#example-snippets","title":"Example Snippets","text":""},{"location":"branding/#ietf-system","title":"IETF System","text":"
          \"ietf-system:system\": {\n    \"hostname\": \"example-%m\",\n    \"ntp\": {\n      \"enabled\": true,\n      \"server\": [\n        {\n          \"name\": \"ntp.org\",\n          \"udp\": {\n            \"address\": \"pool.ntp.org\"\n          }\n        }\n      ]\n    },\n    \"authentication\": {\n      \"user\": [\n        {\n          \"name\": \"admin\",\n          \"password\": \"$factory$\",\n          \"infix-system:shell\": \"bash\"\n        }\n      ]\n    },\n    \"infix-system:motd-banner\": \"Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCAtLSBhIE5ldHdvcmsgT3BlcmF0aW5nIFN5c3RlbQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQuZ2l0aHViLmlvCictJy0tLSctJwo=\"\n  },            # <---- REMEMBER COMMA SEPARATORS IN SNIPPETS!\n                # <---- ... and no comments.\n

        The motd-banner is a binary type, which is basically a Base64 encoded text file without line breaks (-w0):

        $ echo \"Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCAtLSBhIE5ldHdvcmsgT3BlcmF0aW5nIFN5c3RlbQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQuZ2l0aHViLmlvCictJy0tLSctJwo=\" \\\n    | base64 -d\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://kernelkit.github.io\n'-'---'-'\n
        "},{"location":"branding/#ietf-keystore","title":"IETF Keystore","text":"

        Notice how both the public and private keys are left empty here, this cause them to be always automatically regenerated after each factory reset. Keeping the factory-config snippet like this means we can use the same file on multiple devices, without risking them sharing the same host keys or TLS certificates. Sometimes you may want the same keys, but that is the easy use-case and not documented here.

        The genkey entry is the default SSH host key, and gencert is the default self-signed HTTPS certificate used by the web server (nginx). Both are regenerated on factory reset when their keys are empty.

          \"ietf-keystore:keystore\": {\n    \"asymmetric-keys\": {\n      \"asymmetric-key\": [\n        {\n          \"name\": \"genkey\",\n          \"public-key-format\": \"ietf-crypto-types:ssh-public-key-format\",\n          \"public-key\": \"\",\n          \"private-key-format\": \"ietf-crypto-types:rsa-private-key-format\",\n          \"cleartext-private-key\": \"\",\n          \"certificates\": {}\n        },\n        {\n          \"name\": \"gencert\",\n          \"public-key-format\": \"infix-crypto-types:x509-public-key-format\",\n          \"public-key\": \"\",\n          \"private-key-format\": \"infix-crypto-types:rsa-private-key-format\",\n          \"cleartext-private-key\": \"\",\n          \"certificates\": {\n            \"certificate\": [\n              { \"name\": \"self-signed\", \"cert-data\": \"\" }\n            ]\n          }\n        }\n      ]\n    }\n  },\n
        "},{"location":"branding/#ietf-netconf-server","title":"IETF NETCONF Server","text":"
          \"ietf-netconf-server:netconf-server\": {\n    \"listen\": {\n      \"endpoints\": {\n        \"endpoint\": [\n          {\n            \"name\": \"default-ssh\",\n            \"ssh\": {\n              \"tcp-server-parameters\": {\n                \"local-address\": \"::\"\n              },\n              \"ssh-server-parameters\": {\n                \"server-identity\": {\n                  \"host-key\": [\n                    {\n                      \"name\": \"default-key\",\n                      \"public-key\": {\n                        \"central-keystore-reference\": \"genkey\"\n                      }\n                    }\n                  ]\n                }\n              }\n            }\n          }\n        ]\n      }\n    }\n  },\n
        "},{"location":"branding/#infix-services","title":"Infix Services","text":"
          \"infix-services:ssh\": {\n    \"enabled\": true,\n    \"hostkey\": [\n      \"genkey\"\n    ],\n    \"listen\": [\n      {\n        \"name\": \"ipv4\",\n        \"address\": \"0.0.0.0\",\n        \"port\": 22\n      },\n      {\n        \"name\": \"ipv6\",\n        \"address\": \"::1\",\n        \"port\": 22\n      }\n    ]\n  },\n  \"infix-services:web\": {\n    \"certificate\": \"gencert\",\n    \"enabled\": true,\n    \"console\": { \"enabled\": true },\n    \"netbrowse\": { \"enabled\": true },\n    \"restconf\": { \"enabled\": true }\n  }\n

        The certificate leaf references an asymmetric key in the keystore that has an associated certificate. The default gencert entry uses a self-signed certificate. To use a custom (e.g., CA-signed) certificate, create a new keystore entry with x509-public-key-format and point the web certificate leaf to it.

        "},{"location":"branding/#integration","title":"Integration","text":"

        When integrating your software stack with Infix there may be protocols that want to change system settings like hostname and dynamically set IP address and default gateway, e.g. PROFINET. This section detail a few recommendations for maintaining co-existence in this scenario of the multiple producers problem.

        First, there's a clear difference between \"singleton\" like hostsname and an interface IP address. Consider the case of a static IP and a DHCP assigned IP, these can co-exist because of the proto NUM field available in iproute2. This is used in Infix so that static addresses can be flushed independently of DHCP addresses. The same can be done by other \"address providers\", e.g., PROFINET.

        Changing properties like hostname should be done by injecting a change into Infix, by for example calling sysrepocfg -Ediff.xml. Here is an example of how to get the current hostname and apply an XML diff:

        root@infix-00-00-00:~# sysrepocfg -X -x \"/system/hostname\" > hostnm.xml\nroot@infix-00-00-00:~# cat hostnm.xml\n<system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n  <hostname>infix-00-00-00</hostname>\n</system>\nroot@infix-00-00-00:~# edit hostnm.xml\nroot@infix-00-00-00:~# sysrepocfg -Ehostnm.xml\nroot@example:~#\n

        Second, perform all changes on running-config, the running datastore. That way you have a clear state to return to if your application needs to do a factory reset. E.g., in PROFINET a type 1/2 factory reset will reset only the PROFINET specific settings. That way you can actually have your system startup-config disable all physical ports and the PROFINET application enables only ports that are not deactivated. (On factory reset it will not know of any ports to deactivate so it will activate all.)

        You can consider the system composed of two entities:

        • NETCONF starts up the system using startup-config, then
        • Hands over control to your application at runtime

        Infix is prepared for this by already having two \"runlevels\" for these two states. The startup-config is applied in runlevel S (bootstrap) and the system then enters runlevel 2 for normal operation.

        This allow you to keep a set of functionality that is provided by the underlying system, and another managed by your application. You can of course in your br2-external provide a sysrepo plugin that block operations on certain datastores when your application is enabled. E.g., to prevent changes to startup after initial deployment. In that case a proper factory reset would be needed to get back to a \"pre-deployment\" state where you can reconfigure your baseline.

        "},{"location":"branding/#releases","title":"Releases","text":"

        A release build requires the global variable INFIX_RELEASE to be set. It can be derived from GIT, if the source tree is kept in GIT VCS. First, let us talk about versioning in general.

        "},{"location":"branding/#versioning","title":"Versioning","text":"

        Two popular scheme for versioning a product derived from Infix:

        1. Track Infix major.minor, e.g. Foobar v23.08.z, where z is your patch level. I.e., Foobar v23.08.0 could be based on Infix v23.08.0, or v23.08.12, it is up to you. Maybe you based it on v23.08.12 and then back ported changes from v23.10.0, but it was the first release you made to your customer(s).
        2. Start from v1.0.0 and step the major number every time you sync with a new Infix release, or every time Infix bumps to the next Buildroot LTS.

        The important thing is to be consistent, not only for your own sake, but also for your end customers. The major.minor.patch style is the most common and often recommended style, which usually maps well to other systems, e.g. PROFINET GSDML files require this (VX.Y.Z). But you can of course use only two numbers, major.minor, as well.

        Warning

        What could be confusing, however, is if you use the name Infix with your own versioning scheme.

        "},{"location":"branding/#specifying-versioning-information","title":"Specifying Versioning Information","text":"

        Two optional environment variables control the version information recorded in images. Both of these must be a lower-case string (no spaces or other characters outside of 0\u20139, a\u2013z, '.', '_' and '-') identifying the operating system version, excluding any OS name information or release code name, and suitable for processing by scripts or usage in generated filenames.

        "},{"location":"branding/#infix_build_id","title":"INFIX_BUILD_ID","text":"

        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 menuconfig, to the path of an enclosing br2-external.

        "},{"location":"branding/#infix_release","title":"INFIX_RELEASE","text":"

        Used for VERSION and VERSION_ID in /etc/os-release and generated file names like disk images, etc.

        Default: ${INFIX_BUILD_ID}

        1. The base MAC address is defined in the device's Vital Product Data (VPD) EEPROM, or similar, which is used by the kernel to create the system interfaces. This MAC address is usually also printed on a label on the device.\u00a0\u21a9

        "},{"location":"bridging/","title":"Bridging","text":""},{"location":"bridging/#bridging","title":"Bridging","text":"

        This is the most central part of the system. A bridge is a switch, and a switch is a bridge. In Linux, setting up a bridge with ports connected to physical switch fabric, means you manage the actual switch fabric!

        "},{"location":"bridging/#mac-bridge","title":"MAC Bridge","text":"

        In Infix ports are by default not switch ports, unless the customer specific factory config sets it up this way. To enable switching, with offloading if you have a switch chipset, between ports you create a bridge and then add ports to that bridge. Like this:

        admin@example:/> configure\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> up\nadmin@example:/config/> set interface eth0 bridge-port bridge br0\nadmin@example:/config/> set interface eth1 bridge-port bridge br0\nadmin@example:/config/> leave\n

        Here we add two ports to bridge br0: eth0 and eth1.

        Tip

        The CLI has several built-in helpers governed by convention. E.g., naming bridges brN, where N is a number, the type is inferred automatically and unlocks all bridge features. Other conventions are vethNA, where N is a number and A is a letter ('a' for access port and 'b' for bridge side is common), and ethN.M for VLAN M on top of ethN, or dockerN for a IP masquerading container bridge.

        Note, this inference only works with the CLI, configuring networking over NETCONF or RESTCONF requires setting the type explicitly.

        It is possible to create multiple MAC bridges, however, it is currently1 not recommended to use more than one MAC bridge on products with Marvell LinkStreet switching ASICs. A VLAN filtering bridge should be used instead.

        "},{"location":"bridging/#vlan-filtering-bridge","title":"VLAN Filtering Bridge","text":"

        By default bridges in Linux do not filter based on VLAN tags. This can be enabled when creating a bridge by adding a port to a VLAN as a tagged or untagged member. Use the port default VID (PVID) setting to control VLAN association for traffic ingressing a port untagged (default PVID: 1).

        admin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> up\nadmin@example:/config/> set interface eth0 bridge-port bridge br0\nadmin@example:/config/> set interface eth0 bridge-port pvid 10\nadmin@example:/config/> set interface eth1 bridge-port bridge br0\nadmin@example:/config/> set interface eth1 bridge-port pvid 20\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> set bridge vlans vlan 10 untagged eth0\nadmin@example:/config/interface/br0/> set bridge vlans vlan 20 untagged eth1\n

        This sets eth0 as an untagged member of VLAN 10 and eth1 as an untagged member of VLAN 20. Switching between these ports is thus prohibited.

        To terminate a VLAN in the switch itself, either for switch management or for routing, the bridge must become a (tagged) member of the VLAN.

        admin@example:/config/interface/br0/> set bridge vlans vlan 10 tagged br0\nadmin@example:/config/interface/br0/> set bridge vlans vlan 20 tagged br0\n

        To route or to manage via a VLAN, a VLAN interface needs to be created on top of the bridge, see section VLAN Interfaces for more on this topic.

        Note

        In some use-cases only a single management VLAN on the bridge is used. For the example above, if the bridge itself is an untagged member only in VLAN 10, IP addresses can be set directly on the bridge without the need for dedicated VLAN interfaces on top of the bridge.

        "},{"location":"bridging/#multicast-filtering-and-snooping","title":"Multicast Filtering and Snooping","text":"

        Multicast filtering in the bridge is handled by the bridge itself. It can filter both IP multicast and MAC multicast. For IP multicast it also supports \"snooping\", i.e., IGMP and MLD, to automatically reduce the broadcast effects of multicast. See the next section for a summary of the terminology used.

        Important

        Currently there is no way to just enable multicast filtering without also enabling snooping. This may change in the future, in which case a filtering enabled setting will be made available along with the existing snooping setting.

        When creating your bridge you must decide if you need a VLAN filtering bridge or a plain bridge (see previous section). Multicast filtering is supported for either, but take note that it must be enabled and set up per VLAN when VLAN filtering is enabled -- there are no global multicast settings in this operating mode.

        In the following example we have a regular 8-port bridge without VLAN filtering. We focus on the multicast specific settings:

        admin@example:/> configure\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> set bridge multicast snooping\nadmin@example:/config/interface/br0/> set ipv4 address 192.168.2.1 prefix-length 24\nadmin@example:/config/interface/br0/> leave\nadmin@example:/> copy running-config startup-config\n

        Here we enable snooping and set a static IPv4 address so that the switch can take part in IGMP querier elections. (MLD querier election currently not supported.) We can inspect the current state:

        admin@example:/> show ip multicast\nMulticast Overview\nQuery Interval (default): 125 sec\nRouter Timeout          : 255\nFast Leave Ports        :\nRouter Ports            :\nFlood Ports             : e0, e1, e2, e3, e4, e5, e6, e7\n\nInterface       VID  Querier                     State  Interval  Timeout  Ver\nbr0                  192.168.2.1                    Up       125     None    3\n\nBridge          VID  Multicast Group       Ports                              \nbr0                  224.1.1.1             e3, e2\nbr0                  ff02::6a              br0\n

        This is a rather small LAN, so our bridge has already become the elected IGMP querier. We see it is ours because the timeout is None, and we recognize the IP address the system has detected, as ours. We can also see two ports that have joined the same IPv4 multicast group, 224.1.1.1, and one join from the system itself for the IPv6 group ff02::6a.

        Now, let us see what happens when we add another bridge, this time with VLAN filtering enabled. We skip the boring parts about how to move ports e4-e7 to br1 and assign them to VLANs, and again, focus on the multicast bits only:

        admin@example:/> configure\nadmin@example:/config/> edit interface br1\nadmin@example:/config/interface/br1/> set bridge vlans vlan 1 multicast snooping\nadmin@example:/config/interface/br1/> set bridge vlans vlan 2 multicast snooping\nadmin@example:/config/interface/br1/> leave\nadmin@example:/> copy running-config startup-config\n

        Let us see what we get:

        admin@example:/> show ip multicast\nMulticast Overview\nQuery Interval (default): 125 sec\nRouter Timeout          : 255\nFast Leave Ports        : e5\nRouter Ports            : e1, e2, e5, e6, e7\nFlood Ports             : e1, e2, e3, e4, e5, e6, e7, e8\n\nInterface       VID  Querier                     State  Interval  Timeout  Ver\nbr0                  192.168.2.1                    Up       125     None    3\nbr1               1  0.0.0.0                        Up       125     None    3\nbr1               2  0.0.0.0                        Up       125     None    3\n\nBridge          VID  Multicast Group       Ports                              \nbr0                  224.1.1.1             e2\nbr0                  ff02::fb              br0\nbr0                  ff02::6a              br0\nbr0                  ff02::1:ff00:0        br0\nbr1               1  224.1.1.1             e5\nbr1               2  224.1.1.1             e7\nbr1               1  ff02::fb              br1\nbr1               1  ff02::1:ff00:0        br1\n

        In this setup we have a lot more going on. Multiple multicast router ports have been detected, and behind the scenes someone has also added an IGMP/MLD fast-leave port.

        "},{"location":"bridging/#static-multicast-filters","title":"Static Multicast Filters","text":"

        When IGMP/MLD snooping is in use, traffic for an unregistered group is flooded to all ports until a receiver joins. For MAC multicast groups, or for groups where snooping cannot learn membership automatically, you can add static entries to the MDB that immediately restrict forwarding to a given set of ports.

        Note

        Snooping must be enabled on the bridge (or per VLAN) before static multicast filters can be configured.

        On a plain (non-VLAN) bridge, add a static IPv4 or MAC multicast filter like this:

        admin@example:/> configure\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> set bridge multicast-filters multicast-filter 224.1.1.1 ports e2\nadmin@example:/config/interface/br0/> set bridge multicast-filters multicast-filter 224.1.1.1 ports e3\nadmin@example:/config/interface/br0/> set bridge multicast-filters multicast-filter 01:00:5e:01:01:01 ports e2\nadmin@example:/config/interface/br0/> leave\nadmin@example:/> copy running-config startup-config\n

        Each ports entry for the same group adds one port to the filter. Receivers on all other ports will not see traffic for that group.

        On a VLAN-filtering bridge the filter is scoped per VLAN:

        admin@example:/config/interface/br1/> set bridge vlans vlan 10 multicast-filters multicast-filter 224.2.2.2 ports e5\nadmin@example:/config/interface/br1/> set bridge vlans vlan 10 multicast-filters multicast-filter 224.2.2.2 ports e6\n

        To verify the MDB \u2014 both statically configured and dynamically learned entries \u2014 use:

        admin@example:/> show bridge mdb\nBRIDGE   VID  GROUP                 PORTS                              \nbr0           224.1.1.1             e2, e3\nbr0           01:00:5e:01:01:01     e2\n
        "},{"location":"bridging/#terminology-abbreviations","title":"Terminology & Abbreviations","text":"
        • IGMP: Internet Group Membership Protocol, multicast subscription for IPv4, for details see RFC3376
        • MLD: Multicast Listener Discovery (Protocol), multicast subscription for IPv6, for details see RFC3810
        • Unknown/Unregistered multicast: multicast groups that are not in the multicast forwarding database (MDB)
        • Known/Registered multicast: multicast groups that are in the multicast forwarding database (MDB)
        • MDB: the multicast forwarding database, consists of filters for multicast groups, directing where multicast is allowed to egress. A filter entry consists of a group and a port list. The bridge filters with a unique database per VLAN, in the same was as the unicast FDB
        • Join/Leave: the terminology used in earlier versions of the two protocols to subscribe and unsubscribe to a multicast group. For more information, see Membership Report
        • Membership Report A membership report is sent by end-devices and forwarded by switches to the elected querier on the LAN. They consist of multiple \"join\" and \"leave\" operations on groups. They can also, per group, list which senders to allow or block. Switches usually only support the group subscription, and even more common also only support filtering on the MAC level2
        • Querier election: the process of determining who is the elected IGMP/MLD querier on a LAN. Lowest numerical IP address wins, the special address 0.0.0.0 (proxy querier) never wins
        • Proxy querier: when no better querier exists on a LAN, one or more devices can send proxy queries with source address 0.0.0.0 (or :: for IPv6). See Query Interval, below, why this is a good thing
        • Query interval: the time in seconds between two queries from an IGMP/MLD querier. It is not uncommon that end-devices do not send their membership reports unless they first hear a query
        • Fast Leave: set on a bridge port to ensure multicast is pruned as quickly as possible when a \"leave\" membership report is received. In effect, this option marks the port as directly connected to an end-device. When not set (default), a query with timeout is first sent to ensure no unintentional loss of multicast is incurred
        • Router port: can be both configured statically and detected at runtime based on connected devices, usually multicast routers. On a router port all multicast is forwarded, both known and unknown
        • Flood port: set on a bridge port (default: enabled) to ensure all unknown multicast is forwarded
        • Router timeout: the time in seconds until a querier is deemed to have been lost and another device (switch/router) takes over. In the tables shown above, a None timeout is declared when the current device is the active querier

        Tip

        The reason why multicast flooding is enabled by default is to ensure safe co-existence with MAC multicast, which is common in industrial networks. It also allows end devices that do not know of IGMP/MLD to communicate over multicast as long as the group they have chosen is not used by other IGMP/MLD aware devices on the LAN.

        As soon as an IGMP/MLD membership report to \"join\" a group is received the group is added to the kernel MDB and forwarding to other ports stop. The only exception to this rule is multicast router ports.

        If your MAC multicast forwarding is not working properly, it may be because an IP multicast group maps to the same MAC address. Please see RFC 1112 for details. Use static multicast router ports, or static multicast MAC filters, to mitigate.

        "},{"location":"bridging/#forwarding-of-ieee-reserved-group-addresses","title":"Forwarding of IEEE Reserved Group Addresses","text":"

        Addresses in the range 01:80:C2:00:00:0X are used by various bridge signaling protocols, and are not forwarded by default. Still, it is sometimes useful to let the bridge forward such packets, this can be done by specifying protocol names or the last address nibble as decimal value 0..15:

        admin@example:/config/> edit interface br0 bridge\nadmin@example:/config/interface/br0/bridge/> set ieee-group-forward     # Tap the ? key for alternatives\n  [0..15]  List of IEEE link-local protocols to forward, e.g., STP, LLDP\n  dot1x    802.1X Port-Based Network Access Control.\n  lacp     802.3 Slow Protocols, e.g., LACP.\n  lldp     802.1AB Link Layer Discovery Protocol (LLDP).\n  stp      Spanning Tree (STP/RSPT/MSTP).\nadmin@example:/config/interface/br0/bridge/> set ieee-group-forward\n

        The following example configures bridge br0 to forward LLDP packets.

        admin@example:/config/interface/br0/bridge/> set ieee-group-forward lldp\nadmin@example:/config/interface/br0/bridge/>\n
        1. MAC bridges on Marvell Linkstreet devices are currently limited to a single MAC database, this may be a problem if the same MAC address appears in different MAC bridges.\u00a0\u21a9

        2. For example, IPv4 groups are mapped to MAC multicast addresses by mapping the low-order 23-bits of the IP address in the low-order 23 bits of the Ethernet address 01:00:5E:00:00:00. Meaning, more than one IP multicast group maps to the same MAC multicast group.\u00a0\u21a9

        "},{"location":"container/","title":"Docker Containers","text":""},{"location":"container/#docker-container-support","title":"Docker Container Support","text":"

        Infix comes with native support for Docker containers using podman. The YANG model describes the current level of support, complete enough to run both system and application containers.

        Key design features of Infix, like using Linux switchdev, allow users to assign switch ports directly to containers, not just bridged VETH pairs. This is a rare and in many cases unique feature of Infix.

        All network specific settings are done using the IETF interfaces YANG model, with augments for containers to ensure smooth integration with container networking in podman.

        Important

        Even though the podman command can be used directly from a shell prompt, we strongly recommend using the CLI commands instead. They employ the services of a wrapper container script which handles the integration of Docker containers in the system.

        "},{"location":"container/#caution","title":"Caution","text":"

        A word of warning. Containers can run on your system in privileged mode, as root, giving them full access to devices on your system. Even though containers are fenced from the host with Linux namespaces, resource limited using cgroups, and normally run with capped privileges, a privileged container is relatively easy to break out of. A trivial example is given in the Advanced section of this document.

        We recommend avoiding privileged containers, if possible (they do have valid use-cases) and instead use capabilities.

        Remember:

        • If the system is compromised, containers can be used to easily install malicious software in your system and over the network
        • Your system is as secure as anything you run in the container
        • If you run containers, there is no security guarantee of any kind
        • Running 3rd party container images on your system could open a security hole/attack vector/surface
        • An expert with knowledge how to build exploits will be able to jailbreak/elevate to root even if best practices are followed

        This being said, a system suspected of being compromised can always be restored to a safe state with a factory reset. Provided, of course, that it has secure boot enabled.

        "},{"location":"container/#getting-started","title":"Getting Started","text":"

        In the CLI, containers can be run in one of two ways:

        1. container run IMAGE [COMMAND], or
        2. enter configure context, then edit container NAME

        The first is useful mostly for testing, or running single commands in an image. It is a wrapper for podman run -it --rm ....

        The second creates a read-only container that by default automatically start at every boot. It basically wraps podman create ....

        When non-volatile storage is needed two complementary options exist:

        • Volumes: data stored in a volume is persisted until explicitly removed from the configuration, i.e., across host reboots and container upgrades
        • Content Mounts: where the content of a file mounted into the container is kept along with the container configuration in the device's startup-config

        Podman ensures (using tmpfs) all containers have writable directories for certain critical file system paths: /dev, /dev/shm, /run, /tmp, and /var/tmp. Meaning, what you most often need is writable volumes for /var/lib and /etc, or only file mounts for a few files in /etc. The actual requirements depend on your container image and application to run.

        Important

        When running containers from public registries, double-check that they support the CPU architecture of your host system. Remember, unlike virtualization, containers reuse the host's CPU and kernel.

        "},{"location":"container/#example-hello-world","title":"Example: Hello World","text":"

        Classic Hello World:

        admin@example:/> container run docker://hello-world\nStarting docker://hello-world :: use Ctrl-p Ctrl-q to detach\nTrying to pull docker.io/library/hello-world:latest...\nGetting image source signatures\nCopying blob c1ec31eb5944 done\nCopying config d2c94e258d done\nWriting manifest to image destination\nStoring signatures\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n
        "},{"location":"container/#example-web-server","title":"Example: Web Server","text":"

        A web server with nginx, using standard docker bridge. Podman will automatically create a VETH pair for us, connecting the container to the docker0 bridge:

        admin@example:/> configure\nadmin@example:/config/> edit interface docker0\nadmin@example:/config/interface/docker0/> set container-network\nadmin@example:/config/interface/docker0/> end\nadmin@example:/config/> edit container web\nadmin@example:/config/container/web/> set image docker://nginx:alpine\nadmin@example:/config/container/web/> set network publish 8080:80\nadmin@example:/config/container/web/> set network interface docker0\nadmin@example:/config/container/web/> set volume cache target /var/cache\nadmin@example:/config/container/web/> leave\nadmin@example:/> show container\n

        Exit to the shell and verify the service with curl, or try to attach to your device's IP address using your browser:

        admin@example:~$ curl http://localhost:8080\n

        or connect to port 8080 of your running Infix system with a browser. See the following sections for how to add more interfaces and manage your container at runtime.

        "},{"location":"container/#container-images","title":"Container Images","text":"

        The underlying podman project supports importing and fetching images in a variety of ways, the most common ones are also supported by Infix. In this section we present how to use them and in the next section we show how to upgrade to a newer base image.

        The CLI help shows:

        admin@example:/config/container/system/> help image\nNAME\n    image <string>\n\nDESCRIPTION\n    Docker image for the container: [transport]name[:tag|@digest]\n\n    quay.io/username/myimage     -- Pull myimage:latest\n    docker://busybox             -- Pull busybox:latest from Docker Hub\n    docker://ghcr.io/usr/img     -- Pull img:latest from GitHub packages\n    dir:/media/usb/myimage:1.1   -- Use myimage v1.1 from USB media\n    docker-archive:/tmp/archive  -- Use archive:latest from tarball\n    oci-archive:/lib/oci/archive -- Use archive:latest from OCI archive\n                                    May be in .tar or .tar.gz format\n\n    Additionally, the following URIs are also supported for setups\n    that do not use a HUB or similar.  Recommend using 'checksum'!\n\n    ftp://addr/path/to/archive   -- Downloaded using wget\n    http://addr/path/to/archive  -- Downloaded using curl\n    https://addr/path/to/archive -- Downloaded using curl\n\n    Note: if a remote repository cannot be reached, the creation of the\n          container will be put on a queue that retries pull every time\n          there is a route change in the host's system.\n

        Tip

        The built-in help system in the CLI is generated from the YANG model, so the same information is also available for remote NETCONF users.

        The two most common variants are docker:// and oci-archive:/.

        The former requires a working Docker registry and the latter operates on a plain OCI archive. Infix does not come with a built-in registry, so the docker:// option is best used with external services, which in turn require networking to be up. In a deployment phase the easiest may be to set up a single interface on your host system with DHCP client.

        The default method is docker://, so when setting the image for your container, you can omit the docker:// prefix. You can also use the admin-exec command container pull docker://..., and when configuring a container podman will check first if it has the image before trying to download anything. (See also the upgrade section, below.)

        The oci-archive:/ is interesting since many users may not have, or do not want to, publish their images in a registry. Use the Docker OCI exporter or any other tool that supports generating OCI Image format. Infix supports loading both .tar or .tar.gz formats.

        Here we show a simple example of fetching an OCI image to the system, but many others exist, tools like wget, curl, and scp come to mind.

        Shell OCI Example:

        admin@example:~$ cd /var/tmp/\nadmin@example:/var/tmp$ sudo wget https://github.com/kernelkit/curiOS/releases/download/edge/curios-oci-amd64.tar.gz\nConnecting to github.com (140.82.121.3:443)\nwget: note: TLS certificate validation not implemented\nConnecting to objects.githubusercontent.com (185.199.109.133:443)\nsaving to 'curios-oci-amd64.tar.gz'\ncurios-oci-amd64.tar 100% |*********************************| 7091k  0:00:00 ETA\n'curios-oci-amd64.tar.gz' saved\nadmin@example:/var/tmp$ ll\ntotal 7104\ndrwxr-xr-x    3 root     root          4096 Mar 27 14:22 ./\ndrwxr-xr-x   14 root     root          4096 Mar 27 11:57 ../\n-rw-r--r--    1 root     root       7261785 Mar 27 14:22 curios-oci-amd64.tar.gz\ndrwx------    6 frr      frr           4096 Mar 27 11:57 frr/\n

        Importing the image into Podman can be done either from the CLI admin-exec context ...

        admin@example:/var/tmp$ cli\nadmin@example:/> container load /var/tmp/curios-oci-amd64.tar.gz name curios:edge\n

        Tip

        The name curios:edge is the tag you give the imported (raw) archive which you can then reference in your container image configuration: set image curios:edge.

        ... or by giving the container configuration the full path to the OCI archive, which helps greatly with container upgrades (see below):

        admin@example:/config/container/system/> set image oci-archive:/var/tmp/curios-oci-amd64.tar.gz\n

        Checksum Example:

        admin@example:/> configure\nadmin@example:/config/> edit container sys\nadmin@example:/config/container/sys/> set hostname sys\nadmin@example:/config/container/sys/> set image ftp://192.168.122.1/curios-oci-amd64-v24.05.0.tar.gz\nadmin@example:/config/container/sys/> set checksum\n    md5 sha256 sha512\nadmin@example:/config/container/sys/> set checksum sha256 4f01077036527498ed910f1a3e80645ae3eff629d10043cf80ebc6850c99c629\nadmin@example:/config/container/sys/> leave\nadmin@example:/> copy running-config startup-config\nadmin@example:/> show container\nNAME  STATUS         NETWORK  MEMORY (KiB)  CPU%\nsys   Up 5 seconds                  72/512  0.02\n\nadmin@example:/> show log\n...\nNov 20 07:24:56 example container[5040]: Fetching ftp://192.168.122.1/curios-oci-amd64-v24.05.0.tar.gz\nNov 20 07:24:56 example container[5040]: curios-oci-amd64-v24.05.0.tar.gz downloaded successfully.\nNov 20 07:24:56 example container[5040]: curios-oci-amd64-v24.05.0.tar.gz checksum verified OK.\nNov 20 07:24:57 example container[5040]: Cleaning up extracted curios-oci-amd64-v24.05.0\nNov 20 07:24:57 example container[5040]: podman create --name sys --conmon-pidfile=/run/container:sys.pid --read-only --replace --quiet --cgroup-parent=containers  --restart=always --systemd=false --tz=local --hostname sys --log-driver k8s-file --log-opt path=/run/containers/sys.fifo --network=none curios-oci-amd64-v24.05.0\nNov 20 07:24:57 example container[3556]: b02e945c43c9bce2c4be88e31d6f63cfdb1a3c8bdd02179376eb059a49ae05e4\n
        "},{"location":"container/#understanding-image-tags","title":"Understanding Image Tags","text":"

        Docker images use tags to identify different versions of the same image. Understanding the difference between mutable and immutable tags is important for managing container upgrades effectively.

        "},{"location":"container/#mutable-tags","title":"Mutable Tags","text":"

        Tags like :latest, :edge, or :stable are mutable \u2014 they point to different images over time as new versions are published to the registry.

        Advantages:

        • Convenient: upgrade without changing configuration
        • Simple: use the CLI command container upgrade NAME to get the latest version, there is even a convenient RPC for controlling the remotely
        • Good for: development, testing, and systems that auto-update

        Trade-offs:

        • Less reproducible: different systems may run different versions
        • Less predictable: upgrades happen when you pull, not when you plan
        • Harder to rollback: previous version may no longer be available

        Example mutable tags:

        docker://nginx:latest          # Always points to newest release\ndocker://myapp:edge            # Development/bleeding edge version\noci-archive:/var/tmp/app.tar   # Local archive that may be replaced\n
        "},{"location":"container/#immutable-tags","title":"Immutable Tags","text":"

        Version-specific tags like :v1.0.1, :24.11.0, or digest references like @sha256:abc123... are immutable \u2014 they always reference the exact same image content.

        Advantages:

        • Reproducible: all systems run identical versions
        • Predictable: upgrades only happen when you change configuration
        • Auditable: clear history of what ran when
        • Good for: production, compliance, and controlled deployments

        Trade-offs:

        • More explicit: must update configuration to upgrade
        • Requires planning: need to know which version to use

        Example immutable tags:

        docker://nginx:1.25.3          # Specific version number\ndocker://myapp:v2.1.0          # Semantic version tag\ndocker://nginx@sha256:abc123   # Cryptographic digest (most immutable)\n

        Tip

        Best practice for production: Use specific version tags (:v1.0.1) rather than mutable tags (:latest). This ensures all your systems run identical software and upgrades happen only when you decide.

        "},{"location":"container/#upgrading-container-images","title":"Upgrading Container Images","text":"

        The applications in your container are an active part of the system as a whole, so make it a routine to keep your container images up-to-date!

        "},{"location":"container/#how-container-lifecycle-works","title":"How Container Lifecycle Works","text":"

        Infix intelligently manages container lifecycles to provide a smooth experience while minimizing unnecessary work:

        At first setup: When you configure a container for the first time, Infix fetches the image (if needed) and creates the container instance.

        At boot time: Infix checks if the container needs to be recreated by comparing checksums for:

        • The image archive that the container was built from
        • The container configuration script

        When configuration changes: If you modify any container settings (network, volumes, environment, etc.), the container is automatically recreated with the new configuration.

        When explicitly upgraded: Using the container upgrade command forces a fresh pull of the image and recreates the container.

        This means that in most cases, containers persist across reboots and are only recreated when actually necessary. Your container's state stored in volumes is preserved across recreations. Since Infix containers use a read-only root filesystem, any changes written outside of volumes or the writable paths provided by Podman (/dev, /dev/shm, /run, /tmp, /var/tmp) will be lost when the container is recreated.

        "},{"location":"container/#method-1-upgrading-immutable-tags","title":"Method 1: Upgrading Immutable Tags","text":"

        When using version-specific tags, you upgrade by explicitly changing the image reference in your configuration:

        admin@example:/> configure\nadmin@example:/config/> edit container web\nadmin@example:/config/container/web/> set image docker://nginx:1.25.3\nadmin@example:/config/container/web/> leave\n

        What happens:

        1. Podman pulls the new image in the background (if not already present)
        2. Your container is automatically stopped
        3. The container is recreated with the new image
        4. The container is started with your existing volumes intact

        Example: Upgrading from one version to another:

        admin@example:/> configure\nadmin@example:/config/> edit container system\nadmin@example:/config/container/system/> show image\nimage ghcr.io/kernelkit/curios:v24.11.0;\nadmin@example:/config/container/system/> set image ghcr.io/kernelkit/curios:v24.12.0\nadmin@example:/config/container/system/> leave\nadmin@example:/> show log\n...\nDec 13 14:32:15 example container[1523]: Pulling ghcr.io/kernelkit/curios:v24.12.0...\nDec 13 14:32:18 example container[1523]: Stopping old container instance...\nDec 13 14:32:19 example container[1523]: Creating new container with updated image...\nDec 13 14:32:20 example container[1523]: Container system started successfully\n
        "},{"location":"container/#method-2-upgrading-mutable-tags","title":"Method 2: Upgrading Mutable Tags","text":"

        For images using mutable tags like :latest or :edge, use the container upgrade command:

        admin@example:/> container upgrade NAME\n

        This command:

        1. Stops the running container
        2. Pulls the latest version of the image from the registry
        3. Recreates the container with the new image
        4. Starts the container automatically

        Example using registry:

        admin@example:/> container upgrade system\nsystem\nTrying to pull ghcr.io/kernelkit/curios:edge...\nGetting image source signatures\nCopying blob 07bfba95fe93 done\nCopying config 0cb6059c0f done\nWriting manifest to image destination\nStoring signatures\n0cb6059c0f4111650ddbc7dbc4880c64ab8180d4bdbb7269c08034defc348f17\nsystem: not running.\n59618cc3c84bef341c1f5251a62be1592e459cc990f0b8864bc0f5be70e60719\n

        Example using local OCI archive:

        An OCI archive image can be upgraded in a similar manner. First, get the new archive onto the system (see Container Images section above), then, provided the oci-archive:/path/to/archive format is used in your configuration, call the upgrade command:

        admin@example:/> container upgrade system\nUpgrading container system with local archive: oci-archive:/var/tmp/curios-oci-amd64.tar.gz ...\n7ab4a07ee0c6039837419b7afda4da1527a70f0c60c0f0ac21cafee05ba24b52\n

        OCI archives can also be fetched from ftp/http/https URLs. In that case, the upgrade works the same way as a registry image \u2014 Infix downloads the new archive and recreates the container.

        "},{"location":"container/#embedded-container-images","title":"Embedded Container Images","text":"

        Tip

        Containers running from OCI images embedded in the operating system, e.g., /lib/oci/mycontainer.tar.gz, are automatically kept in sync with the Infix system image version.

        How it works: When you build a custom Infix image with embedded OCI archives, those containers will be upgraded whenever you upgrade the Infix operating system itself. At boot, Infix checks if the embedded image has changed and automatically recreates the container if needed.

        Example: default builds of Infix include a couple of OCI images for reference, one is /lib/oci/curios-nftables-v24.11.0.tar.gz, but there is also a symlink called curios-nftables-latest.tar.gz in the same directory, which is what the Infix regression tests use in the image configuration of the container. When the system is upgraded and the embedded image changes, the test containers are automatically recreated with the new version.

        This approach ensures your embedded containers always match your system version without any manual intervention.

        "},{"location":"container/#capabilities","title":"Capabilities","text":"

        An unprivileged container works for almost all use-cases, but there are occasions where they are too restricted and users start looking for the privileged flag. Capabilities offers a middle ground.

        For example, a system container from which ping does not work:

        admin@example:/config/container/system/> edit capabilities\nadmin@example:/config/container/system/capabilities/> set add net_raw\nadmin@example:/config/container/system/capabilities/> end\nadmin@infix-00-00-00:/config/container/system/> show\n...\ncapabilities {\n  add net_raw;\n}\n...\n

        Infix supports a subset of all capabilities that are relevant for containers. Please note, that this is an advanced topic that require time and analysis of your container application to figure out which capabilities you need.

        "},{"location":"container/#resource-limits","title":"Resource Limits","text":"

        Containers can be configured with resource limits to control their memory and CPU usage. This helps prevent containers from consuming excessive system resources and ensures fair resource allocation across multiple containers.

        "},{"location":"container/#configuring-resource-limits","title":"Configuring Resource Limits","text":"

        Resource limits are set per container and include:

        • Memory: Maximum memory usage in kibibytes (KiB)
        • CPU: Maximum CPU usage in millicores (1000 millicores = 1 CPU core)

        Example configuration limiting a container to 512 MiB of memory and 1.5 CPU cores:

        admin@example:/> configure\nadmin@example:/config/> edit container web\nadmin@example:/config/container/web/> edit resource-limit\nadmin@example:/config/container/web/resource-limit/> set memory 524288\nadmin@example:/config/container/web/resource-limit/> set cpu 1500\nadmin@example:/config/container/web/resource-limit/> leave\n

        Common CPU limit examples:

        • 500 = 0.5 cores (50% of one core)
        • 1000 = 1.0 cores (one full core)
        • 2000 = 2.0 cores (two full cores)
        "},{"location":"container/#monitoring-resource-usage","title":"Monitoring Resource Usage","text":"

        Runtime resource usage statistics are available in the operational datastore:

        admin@example:/> show container web\n...\n

        Use show container usage to see resource consumption across all containers, including memory, CPU, block I/O, network I/O, and process counts.

        "},{"location":"container/#networking-and-containers","title":"Networking and Containers","text":"

        By default, unlike other systems, persistent2 containers have no networking enabled. All network access has to be set up explicitly. Currently two types of of container networks are supported:

        • host: an managed host interface, e.g., one end of a VETH pair, or even a physical interface
        • bridge: an IP masquerading bridge

        In the former the interface is delegated to (moved into) the container, while in the latter a VETH pair is automatically created by Podman and one end delegated to the container, while the other end is assigned to the bridge (see the next section).

        Tip

        For more information on VETH pairs, see the Networking Guide.

        "},{"location":"container/#container-bridge","title":"Container Bridge","text":"

        A container bridge is what most container setups use and users want.

        The difference from a regular bridge is that the container runtime fully manages them -- connecting containers with automatically created VETH pairs (look at the bridge port names) and setting up firewall rules between the host and other containers, as well as managing port forwarding. This transparent background management is what makes container use seem to be so simple.

        All interface configuration is done in configure context.

        admin@example:/> configure\nadmin@example:/config> edit interface docker0\nadmin@example:/config/interface/docker0/> set container-network\nadmin@example:/config/interface/docker0/> leave\n

        There is more to this story. When using the CLI, and sticking to common interface nomenclature, Infix helps you with some of the boring stuff. E.g., creating a new interface with a name like brN or dockerN automatically infers the interface types, which you would otherwise have to set manually:

        admin@example:/config/interface/docker0/> set type bridge\nadmin@example:/config/interface/docker0/> set container-network type bridge\n

        Important

        When configuring the system via an API such as NETCONF or RESTCONF, no settings are inferred. Instead it is up to the caller to fully define the desired setup. This makes the CLI very useful for first setup and then extracting the resulting XML from the shell using the cfg -X command.

        We have to declare the interface as a container network, ensuring the interface cannot be used by the system for any other purpose. E.g., a container host interface is supposed to be used by a container, by declaring it as such we can guarantee that it would never accidentally be added as a bridge or lag port. Hence, to move an interface currently set as a bridge-port it must be removed from the bridge before being given to a container.

        The default subnet for a container bridge is 172.17.0.0/16, the bridge takes the .1 address and hand out the rest of the range to containers in a round-robin like fashion. A container with this network get an automatically created VETH pair connection to the bridge and a lot of other networking parameters (DNS, default route) are set up.

        Some of the defaults of a container bridge can be changed, e.g., instead of set container-network type bridge, above, do:

        admin@example:/config/interface/docker0/> edit container-network\nadmin@example:/config/\u2026/container-network/> set type bridge\nadmin@example:/config/\u2026/container-network/> edit subnet 192.168.0.0/16\nadmin@example:/config/\u2026/subnet/192.168.0.0/16/> set gateway 192.168.255.254\nadmin@example:/config/\u2026/subnet/192.168.0.0/16/> end\nadmin@example:/config/\u2026/container-network/> edit route 10.0.10.0/24\nadmin@example:/config/\u2026/route/10.0.10.0/24/> set gateway 192.168.10.254\nadmin@example:/config/\u2026/route/10.0.10.0/24/> end\nadmin@example:/config/\u2026/container-network/> end\nadmin@example:/config/interface/docker0/> leave\n

        Other network settings, like DNS and domain, use built-in defaults, but can be overridden from each container. Other common settings per container is the IP address and name of the network interface inside the container. The default, after each stop/start cycle, or reboot of the host, is to name the interfaces eth0, eth1, in the order they are given in the network list, and to give the container the next address in a bridge. Below an example of a system container calls set network interface docker0, here we show how to set options for that network:

        admin@example:/config/container/ntpd/> edit network interface docker0\nadmin@example:/config/\u2026/network/interface/docker0/>\nadmin@example:/config/\u2026/network/interface/docker0/> set option\n<string>  Options for masquerading container bridges.\nadmin@example:/config/\u2026/network/interface/docker0/> help option\nNAME\n        option <string>\n\nDESCRIPTION\n        Options for masquerading container bridges.\n\n        Example: ip=1.2.3.4            -- request a specific IP (IPv4 or IPv6)\n                 mac=00:01:02:c0:ff:ee -- set fixed MAC address in container\n                 interface_name=foo0   -- set interface name inside container\n\nadmin@example:/config/\u2026/network/interface/docker0/> set option ip=172.17.0.2\nadmin@example:/config/\u2026/network/interface/docker0/> set option interface_name=wan\nadmin@example:/config/\u2026/network/interface/docker0/> leave\n
        "},{"location":"container/#container-host-interface","title":"Container Host Interface","text":"

        Another common use-case is to move network interfaces into the network namespace of a container1. This of course works with plain Ethernet interfaces as well, but here we will use one end of a VETH pair as an example.

        The network option setting is available also for this case, but only the interface_name=foo0 option works. Which is still very useful. To set:

        • IP address, use IPv4/IPv6 settings in the interface settings
        • MAC address, to use the custom-phys-address in the interface settings

        For an example of both, see the next section.

        Important

        VETH Pair Limitation: When using VETH pairs with containers, at least one side of the pair must remain in the host namespace. It is currently not possible to create VETH pairs where both ends are assigned to different containers. One end must always be accessible from the host.

        "},{"location":"container/#routed-setup","title":"Routed Setup","text":"

        In this routed setup we reserve 192.168.0.0/24 for the network between the host and the ntpd container.

        Configuration is a straight-forward VETH pair setup where we name the container-end of pair ntpd. This is just a convenience for us when reading the configuration later. The real action happens on the last line where we declare the ntpd end as a container network interface:

        admin@example:/config/> edit interface veth0\nadmin@example:/config/interface/veth0/> set veth peer ntpd\nadmin@example:/config/interface/veth0/> set ipv4 address 192.168.0.1 prefix-length 24\nadmin@example:/config/interface/veth0/> end\nadmin@example:/config/> edit interface ntpd\nadmin@example:/config/interface/ntpd/> set ipv4 address 192.168.0.2 prefix-length 24\nadmin@example:/config/interface/ntpd/> set custom-phys-address static 00:c0:ff:ee:00:01\nadmin@example:/config/interface/ntpd/> set container-network\n

        Tip

        Notice how you can also set a custom MAC address at the same time.

        Adding the interface to the container is the same as before, but since everything for host interfaces is set up in the interfaces context, we can take a bit of a shortcut.

        admin@example:/config/container/ntpd/> set network interface ntpd\nadmin@example:/config/container/ntpd/> leave\n

        Tip

        Use the set network interface ntpd option interface_name=foo0 to set the name of the interface inside the container to foo0.

        The point of the routed case is that port forwarding from the container in this case is limited to a single interface, not all interfaces as is the default in the masquerading container bridge setup.

        "},{"location":"container/#bridged-setup","title":"Bridged Setup","text":"

        A perhaps more common case is to bridge the other end of the VETH pair with other physical ports. In this section we show how to add a new pair to give our container two interfaces:

        We start by adding the second VETH pair:

        admin@example:/config/> edit interface veth1a\nadmin@example:/config/interface/veth1a/> set veth peer veth1\nadmin@example:/config/interface/veth1a/> set ipv4 address 192.168.1.2 prefix-length 24\n

        Note

        The LAN bridge (br1) in this example has IP address 192.168.1.1.

        When a container has multiple host interfaces it can often be useful to have a default route installed. This can be added from the host with a 0.0.0.0/0 route on one of the interfaces:

        admin@example:/config/interface/veth1a/> set container-network route 0.0.0.0/0 gateway 192.168.1.1\nadmin@example:/config/interface/veth1a/> show\ntype veth;\ncontainer-network {\n  type host;\n  route 0.0.0.0/0 {\n    gateway 192.168.1.1;\n  }\n}\nveth {\n  peer veth1;\n}\nadmin@example:/config/interface/veth1a/> end\nadmin@example:/config/> set interface veth1 bridge-port bridge br1\n

        Please note, container network routes require the base interface also have a static IP address set. Setting only the route, but no address, means the route is skipped.

        "},{"location":"container/#host-networking","title":"Host Networking","text":"

        The third use-case is host networking, this is where a container share the network namespace of the host. An example here could be a nftables or ntpd container -- single applications which add core functionality to the host operating system.

        The host networking setup cannot be combined with any other network.

        For an example, see below.

        "},{"location":"container/#mounts-and-volumes","title":"Mounts and Volumes","text":"

        It is possible to mount files, directories, and even files matching a glob, into a container. This gives precise control over the container's file system:

        admin@example:/config/container/system/> edit mount leds\nadmin@example:/config/container/system/mount/leds> set source /sys/class/leds\nadmin@example:/config/container/system/mount/leds> set target /sys/class/leds\nadmin@example:/config/container/system/mount/leds> end\nadmin@example:/config/container/system/>\n

        Any type of file can be bind mounted into the container, just watch out for permissions though. In the example above, /sys/class/leds is not writable from a container unless it runs in privileged mode. For plain configuration files you get more freedom, and your container can rely on, e.g., inotify events to trigger reloading its services when you change the file on the host.

        So it depends on the container, and indeed your overall setup, what to use. An intriguing option is Content Mounts, which when changed also trigger a container restart.

        Other times volumes are a better fit. A volume is an automatically created read-writable entity that follows the life of your container.

        admin@example:/config/container/ntpd/> set volume varlib target /var/lib\n

        Volumes are persistent across both reboots and upgrades of the base image. They are created by Podman when the container first starts up, unlike a regular bind mount it synchronizes with the contents of the underlying container image's path at first use. I.e., \"bind-mount, if empty: then rsync\".

        Note

        Infix support named volumes (only), and it is not possible to share a volume between containers. All the tricks possible with volumes may be added in a later release.

        "},{"location":"container/#volume-management","title":"Volume Management","text":"

        Volumes are persistent storage that survive container restarts and image upgrades, making them ideal for application data. However, this also means they are not automatically removed when a container is deleted from the configuration.

        This design choice prevents accidental data loss, especially in scenarios where:

        • A container is temporarily removed and re-added with the same name
        • A container is replaced with a different configuration but same name
        • System upgrades or configuration changes affect container definitions

        To clean up unused volumes and reclaim disk space, use the admin-exec command:

        admin@example:/> container prune\nDeleted Images\n...\nDeleted Volumes\nntpd-varlib\nsystem-data\n\nTotal reclaimed space: 45.2MB\n

        The container prune command safely removes:

        • Unused container images
        • Volumes not attached to any container (running or stopped)
        • Other unused container resources

        Tip

        You can monitor container resource usage with the command:

        admin@example:/> show container usage\n

        This displays disk space used by images, containers, and volumes, helping you decide when to run the prune command.

        To see which volumes exist and which containers use them:

        admin@example:/> show container volumes\n

        "},{"location":"container/#content-mounts","title":"Content Mounts","text":"

        Content mounts are a special type of file mount where the file contents is stored with the container configuration. This can be very useful when deploying similar systems at multiple sites. When the host loads its startup-config (or even factory-config) a temporary file is created using the decoded base64 data from the content node.

        admin@example:/config/container/ntpd/> edit mount ntpd.conf\nadmin@example:/config/container/ntpd/mount/ntpd.conf> text-editor content\n... interactive editor starts up ...\nadmin@example:/config/container/ntpd/mount/ntpd.conf> set target /etc/ntpd.conf\nadmin@example:/config/container/ntpd/mount/ntpd.conf> end\nadmin@example:/config/container/ntpd/>\n

        The editor is a small Emacs clone called Mg, see the built-in help text, or press Ctrl-x Ctrl-c to exit and save. When the editor exits the contents are base64 encoded and stored in the candidate datastore.

        Note

        Since these files are always recreated when the host is restarted, changes made by the container are not preserved, or saved back to the host's startup-config.

        Infix has three different text editors available. For more information, see CLI Text Editor.

        "},{"location":"container/#example-containers","title":"Example Containers","text":""},{"location":"container/#system-container","title":"System Container","text":"

        Let's try out what we've learned by setting up a system container, a container providing multiple services, using the docker0 interface we created previously:

        admin@example:/> configure\nadmin@example:/config> edit container system\nadmin@example:/config/container/system/> set image ghcr.io/kernelkit/curios:edge\nadmin@example:/config/container/system/> set network interface docker0\nadmin@example:/config/container/system/> set publish 222:22\nadmin@example:/config/container/system/> leave\n

        Note

        Ensure you have a network connection to the registry. If the image cannot be pulled, creation of the container will be put in a queue and be retried every time there is a change in the routing table, e.g., default route is added, and every 60 seconds.

        Provided the image is downloaded successfully, a new system container now runs behind the docker0 interface, forwarding container port 22 to port 222 on all of the host's interfaces. (See help publish in the container configuration context for the full syntax.)

        Available containers can be accessed from admin-exec:

        admin@example:/> show container\nNAME     STATUS        NETWORK  MEMORY (KiB)  CPU%\nsystem   Up 16 hours   docker0       136/512  0.02\n

        This is a system container, so you can \"attach\" to it by starting a shell (or logging in with SSH):

        admin@example:/> container shell system\nroot@439af2917b44:/#\n

        Notice how the hostname inside the container changes. By default the container ID (hash) is used, but this can be easily changed:

        root@439af2917b44:/# exit\nadmin@example:/> configure\nadmin@example:/config/> edit container system\nadmin@example:/config/container/system/> set hostname sys101\nadmin@example:/config/container/system/> leave\nadmin@example:/> container shell system\nroot@sys101:/#\n

        In fact, the container hostname setting supports the same format specifiers as the host's hostname setting:

        • %i: OS ID, from /etc/os-release, from Menuconfig branding
        • %h: Default hostname, from /etc/os-release, from branding
        • %m: NIC specific part of base MAC, e.g., to c0-ff-ee
        • %%: Literal %

        The most useful combination is probably \"container-name-%m\", which in this example give the container hostname container-name-c0-ff-ee.

        "},{"location":"container/#application-container-nftables","title":"Application Container: nftables","text":"

        Infix currently does not have a native firewall configuration, and even when it does it will never expose the full capabilities of nftables. For advanced setups, the following is an interesting alternative.

        admin@example:/> configure\nadmin@example:/config> edit container nftables\nadmin@example:/config/container/nftables/> set image ghcr.io/kernelkit/curios-nftables:edge\nadmin@example:/config/container/nftables/> set network host\nadmin@example:/config/container/nftables/> set privileged\nadmin@example:/config/container/nftables/> edit mount nftables.conf\nadmin@example:/config/\u2026/mount/nftables.conf/> set target /etc/nftables.conf\nadmin@example:/config/\u2026/mount/nftables.conf/> text-editor content\n... interactive editor starts up where you can paste your rules ...\nadmin@example:/config/\u2026/mount/nftables.conf/> leave\n

        Notice how we set network host, so the container can see and act on all the host's interfaces, and that we also have to run the container in privileged mode.

        "},{"location":"container/#application-container-ntpd","title":"Application Container: ntpd","text":"

        The default NTP server/client in Infix is Chrony, a fully working and capable workhorse for most use-cases. However, it does not support a feature like multicasting, for that you need ISC ntpd.

        As we did with nftables, previously, we can use host networking and set up a read-only config file that is bind-mounted into the container's file system and store in the host's startup-config. However, ntpd also saves clock drift information in /var/lib/ntpd, so we will also use volumes in this example.

        admin@example:/> configure\nadmin@example:/config> edit container ntpd\nadmin@example:/config/container/ntpd/> set image ghcr.io/kernelkit/curios-ntpd:edge\nadmin@example:/config/container/ntpd/> set network interface ntpd    # From veth0 above\nadmin@example:/config/container/ntpd/> edit mount ntp.conf\nadmin@example:/config/container/ntpd/mount/ntp.conf/> set target /etc/ntp.conf\nadmin@example:/config/container/ntpd/mount/ntp.conf/> text-editor content\n... interactive editor starts up where you can paste your rules ...\nadmin@example:/config/container/ntpd/mount/ntp.conf/> end\nadmin@example:/config/container/ntpd/> edit volume varlib\nadmin@example:/config/container/ntpd/volume/varlib/> set target /var/lib\nadmin@example:/config/container/ntpd/volume/varlib/> leave\nadmin@example:/> copy running-config startup-config\n

        The ntp.conf file is stored in the host's startup-config and any state data in the container's /var/lib is retained between reboots and across image upgrades.

        "},{"location":"container/#advanced","title":"Advanced","text":"

        This section covers advanced, and sometimes dangerous, topics. Please read any warnings and always consider the security aspects.

        "},{"location":"container/#running-host-commands-from-container","title":"Running Host Commands From Container","text":"

        SSH login with keys is very handy, both remote scripting friendly and secure, but it does require a few extra configuration steps. The way to set it up is covered in part in SSH Authorized Key.

        Another insecure approach is to access the host system directly, bypassing the namespaces that make up the boundary between host and container.

        Caution

        Please note, this completely demolishes the isolation barrier between container and host operating system. It is only suitable in situations where the container serves more as a unit of distribution rather than as a separate component of the system. Strongly recommended to use this only in trusted setups! Consider also limiting the time frame in which this is active!

        First, enable Privileged mode, this unlocks the door and allows the container to manage resources on the host system. An example is the nftables container mentioned previously.

        admin@example:/config/container/system/> set privileged\n

        Second, mount the host's /proc/1 directory to somewhere inside your container. Here we pick /1:

        admin@example:/config/container/system/> edit mount host\nadmin@example:/config/container/system/mount/host/> set source /proc/1\nadmin@example:/config/container/system/mount/host/> set target /1\nadmin@example:/config/container/system/mount/host/> leave\n

        Third, from inside the container, use the host's PID 1 namespaces with the nsenter3 command to slide through the container's walls. Here we show two example calls to hostname, first the container's own name and then asking what the hostname is on the host:

        root@sys101:/# hostname\nsys101\nroot@sys101:/# nsenter -m/1/ns/mnt -u/1/ns/uts -i/1/ns/ipc -n/1/ns/net hostname\nexample\n

        One use-case for this method is when extending Infix with a management container that connects to other systems. For some tips on how to control an Infix system this way, see Scripting Infix.

        "},{"location":"container/#container-requirements","title":"Container Requirements","text":"

        In addition to general best practices for container images, there are a few more things to consider when targeting embedded systems:

        • Ensure the image targets the CPU architecture of the target system, learn more about Multi-platform Builds
        • Follow best practices for naming and tagging, e.g., :latest vs :1.0
        • Follow OCI recommendations and layout, learn more about OCI and Docker Exporters

        If the Docker documentation is not enough, there are plenty of guides online with examples on how to create your own container image. For the more advanced, please see the next section.

        "},{"location":"container/#advanced-users","title":"Advanced Users","text":"

        Most people prefer their system containers small, often based on Alpine Linux, or similar, with only a few small applications, including their own, and an SSH server perhaps. For some developers, even this is too big, so they roll their own from source. This section is for you.

        Depending on your needs, here is a checklist:

        • you need something that can forward signals, e.g.,
        • tini
        • Bash only, or
        • BusyBox init, a classic most embedded developers know, but read on ...
        • a system container only need the bare necessities of a system bringup
        • E.g., BusyBox's init, but not everything
        • Some of the networking is set up by Podman and CNI for you, but you may want to run a DHCP client?
        • Do not rename interfaces inside the container, use the dedicated interface_name option in the configuration instead
        • Remember, Podman provides a tmpfs for all critical system paths: /dev, /dev/shm, /run, /tmp, and /var/tmp, so you don't need to clean or set up any of these mount points

        Examples using tini and BusyBox init are available from the KernelKit curiOS project. It is a small Buildroot based container image builder that generates OCI compatible image tarballs without any tools from Docker or Podman -- ready-made images exist for testing on both AMD64 and ARM64 targets, as well as docker pull images and and OCI tarballs with SHA256 checksums for integrity checking.

        Finally, if you build your own version of Infix, and embed OCI tarballs in the system image, then see the tip at the end of Upgrading a Container Image (above).

        1. Something which the container bridge network type does behind the scenes with one end of an automatically created VETH pair.\u00a0\u21a9

        2. this does not apply to the admin-exec command container run. This command is intended to be used for testing and evaluating container images. Such containers are given a private network behind an IP masquerading bridge.\u00a0\u21a9

        3. The nsenter program is available from either the util-linux package in Debian/Ubuntu/Mint, or in BusyBox. Note, however, it may not be enabled by default in BusyBox.\u00a0\u21a9

        "},{"location":"developers-guide/","title":"Developer's Guide","text":""},{"location":"developers-guide/#developers-guide","title":"Developer's Guide","text":"

        Please note, by default the root account is disabled in Infix NETCONF builds. Meaning, the only way to access the system is with the admin account, which is created based on credentials found in the VPD area -- for Qemu devices this is emulated using qemu_fw_cfg.

        For developers this can be quite frustrating to be blocked from logging in to debug the system. The quickest way to enable root login is to apply the dev configuration snippet:

        make apply-dev\n

        See Configuration Snippets for more details.

        Important

        Please see the Contributing section, below, for details on how to fork and clone when contributing to Infix.

        "},{"location":"developers-guide/#cloning","title":"Cloning","text":"

        When pre-built releases are not enough, for instance when you want to add or modify some Open Source components, you can clone the Infix tree to your PC:

        $ mkdir ~/Projects; cd ~/Projects\n$ git clone https://github.com/kernelkit/infix.git\n..\n$ cd infix/\n$ git submodule update --init\n..\n
        "},{"location":"developers-guide/#customer-builds","title":"Customer Builds","text":"

        Customer builds add product specific device trees, more OSS packages, e.g., Frr and podman, and sometimes integrates proprietary software. What's important to remember, however, is that they are all made by setting up Infix as a GIT submodule, similar to how Infix set up a GIT submodule for Buildroot.

        So, in addition to using the customer's specific defconfig(s), one must also make sure to update all submodules, otherwise you will likely end up with a broken build.

        $ ...\n$ git submodule update --init --recursive\n                              ~~~~~~~~~~~\n

        Other caveats should be documented in the customer specific trees.

        "},{"location":"developers-guide/#building","title":"Building","text":"

        Tip

        For more details, see the Getting Started and System Requirements sections of the excellent Buildroot manual.

        Buildroot is almost stand-alone, it needs a few locally installed tools to bootstrap itself. The most common ones are usually part of the base install of the OS, but specific ones for building need the following. The instructions here are for Debian/Ubuntu based systems (YMMV):

        $ sudo apt install bc binutils build-essential bzip2 cpio \\\n                   diffutils file findutils git gzip      \\\n                   libncurses-dev libssl-dev perl patch   \\\n                   python3 rsync sed tar unzip wget       \\\n                   autopoint bison flex autoconf automake \\\n                   mtools\n

        To build an Infix image; select the target and then make:

        make x86_64_defconfig\nmake\n

        Online help is available:

        make help\n

        To see available defconfigs for supported targets, use:

        make list-defconfigs\n
        "},{"location":"developers-guide/#test","title":"Test","text":"

        Working with the regression test framework, Infamy, a few more tools and services are required on your system:

        $ sudo apt install jq graphviz qemu-system-x86 qemu-system-arm \\\n                   ethtool gdb-multiarch tcpdump tshark\n..\n

        To be able to build the test specification you also need:

        $ sudo apt-get install python3-graphviz ruby-asciidoctor-pdf\n..\n
        "},{"location":"developers-guide/#documentation","title":"Documentation","text":"

        The documentation is written in Markdown, with GitHub extensions, and published using MkDocs, material theme. This means some features require MkDocs hinting which may not render fully when previewing on GitHub -- this is OK.

        MkDocs is packaged and available to install via apt, but not all of the plugins and extensions we rely on are available, so instead we do recommend using pipx to install the necessary tooling:

        $ sudo apt install pipx\n$ pipx install mkdocs\n$ pipx inject mkdocs mkdocs-material pymdown-extensions mkdocs-callouts mike mkdocs-to-pdf\n

        The last two packages, mike and mkdocs-to-pdf, are used for online versioning and PDF generation by GitHub Actions, but since they are in the mkdocs.yml file, everyone who wants to preview the documentation have to install all the tooling.

        Preview with:

        $ cd ~/src/infix/\n$ mkdocs serve\n
        "},{"location":"developers-guide/#development","title":"Development","text":"

        Developing with Infix is the same as developing with Buildroot. When working with a package, be it locally kept sources, or when using local.mk, you only want to rebuild the parts you have modified:

        make foo-rebuild\n

        or

        make foo-reconfigure\n

        or, as a last resort when nothing seems to bite:

        make foo-dirclean foo-rebuild\n

        As shown here, you can combine multiple build targets and steps in one go, like this:

        make foo-rebuild bar-rebuild all run\n

        This rebuilds (and installs) foo and bar, the all target calls on Buildroot to finalize the target filesystem and generate the images. The final run argument is explained below.

        "},{"location":"developers-guide/#configuration-snippets","title":"Configuration Snippets","text":"

        Infix ships a set of Kconfig fragments in configs/snippets/ that can be merged into your active .config on demand. This avoids polluting defconfigs with settings that are only useful during development.

        To see what snippets are available:

        make list-snippets\n

        To apply a single snippet to the current output directory:

        make apply-dev          # enable root login\nmake apply-ext4         # build an ext4 rootfs (needed for boards\n                        # whose bootloader lacks squashfs support,\n                        # e.g. Marvell ESPRESSObin)\n

        The apply-* targets require an existing .config (i.e. you must have already run a make <board>_defconfig). The snippet is merged using Buildroot's merge_config.sh, so it behaves like make menuconfig: unrelated settings are preserved, conflicting ones are overridden.

        To apply all snippets at once and then build:

        make dev\n

        This is the recommended one-shot command for setting up a development build from a freshly selected defconfig.

        "},{"location":"developers-guide/#yang-model","title":"YANG Model","text":"

        When making changes to the confd and statd services, you will often need to update the YANG models. If you are adding a new YANG module, it's best to follow the structure of an existing one. However, before making any changes, always discuss them with the Infix core team. This helps avoid issues later in development and makes pull request reviews smoother.

        "},{"location":"developers-guide/#configuration-migration","title":"Configuration Migration","text":"

        Important

        Whenever a YANG model change makes existing startup-config files incompatible \u2014 removing a node, renaming a key, restructuring a container \u2014 you must include a migration script so that devices upgrading in the field are not left unbootable.

        Migration scripts live in src/confd/share/migrate/<version>/ where <version> is the confd version (defined in src/confd/configure.ac) that introduces the breaking change. Each script receives the path to the startup configuration file as its first argument and must edit it in-place. Scripts are run in lexicographic order, so prefix them with a number (e.g. 40-my-change.sh).

        See src/confd/share/migrate/1.6/40-bridge-port-remove-ip.sh for a worked example, and the Configuration Migration section of the Upgrade documentation for the user-facing side of this mechanism.

        "},{"location":"developers-guide/#confd","title":"confd","text":"

        The Infix src/confd/ is the engine of the system. Currently it is a plugin for systemd-plugind and contains XPath subscriptions to all the supported YANG models.

        There are essentially two ways of adding support for a new YANG model:

        • The sysrepo way, or
        • The Infix way, using libsrx (the lydx_*() functions)

        The former is well documented in sysrepo, and the latter is best taught by example, e.g., src/confd/src/infix-dhcp.c. Essentially libsrx is a way of traversing the libyang tree instead of fetching changes by XPath.

        When working with confd you likely want to enable full debug mode, this is how you do it:

        1. Open the file package/confd/confd.conf
        2. Uncomment the first line set DEBUG=1
        3. Change the following line to add -v3 at the end
          [S12345] sysrepo-plugind -f -p /run/confd.pid -n -- Configuration daemon\n

        to:

        [S12345] sysrepo-plugind -f -p /run/confd.pid -n -v3 -- Configuration daemon\n

        Now you can rebuild confd, just as described above, and restart Infix:

        make confd-rebuild all run\n
        "},{"location":"developers-guide/#statd","title":"statd","text":"

        The Infix status daemon, src/statd, is responsible for populating the sysrepo operational datastore. Like confd, it uses XPath subscriptions, but unlike confd, it relies entirely on yanger, a Python script that gathers data from local linux services and feeds it into sysrepo.

        To apply changes, rebuild the image:

        make statd-rebuild all\n

        Rebuilding the image and testing on target for every change during development process can be tedious. Instead, yanger allows remote execution, running the script directly on the host system (test container):

        infamy0:test # ../src/statd/python/yanger/yanger -x \"../utils/ixll -A ssh d3a\" ieee802-dot1ab-lldp\n

        ixll is a utility script that lets you run network commands using an interface name instead of a hostname. It makes operations like ssh, scp, and network discovery easier.

        Normally, yanger runs commands locally to retrieve data (e.g., lldpcli when handling ieee802-dot1ab-lldp). However, when executed with -x \"../utils/ixll -A ssh d3a\" it redirects these commands to a remote system connected to the local d3a interface via SSH. This setup is used for running yanger in an interactive test environment. The yanger script runs on the host system, but key commands are executed on the target system.

        For debugging or testing, you can capture system command output and replay it later without needing a live system.

        To capture:

        infamy0:test # ../src/statd/python/yanger/yanger -c /tmp/capture ieee802-dot1ab-lldp\n

        To replay:

        infamy0:test # ../src/statd/python/yanger/yanger -r /tmp/capture ieee802-dot1ab-lldp\n

        This is especially useful when working in isolated environments or debugging issues without direct access to the DUT.

        "},{"location":"developers-guide/#upgrading-packages","title":"Upgrading Packages","text":""},{"location":"developers-guide/#buildroot","title":"Buildroot","text":"

        The Kernelkit team maintains an internal fork of Buildroot, with branches following the naming scheme YYYY.MM.patch-kkit e.g. 2025.02.1-kkit, which means a new branch should be created whenever Buildroot is updated. These branches should contain only changes to existing packages (but no new patches), modifications to Buildroot itself or upstream backports.

        The team tracks the latest Buildroot LTS (Long-Term Support) release and updates. The impact of minor LTS release upgrades is expected to have a very low impact and should be done as soon there is a patch release of a Buildroot LTS available.

        Depending on your setup, follow the appropriate steps below.

        "},{"location":"developers-guide/#repo-locally-cloned-already","title":"Repo locally cloned already","text":"
        1. Navigate to the Buildroot directory

          cd buildroot/\n
        2. Pull the latest changes from KernelKit

          git pull\n
        3. Fetch the latest tags from upstream

          git fetch upstream --tags\n
        "},{"location":"developers-guide/#no-local-repo-yet","title":"No local repo yet","text":"
        1. Clone the Kernelkit Buildroot repository

          git clone git@github.com:kernelkit/buildroot.git\n
        2. Add the upstream remote

          git remote add upstream https://gitlab.com/buildroot.org/buildroot.git\n
        3. Checkout old KernelKit branch

          git checkout 2025.02.1-kkit\n

        Note

        Below, it is not allowed to rebase the branch when bumped in Infix.

        "},{"location":"developers-guide/#continue-here","title":"Continue Here","text":"
        1. Create a new branch based on the previous KernelKit Buildroot release (e.g. 2025.02.1-kkit) and name it according to the naming scheme (e.g. 2025.02.2-kkit)

          git checkout -b 2025.02.2-kkit\n
        2. Rebase the new branch onto the corresponding upstream release

          git rebase 2025.02.2\n
        3. Push the new branch and tags

          git push origin 2025.02.2-kkit --tags\n
        4. In Infix, checkout new branch of Buildroot

          cd buildroot\ngit fetch\ngit checkout 2025.02.2-kkit\n
        5. Commit and push the changes. Remember to update the ChangeLog!

        6. Create a pull request.

        Note

        Remember to set the pull request label to ci:main to ensure full CI coverage.

        "},{"location":"developers-guide/#linux-kernel","title":"Linux kernel","text":"

        The KernelKit team maintains an internal fork of Linux kernel, with branches following the naming scheme kkit-linux-[version].y, e.g. kkit-6.12.y, which means a new branch should be created whenever the major kernel version is updated. This branch should contain all kernel patches used by Infix.

        The team tracks the latest Linux kernel LTS (Long-Term Support) release and updates. The upgrade of LTS minor releases is expected to have low impact and should be done as soon as a patch release of the LTS Linux kernel is available.

        "},{"location":"developers-guide/#repo-locally-cloned-already_1","title":"Repo locally cloned already","text":"
        • ./utils/kernel-upgrade.sh /path/to/linux/tree
        • Update Changelog
        • push and create a pull request

        Note

        Remember to set the pull request label to ci:main to ensure full CI coverage.

        "},{"location":"developers-guide/#testing","title":"Testing","text":"

        Manual testing can be done using Qemu by calling make run, see also Infix in Virtual Environments, or on a physical device by upgrading to the latest build or \"netbooting\" and running the image from RAM. The latter is how most board porting work is done -- much quicker change-load-test cycles.

        The Infix automated test suite is built around Qemu and Qeneth, see:

        • Regression Testing with Infamy
        • Docker Image

        With any new feature added to Infix, it is essential to include relevant test case(s). See the Test Development section for guidance on adding test cases.

        "},{"location":"developers-guide/#reviewing","title":"Reviewing","text":"

        While reviewing a pull request, you might find yourself wanting to play around with a VM running that exact version. For such occasions, gh-dl-artifact.sh is your friend in need! It employs the GitHub CLI (gh) to locate a prebuilt image from our CI workflow, download it, and prepare a local output directory from which you can launch both make run instances, and run regression tests with make test and friends.

        For example, if you are curious about how PR 666 behaves in some particular situation, you can use gh to switch to that branch, from which gh-dl-artifact.sh can then download and prepare the corresponding image for execution with our normal tooling:

        gh pr checkout 666\n./utils/gh-dl-artifact.sh\ncd x-artifact-a1b2c3d4-x86_64\nmake run\n

        Note

        CI artifacts are built from a merge commit of the source and target branches. Therefore, the version in the Infix banner will not match the SHA of the commit you have checked out.

        "},{"location":"developers-guide/#contributing","title":"Contributing","text":"

        Infix is built from many components, when contributing you need to set up your own fork, create a local branch for your change, push to your fork, and then use GitHub to create a Pull Reqeuest.

        For this to work as painlessly as possible for everyone involved:

        1. Fork Infix to your own user or organization1
        2. Fork all the Infix submodules, e.g., kernelkit/buildroot to your own user or organization as well
        3. Clone your fork of Infix to your laptop/workstation
        4. Deactivate the Actions you don't want in your fork
        5. Please read the Contributing Guidelines as well!
        $ cd ~/Projects\n$ git clone https://github.com/YOUR_USER_NAME/infix.git\n...\n$ cd infix/\n$ git submodule update --init\n...\n

        Note

        When updating/synchronizing with upstream Infix changes you may have to synchronize your forks as well. GitHub have a Sync fork button in the GUI for your fork for this purpose. A cronjob on your server of choice can do this for you with the GitHub CLI tool.

        1. Organizations should make sure to lock the main (or master) branch of their clones to ensure members do not accidentally merge changes there. Keeping these branches in sync with upstream Infix is highly recommended as a baseline and reference. For integration of local changes another company-specific branch can be used instead.\u00a0\u21a9

        "},{"location":"dhcp/","title":"DHCP Server","text":""},{"location":"dhcp/#dhcp-server","title":"DHCP Server","text":"

        The DHCPv4 server provides automatic IP address assignment and network configuration for clients. It supports address pools, static host assignments, and customizable DHCP options. It also serves as a DNS proxy for local subnets and can even forward queries to upstream DNS servers1.

        Note

        When using the CLI, the system automatically enables essential options like DNS servers and default gateway based on the system's network configuration. These options can be disabled, changed or overridden, at any level: global, subnet, or per-host.

        "},{"location":"dhcp/#basic-configuration","title":"Basic Configuration","text":"

        The following example configures a DHCP server for subnet 192.168.2.0/24 with an address pool:

        admin@example:/> configure\nadmin@example:/config/> edit dhcp-server subnet 192.168.2.0/24\nadmin@example:/config/dhcp-server/\u2026/192.168.2.0/24/> set pool start-address 192.168.2.100 end-address 192.168.2.200\nadmin@example:/config/dhcp-server/\u2026/192.168.2.0/24/> leave\n

        When setting up the server from the CLI, the system automatically adds a few default DHCP options that will be sent to clients: both DNS server and default gateway will use the system address on the matching interface.

        admin@example:/> show running-config\n  \"infix-dhcp-server:dhcp-server\": {\n    \"subnet\": [\n      {\n        \"subnet\": \"192.168.2.0/24\",\n        \"option\": [\n          {\n            \"id\": \"dns-server\",\n            \"address\": \"auto\"\n          },\n          {\n            \"id\": \"router\",\n            \"address\": \"auto\"\n          }\n        ],\n        \"pool\": {\n          \"start-address\": \"192.168.2.100\",\n          \"end-address\": \"192.168.2.200\"\n        }\n      }\n    ]\n  }\n

        Important

        Remember to set up an interface in this subnet, avoid using addresses in the DHCP pool, or reserved for static hosts. In Class C networks the router usually has address .1. Depending on the use-case, you may also want to set up routing.

        "},{"location":"dhcp/#static-host-assignment","title":"Static Host Assignment","text":"

        To reserve specific IP addresses for clients based on their MAC address, hostname, or client ID:

        admin@example:/config/dhcp-server/\u2026/192.168.2.0/24/> edit host 192.168.2.10\nadmin@example:/config/dhcp-server/\u2026/192.168.2.10/> set match mac-address 00:11:22:33:44:55\nadmin@example:/config/dhcp-server/\u2026/192.168.2.10/> set hostname printer\nadmin@example:/config/dhcp-server/\u2026/192.168.2.10/> leave\n

        Match hosts using a client identifier instead of MAC address:

        admin@example:/config/dhcp-server/\u2026/192.168.1.0/24/> edit host 192.168.1.50\nadmin@example:/config/dhcp-server/\u2026/192.168.1.50/> edit match\nadmin@example:/config/dhcp-server/\u2026/match/> set client-id hex c0:ff:ee\nadmin@example:/config/dhcp-server/\u2026/match/> leave\nadmin@example:/config/dhcp-server/\u2026/192.168.1.50/> set lease-time infinite\nadmin@example:/config/dhcp-server/\u2026/192.168.1.50/> leave\n

        The hex prefix here ensures matching of client ID is done using the hexadecimal octets c0:ff:ee, three bytes. Without the prefix the ASCII string \"c0:ff:ee\", eight bytes, is used.

        Note

        The DHCP server is fully RFC conformant, in the case of option 61 this means that using the hex prefix will require the client to set the htype field of the option to 00. See RFC 2132 for details.

        "},{"location":"dhcp/#custom-dhcp-options","title":"Custom DHCP Options","text":"

        Configure additional DHCP options globally, per subnet, or per host:

        admin@example:/config/dhcp-server/> edit subnet 192.168.2.0/24\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/> edit option dns-server\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/option/dns-server/> set address 8.8.8.8\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/option/dns-server/> leave\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/> edit option ntp-server\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/option/ntp-server/> set address 192.168.2.1\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/option/ntp-server/> leave\n

        When configuring, e.g., dns-server, or router options with the value auto, the system uses the IP address from the interface matching the subnet. For example:

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv4                   192.168.1.1/24 (static)\neth1            ethernet   UP          02:00:00:00:00:01\n                ipv4                   192.168.2.1/24 (static)\n\nadmin@example:/config/dhcp-server/subnet/192.168.1.0/24/> edit option dns-server\nadmin@example:/config/dhcp-server/subnet/192.168.1.0/24/option/dns-server/> set address auto\nadmin@example:/config/dhcp-server/subnet/192.168.1.0/24/option/dns-server/> leave\n

        In this case, clients in subnet 192.168.1.0/24 will receive 192.168.1.1 as their DNS server address.

        "},{"location":"dhcp/#multiple-subnets","title":"Multiple Subnets","text":"

        Configure DHCP for multiple networks:

        admin@example:/> configure\nadmin@example:/config/> edit dhcp-server\nadmin@example:/config/dhcp-server/> edit subnet 192.168.1.0/24\nadmin@example:/config/dhcp-server/subnet/192.168.1.0/24/> set pool start-address 192.168.1.100 end-address 192.168.1.200\nadmin@example:/config/dhcp-server/subnet/192.168.1.0/24/> leave\nadmin@example:/config/dhcp-server/> edit subnet 192.168.2.0/24\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/> set pool start-address 192.168.2.100 end-address 192.168.2.200\nadmin@example:/config/dhcp-server/subnet/192.168.2.0/24/> leave\n
        "},{"location":"dhcp/#monitoring","title":"Monitoring","text":"

        View active leases and server statistics:

        admin@example:/> show dhcp-server\nIP ADDRESS       MAC                HOSTNAME            CLIENT ID             EXPIRES\n192.168.2.22     00:a0:85:00:02:05                      00:c0:ff:ee           3591s\n192.168.1.11     00:a0:85:00:04:06  foo                 01:00:a0:85:00:04:06  3591s\n\nadmin@example:/> show dhcp-server statistics\nDHCP offers sent                : 6\nDHCP ACK messages sent          : 5\nDHCP NAK messages sent          : 0\nDHCP decline messages received  : 0\nDHCP discover messages received : 6\nDHCP request messages received  : 5\nDHCP release messages received  : 6\nDHCP inform messages received   : 6\n
        1. This requires the system DNS resolver to be configured.\u00a0\u21a9

        "},{"location":"discovery/","title":"Device Discovery","text":""},{"location":"discovery/#device-discovery","title":"Device Discovery","text":"

        Infix advertises itself via the mDNS-SD and LLDP discovery protocols. mDNS-SD has good client support in Windows, macOS and on Linux systems. More on these protocols later.

        An even simpler method is available when directly attached to an Infix device:

        .----.       Ethernet      .--------.\n| PC +---------------------+ Device |\n'----' if1              e1 '--------'\n

        With IPv6 you can ping the all-hosts address (ff02::1), the device's link-local IPv6 address is then seen in the response. In the following example, the PC here uses tap0 as if1, Infix responds with address fe80::ff:fec0:ffed.

        linux-pc:# ping -6 -L -c 3 ff02::1%tap0\nPING ff02::1%tap0(ff02::1%tap0) 56 data bytes\n64 bytes from fe80::ff:fec0:ffed%tap0: icmp_seq=1 ttl=64 time=0.558 ms\n64 bytes from fe80::ff:fec0:ffed%tap0: icmp_seq=2 ttl=64 time=0.419 ms\n64 bytes from fe80::ff:fec0:ffed%tap0: icmp_seq=3 ttl=64 time=0.389 ms\n\n--- ff02::1%tap0 ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2043ms\nrtt min/avg/max/mdev = 0.389/0.455/0.558/0.073 ms\nlinux-pc:#\n

        Tip

        The -L option ignores local responses from the PC.

        This address can then be used to connect to the device, e.g., using SSH. Notice the syntax username@address%interface:

        linux-pc:# ssh admin@fe80::ff:fec0:ffed%tap0\nadmin@fe80::ff:fec0:ffed%tap0's password: admin\nadmin@infix-c0-ff-ee:~$\n
        "},{"location":"discovery/#windows","title":"Windows","text":"

        Infix advertises a _workstation._tcp DNS-SD record alongside its other mDNS services. Windows 10 (build 1709+) and Windows 11 recognise this record and show the device in File Explorer \u2192 Network automatically \u2014 no extra software required.

        From the command line, use the .local hostname directly:

        C:\\> ping infix-c0-ff-ee.local\nC:\\> ssh admin@infix-c0-ff-ee.local\n

        Note

        IPv6 multicast ping (ping ff02::1%if1) may not display responses on Windows even though the Infix device replies correctly. If you need to confirm connectivity, Wireshark will show the ICMPv6 echo replies arriving. Use mDNS (see mDNS-SD below) as the reliable alternative.

        "},{"location":"discovery/#lldp","title":"LLDP","text":"

        Infix supports LLDP (IEEE 802.1AB). For a device with factory default settings, the link-local IPv6 address can be read from the Management Address TLV using tcpdump or other sniffing tools1:

        linux-pc:# tcpdump -i tap0 -Qin -v ether proto 0x88cc\ntcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes\n15:51:52.061071 LLDP, length 193\n    Chassis ID TLV (1), length 7\n      Subtype MAC address (4): 02:00:00:c0:ff:ee (oui Unknown)\n    Port ID TLV (2), length 7\n      Subtype MAC address (3): 02:00:00:c0:ff:ee (oui Unknown)\n    Time to Live TLV (3), length 2: TTL 120s\n    System Name TLV (5), length 14: infix-c0-ff-ee\n    System Description TLV (6), length 91\n      Infix by KernelKit Linux 5.19.17 #1 SMP PREEMPT_DYNAMIC Wed Jun 7 08:47:23 CEST 2023 x86_64\n    System Capabilities TLV (7), length 4\n      System  Capabilities [Bridge, WLAN AP, Router, Station Only] (0x009c)\n      Enabled Capabilities [Station Only] (0x0080)\n    Management Address TLV (8), length 24\n      Management Address length 17, AFI IPv6 (2): fe80::ff:fec0:ffed\n      Interface Index Interface Numbering (2): 2\n    Port Description TLV (4), length 4: eth0\n    Organization specific TLV (127), length 9: OUI IEEE 802.3 Private (0x00120f)\n      Link aggregation Subtype (3)\n        aggregation status [supported], aggregation port ID 0\n    Organization specific TLV (127), length 9: OUI IEEE 802.3 Private (0x00120f)\n      MAC/PHY configuration/status Subtype (1)\n        autonegotiation [none] (0x00)\n        PMD autoneg capability [unknown] (0x8000)\n        MAU type Unknown (0x0000)\n    End TLV (0), length 0\n^C\n1 packet captured\nlinux-pc:#\n

        If the device has an IPv4 address assigned, it is shown in an additional Management Address TLV.

        Note

        The Management Addresses shown by LLDP are not necessarily associated with the port transmitting the LLDP message.

        In the example below, the IPv4 address (10.0.1.1) happens to be assigned to eth0, while the IPv6 address (2001:db8::1) is not.

        linux-pc:# sudo tcpdump -i tap0 -Qin -v ether proto 0x88cc\ntcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes\n15:46:07.908665 LLDP, length 207\n    Chassis ID TLV (1), length 7\n      Subtype MAC address (4): 02:00:00:c0:ff:ee (oui Unknown)\n    Port ID TLV (2), length 7\n      Subtype MAC address (3): 02:00:00:c0:ff:ee (oui Unknown)\n    Time to Live TLV (3), length 2: TTL 120s\n    System Name TLV (5), length 14: infix-c0-ff-ee\n    System Description TLV (6), length 91\n      Infix by KernelKit Linux 5.19.17 #1 SMP PREEMPT_DYNAMIC Wed Jun 7 08:47:23 CEST 2023 x86_64\n    System Capabilities TLV (7), length 4\n      System  Capabilities [Bridge, WLAN AP, Router, Station Only] (0x009c)\n      Enabled Capabilities [Station Only] (0x0080)\n    Management Address TLV (8), length 12\n      Management Address length 5, AFI IPv4 (1): 10.0.1.1\n      Interface Index Interface Numbering (2): 2\n    Management Address TLV (8), length 24\n      Management Address length 17, AFI IPv6 (2): 2001:db8::1\n      Interface Index Interface Numbering (2): 3\n    Port Description TLV (4), length 4: eth0\n    Organization specific TLV (127), length 9: OUI IEEE 802.3 Private (0x00120f)\n      Link aggregation Subtype (3)\n        aggregation status [supported], aggregation port ID 0\n    Organization specific TLV (127), length 9: OUI IEEE 802.3 Private (0x00120f)\n      MAC/PHY configuration/status Subtype (1)\n        autonegotiation [none] (0x00)\n        PMD autoneg capability [unknown] (0x8000)\n        MAU type Unknown (0x0000)\n    End TLV (0), length 0\n^C\n1 packet captured\n2 packets received by filter\n0 packets dropped by kernel\nlinux-pc:#\n

        The following capabilities are available via NETCONF/RESTCONF or the Infix CLI.

        "},{"location":"discovery/#lldp-enabledisable","title":"LLDP Enable/Disable","text":"

        The LLDP service can be disabled using the following commands.

        admin@infix-c0-ff-ee:/> configure\nadmin@infix-c0-ff-ee:/config/> no lldp\nadmin@infix-c0-ff-ee:/config/> leave\nadmin@infix-c0-ff-ee:/>\n

        To reenable it from the CLI config mode:

        admin@test-00-01-00:/config/> set lldp enabled\nadmin@test-00-01-00:/config/> leave\n
        "},{"location":"discovery/#lldp-message-transmission-interval","title":"LLDP Message Transmission Interval","text":"

        By default, LLDP uses a message-tx-interval of 30 seconds, as defined by the IEEE standard. Infix allows this value to be customized. To change it using the CLI:

        admin@test-00-01-00:/config/> set lldp message-tx-interval 1\nadmin@test-00-01-00:/config/> leave\n
        "},{"location":"discovery/#lldp-administrative-status-per-interface","title":"LLDP Administrative Status per Interface","text":"

        Infix supports configuring the LLDP administrative status on a per-port basis. The default mode is tx-and-rx, but the following options are also supported:

        • rx-only \u2013 Receive LLDP packets only
        • tx-only \u2013 Transmit LLDP packets only
        • disabled \u2013 Disable LLDP on the interface

        Example configuration:

        admin@test-00-01-00:/config/> set lldp port e8 dest-mac-address 01:80:C2:00:00:0E admin-status disabled\nadmin@test-00-01-00:/config/> set lldp port e5 dest-mac-address 01:80:C2:00:00:0E admin-status rx-only\nadmin@test-00-01-00:/config/> set lldp port e6 dest-mac-address 01:80:C2:00:00:0E admin-status tx-only\nadmin@test-00-01-00:/config/> leave\n

        Note

        The destination MAC address must be the standard LLDP multicast address: 01:80:C2:00:00:0E.

        "},{"location":"discovery/#displaying-lldp-neighbor-information","title":"Displaying LLDP Neighbor Information","text":"

        In CLI mode, Infix also provides a convenient show lldp command to list LLDP neighbors detected on each interface:

        admin@test-00-01-00:/> show lldp\nINTERFACE       REM-IDX   TIME        CHASSIS-ID          PORT-ID                           \ne5              1         902         00:a0:85:00:04:01   00:a0:85:00:04:07\ne6              3         897         00:a0:85:00:03:01   00:a0:85:00:03:07\ne8              2         901         00:a0:85:00:02:01   00:a0:85:00:02:05\n
        "},{"location":"discovery/#mdns-sd","title":"mDNS-SD","text":"

        DNS-SD/mDNS-SD can be used to discover Infix devices and services. By default, Infix use the .local domain for advertising services. Some networks use .lan instead, so this configurable:

        admin@infix-c0-ff-ee:/> configure\nadmin@infix-c0-ff-ee:/config/> edit mdns\nadmin@infix-c0-ff-ee:/config/mdns/> set domain lan\n

        Other available settings include limiting the interfaces mDNS responder acts on, allow:

        admin@infix-c0-ff-ee:/config/> set interfaces allow e1\n

        or deny. The allow and deny settings are complementary, deny always wins.

        admin@infix-c0-ff-ee:/config/> set interfaces deny wan\n

        Use leave to activate the new settings, then inspect the operational state and any detected neighbors with show mdns from admin-exec context:

        admin@gateway:/> show mdns\nEnabled         : yes\nDomain          : local\nDeny            : wan\n\nHOSTNAME           ADDRESS        LAST SEEN  SERVICES                                       \nLiving-Room.local  192.168.0.139  17:28:43   trel(59813) sleep-proxy(61936) raop(7000) srpl-tls(853)\nfirefly-4.local    192.168.0.122  17:28:37   workstation(9)\ngimli.local        192.168.0.180  17:28:37   smb(445)\ninfix.local        192.168.0.1    17:28:38   https(443) workstation(9) ssh(22) https(443)\n

        In Linux, tools such as avahi-browse or mdns-scan2 can be used to search for devices advertising their services via mDNS.

        linux-pc:# avahi-browse -ar\n+   tap0 IPv6 infix-c0-ff-ee                                SFTP File Transfer   local\n+   tap0 IPv4 infix-c0-ff-ee                                SFTP File Transfer   local\n+   tap0 IPv6 infix-c0-ff-ee                                SSH Remote Terminal  local\n+   tap0 IPv4 infix-c0-ff-ee                                SSH Remote Terminal  local\n=   tap0 IPv4 infix-c0-ff-ee                                SFTP File Transfer   local\n   hostname = [infix-c0-ff-ee.local]\n   address = [10.0.1.1]\n   port = [22]\n   txt = []\n=   tap0 IPv4 infix-c0-ff-ee                                SSH Remote Terminal  local\n   hostname = [infix-c0-ff-ee.local]\n   address = [10.0.1.1]\n   port = [22]\n   txt = []\n=   tap0 IPv6 infix-c0-ff-ee                                SFTP File Transfer   local\n   hostname = [infix-c0-ff-ee.local]\n   address = [fe80::ff:fec0:ffed]\n   port = [22]\n   txt = []\n=   tap0 IPv6 infix-c0-ff-ee                                SSH Remote Terminal  local\n   hostname = [infix-c0-ff-ee.local]\n   address = [fe80::ff:fec0:ffed]\n   port = [22]\n   txt = []\n^C\nlinux-pc:#\n

        Tip

        The -t option is also very useful, it stops browsing automatically when a \"more or less complete list\" has been printed. However, some devices on the LAN may be in deep sleep so run the command again if you cannot find the device you are looking for.

        Additionally, avahi-resolve-host-name can be used to verify domain name mappings for IP addresses. By default, it translates from IPv4 addresses. This function allows users to confirm that addresses are mapped correctly.

        linux-pc:# avahi-resolve-host-name infix-c0-ff-ee.local\ninfix-c0-ff-ee.local    10.0.1.1\nlinux-pc:#\n

        Thanks to mDNS we can use the advertised name instead of the IP address for operations like ping and ssh as shown below:

        linux-pc:# ping infix-c0-ff-ee.local -c 3\nPING infix-c0-ff-ee.local (10.0.1.1) 56(84) bytes of data.\n64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.852 ms\n64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=1.12 ms\n64 bytes from 10.0.1.1: icmp_seq=3 ttl=64 time=1.35 ms\n\n--- infix-c0-ff-ee.local ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2003ms\nrtt min/avg/max/mdev = 0.852/1.105/1.348/0.202 ms\n\nlinux-pc:# ssh admin@infix-c0-ff-ee.local\n(admin@infix-c0-ff-ee.local) Password:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-\n\nRun the command 'cli' for interactive OAM\n\nlinux-pc:#\n

        To disable mDNS/mDNS-SD, type the commands:

        admin@infix-c0-ff-ee:/> configure\nadmin@infix-c0-ff-ee:/config/> no mdns\nadmin@infix-c0-ff-ee:/config/> leave\n
        "},{"location":"discovery/#human-friendly-hostname-alias","title":"Human-Friendly Hostname Alias","text":"

        Each Infix device advertises itself as infix.local, in addition to its full hostname (e.g., infix-c0-ff-ee.local or foo.local). This alias works seamlessly on a network with a single Infix device, and makes it easy to connect when the exact hostname is not known in advance. The examples below show how the alias can be used for actions such as pinging or establishing an SSH connection:

        linux-pc:# ping infix.local -c 3\nPING infix.local (10.0.1.1) 56(84) bytes of data.\n64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.751 ms\n64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=2.28 ms\n64 bytes from 10.0.1.1: icmp_seq=3 ttl=64 time=1.42 ms\n\n--- infix.local ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2003ms\nrtt min/avg/max/mdev = 0.751/1.482/2.281/0.626 ms\n\nlinux-pc:# ssh admin@infix.local\n(admin@infix.local) Password:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-\n\nRun the command 'cli' for interactive OAM\n\nadmin@infix-c0-ff-ee:~$\n

        When multiple Infix devices are present on the LAN the alias will not uniquely identify a device; infix.local will refer to any of the Infix devices, likely the one that first appeared.

        Note

        When multiple Infix devices are present on the LAN, use the full name, e.g., infix-c0-ff-ee.local or foo.local rather than the alias infix.local to deterministically connect to the device.

        "},{"location":"discovery/#browse-network-using-networklocal","title":"Browse Network Using network.local","text":"

        Another mDNS alias that all Infix devices advertise is network.local. This is a web service which basically runs avahi-browse and displays a table of other Infix devices and their services.

        With multiple Infix devices on the LAN, one will take the role of your portal to access all others, if it goes down another takes its place.

        To disable the netbrowse service, and the network.local alias, the following commands can be used:

        admin@infix-c0-ff-ee:/> configure\nadmin@infix-c0-ff-ee:/config/> edit web\nadmin@infix-c0-ff-ee:/config/web/> no netbrowse\nadmin@infix-c0-ff-ee:/config/web/> leave\n
        1. E.g., lldpd which includes the lldpcli too, handy to sniff and display LLDP packets.\u00a0\u21a9

        2. mdns-scan: a tool for scanning for mDNS/DNS-SD services on the local network.\u00a0\u21a9

        "},{"location":"eth-counters/","title":"RMON Counters","text":""},{"location":"eth-counters/#rmon-counters","title":"RMON Counters","text":"

        This document show the mapping between YANG and Linux / Ethtool counters.

        YANG Linux / Ethtool in-total-octets FramesReceivedOK, FrameCheckSequenceErrors, FramesLostDueToIntMACRcvError, AlignmentErrors, etherStatsOversizePkts, etherStatsJabbers in-frames FramesReceivedOK in-multicast-frames MulticastFramesReceivedOK in-broadcast-frames BroadcastFramesReceivedOK in-error-fcs-frames FrameCheckSequenceErrors in-error-undersize-frames undersize_pkts in-error-oversize-frames etherStatsJabbers, etherStatsOversizePkts in-error-mac-internal-frames FramesLostDueToIntMACRcvError out-frames FramesTransmittedOK out-multicast-frames MulticastFramesXmittedOK out-broadcast-frames BroadcastFramesXmittedOK infix-eth:out-good-octets OctetsTransmittedOK infix-eth:in-good-octets OctetsReceivedOK"},{"location":"ethernet/","title":"Ethernet Interfaces","text":""},{"location":"ethernet/#ethernet-interfaces","title":"Ethernet Interfaces","text":"

        This document covers VLAN interfaces, physical Ethernet interfaces, and virtual Ethernet (VETH) pairs.

        "},{"location":"ethernet/#vlan-interfaces","title":"VLAN Interfaces","text":"

        Creating a VLAN can be done in many ways. This section assumes VLAN interfaces created atop another Linux interface. E.g., the VLAN interfaces created on top of the Ethernet interface or bridge in the picture below.

        A VLAN interface is basically a filtering abstraction. When you run tcpdump on a VLAN interface you will only see the frames matching the VLAN ID of the interface, compared to all the VLAN IDs if you run tcpdump on the lower-layer interface.

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0.20\nadmin@example:/config/interface/eth0.20/> show\ntype vlan;\nvlan {\n  tag-type c-vlan;\n  id 20;\n  lower-layer-if eth0;\n}\nadmin@example:/config/interface/eth0.20/> leave\n

        The example below assumes bridge br0 is already created, see VLAN Filtering Bridge.

        admin@example:/> configure\nadmin@example:/config/> edit interface vlan10\nadmin@example:/config/interface/vlan10/> set vlan id 10\nadmin@example:/config/interface/vlan10/> set vlan lower-layer-if br0\nadmin@example:/config/interface/vlan10/> leave\n

        As conventions, a VLAN interface for VID 20 on top of an Ethernet interface eth0 is named eth0.20, and a VLAN interface for VID 10 on top of a bridge interface br0 is named vlan10.

        Note

        If you name your VLAN interface foo0.N or vlanN, where N is a number, the CLI infers the interface type automatically.

        "},{"location":"ethernet/#physical-ethernet-interfaces","title":"Physical Ethernet Interfaces","text":""},{"location":"ethernet/#ethernet-settings-and-status","title":"Ethernet Settings and Status","text":"

        Physical Ethernet interfaces provide low-level settings for speed/duplex as well as packet status and statistics.

        By default, Ethernet interfaces defaults to auto-negotiating speed/duplex modes, advertising all speed and duplex modes available. In the example below, the switch would by default auto-negotiate speed 1 Gbit/s on port eth1 and 100 Mbit/s on port eth4, as those are the highest speeds supported by H1 and H2 respectively.

        The speed and duplex status for the links can be listed as shown below, assuming the link operational status is 'up'.

        admin@example:/> show interface eth1\nname                : eth1\nindex               : 2\nmtu                 : 1500\noperational status  : up\nauto-negotiation    : on\nduplex              : full\nspeed               : 1000\nphysical address    : 00:53:00:06:11:01\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 75581\nout-octets          : 43130\n...\nadmin@example:/> show interface eth4\nname                : eth4\nindex               : 5\nmtu                 : 1500\noperational status  : up\nauto-negotiation    : on\nduplex              : full\nspeed               : 100\nphysical address    : 00:53:00:06:11:04\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 75439\nout-octets          : 550704\n...\nadmin@example:/>\n
        "},{"location":"ethernet/#configuring-fixed-speed-and-duplex","title":"Configuring fixed speed and duplex","text":"

        Auto-negotiation of speed/duplex mode is desired in almost all use-cases, but it is possible to disable auto-negotiation and specify a fixed speed and duplex mode.

        Important

        When setting a fixed speed and duplex mode, ensure both sides of the link have matching configuration. If speed does not match, the link will not come up. If duplex mode does not match, the result is reported collisions and/or bad throughput.

        The example below configures port eth3 to fixed speed 100 Mbit/s half-duplex mode.

        admin@example:/> configure\nadmin@example:/config/> edit interface eth3 ethernet\nadmin@example:/config/interface/eth3/ethernet/> set speed 0.1\nadmin@example:/config/interface/eth3/ethernet/> set duplex half\nadmin@example:/config/interface/eth3/ethernet/> set auto-negotiation enable false\nadmin@example:/config/interface/eth3/ethernet/> show\nauto-negotiation {\n  enable false;\n}\nduplex half;\nspeed 0.1;\nadmin@example:/config/interface/eth3/ethernet/> leave\nadmin@example:/>\n

        Speed metric is in Gbit/s. Auto-negotiation needs to be disabled in order for fixed speed/duplex to apply. Only speeds 0.1(100 Mbit/s) and 0.01 (10 Mbit/s) can be specified. 1 Gbit/s and higher speeds require auto-negotiation to be enabled.

        "},{"location":"ethernet/#ethernet-statistics","title":"Ethernet statistics","text":"

        Ethernet packet statistics1 can be listed as shown below.

        admin@example:/> show interface eth1\nname                : eth1\nindex               : 2\nmtu                 : 1500\noperational status  : up\nauto-negotiation    : on\nduplex              : full\nspeed               : 1000\nphysical address    : 00:53:00:06:11:0a\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 75581\nout-octets          : 43130\n\neth-in-frames                : 434\neth-in-multicast-frames      : 296\neth-in-broadcast-frames      : 138\neth-in-error-fcs-frames      : 0\neth-in-error-oversize-frames : 0\neth-out-frames               : 310\neth-out-multicast-frames     : 310\neth-out-broadcast-frames     : 0\neth-out-good-octets          : 76821\neth-in-good-octets           : 60598\nadmin@example:/>\n
        "},{"location":"ethernet/#veth-pairs","title":"VETH Pairs","text":"

        A Virtual Ethernet (VETH) pair is basically a virtual Ethernet cable. A cable can be \"plugged in\" to a bridge and the other end can be given to a container, or plugged into another bridge.

        The latter example is useful if you have multiple bridges in the system with different properties (VLAN filtering, IEEE group forwarding, etc.), but still want some way of communicating between these domains.

        admin@example:/> configure\nadmin@example:/config/> edit interface veth0a\nadmin@example:/config/interface/veth0a/> set veth peer veth0b\nadmin@example:/config/interface/veth0a/> end\nadmin@example:/config/> diff\ninterfaces {\n+  interface veth0a {\n+    type veth;\n+    veth {\n+      peer veth0b;\n+    }\n+  }\n+  interface veth0b {\n+    type veth;\n+    veth {\n+      peer veth0a;\n+    }\n+  }\n}\nadmin@example:/config/>\n

        Tip

        This is another example of the automatic inference of the interface type from the name. Any name can be used, but then you have to set the interface type to veth manually.

        1. Ethernet counters are described in ieee802-ethernet-interface.yang and infix-ethernet-interface.yang. There is a dedicated document on Ethernet Counters that provide additional details on the statistics support.\u00a0\u21a9

        "},{"location":"firewall/","title":"Firewall Configuration","text":""},{"location":"firewall/#firewall-documentation","title":"Firewall Documentation","text":""},{"location":"firewall/#introduction","title":"Introduction","text":"

        A zone-based firewall aims to simplify network security. Instead of complex per-interface rules, you work with zones and policies. Briefly, zones define a level of trust for all interfaces or networks assigned to it, and policies regulate the traffic flow between zones.

        Figure 1. Zones group interfaces, policies control traffic flows.

        Three distinct traffic flows exist: traffic destined for the host itself, traffic between interfaces within the same zone (intra-zone), and traffic between different zones (inter-zones).

        The zone approach is not just more intuitive and maintainable, it allows you to think more in terms of trust relationships:

        • \"internal networks can access the Internet\"
        • \"Internet cannot access my internal network, except this port forward\"

        When you add new interfaces to existing zones, they automatically inherit the established security policies. The amount of actual rules that matter to you is kept to a minimum.

        Impatient and ready to get going?

        Fast forward to the Examples: End Device, Home/Office Router, Enterprise Gateway

        "},{"location":"firewall/#visual-overview","title":"Visual Overview","text":"

        Use the zone matrix to quickly audit your firewall configuration and identify potential security gaps. It provides an overview and shows the relationship between zones and the policies that connect them. Each cell in the matrix represents a potential traffic flow, with rows indicating the ingress zone and columns the egress zone.

        Figure 2. Example output from show firewall command.

        The matrix uses visual indicators to show the status of each zone and policy:

        \u2713 Green checkmark \u2014 traffic is explicitly allowed by policy \u2717 Red cross \u2014 traffic is blocked (default behavior) \u26a0 Yellow warning \u2014 watch out! Some traffic allowed, such as port forwarding rules

        This visualization helps you quickly understand your firewall's behavior and identify any unintended gaps or overly permissive rules in your configuration.

        Use the ? key in the CLI

        This admin-exec (top-level) CLI command has sub-commands that you can use to drill down on the operational data. Tap the ? key once to see an overview after show firewall, or just use the classic UNIX Tab key to complete everything until you've found your command.

        "},{"location":"firewall/#zones","title":"Zones","text":"

        Zones are logical groupings of network interfaces or IP networks that share the same trust level. Each zone has a default action that determines what happens to traffic destined for the host itself (INPUT chain). A LAN zone may have this set to accept, while a DMZ zone may be set to reject by default and only allow a subset of available services (e.g., DHCP, DNS, SSH) that devices in the DMZ can use to reach the host.

        Important

        Interfaces and networks are mutually exclusive in zones \u2014 attempting to configure both will result in a validation error. When setting up port forwarding from one zone to another, the destination network must be declared in a zone.

        "},{"location":"firewall/#default-zone","title":"Default Zone","text":"

        You must specify a default zone. This serves as a safety net: any interface not explicitly assigned to a zone automatically belongs to the default zone, ensuring network interfaces remain protected by the firewall. This automatic assignment is particularly useful when configuring new interfaces (e.g., VLANs, bridges, or hotplugged devices).

        Choose your default zone carefully \u2014 it should be the most restrictive zone appropriate for unmanaged interfaces. For routers, this is typically the wan zone, but you can of course also set up a dedicated block zone. In the CLI, when first enabling the firewall, a public zone is created. See more about this in the example below.

        Remember IP forwarding on interfaces!

        Firewall policies only control whether traffic is allowed on input, to be forwarded, or blocked (default). For the actual routing between interfaces to work, you must also enable IP forwarding on the relevant interfaces.

        "},{"location":"firewall/#intra-zone-traffic","title":"Intra-Zone Traffic","text":"

        Traffic between different interfaces, or networks, in the same zone is not forwarded by default. In most cases, if devices on separate interfaces need to communicate, they should be in different zones with a policy between them. Alternatively, if you want true LAN-like behavior, bridge the interfaces at layer-2 instead of routing between them at layer-3.

        Intra-zone forwarding \u2014 routing traffic within a single zone \u2014 is rarely needed. But if you do require it, create a policy where both ingress and egress are set to the same zone, e.g., lan \u2192 lan.

        "},{"location":"firewall/#port-forwarding","title":"Port Forwarding","text":"

        Port forwarding, also known as destination NAT (DNAT), redirects inbound traffic to another address and/or port. This allows external access to internal services. See Enterprise Gateway for an example.

        Each zone can have port forwarding rules that apply to traffic arriving at that zone's interfaces or matching its networks. The forwarded traffic must then be allowed by appropriate policies to reach the destination zone.

        The Zone Matrix shows a \u26a0 conditional warning flag, coloring the cell yellow, when exceptions like port forwarding are active.

        "},{"location":"firewall/#policies","title":"Policies","text":"

        In short, policy rules control traffic between zones. By default all inter-zone (and intra-zone) traffic is rejected. Meaning you must explicitly allow the traffic flows you intend.

        Figure 3. Example of common traffic flows (policies) between zones.

        IP masquerading (SNAT) is a policy setting that applies to traffic egressing a target zone. (Essential for Internet access from private networks.)

        A policy, like zones, have a default action. If it is not set to accept you must specify which services on the host any zone interface and network are allowed access to.

        Note

        Policy rules apply in-order, the first matching rule with action drop will terminate the traffic flow. Use action continue to allow processing to go to the next rule, until the last (implicit) default-drop rule at the end.

        The CLI currently does not support reordering rules. As a workaround, save your running-config to startup-config, then exit to the shell and edit the file with edit /cfg/startup-config.cfg.

        See the examples below for how to set up a policy. The built-in help system can also be useful:

        admin@example:/config/firewall/policy/lan-to-dmz/> help masquerade\nNAME\n        masquerade \nDESCRIPTION\n        Enable masquerading (SNAT) for traffic matching this policy.\n        Matching traffic will have their source IP address changed on egress,\n        using the IP address of the interface the traffic egresses.\nadmin@example:/config/firewall/policy/lan-to-dmz/>\n
        "},{"location":"firewall/#symbolic-names","title":"Symbolic Names","text":"

        The symbolic names HOST and ANY are available for use in both ingress and egress zones. In fact, the CLI uses inference when first enabling the firewall to inject a default policy to allow automatic IPv6 address assignment.

        • HOST \u2192 ANY: Control device to any outbound connection (default: allowed)
        • ANY \u2192 HOST: Control what can reach device services (uncommon, usually per-zone)
        • Zone \u2192 HOST: Allow specific zone to access device services
        "},{"location":"firewall/#custom-filters","title":"Custom Filters","text":"

        For more advanced firewall scenarios custom filters can be used. Currently only various ICMP type traffic control is supported. Enough to support the built-in allow-host-ipv6 policy and allow certain ICMP types on input or forward.

        You can inspect this built-in (locked) policy from admin-exec level with the command: show firewall policy allow-host-ipv6.

        "},{"location":"firewall/#default-behavior","title":"Default Behavior","text":"

        ICMP messages (particularly echo-request and echo-reply) can be used to reveal network information for malicious purposes. Therefore, the firewall blocks ICMP requests by default. This applies unless the zone's default action is accept.

        To enable echo-request (IPv4) for any interface, or per zone when action is set to drop or reject, set up a dedicated policy with ingress ANY and egress HOST that use a custom filter to accept that ICMP type. Make this policy the first rule in your list of policies, and remember to use continue for non-matching traffic.

        Another built-in behavior is automatically allowing \"established,related\" return traffic flows. This uses connection tracking - the firewall remembers outbound connections you initiate and automatically allows the corresponding inbound response traffic. This means you only need to configure rules for new connections; the firewall handles return traffic automatically without additional rules.

        "},{"location":"firewall/#services","title":"Services","text":"

        Several pre-defined services exist, that cover most use-cases, but you can also define custom services for applications not covered by the built-in ones.

        The firewall includes over 100 pre-defined services, such as:

        • ssh \u2014 Secure Shell (port 22/tcp)
        • http \u2014 Web traffic (port 80/tcp)
        • https \u2014 Secure web traffic (port 443/tcp)
        • dns \u2014 Domain Name System (port 53/tcp and 53/udp)
        • dhcp \u2014 DHCP server (port 67/udp)
        • dhcpv6-client \u2014 DHCPv6 client traffic
        • netconf \u2014 Network Configuration Protocol (port 830/tcp)
        • restconf \u2014 REST-based Network Configuration Protocol (port 443/tcp)

        Use the ? key in the CLI

        See the YANG model for the full list, or tap the ? key when setting up an allowed host service in a zone set service

        "},{"location":"firewall/#examples","title":"Examples","text":""},{"location":"firewall/#end-device-protection","title":"End Device Protection","text":"

        This is the default firewall setup, useful for end devices on untrusted networks. It provides maximum protection while allowing essential connectivity.

        admin@example:/> configure\nadmin@example:/config/> edit firewall\nadmin@example:/config/firewall/> show\ndefault public;\nzone public {\n  action reject;\n  description \"Public, unknown network. Only SSH and DHCPv6 client allowed.\";\n  service dhcpv6-client;\n  service ssh;\n}\nadmin@example:/config/firewall/> leave\n

        The reject action differs from drop in that it responds to ICMP messages, although maybe not how you may think. Pinging the device we may1 see this:

        $ ping 192.168.122.161\nFrom 192.168.122.161 icmp_seq=1 Packet filtered\n

        If we run tcpdump it shows us why:

        $ tcpdump -lni eth0\n20:10:40.245707 IP 192.168.122.1 > 192.168.122.161: ICMP echo request, id 56838, seq 1, length 64\n20:10:40.245961 IP 192.168.122.161 > 192.168.122.1: ICMP host 192.168.122.161 unreachable - admin prohibited filter, length 92\n

        The key here is that, yes the device responds, but not with ICMP reply but ICMP unreachable, and a little helpful message.

        The default zone is public, so all interfaces that are not explicitly assigned to another zone will be operationally placed in this zone as a safeguard. Inspect this from admin-exec context with show firewall, as can be seen in the below screenshot, the only interface e1 has been assigned automatically to the public zone. This information is also saved to the system log.

        Figure 4. Zone matrix and firewall overview from show firewall.

        Important

        These defaults are inferred for interactive CLI users. Enabling the firewall using NETCONF/RESTCONF will not yield the same results.

        "},{"location":"firewall/#homeoffice-router","title":"Home/Office Router","text":"

        For typical routers that need to protect internal devices while providing internet access. The LAN zone trusts internal devices, while the WAN zone blocks external threats.

        admin@example:/> configure\nadmin@example:/config/> edit firewall\nadmin@example:/config/firewall/> set default wan\nadmin@example:/config/firewall/> edit zone lan\nadmin@example:/config/firewall/\u2026/lan/> set description \"Internal LAN network - trusted\"\nadmin@example:/config/firewall/\u2026/lan/> set action accept\nadmin@example:/config/firewall/\u2026/lan/> set interface eth1\nadmin@example:/config/firewall/\u2026/lan/> set service ssh\nadmin@example:/config/firewall/\u2026/lan/> set service dhcp\nadmin@example:/config/firewall/\u2026/lan/> set service dns\nadmin@example:/config/firewall/\u2026/lan/> end\nadmin@example:/config/firewall/> edit zone wan\nadmin@example:/config/firewall/\u2026/wan/> set description \"External WAN interface - untrusted\"\nadmin@example:/config/firewall/\u2026/wan/> set action drop\nadmin@example:/config/firewall/\u2026/wan/> set interface eth0\nadmin@example:/config/firewall/\u2026/wan/> end\nadmin@example:/config/firewall/> edit policy loc-to-wan\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set description \"Allow LAN traffic to WAN with SNAT\"\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set ingress lan\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set egress wan\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set action accept\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set masquerade\nadmin@example:/config/firewall/\u2026/loc-to-wan/> leave\n
        "},{"location":"firewall/#enterprise-gateway","title":"Enterprise Gateway","text":"

        For businesses that need to host public services while protecting internal resources. We can build upon the Home/Office Router example above and add a DMZ zone with additional policies for controlled access.

        admin@example:/> configure\nadmin@example:/config/> edit firewall zone dmz\nadmin@example:/config/firewall/\u2026/dmz/> set description \"Semi-trusted public services\"\nadmin@example:/config/firewall/\u2026/dmz/> set action drop\nadmin@example:/config/firewall/\u2026/dmz/> set interface eth1\nadmin@example:/config/firewall/\u2026/dmz/> set service ssh\nadmin@example:/config/firewall/\u2026/dmz/> end\nadmin@example:/config/firewall/> edit policy loc-to-wan\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set description \"Allow local networks (LAN+DMZ) to WAN with SNAT\"\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set ingress dmz\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set egress wan\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set action accept\nadmin@example:/config/firewall/\u2026/loc-to-wan/> set masquerade\nadmin@example:/config/firewall/\u2026/loc-to-wan/> end\nadmin@example:/config/firewall/> edit policy lan-to-dmz\nadmin@example:/config/firewall/\u2026/lan-to-dmz/> set description \"Allow LAN to manage DMZ services\"\nadmin@example:/config/firewall/\u2026/lan-to-dmz/> set ingress lan\nadmin@example:/config/firewall/\u2026/lan-to-dmz/> set egress dmz\nadmin@example:/config/firewall/\u2026/lan-to-dmz/> set action accept\nadmin@example:/config/firewall/\u2026/lan-to-dmz/> end\nadmin@example:/config/firewall/> edit zone wan port-forward 8080 tcp\nadmin@example:/config/firewall/\u2026/tcp/> set to addr 192.168.2.10\nadmin@example:/config/firewall/\u2026/tcp/> set to port 80\nadmin@example:/config/firewall/\u2026/tcp/> leave\n

        This adds a DMZ zone for public services, updates the internet access policy to include DMZ traffic, allows LAN management of DMZ services, and forwards external web traffic to the DMZ server.

        "},{"location":"firewall/#logging-and-monitoring","title":"Logging and Monitoring","text":"

        Different log levels are available to monitor and debug firewall behavior. Configure logging using the CLI:

        admin@example:/> configure\nadmin@example:/config/> edit firewall\nadmin@example:/config/firewall/> set logging all\nadmin@example:/config/firewall/> leave\n

        Firewall logs help you understand traffic patterns and security events. The CLI admin-exec command show firewall shows the last 10 log messages in the overview:

        Figure 5. Summary of recent logs at the end of show firewall.

        Use the command show log firewall.log to display the full logfile (remember, the syslog daemon rotates and zips too big log files). You can also use the follow firewall.log command to continuously monitor firewall log messages.

        "},{"location":"firewall/#netfilter-integration","title":"Netfilter Integration","text":"

        The Infix firewall operates through Linux netfilter hooks. Understanding how the zones and policy concepts map to these hooks will hopefully help you understand the firewall's behavior and ease troubleshooting.

        "},{"location":"firewall/#packet-flow","title":"Packet Flow","text":"

        Figure 6. Linux netfilter hooks in layer-3 traffic flow.

        Netfilter Hook Function Description prerouting ZONE Classification of incoming traffic, match interfaces/networks with zones prerouting ZONE Port forwarding (DNAT) from zone configuration input ZONE Host input filtering (services) input ZONE Default action for non-matching services (action) forward POLICY Allow traffic between zones (inter-zone rules) postrouting POLICY Masquerade (SNAT) when traffic egresses a zone"},{"location":"firewall/#prerouting-hook","title":"PREROUTING Hook","text":"
        • Zone Classification: Traffic is tagged based on ingress interface or source network
        • Port Forwarding: DNAT from zone configuration occurs before routing decisions
        • Connection Tracking: Early state establishment for stateful filtering
        "},{"location":"firewall/#input-hook","title":"INPUT Hook","text":"
        • ANY-to-HOST Policies: Enforces policy rules for traffic destined to the host itself
        • Zone Services: Allows configured services (SSH, HTTP, etc.) based on zone trust level
        • Zone Action: Applies a default action (accept/reject/drop) for unmatched traffic
        "},{"location":"firewall/#forward-hook","title":"FORWARD Hook","text":"
        • Policy Enforcement: Primary location for inter-zone traffic filtering
        • Custom Filters: ICMP and other protocol-specific rules within policies
        • Service Matching: Allows or denies services based on policy configuration
        "},{"location":"firewall/#postrouting-hook","title":"POSTROUTING Hook","text":"
        • Masquerading: Source NAT for outbound traffic when policies enable masquerading
        "},{"location":"firewall/#emergency-lockdown","title":"Emergency Lockdown","text":"

        For security emergencies (active breaches, suspicious activity), the firewall supports an immediate lockdown mode that blocks ALL traffic.

        Danger

        This will immediately terminate all network connections, including SSH. Physical console access is required to restore normal operation. It is also possible to restore normal operation by power-cycling the device.

        To activate emergency lockdown:

        ~$ curl -kX POST -u admin:admin -H \"Content-Type: application/yang-data+json\" \\\n        -d '{\"infix-firewall:input\": {\"operation\": \"now\"}}'                   \\\n        https://example.local/restconf/operations/infix-firewall:firewall/lockdown-mode\n

        To cancel lockdown mode (requires console access):

        ~$ curl -kX POST -u admin:admin -H \"Content-Type: application/yang-data+json\" \\\n        -d '{\"infix-firewall:input\": {\"operation\": \"cancel\"}}'                \\\n        https://example.local/restconf/operations/infix-firewall:firewall/lockdown-mode\n

        You can check the current lockdown state:

        ~$ curl -kX GET -u admin:admin -H 'Accept: application/yang-data+json' \\\n        https://example.local/restconf/data/infix-firewall:firewall/lockdown\n{\n  \"infix-firewall:firewall\": {\n    \"lockdown\": false\n  }\n}\n
        1. The output from ping clients differ A LOT. Some do not consider ICMP unreachable to be a proper response and it will appear as if the device is not responding at all. Use tcpdump or wireshark to get to the bottom of network mysteries.\u00a0\u21a9

        "},{"location":"hardware/","title":"Hardware Info & Status","text":""},{"location":"hardware/#hardware-information-and-status","title":"Hardware Information and Status","text":"

        The hardware infomation and status is handled by the YANG model IETF hardware, with deviations and augmentations in infix-hardware.

        "},{"location":"hardware/#gpsgnss-receivers","title":"GPS/GNSS Receivers","text":"

        Infix supports GPS/GNSS receivers for hardware status monitoring and NTP time synchronization. USB GPS receivers using the USB ACM interface are supported (e.g., u-blox). When connected, devices are automatically discovered and named gps0, gps1, etc.

        "},{"location":"hardware/#current-status","title":"Current status","text":"
        admin@example:/> show hardware\nHARDWARE COMPONENTS                                           \n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nGPS/GNSS Receivers                                           \nName                : gps0\nDevice              : /dev/gps0\nDriver              : u-blox\nStatus              : Active\nFix                 : 3D\nSatellites          : 10/15 (used/visible)\nPosition            : 59.334567N 18.063456E 42.3m\nPPS                 : Available\n

        Available information per receiver:

        Field Description Name Component name (gps0, gps1, ...) Device Device path (/dev/gps0) Driver Protocol driver (e.g., u-blox, NMEA, SiRF) Status Active or Inactive Fix NONE, 2D, or 3D Satellites Used/visible count Position Latitude, longitude, altitude (when fix acquired) PPS Pulse Per Second signal availability"},{"location":"hardware/#configure-gps-receiver","title":"Configure GPS receiver","text":"

        GPS receivers are hardware components with class gps. The class is auto-inferred from the component name, similar to WiFi radios (radioN):

        admin@example:/> configure\nadmin@example:/config/> set hardware component gps0\nadmin@example:/config/> leave\n

        To use a GPS receiver as an NTP reference clock source, see NTP \u2014 GPS Reference Clock.

        "},{"location":"hardware/#usb-ports","title":"USB Ports","text":"

        For Infix to be able to control USB port(s), a device tree modification is needed (see alder.dtsi for full example).

         chosen {\n   infix {\n     usb-ports = <&cp0_usb3_1>;\n     usb-port-names = \"USB\";\n   };\n };\n

        Two USB ports are also exposed in QEMU for test purpose.

        All USB ports in the system will be disabled during boot due to the file board/common/rootfs/etc/modprobe.d/usbcore.conf. If you do not want Infix to control USB port(s), remove the file or manually enable the USB bus, here is an example:

        # Enable the bus\necho 1 > /sys/bus/usb/devices/usb1/authorized\n

        And then enable sub-devices (e.g. USB memory)

        # Enable a device plugged into usb1\necho 1 >  /sys/bus/usb/devices/usb1/1-1/authorized\n
        "},{"location":"hardware/#current-status_1","title":"Current status","text":"
        admin@example:/> show hardware\nHARDWARE COMPONENTS                                           \n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nBoard Information                                            \nModel               : FriendlyElec NanoPi R2S\nManufacturer        : FriendlyElec\nSerial Number       : 9d1fbfdab6d171ce\nBase MAC Address    : 4a:dc:d8:20:0d:85\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nUSB Ports                                                    \nNAME                                         STATE     OPER   \nUSB                                          unlocked  enabled\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nSensors                                                      \nNAME                          VALUE                 STATUS    \nsoc                           44.1 \u00b0C               ok        \nsoc                           44.5 \u00b0C               ok        \n

        An USB port can be in two states unlocked and locked. When a port is locked, all connected devices will get power, but never authorized by Linux to use.

        "},{"location":"hardware/#configure-usb-port","title":"Configure USB port","text":"

        Note

        You can only configure USB ports known to the system. See the CLI command show hardware in admin-exec context. (Use do prefix in configure context.)

        admin@example:/> configure\nadmin@example:/config/> set hardware component USB state admin-state unlocked\nadmin@example:/config/> leave\nadmin@example:/>\n
        "},{"location":"hardware/#using-a-usb-stick","title":"Using a USB Stick","text":"

        With the USB port unlocked, a memory stick can be used to expand the storage of the device. Plug it in, either directly, or via a USB hub. All partitions of VFAT or exFAT type are automatically mounted to the /media/<LABLEL> directory, e.g., /media/memorystick, or if a partition does not have a label, using the kernel device name, e.g., /media/sda.

        Depending on the partition type the the media is either mounted in flush or sync mode to ensure files are properly flushed to the backing store of the memory stick. This to protect against users yanking out a device before file(s) have been written to it.

        The only way currently to safely \"eject\" a USB memory stick is to use umount command from a UNIX shell, which explicitly synchronizes any cached data to disk before returning the prompt:

        admin@example:~$ sudo umount /media/log\n
        "},{"location":"iface/","title":"Common Settings","text":""},{"location":"iface/#common-interface-settings","title":"Common Interface Settings","text":"

        Common interface settings include name, type, enabled, description, and custom MAC address. Type-specific settings are covered in the dedicated sections for Bridging, Link Aggregation, Ethernet, IP Addressing, and Routing.

        "},{"location":"iface/#interface-name","title":"Interface Name","text":"

        The interface name is limited to 1-15 characters due to Linux kernel constraints. Physical interfaces use their system-assigned names (e.g., eth0, eth1), while user-created interfaces can be named freely within this limit.

        Tip

        Naming conventions like br0, lag0, vlan10, or eth0.20 allow the CLI to automatically infer the interface type.

        "},{"location":"iface/#interface-type","title":"Interface Type","text":"

        The type setting defines what kind of interface this is: bridge, lag, vlan, veth, etc. When configuring via the CLI, the type is often inferred from the interface name. However, when configuring remotely via NETCONF or RESTCONF, the type must be set explicitly.

        admin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> set type bridge\n

        Available types can be listed from the CLI:

        admin@example:/config/interface/br0/> set type ?\n  bridge     IEEE bridge interface.\n  dummy      Linux dummy interface.  Useful mostly for testing.\n  ethernet   Any Ethernet interfaces, regardless of speed, RFC 3635.\n  gre        GRE tunnel interface.\n  gretap     GRETAP (Ethernet over GRE) tunnel interface.\n  lag        IEEE link aggregate interface.\n  loopback   Linux loopback interface.\n  other      Other interface, i.e., unknown.\n  veth       Linux virtual Ethernet pair.\n  vlan       Layer 2 Virtual LAN using 802.1Q.\n  vxlan      Virtual eXtensible LAN tunnel interface.\n  wifi       WiFi (802.11) interface\n  wireguard  WireGuard VPN tunnel interface.\n
        "},{"location":"iface/#enabledisable","title":"Enable/Disable","text":"

        An interface can be administratively disabled using the enabled setting. By default, interfaces are enabled (true).

        admin@example:/config/> edit interface eth0\nadmin@example:/config/interface/eth0/> set enabled false\nadmin@example:/config/interface/eth0/> leave\n

        The operational status can be inspected to see both administrative and actual link state:

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\neth0            ethernet   DISABLED    02:00:00:00:00:00\neth1            ethernet   UP          02:00:00:00:00:01\n...\n
        "},{"location":"iface/#description","title":"Description","text":"

        The description is a free-form text string (max 64 characters) saved as the Linux interface alias (ifalias). Use it to document an interface's purpose or add notes for remote debugging.

        admin@example:/config/> edit interface eth0\nadmin@example:/config/interface/eth0/> set description \"Uplink to core switch\"\nadmin@example:/config/interface/eth0/> leave\n

        The description is visible in the operational datastore and in show commands:

        admin@example:/> show interface eth0\nname                : eth0\ndescription         : Uplink to core switch\nindex               : 2\n...\n
        "},{"location":"iface/#custom-mac-address","title":"Custom MAC Address","text":"

        The custom-phys-address can be used to set an interface's MAC address. This is an extension to the ietf-interfaces YANG model, which defines phys-address as read-only1.

        Caution

        There is no validation or safety checks performed by the system when using custom-phys-address. In particular the offset variant can be dangerous to use -- pay attention to the meaning of bits in the upper-most octet: local bit, multicast/group, etc.

        "},{"location":"iface/#fixed-custom-mac","title":"Fixed custom MAC","text":"

        Use a fixed custom MAC address when the interface must present a specific, deterministic identity on the network. This option bypasses any chassis-derived logic and applies the configured address verbatim.

        admin@example:/config/> edit interface veth0a\nadmin@example:/config/interface/veth0a/> set custom-phys-address static 00:ab:00:11:22:33\n\n=> 00:ab:00:11:22:33\n
        "},{"location":"iface/#chassis-mac","title":"Chassis MAC","text":"

        Chassis MAC, sometimes also referred to as base MAC. In these two examples it is 00:53:00:c0:ff:ee.

        admin@example:/config/> edit interface veth0a\nadmin@example:/config/interface/veth0a/> set custom-phys-address chassis\n\n=> 00:53:00:c0:ff:ee\n
        "},{"location":"iface/#chassis-mac-with-offset","title":"Chassis MAC, with offset","text":"

        When constructing a derived address it is recommended to set the locally administered bit. Same chassis MAC as before.

        admin@example:/config/> edit interface veth0a\nadmin@example:/config/interface/veth0a/> set custom-phys-address chassis offset 02:00:00:00:00:02\n\n=> 02:53:00:c0:ff:f0\n
        "},{"location":"iface/#dummy-interface","title":"Dummy Interface","text":"

        A dummy interface is a virtual interface that is always administratively and operationally UP, regardless of any physical link state. It can hold IP addresses just like any other interface.

        The two most common uses are:

        • Stable OSPF router-ID: OSPF picks its router-ID from an interface address. If that interface goes down, adjacencies can flap. Binding the router-ID to a /32 address on a dummy avoids this.
        • Stable management address: A /32 on a dummy gives the device a permanent identity on the network, reachable as long as at least one uplink is up and the address is redistributed into the routing domain.

        Tip

        WiFi interfaces also use dummies as placeholders when the radio hardware is not detected at boot (e.g., a USB dongle that was unplugged). See WiFi for details.

        "},{"location":"iface/#example-stable-ospf-router-id","title":"Example: Stable OSPF Router-ID","text":"

        Create a dummy interface with a /32 address and use it as the OSPF router-ID so that the ID never changes when physical ports bounce:

        admin@example:/> configure\nadmin@example:/config/> edit interface lo0\nadmin@example:/config/interface/lo0/> set type dummy\nadmin@example:/config/interface/lo0/> set ipv4 address 192.0.2.1 prefix-length 32\nadmin@example:/config/interface/lo0/> leave\nadmin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set explicit-router-id 192.0.2.1\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/> copy running-config startup-config\n

        To also make the address reachable by other routers, redistribute connected routes (or add lo0 as an OSPF interface):

        admin@example:/config/routing/\u2026/ospf/> set redistribute connected\n
        1. A YANG deviation was previously used to make it possible to set phys-address, but this has been replaced with the more flexible custom-phys-address.\u00a0\u21a9

        "},{"location":"ip/","title":"IP Addressing","text":""},{"location":"ip/#ip-address-configuration","title":"IP Address Configuration","text":"

        This section details IP Addresses And Other Per-Interface IP settings.

        Infix support several network interface types, each can be assigned one or more IP addresses, both IPv4 and IPv6 are supported. (There is no concept of a \"primary\" address.)

        "},{"location":"ip/#ipv4-address-assignment","title":"IPv4 Address Assignment","text":"

        Multiple address assignment methods are available:

        Type Yang Model Description static ietf-ip Static assignment of IPv4 address, e.g., 10.0.1.1/24 link-local infix-ip Auto-assignment of IPv4 address in 169.254.x.x/16 range dhcp infix-dhcp-client Assignment of IPv4 address by DHCP server, e.g., 10.0.1.1/24

        Note

        The DHCP address method is only available for LAN interfaces (Ethernet, virtual Ethernet (veth), bridge, link aggregates, etc.)

        Supported DHCP (request) options, configurability (Cfg) and defaults, are listed below. Configurable options can be disabled on a per client interface basis, some options, like clientid and option 81, are possible to set the value of as well.

        Opt Name Cfg Description 1 netmask No Request IP address and netmask 3 router Yes Default route(s), see also option 121 and 249 6 dns-server Yes DNS server(s), static ones take precedence 12 hostname Yes DHCP cannot set hostname, only for informing server 15 domain Yes Default domain name, for name resolution 28 broadcast Yes Broadcast address, calculated if disabled 42 ntp-server Yes NTP server(s), static ones take precedence 50 address Yes Request (previously cached) address 61 client-id Yes Default MAC address (and option 12) 81 fqdn Yes Similar to option 12, request FQDN update in DNS 119 search Yes Request domain search list 121 classless-static-route Yes Classless static routes 249 ms-classless-static-route Yes Microsoft static route, same as option 121

        Default: router, dns-server, domain, broadcast, ntp-server, search, address, classless-static-route, ms-classless-static-route

        When configuring a DHCP client, ensure that the NTP client is enabled for the ntp-server DHCP option to be processed correctly. If the NTP client is not enabled, any NTP servers provided by the DHCP server will be ignored. For details on how to enable the NTP client, see the NTP Client Configuration section.

        Important

        Per RFC3442, if the DHCP server returns both a Classless Static Routes option (121) and Router option (3), the DHCP client must ignore the latter.

        "},{"location":"ip/#ipv6-address-assignment","title":"IPv6 Address Assignment","text":"

        Multiple address assignment methods are available:

        Type Yang Model Description static ietf-ip Static assignment of IPv6 address, e.g., 2001:db8:0:1::1/64 link-local ietf-ip1 (RFC4862) Auto-configured link-local IPv6 address (fe80::0 prefix + interface identifier, e.g., fe80::ccd2:82ff:fe52:728b/64) global auto-conf ietf-ip (RFC4862) Auto-configured (stateless) global IPv6 address (prefix from router + interface identifier, e.g., 2001:db8:0:1:ccd2:82ff:fe52:728b/64 dhcp infix-dhcpv6-client Assignment of IPv6 address by DHCPv6 server, e.g., 2001:db8::42/128

        Both for link-local and global auto-configuration, it is possible to auto-configure using a random suffix instead of the interface identifier.

        Note

        The DHCPv6 address method is only available for LAN interfaces (Ethernet, virtual Ethernet (veth), bridge, link aggregates, etc.)

        Supported DHCPv6 (request) options, configurability (Cfg) and defaults, are listed below. Configurable options can be disabled on a per client interface basis, some options, like client-id and client-fqdn, are possible to set the value of as well.

        Opt Name Cfg Description 1 client-id Yes Client identifier (DUID), auto-generated by default 2 server-id Yes Server identifier (DUID) 23 dns-server Yes DNS recursive name servers, static ones take precedence 24 domain-search Yes Domain search list 25 ia-pd Yes Prefix delegation for downstream networks 31 sntp-server Yes Simple Network Time Protocol servers 32 information-refresh-time Yes Refresh time for stateless DHCPv6 39 client-fqdn Yes Client FQDN, request DNS update from server 56 ntp-server Yes NTP time servers, static ones take precedence

        Default: dns-server, domain-search, ntp-server

        DHCPv6 supports both stateful (address assignment) and stateless (information-only) modes:

        • Stateful DHCPv6: The server assigns IPv6 addresses to clients. This is the default mode when enabling the DHCPv6 client.
        • Stateless DHCPv6: Used with SLAAC (Stateless Address Autoconfiguration) when only configuration information (DNS, NTP, etc.) is needed. Enable with the information-only setting.

        When configuring a DHCPv6 client, ensure that the NTP client is enabled for the ntp-server DHCPv6 option to be processed correctly. If the NTP client is not enabled, any NTP servers provided by the DHCPv6 server will be ignored. For details on how to enable the NTP client, see the NTP Client Configuration section.

        "},{"location":"ip/#examples","title":"Examples","text":"
        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        To illustrate IP address configuration, the examples below uses a switch with a single Ethernet interface (eth0) and a loopback interface (lo). As shown above, these examples assume eth0 has an IPv6 link-local address and lo has static IPv4 and IPv6 addresses by default.

        "},{"location":"ip/#static-and-link-local-ipv4-addresses","title":"Static and link-local IPv4 addresses","text":"
        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv4\nadmin@example:/config/interface/eth0/ipv4/> set address 10.0.1.1 prefix-length 24\nadmin@example:/config/interface/eth0/ipv4/> set autoconf\nadmin@example:/config/interface/eth0/ipv4/> diff\n+interfaces {\n+  interface eth0 {\n+    ipv4 {\n+      address 10.0.1.1 {\n+        prefix-length 24;\n+      }\n+      autoconf;\n+    }\n+  }\n+}\nadmin@example:/config/interface/eth0/ipv4/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv4                   169.254.1.3/16 (random)\n                ipv4                   10.0.1.1/24 (static)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        As shown, the link-local IPv4 address is configured with set autoconf. The presence of the autoconf container enables IPv4 link-local address assignment. The resulting address (169.254.1.3/16) is of type random (ietf-ip.yang).

        The IPv4LL client also supports a request-address setting which can be used to \"seed\" the client's starting address. If the address is free it will be used, otherwise it falls back to the default algorithm.

        admin@example:/config/interface/eth0/ipv4/> edit autoconf\nadmin@example:/config/interface/eth0/ipv4/autoconf/> set request-address 169.254.1.2\nadmin@example:/config/interface/eth0/ipv4/autoconf/> leave\n
        "},{"location":"ip/#use-of-dhcp-for-ipv4-address-assignment","title":"Use of DHCP for IPv4 address assignment","text":"
        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv4\nadmin@example:/config/interface/eth0/ipv4/> set dhcp\nadmin@example:/config/interface/eth0/ipv4/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv4                   10.1.2.100/24 (dhcp)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        The resulting address (10.1.2.100/24) is of type dhcp.

        To configure DHCP client options, such as sending a specific hostname to the server, you can specify options with values:

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv4 dhcp\nadmin@example:/config/interface/eth0/ipv4/dhcp/> set option hostname value myhost\nadmin@example:/config/interface/eth0/ipv4/dhcp/> show\noption hostname {\n  value myhost;\n}\nadmin@example:/config/interface/eth0/ipv4/dhcp/> leave\nadmin@example:/>\n

        Tip

        The special value auto can be used with the hostname option to automatically use the configured system hostname.

        Other useful DHCP options include:

        • client-id - Send a specific client identifier to the server
        • route-preference - Set the administrative distance for DHCP-learned routes (default: 5)

        For advanced usage with vendor-specific options, see the YANG model.

        "},{"location":"ip/#use-of-dhcpv6-for-ipv6-address-assignment","title":"Use of DHCPv6 for IPv6 address assignment","text":"
        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6\nadmin@example:/config/interface/eth0/ipv6/> set dhcp\nadmin@example:/config/interface/eth0/ipv6/> leave\nadmin@example:/> show interface\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   2001:db8::42/128 (dhcp)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        The resulting address (2001:db8::42/128) is of type dhcp.

        To configure DHCPv6 client options, such as requesting prefix delegation for downstream networks, you can specify options:

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6 dhcp\nadmin@example:/config/interface/eth0/ipv6/dhcp/> set option ia-pd\nadmin@example:/config/interface/eth0/ipv6/dhcp/> set option dns-server\nadmin@example:/config/interface/eth0/ipv6/dhcp/> show\noption dns-server;\noption ia-pd;\nadmin@example:/config/interface/eth0/ipv6/dhcp/> leave\nadmin@example:/>\n

        For stateless DHCPv6 (used with SLAAC to get only configuration information):

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6 dhcp\nadmin@example:/config/interface/eth0/ipv6/dhcp/> set information-only true\nadmin@example:/config/interface/eth0/ipv6/dhcp/> show\ninformation-only true;\noption dns-server;\noption domain-search;\nadmin@example:/config/interface/eth0/ipv6/dhcp/> leave\nadmin@example:/>\n

        Other useful DHCPv6 options include:

        • duid - Set a specific DHCPv6 Unique Identifier (auto-generated by default)
        • client-fqdn - Request the server to update DNS records with client's FQDN
        • route-preference - Set the administrative distance for DHCPv6-learned routes (default: 5)

        For advanced usage with vendor-specific options, see the YANG model.

        "},{"location":"ip/#disabling-ipv6-link-local-addresses","title":"Disabling IPv6 link-local address(es)","text":"

        The (only) way to disable IPv6 link-local addresses is by disabling IPv6 on the interface.

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6\nadmin@example:/config/interface/eth0/ipv6/> set enabled false\nadmin@example:/config/interface/eth0/ipv6/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n
        "},{"location":"ip/#static-ipv6-address","title":"Static IPv6 address","text":"
        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6\nadmin@example:/config/interface/eth0/ipv6/> set address 2001:db8::1 prefix-length 64\nadmin@example:/config/interface/eth0/ipv6/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   2001:db8::1/64 (static)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n
        "},{"location":"ip/#stateless-auto-configuration-of-global-ipv6-address","title":"Stateless Auto-configuration of Global IPv6 Address","text":"

        Stateless address auto-configuration of global addresses is enabled by default. The address is formed by concatenating the network prefix advertised by the router (here 2001:db8:0:1::0/64) and the interface identifier. The resulting address is of type link-layer, as it is formed based on the interface identifier (ietf-ip.yang).

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   2001:db8:0:1:0:ff:fe00:0/64 (link-layer)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        Disabling auto-configuration of global IPv6 addresses can be done as shown below.

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6\nadmin@example:/config/interface/eth0/ipv6/> set autoconf create-global-addresses false\nadmin@example:/config/interface/eth0/ipv6/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n
        "},{"location":"ip/#random-link-identifiers-for-ipv6-stateless-autoconfiguration","title":"Random Link Identifiers for IPv6 Stateless Autoconfiguration","text":"

        By default, the auto-configured link-local and global IPv6 addresses are formed from a link-identifier based on the MAC address.

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   2001:db8:0:1:0:ff:fe00:0/64 (link-layer)\n                ipv6                   fe80::ff:fe00:0/64 (link-layer)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        To avoid revealing identity information in the IPv6 address, it is possible to specify use of a random identifier (ietf-ip.yang and RFC8981).

        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6\nadmin@example:/config/interface/eth0/ipv6/> set autoconf create-temporary-addresses true\nadmin@example:/config/interface/eth0/ipv6/> leave\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \neth0            ethernet   UP          02:00:00:00:00:00\n                ipv6                   2001:db8:0:1:b705:8374:638e:74a8/64 (random)\n                ipv6                   fe80::ad3d:b274:885a:9ffb/64 (random)\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        Both the link-local address (fe80::) and the global address (2001:) have changed type to random.

        "},{"location":"ip/#ipv4-forwarding","title":"IPv4 forwarding","text":"

        To be able to route (static or dynamic) on the interface it is required to enable forwarding. This setting controls if packets received on this interface can be forwarded.

        admin@example:/config/> edit interface eth0\nadmin@example:/config/interface/eth0/> set ipv4 forwarding\nadmin@example:/config/interface/eth0/> leave\nadmin@example:/>\n
        "},{"location":"ip/#ipv6-forwarding","title":"IPv6 forwarding","text":"

        Forwarding must be enabled on an interface for it to route IPv6 traffic (static or dynamic). The setting is per-interface and works the same way as IPv4 forwarding.

        The following table shows the IPv6 features that the forwarding setting controls when it is Enabled or Disabled:

        IPv6 Feature Enabled Disabled IsRouter set in Neighbour Advertisements Yes No Transmit Router Solicitations No Yes Router Advertisements are ignored Yes Yes Accept Redirects No Yes
        admin@example:/config/> edit interface eth0\nadmin@example:/config/interface/eth0/> set ipv6 forwarding\nadmin@example:/config/interface/eth0/> leave\nadmin@example:/>\n
        "},{"location":"ip/#arp-and-neighbor-cache","title":"ARP and Neighbor Cache","text":"

        Static ARP entries (IPv4) and neighbor cache entries (IPv6) can be configured per interface. The most common reasons to do so are:

        • Security \u2014 prevent ARP/NDP spoofing by locking critical hosts (e.g., a default gateway) to their known MAC addresses
        • Reliability \u2014 ensure reachability of a host even when ARP/NDP traffic is suppressed or filtered (e.g., across a strict firewall)

        Dynamic entries are learned automatically by the kernel using ARP and Neighbor Discovery Protocol (NDP), and are visible as read-only operational state alongside the static ones.

        "},{"location":"ip/#static-ipv4-arp-entry","title":"Static IPv4 ARP Entry","text":"
        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv4\nadmin@example:/config/interface/eth0/ipv4/> set neighbor 192.168.1.100 link-layer-address 00:11:22:33:44:55\nadmin@example:/config/interface/eth0/ipv4/> diff\n+interfaces {\n+  interface eth0 {\n+    ipv4 {\n+      neighbor 192.168.1.100 {\n+        link-layer-address 00:11:22:33:44:55;\n+      }\n+    }\n+  }\n+}\nadmin@example:/config/interface/eth0/ipv4/> leave\nadmin@example:/>\n
        "},{"location":"ip/#static-ipv6-neighbor-entry","title":"Static IPv6 Neighbor Entry","text":"
        admin@example:/> configure\nadmin@example:/config/> edit interface eth0 ipv6\nadmin@example:/config/interface/eth0/ipv6/> set neighbor 2001:db8::100 link-layer-address 00:11:22:33:44:55\nadmin@example:/config/interface/eth0/ipv6/> diff\n+interfaces {\n+  interface eth0 {\n+    ipv6 {\n+      neighbor 2001:db8::100 {\n+        link-layer-address 00:11:22:33:44:55;\n+      }\n+    }\n+  }\n+}\nadmin@example:/config/interface/eth0/ipv6/> leave\nadmin@example:/>\n

        The full neighbor table \u2014 including dynamically learned entries (origin: dynamic) \u2014 is available as operational state via NETCONF or RESTCONF.

        Note

        Static neighbor entries take effect immediately on leave (commit). They are installed as permanent entries in the kernel neighbor table, which means they are never evicted by the normal ARP/NDP aging process.

        1. Link-local IPv6 addresses are implicitly enabled when enabling IPv6. IPv6 can be enabled/disabled per interface in the ietf-ip YANG model.\u00a0\u21a9

        "},{"location":"keystore/","title":"Keystore","text":""},{"location":"keystore/#keystore","title":"Keystore","text":"

        The Infix keystore is a centralized storage system for cryptographic keys used throughout the system. It is based on the IETF standards RFC 9641 (Keystore) and RFC 9640 (Cryptographic Types), with Infix extensions for WiFi and WireGuard key formats.

        "},{"location":"keystore/#overview","title":"Overview","text":"

        The keystore supports two types of cryptographic keys:

        1. Asymmetric Keys \u2014 public/private key pairs used for:

          • SSH host authentication (RSA keys)
          • HTTPS/TLS certificates (X.509 keys)
          • WireGuard VPN tunnels (X25519 keys)
        2. Symmetric Keys \u2014 shared secrets used for:

          • WiFi authentication (WPA2/WPA3 pre-shared keys)
          • WireGuard VPN pre-shared keys

        All keys are stored under the ietf-keystore configuration path and can be managed via CLI, NETCONF, or RESTCONF.

        "},{"location":"keystore/#supported-formats","title":"Supported Formats","text":"Asymmetric Key Format Use Case Key Type rsa-private-key-format / ssh-public-key-format SSH host keys RSA rsa-private-key-format / x509-public-key-format TLS certificates RSA + X.509 x25519-private-key-format / x25519-public-key-format WireGuard VPN Curve25519 Symmetric Key Format Use Case passphrase-key-format Human-readable passphrases (WiFi) octet-string-key-format Raw symmetric keys (WireGuard)"},{"location":"keystore/#asymmetric-keys","title":"Asymmetric Keys","text":"

        Asymmetric keys consist of a public/private key pair. The public key can be shared freely, while the private key must be kept secure.

        "},{"location":"keystore/#ssh-host-keys","title":"SSH Host Keys","text":"

        SSH host keys identify the system during SSH and NETCONF connections. The default host key is automatically generated on first boot and stored in the keystore with the name genkey.

        See SSH Management for details on generating and importing custom SSH host keys.

        "},{"location":"keystore/#tls-certificates","title":"TLS Certificates","text":"

        TLS certificates are used by the web server (nginx) for HTTPS connections. The default certificate is a self-signed certificate automatically generated on first boot and stored in the keystore with the name gencert. Like SSH host keys, the certificate is regenerated on factory reset when its keys are empty.

        The web server's certificate leaf references which keystore entry to use:

        \"infix-services:web\": {\n    \"certificate\": \"gencert\",\n    \"enabled\": true\n}\n

        To use a custom (e.g., CA-signed) certificate, create a new asymmetric key entry with x509-public-key-format, populate it with your certificate and private key, then point the web certificate leaf to it:

        \"ietf-keystore:keystore\": {\n    \"asymmetric-keys\": {\n        \"asymmetric-key\": [\n            {\n                \"name\": \"my-cert\",\n                \"public-key-format\": \"infix-crypto-types:x509-public-key-format\",\n                \"public-key\": \"<base64-encoded-cert>\",\n                \"private-key-format\": \"infix-crypto-types:rsa-private-key-format\",\n                \"cleartext-private-key\": \"<base64-encoded-key>\",\n                \"certificates\": {\n                    \"certificate\": [\n                        { \"name\": \"ca-signed\", \"cert-data\": \"<base64-encoded-cert>\" }\n                    ]\n                }\n            }\n        ]\n    }\n}\n

        Note

        The public-key and cert-data fields contain base64-encoded PEM data with the -----BEGIN/END----- markers stripped. The system reconstructs the PEM files when writing them to disk for nginx.

        "},{"location":"keystore/#wireguard-keys","title":"WireGuard Keys","text":"

        WireGuard uses X25519 elliptic curve cryptography for key exchange. Each WireGuard interface requires a public/private key pair stored as an asymmetric key in the keystore. Key pairs can be generated directly from the CLI:

        admin@example:/> wireguard genkey\nPrivate: aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=\nPublic:  bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=\n

        See WireGuard VPN for key generation and configuration examples.

        "},{"location":"keystore/#symmetric-keys","title":"Symmetric Keys","text":"

        Symmetric keys are shared secrets where the same key must be configured on all systems that need to communicate.

        "},{"location":"keystore/#wifi-pre-shared-keys","title":"WiFi Pre-Shared Keys","text":"

        WiFi networks secured with WPA2 or WPA3 use pre-shared keys stored as symmetric keys in the keystore with passphrase-key-format. The passphrase must be 8-63 printable ASCII characters.

        Since symmetric keys are stored as binary (base64-encoded), the CLI provides the change command to enter passphrases interactively:

        admin@example:/config/keystore/\u2026/my-wifi-key/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\n

        See WiFi for complete configuration examples.

        "},{"location":"keystore/#wireguard-pre-shared-keys","title":"WireGuard Pre-Shared Keys","text":"

        WireGuard supports optional pre-shared keys (PSK) that add a layer of symmetric encryption alongside Curve25519. PSKs use the standard IETF octet-string-key-format (32 random bytes). This provides defense-in-depth against future quantum computers that might break elliptic curve cryptography. Note, however, that WireGuard\u2019s authentication and initial key agreement remain Curve25519-based, so PSKs only protect the session encryption, not the handshake itself.

        PSKs can be generated directly from the CLI:

        admin@example:/> wireguard genpsk\ncO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=\n

        See WireGuard VPN for PSK generation and usage examples.

        "},{"location":"keystore/#viewing-keys","title":"Viewing Keys","text":"

        The show keystore command in admin-exec mode gives an overview of all keys in the keystore. Passphrases (WiFi passwords) are decoded and shown in cleartext, while binary keys (WireGuard PSKs) are shown as base64:

        admin@example:/> show keystore\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nSymmetric Keys\nNAME                         FORMAT        VALUE                        \nmy-wifi-key                  passphrase    MySecretPassword\nwg-psk                       octet-string  zYr83O4Ykj9i1gN+/aaosJxQx...\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nAsymmetric Keys\nNAME                         TYPE    PUBLIC KEY                         \ngenkey                       rsa     MIIBCgKCAQEAnj0YinjhYDgYbEGuh7...\ngencert                      x509    MIIDXTCCAkWgAwIBAgIJAJC1HiIAZA...\nwg-tunnel                    x25519  bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1...\n

        To see the full (untruncated) details of a specific key, use the symmetric or asymmetric qualifier with the key name:

        admin@example:/> show keystore symmetric my-wifi-key\nname                : my-wifi-key\nformat              : passphrase\nvalue               : MySecretPassword\n\nadmin@example:/> show keystore asymmetric genkey\nname                : genkey\nalgorithm           : rsa\npublic key format   : ssh-public-key\npublic key          : MIIBCgKCAQEAnj0YinjhY...full key...IDAQAB\n

        Note

        The show keystore command is protected by NACM. Only users in the admin group can view keystore data. Operator-level users will see a message indicating that no keystore data is available.

        The full configuration-mode view (including private keys) is still available via configure and then show keystore:

        admin@example:/config/> show keystore\n

        Warning

        The configuration-mode show keystore displays private keys in cleartext. Be careful when viewing keys on shared screens or in logged sessions. The admin-exec show keystore command never displays private keys.

        "},{"location":"keystore/#deleting-keys","title":"Deleting Keys","text":"
        admin@example:/> configure\nadmin@example:/config/> delete keystore asymmetric-key mykey\nadmin@example:/config/> leave\n

        Caution

        Deleting a key that is referenced by a service (SSH, WireGuard, WiFi) will cause that service to fail. Verify the key is not in use before deletion.

        "},{"location":"keystore/#security-considerations","title":"Security Considerations","text":"

        The keystore is protected by NACM (Network Access Control Model) rules. Only users in the admin group can view or modify cryptographic keys. See NACM for details on access control.

        Private keys are stored in cleartext in the configuration database. Configuration files and backups containing the keystore should be treated as sensitive and protected accordingly.

        "},{"location":"keystore/#key-validation","title":"Key Validation","text":"

        Symmetric key values are stored as binary (base64-encoded). The system validates them based on their declared format:

        • passphrase-key-format: Used by WiFi, must decode to 8-63 ASCII characters
        • octet-string-key-format: Used by Wireguard, must decode to exactly 32 bytes (256 bits)
        "},{"location":"keystore/#references","title":"References","text":"
        • RFC 9641 - A YANG Data Model for a Keystore
        • RFC 9640 - YANG Data Types and Groupings for Cryptography
        • WiFi Documentation
        • WireGuard VPN Documentation
        • SSH Management
        • NACM Access Control
        "},{"location":"lag/","title":"Link Aggregation","text":""},{"location":"lag/#link-aggregation","title":"Link Aggregation","text":"

        A link aggregate, or lag, allows multiple physical interfaces to be combined into a single logical interface, providing increased bandwidth (in some cases) and redundancy (primarily). Two modes of qualifying lag member ports are available:

        1. static: Active members selected based on link status (carrier)
        2. lacp: IEEE 802.3ad Link Aggregation Control Protocol

        In LACP mode, LACPDUs are exchanged by the link partners to qualify each lag member, while in static mode only carrier is used. This additional exchange in LACP ensures traffic can be forwarded in both directions.

        Traffic distribution, for both modes, across the active lag member ports is determined by the hash policy1. It uses an XOR of the source, destination MAC addresses and the EtherType field. This, IEEE 802.3ad-compliant, algorithm will place all traffic to a particular network peer on the same link. Meaning there is no increased bandwidth for communication between two specific devices.

        Tip

        Similar to other interface types, naming your interface lagN, where N is a number, allows the CLI to automatically infer the interface type as LAG.

        "},{"location":"lag/#basic-configuration","title":"Basic Configuration","text":"

        Creating a link aggregate interface and adding member ports:

        admin@example:/> configure\nadmin@example:/config/> edit interface lag0\nadmin@example:/config/interface/lag0/> set lag mode static\nadmin@example:/config/interface/lag0/> end\nadmin@example:/config/> set interface eth7 lag-port lag lag0\nadmin@example:/config/> set interface eth8 lag-port lag lag0\nadmin@example:/config/> leave\n

        A static lag responds only to link (carrier) changes of member ports. E.g., in this example egressing traffic is continuously distributed over the two links until link down on one link is detected, triggering all traffic to be steered to the sole remaining link.

        "},{"location":"lag/#lacp-configuration","title":"LACP Configuration","text":"

        LACP mode provides dynamic negotiation of the link aggregate. Key settings include:

        admin@example:/> configure\nadmin@example:/config/> edit interface lag0\nadmin@example:/config/interface/lag0/> set lag mode lacp\nadmin@example:/config/interface/lag0/> set lag lacp mode passive\nadmin@example:/config/interface/lag0/> set lag lacp rate fast\nadmin@example:/config/interface/lag0/> set lag lacp system-priority 100\n

        LACP mode supports two operational modes:

        • active: Initiates negotiation by sending LACPDUs (default)
        • passive: Waits for peer to initiate negotiation

        Note

        At least one end of the link must be in active mode for negotiation to occur.

        The LACP rate setting controls protocol timing:

        • slow: LACPDUs sent every 30 seconds, with 90 second timeout (default)
        • fast: LACPDUs sent every second, with 3 second timeout
        "},{"location":"lag/#link-flapping","title":"Link Flapping","text":"

        To protect against link flapping, debounce timers can be configured to delay link qualification. Usually only the up delay is needed:

        admin@example:/config/interface/lag0/lag/link-monitor/> edit debounce\nadmin@example:/config/interface/lag0/lag/link-monitor/debounce/> set up 500\nadmin@example:/config/interface/lag0/lag/link-monitor/debounce/> set down 200\n
        "},{"location":"lag/#operational-status-overview","title":"Operational Status, Overview","text":"

        Like other interfaces, link aggregates are also available in the general interfaces overview in the CLI admin-exec context. Here is the above static mode aggregate:

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\n.\n.\n.\nlag0            lag        UP          static: balance-xor, hash: layer2\n\u2502               ethernet   UP          00:a0:85:00:02:00\n\u251c eth7          lag        ACTIVE\n\u2514 eth8          lag        ACTIVE\n

        Same aggregate, but in LACP mode:

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\n.\n.\n.\nlag0            lag        UP          lacp: active, rate: fast (1s), hash: layer2\n\u2502               ethernet   UP          00:a0:85:00:02:00\n\u251c eth7          lag        ACTIVE      active, short_timeout, aggregating, in_sync, collecting, distributing\n\u2514 eth8          lag        ACTIVE      active, short_timeout, aggregating, in_sync, collecting, distributing\n
        "},{"location":"lag/#operational-status-detail","title":"Operational Status, Detail","text":"

        In addition to basic status shown in the interface overview, detailed LAG status can be inspected:

        admin@example:/> show interface lag0\nname                : lag0\nindex               : 25\nmtu                 : 1500\noperational status  : up\nphysical address    : 00:a0:85:00:02:00\nlag mode            : static\nlag type            : balance-xor\nlag hash            : layer2\nlink debounce up    : 0 msec\nlink debounce down  : 0 msec\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 0\nout-octets          : 2142\n

        Same aggregate, but in LACP mode:

        admin@example:/> show interface lag0\nname                : lag0\nindex               : 24\nmtu                 : 1500\noperational status  : up\nphysical address    : 00:a0:85:00:02:00\nlag mode            : lacp\nlag hash            : layer2\nlacp mode           : active\nlacp rate           : fast (1s)\nlacp aggregate id   : 1\nlacp system priority: 65535\nlacp actor key      : 9\nlacp partner key    : 9\nlacp partner mac    : 00:a0:85:00:03:00\nlink debounce up    : 0 msec\nlink debounce down  : 0 msec\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 100892\nout-octets          : 111776\n

        Member ports provide additional status information:

        • Link failure counter: number of detected link failures
        • LACP state flags: various states of LACP negotiation:
        • active: port is actively sending LACPDUs
        • short_timeout: using fast rate (1s) vs. slow rate (30s)
        • aggregating: port is allowed to aggregate in this LAG
        • in_sync: port is synchronized with partner
        • collecting: port is allowed to receive traffic
        • distributing: port is allowed to send traffic
        • defaulted: using default partner info (partner not responding)
        • expired: partner info has expired (no LACPDUs received)
        • Aggregator ID: unique identifier for this LAG group
        • Actor state: LACP state flags for this port (local)
        • Partner state: LACP state flags from the remote port

        Example member port status:

        admin@example:/> show interface eth7\nname                : eth7\nindex               : 8\nmtu                 : 1500\noperational status  : up\nphysical address    : 00:a0:85:00:02:00\nlag member          : lag0\nlag member state    : active\nlacp aggregate id   : 1\nlacp actor state    : active, short_timeout, aggregating, in_sync, collecting, distributing\nlacp partner state  : active, short_timeout, aggregating, in_sync, collecting, distributing\nlink failure count  : 0\nipv4 addresses      :\nipv6 addresses      :\nin-octets           : 473244\nout-octets          : 499037\n
        "},{"location":"lag/#example-switch-uplink-with-lacp","title":"Example: Switch Uplink with LACP","text":"

        LACP mode provides the most robust operation, automatically negotiating the link aggregate and detecting configuration mismatches.

        A common use case is connecting a switch to an upstream device:

        admin@example:/> configure\nadmin@example:/config/> edit interface lag0\nadmin@example:/config/interface/lag0/> set lag mode lacp\n

        Enable fast LACP for quicker fail-over:

        admin@example:/config/interface/lag0/> set lag lacp rate fast\n

        Add uplink ports

        admin@example:/config/interface/lag0/> end\nadmin@example:/config/> set interface eth7 lag-port lag lag0\nadmin@example:/config/> set interface eth8 lag-port lag lag0\n

        Enable protection against \"link flapping\".

        admin@example:/config/interface/lag0/> edit lag link-monitor\nadmin@example:/config/interface/lag0/lag/link-monitor/> edit debounce\nadmin@example:/config/interface/lag0/lag/link-monitor/debounce/> set up 500\nadmin@example:/config/interface/lag0/lag/link-monitor/debounce/> set down 200\nadmin@example:/config/interface/lag0/lag/link-monitor/debounce/> top\n

        Add to bridge for switching

        admin@example:/config/interface/lag0/lag/link-monitor/debounce/> end\nadmin@example:/config/> set interface lag0 bridge-port bridge br0\nadmin@example:/config/> leave\n
        1. (source MAC XOR destination MAC XOR EtherType) MODULO num_links \u21a9

        "},{"location":"license/","title":"Origin & Licensing","text":""},{"location":"license/#origin-licensing","title":"Origin & Licensing","text":"

        Infix is entirely built on Open Source components (packages). Most of them, as well as the build system with its helper scripts and tools, is from Buildroot, which is distributed under the terms of the GNU General Public License (GPL). See the file COPYING for details.

        Some files in Buildroot may contain a different open source license statement. Those files are licensed under the license contained in the file itself.

        Buildroot and Infix also bundle patch files, which are applied to the sources of the various packages. Those patches are not covered by the license of Buildroot or Infix. Instead, they are covered by the license of the software to which the patches are applied. When said software is available under multiple licenses, the patches are only provided under the publicly accessible licenses.

        Infix releases include the license information covering all Open Source packages. This is extracted automatically at build time using the tool make legal-info. Any proprietary software built on top of Infix, or Buildroot, would need separate auditing to ensure it does not link with any GPL1 licensed library.

        1. Infix image builds use GNU libc (GLIBC) which is covered by the LGPL. The LGPL does allow proprietary software, as long as said software is linking dynamically, not statically, to GLIBC.\u00a0\u21a9

        "},{"location":"management/","title":"Management","text":""},{"location":"management/#system-management","title":"System Management","text":"

        The system utilizes YANG models for keeping configuration and operational data. These databases can be managed through different interfaces such as NETCONF, RESTCONF, and CLI via SSH or Console.

        "},{"location":"management/#ssh-management","title":"SSH Management","text":"

        An SSH server (SSHv2) is provided for remote management. It can be enabled/disabled as shown below.

        admin@example:/> configure\nadmin@example:/config/> edit ssh\nadmin@example:/config/ssh/> set enabled\nadmin@example:/config/ssh/>\n

        By default the SSH server accepts connections to port 22 on all its IP addresses, but this can be adjusted using the listen command. To make the server (only) listen for incoming connections to IP address 192.168.1.1 and port 12345 the following commands can be used.

        admin@example:/> configure\nadmin@example:/config/> edit ssh\nadmin@example:/config/ssh/> show\nenabled true;\nhostkey genkey;\nlisten ipv4 {\n  address 0.0.0.0;\n  port 22;\n}\nlisten ipv6 {\n  address ::;\n  port 22;\n}\nadmin@example:/config/ssh/> no listen ipv6\nadmin@example:/config/ssh/> edit listen ipv4\nadmin@example:/config/ssh/listen/ipv4/> set address 192.168.1.1\nadmin@example:/config/ssh/listen/ipv4/> set port 12345\nadmin@example:/config/ssh/listen/ipv4/>\n

        The default SSH hostkey is generated on first boot and is used in both SSH and NETCONF (SSH transport). Custom keys can be added to the configuration in ietf-keystore. The only supported hostkey type is RSA for now, thus the private key must be ietf-crypto-types:rsa-private-key-format and the public key ietf-crypto-types:ssh-public-key-format

        Tip

        For comprehensive information about the keystore, including key management, security considerations, and examples for different key types, see the Keystore documentation.

        "},{"location":"management/#use-your-own-ssh-hostkeys","title":"Use your own SSH hostkeys","text":"

        Hostkeys can be generated with OpenSSL:

        openssl genpkey -quiet -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -outform PEM > mykey\nopenssl rsa -RSAPublicKey_out < mykey > mykey.pub\n
        Store the keys in ietf-keystore without the header and footer information created by OpenSSL.

        After the key has been stored in the keystore and given the name mykey it can be added to SSH configuration:

        admin@example:/> configure\nadmin@example:/config/> edit ssh\nadmin@example:/config/ssh/> set hostkey mykey\n
        "},{"location":"management/#console-port","title":"Console Port","text":"

        For units with a console port, it is possible for users to login to shell/CLI with functionality similar to what is provided via SSH.

        The type and setup for your console port is product specific. For instance, it can be a USB-C port connected to the CPU serial port using a USB-to-serial converter. To connect you would need a USB-C cable connected to the console port of the device. The serial port is typically setup to run at 115200 baud, 8N1.

        Infix OS \u2014 Immutable.Friendly.Secure v24.11.1 (ttyS0)\nexample login: admin\nPassword:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-'\n\nRun the command 'cli' for interactive OAM\n\nadmin@example:~$\n

        The resize command can be used to update terminal settings to the size of your terminal window.

        admin@example:~$ resize\nCOLUMNS=115;LINES=59;export COLUMNS LINES;\nadmin@example:~$\n

        CLI can be entered from shell in the same way as for SSH.

        admin@example:~$ cli\n\nSee the 'help' command for an introduction to the system\n\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA                                    \nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\ne1              ethernet   LOWER-DOWN  00:53:00:06:03:01\ne2              ethernet   LOWER-DOWN  00:53:00:06:03:02\n...\nadmin@example:/>\n
        "},{"location":"management/#web-web-console-and-restconf","title":"Web, Web-console and RESTCONF","text":"

        The system provides a set of Web services:

        • a rudimentary Web server, currently limited to an information page
        • a RESTCONF server with equivalent management capabilities as NETCONF
        • a Web console service, where the shell/CLI can be accessed via HTTPS, similar to connecting via a console port or SSH

        There is also a Netbrowse Web service presenting information about the unit's neighbors, collected via mDNS (see Discovery for more details).

        admin@example:/> configure\nadmin@example:/config/> edit web\nadmin@example:/config/web/> help\n  certificate                       Reference to asymmetric key in central keystore.\n  enabled                           Enable or disable on all web services.\n  console                           Web console interface.\n  netbrowse                         mDNS Network Browser.\n  restconf                          IETF RESTCONF Server.\nadmin@example:/config/web/>\n
        "},{"location":"management/#enabledisable-web-service-and-server","title":"Enable/disable Web Service and Server","text":"

        The Web service can be enabled as shown below.

        admin@example:/> configure\nadmin@example:/config/> edit web\nadmin@example:/config/web/> set enabled\nadmin@example:/config/web/>\n

        Enabling the Web service implies that a Web server is enabled. Currently this Web server provides generic Infix information, as well as a link to a Web console. The Web server uses HTTPS; any HTTP request is redirected to HTTPS.

        The enabled setting for the Web service acts as a global enable/disable setting for the other Web services (Web console, RESTCONF and Netbrowse).

        "},{"location":"management/#enabledisable-web-console","title":"Enable/disable Web Console","text":"

        The Web console service provides a terminal service similar to Console or SSH. The Web console is secured via HTTPS on port 7861.

        The Web console has its own enable/disable setting, but will only be activated if the Web service is enabled. The example below shows how to disable the Web console.

        admin@example:/config/web/> edit console\nadmin@example:/config/web/console/> no enabled\nadmin@example:/config/web/console/>\n
        "},{"location":"management/#enabledisable-restconf-service","title":"Enable/disable RESTCONF Service","text":"

        Alternatively, the system can be managed remotely using RESTCONF. Meaning you can curl it instead of using a dedicated NETCONF client.

        The RESTCONF service has its own enable/disable setting, but will only be activated if the Web service is enabled. The example below shows how to disable the RESTCONF service.

        admin@example:/config/web/> edit restconf\nadmin@example:/config/web/restconf/> no enabled\nadmin@example:/config/web/restconf/>\n
        "},{"location":"management/#https-certificate","title":"HTTPS Certificate","text":"

        The Web server uses a TLS certificate from the central keystore. By default it uses gencert, a self-signed certificate that is automatically generated on first boot.

        To use a different certificate, e.g., one signed by a CA, first add it to the keystore as an asymmetric key with x509-public-key-format, then point the web certificate leaf to it:

        admin@example:/config/web/> set certificate my-cert\nadmin@example:/config/web/>\n

        See Keystore for details on managing TLS certificates.

        "},{"location":"management/#system-upgrade","title":"System Upgrade","text":"

        See Upgrade & Boot Order for information on upgrading.

        "},{"location":"nacm/","title":"Access Control (NACM)","text":""},{"location":"nacm/#network-access-control-model-nacm","title":"Network Access Control Model (NACM)","text":"

        NETCONF Access Control Model (RFC 8341) provides fine-grained access control for YANG data models. NACM controls who can read, write, and execute operations on specific parts of the configuration and operational state.

        This document provides technical details about how NACM works, how rules are evaluated, and best practices for creating custom access control policies.

        Tip

        For a practical introduction to user management and the built-in user levels (admin, operator, guest), see the Multiple Users section in the System Configuration guide.

        "},{"location":"nacm/#tldr-the-factory-defaults-work","title":"TL;DR - The Factory Defaults Work","text":"

        You don't need to understand NACM to use the system securely.

        The factory configuration implements a \"permit by default, deny sensitive items\" policy that provides:

        • Immediate usability - Operators can configure the entire system without custom NACM rules
        • Automatic security - Passwords, cryptographic keys, and dangerous operations are protected out-of-the-box
        • Future-proof design - New features work immediately without NACM updates

        The three built-in user levels (admin, operator, guest) cover most use cases. Only read on if you need to create custom access control policies.

        "},{"location":"nacm/#overview","title":"Overview","text":"

        NACM provides three types of access control:

        • Data node access - Control read/write access to configuration and state
        • Operation access - Control execution of RPCs (remote procedure calls)
        • Notification access - Control subscription to event notifications (not covered here)

        Access is controlled through:

        1. Global defaults - Default permissions for read/write/exec
        2. YANG-level annotations - Security markers in YANG modules (see below)
        3. NACM rules - Explicit permit/deny rules organized in rule-lists
        "},{"location":"nacm/#rule-evaluation","title":"Rule Evaluation","text":"

        NACM rules are evaluated in a specific order:

        1. Rule-lists are processed sequentially in the order they appear in the configuration
        2. Within each rule-list, rules are evaluated sequentially
        3. First matching rule wins - no further rules are evaluated
        4. If no rule matches, global defaults apply (read-default, write-default, exec-default)
        5. YANG annotations override everything - nacm:default-deny-all in a YANG module requires an explicit permit rule regardless of global defaults
        "},{"location":"nacm/#example-rule-evaluation","title":"Example Rule Evaluation","text":"

        Given this configuration:

        {\n  \"read-default\": \"permit\",\n  \"write-default\": \"permit\",\n  \"rule-list\": [\n    {\n      \"name\": \"operator-acl\",\n      \"group\": [\"operator\"],\n      \"rule\": [\n        {\n          \"name\": \"permit-system-rpcs\",\n          \"module-name\": \"ietf-system\",\n          \"access-operations\": \"exec\",\n          \"action\": \"permit\"\n        }\n      ]\n    },\n    {\n      \"name\": \"default-deny-all\",\n      \"group\": [\"*\"],\n      \"rule\": [\n        {\n          \"name\": \"deny-passwords\",\n          \"path\": \"/ietf-system:system/authentication/user/password\",\n          \"access-operations\": \"*\",\n          \"action\": \"deny\"\n        }\n      ]\n    }\n  ]\n}\n

        When operator user \"jacky\" tries to:

        • Read password: Matches \"deny-passwords\" \u2192 DENIED
        • Write interface config: No match, uses write-default \u2192 PERMITTED (write-default=permit)
        • Write hostname: No match, uses write-default \u2192 PERMITTED (write-default=permit)
        • Reboot system: Matches \"permit-system-rpcs\" \u2192 PERMITTED (overrides nacm:default-deny-all)
        "},{"location":"nacm/#global-defaults","title":"Global Defaults","text":"

        NACM has three global defaults that apply when no rule matches:

        {\n  \"read-default\": \"permit\",\n  \"write-default\": \"permit\",\n  \"exec-default\": \"permit\"\n}\n

        Factory configuration defaults:

        • read-default: \"permit\" - Users can read configuration and state by default
        • write-default: \"permit\" - Users can modify configuration by default
        • exec-default: \"permit\" - Users can execute RPCs by default

        This permit-by-default approach, combined with targeted denials for sensitive items (passwords, cryptographic keys), provides a balance between usability and security. It also makes the system \"future proof\" - when new YANG modules are added, operators can immediately configure them without updating NACM rules.

        Important

        YANG modules with nacm:default-deny-all or nacm:default-deny-write annotations override these global defaults. You must create explicit permit rules for those operations.

        "},{"location":"nacm/#module-name-vs-path","title":"Module-Name vs Path","text":"

        NACM rules can match operations using either module-name or path. The following sub-sections provide detailed information and examples of both.

        "},{"location":"nacm/#module-name-matching","title":"Module-Name Matching","text":"

        Matches all nodes defined in a specific YANG module:

        {\n  \"name\": \"permit-keystore\",\n  \"module-name\": \"ietf-keystore\",\n  \"access-operations\": \"*\",\n  \"action\": \"permit\"\n}\n

        This permits all operations on data defined in ietf-keystore, but does NOT cover augments from other modules.

        Example: The /interfaces/interface/ipv4/address path:

        • Interface is defined in ietf-interfaces
        • IPv4 config is defined in ietf-ip (augments ietf-interfaces)
        • A rule with module-name: \"ietf-interfaces\" does NOT cover ipv4/address
        "},{"location":"nacm/#path-matching","title":"Path Matching","text":"

        Matches a specific data tree path and all nodes under it, including augments from other modules:

        {\n  \"name\": \"permit-network-config\",\n  \"path\": \"/ietf-interfaces:interfaces/interface\",\n  \"access-operations\": \"*\",\n  \"action\": \"permit\"\n}\n

        This permits operations on /interfaces/interface and all child nodes, including augments like:

        • /interfaces/interface/ipv4 (from ietf-ip)
        • /interfaces/interface/ipv6 (from ietf-ip)
        • /interfaces/interface/bridge-port (from infix-interfaces)

        Path syntax:

        • When used with module-name: Path is module-relative (no prefix)

          \"module-name\": \"ietf-system\",\n\"path\": \"/system/authentication/user/password\"\n
        • When used without module-name: Path must include module prefix

          \"path\": \"/ietf-system:system/authentication/user/password\"\n

        Use path-based rules

        When you want to permit/deny access to a configuration subtree including all augments, e.g., all IP settings below /ietf-interfaces:interfaces/. This is more flexible and requires less maintenance as new features are added.

        "},{"location":"nacm/#yang-level-annotations","title":"YANG-Level Annotations","text":"

        Many YANG modules include NACM annotations that provide baseline security:

        "},{"location":"nacm/#nacmdefault-deny-all","title":"nacm:default-deny-all","text":"

        Requires an explicit permit rule, regardless of global defaults:

        rpc system-restart {\n  nacm:default-deny-all;\n  description \"Restart the system\";\n}\n

        Even with exec-default: \"permit\", users need an explicit permit rule to execute system-restart.

        "},{"location":"nacm/#nacmdefault-deny-write","title":"nacm:default-deny-write","text":"

        Write operations require an explicit permit rule:

        container authentication {\n  nacm:default-deny-write;\n  description \"User authentication configuration\";\n}\n

        Even with write-default: \"permit\", users need an explicit permit rule to modify authentication settings.

        "},{"location":"nacm/#protected-operations","title":"Protected Operations","text":"

        The following are protected by YANG annotations and require explicit permits:

        RPC Operations:

        • ietf-system:system-restart (ietf-system)
        • ietf-system:system-shutdown (ietf-system)
        • ietf-system:set-current-datetime (ietf-system)
        • infix-factory-default:factory-default
        • ietf-factory-default:factory-reset (RFC 8808)
        • infix-system-software:install-bundle
        • infix-system-software:set-boot-order

        Data Containers:

        • /system/authentication (nacm:default-deny-write, ietf-system)
        • /nacm (nacm:default-deny-all, RFC 8341)
        • Routing protocol key chains (ietf-key-chain)
        • RADIUS shared secrets (ietf-system)
        • TLS client/server credentials (ietf-tls-client)

        This provides defense-in-depth - even if NACM rules are misconfigured, these critical operations remain protected.

        "},{"location":"nacm/#access-operations","title":"Access Operations","text":"

        NACM supports the following access operations:

        • create - Create new data nodes
        • read - Read existing data nodes
        • update - Modify existing data nodes
        • delete - Delete data nodes
        • exec - Execute RPC operations
        • * - All operations (wildcard)

        Common combinations:

        • \"create update delete\" - All write operations
        • \"*\" - Everything (read, write, execute)
        • \"read\" - Read-only access
        "},{"location":"nacm/#rule-list-groups","title":"Rule-List Groups","text":"

        Each rule-list applies to one or more user groups:

        {\n  \"name\": \"operator-acl\",\n  \"group\": [\"operator\"],\n  \"rule\": [...]\n}\n

        Special group names:

        • \"*\" - Matches all users (including those not in any NACM group)

        Evaluation: A user can be in multiple NACM groups. All rule-lists matching the user's groups are evaluated in order until a matching rule is found.

        "},{"location":"nacm/#example-factory-configuration","title":"Example: Factory Configuration","text":"

        The factory configuration uses a \"permit by default, deny sensitive items\" approach. This design is \"future proof\" - when new YANG modules are added, operators can immediately configure them without updating NACM rules.

        {\n  \"ietf-netconf-acm:nacm\": {\n    \"enable-nacm\": true,\n    \"read-default\": \"permit\",\n    \"write-default\": \"permit\",\n    \"exec-default\": \"permit\",\n    \"groups\": {\n      \"group\": [\n        {\"name\": \"admin\", \"user-name\": [\"admin\"]},\n        {\"name\": \"operator\", \"user-name\": []},\n        {\"name\": \"guest\", \"user-name\": []}\n      ]\n    },\n    \"rule-list\": [\n      {\n        \"name\": \"admin-acl\",\n        \"group\": [\"admin\"],\n        \"rule\": [\n          {\n            \"name\": \"permit-all\",\n            \"module-name\": \"*\",\n            \"access-operations\": \"*\",\n            \"action\": \"permit\",\n            \"comment\": \"Admin has full unrestricted access\"\n          }\n        ]\n      },\n      {\n        \"name\": \"operator-acl\",\n        \"group\": [\"operator\"],\n        \"rule\": [\n          {\n            \"name\": \"permit-system-rpcs\",\n            \"module-name\": \"ietf-system\",\n            \"rpc-name\": \"*\",\n            \"access-operations\": \"exec\",\n            \"action\": \"permit\",\n            \"comment\": \"Operators can reboot, shutdown, and set system time\"\n          }\n        ]\n      },\n      {\n        \"name\": \"guest-acl\",\n        \"group\": [\"guest\"],\n        \"rule\": [\n          {\n            \"name\": \"deny-all-write+exec\",\n            \"module-name\": \"*\",\n            \"access-operations\": \"create update delete exec\",\n            \"action\": \"deny\",\n            \"comment\": \"Guests can only read, not modify or execute\"\n          }\n        ]\n      },\n      {\n        \"name\": \"default-deny-all\",\n        \"group\": [\"*\"],\n        \"rule\": [\n          {\n            \"name\": \"deny-password-access\",\n            \"path\": \"/ietf-system:system/authentication/user/password\",\n            \"access-operations\": \"*\",\n            \"action\": \"deny\",\n            \"comment\": \"No user except admins can access password hashes\"\n          },\n          {\n            \"name\": \"deny-keystore-access\",\n            \"module-name\": \"ietf-keystore\",\n            \"access-operations\": \"*\",\n            \"action\": \"deny\",\n            \"comment\": \"No user except admins can access cryptographic keys\"\n          },\n          {\n            \"name\": \"deny-truststore-access\",\n            \"module-name\": \"ietf-truststore\",\n            \"access-operations\": \"*\",\n            \"action\": \"deny\",\n            \"comment\": \"No user except admins can access trust store\"\n          }\n        ]\n      }\n    ]\n  }\n}\n

        Key design decisions:

        1. Permit by default - write-default: permit allows operators to configure any module
        2. Minimal operator rules - Only one rule to permit system RPCs (reboot, set time)
        3. Future proof - New YANG modules automatically configurable by operators
        4. Targeted denials - Only sensitive items (passwords, keys) are explicitly denied
        5. Global denials - Password/keystore/truststore denied for everyone via group \"*\"
        6. YANG annotations - Sensitive operations (factory-reset, software upgrades) still protected by nacm:default-deny-all in YANG modules

        Effective permissions by group:

        Group Read Write Exec Exceptions admin All All All None operator All All All Cannot access passwords, keystore, truststore guest All None None Read-only access"},{"location":"nacm/#common-patterns","title":"Common Patterns","text":""},{"location":"nacm/#permit-by-default","title":"Permit-by-Default","text":"

        The factory default approach - allow everything except sensitive items:

        {\n  \"write-default\": \"permit\",\n  \"exec-default\": \"permit\",\n  \"rule-list\": [\n    {\n      \"name\": \"admin-acl\",\n      \"group\": [\"admin\"],\n      \"rule\": [\n        {\n          \"name\": \"permit-all\",\n          \"module-name\": \"*\",\n          \"access-operations\": \"*\",\n          \"action\": \"permit\"\n        }\n      ]\n    },\n    {\n      \"name\": \"global-denials\",\n      \"group\": [\"*\"],\n      \"rule\": [\n        {\n          \"name\": \"deny-passwords\",\n          \"path\": \"/ietf-system:system/authentication/user/password\",\n          \"access-operations\": \"*\",\n          \"action\": \"deny\"\n        }\n      ]\n    }\n  ]\n}\n

        This approach is \"future proof\" - new YANG modules are automatically accessible without rule updates. Admins bypass the global denials because their permit-all rule is evaluated first.

        "},{"location":"nacm/#deny-by-default","title":"Deny-by-Default","text":"

        More restrictive approach - deny everything except what is explicitly allowed:

        {\n  \"write-default\": \"deny\",\n  \"exec-default\": \"deny\",\n  \"rule-list\": [\n    {\n      \"group\": [\"limited-user\"],\n      \"rule\": [\n        {\n          \"name\": \"permit-interface-config\",\n          \"path\": \"/ietf-interfaces:interfaces/interface\",\n          \"access-operations\": \"create update delete\",\n          \"action\": \"permit\"\n        }\n      ]\n    }\n  ]\n}\n

        Note

        This requires updating NACM rules whenever new features are added.

        "},{"location":"nacm/#global-restrictions","title":"Global Restrictions","text":"

        Deny access to sensitive data for all users (except admins with permit-all):

        {\n  \"rule-list\": [\n    {\n      \"group\": [\"*\"],\n      \"rule\": [\n        {\n          \"name\": \"deny-passwords\",\n          \"path\": \"/ietf-system:system/authentication/user/password\",\n          \"access-operations\": \"*\",\n          \"action\": \"deny\"\n        }\n      ]\n    }\n  ]\n}\n
        "},{"location":"nacm/#debugging-nacm","title":"Debugging NACM","text":""},{"location":"nacm/#viewing-effective-permissions","title":"Viewing Effective Permissions","text":"

        Check what NACM groups a user belongs to:

        admin@example:/> show nacm\nenabled              : yes\ndefault read access  : permit\ndefault write access : permit\ndefault exec access  : permit\ndenied operations    : 0\ndenied data writes   : 0\ndenied notifications : 0\n\n          \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n          \u2502 GROUP    \u2502  READ   \u2502  WRITE  \u2502  EXEC   \u2502\n          \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n          \u2502 admin    \u2502    \u2713    \u2502    \u2713    \u2502    \u2713    \u2502\n          \u2502 operator \u2502    \u26a0    \u2502    \u26a0    \u2502    \u26a0    \u2502\n          \u2502 guest    \u2502    \u26a0    \u2502    \u2717    \u2502    \u2717    \u2502\n          \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n              \u2713 Full    \u26a0 Restricted    \u2717 Denied\n\nUSER                   SHELL   LOGIN                            \nadmin                  bash    password+key\njacky                  bash    password\nmonitor                false   key\n\nGROUP                  USERS                                    \nadmin                  admin\noperator               jacky\nguest                  monitor\n

        For details about a group's restrictions, use show nacm group <name>:

        admin@example:/> show nacm group operator\nmembers          : jacky\nread permission  : restricted\nwrite permission : restricted\nexec permission  : restricted\napplicable rules : 4\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\npermit-system-rpcs\n  action     : permit\n  operations : exec\n  target     : ietf-system (rpc: *)\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-password-access (via '*')\n  action     : deny\n  operations : *\n  target     : /ietf-system:system/authentication/user/password\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-keystore-access (via '*')\n  action     : deny\n  operations : *\n  target     : ietf-keystore\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-truststore-access (via '*')\n  action     : deny\n  operations : *\n  target     : ietf-truststore\n
        "},{"location":"nacm/#testing-access","title":"Testing Access","text":"

        The easiest way to test NACM permissions is to log in as the user and try the operation:

        $ ssh jacky@host\njacky@example:/> configure\njacky@example:/config/> edit system authentication user admin\njacky@example:/config/system/authentication/user/admin/> set authorized-key foo\nError: Access to the data model \"ietf-system\" is denied because \"jacky\" NACM authorization failed.\nError: Failed applying changes (2).\n
        "},{"location":"nacm/#nacm-statistics","title":"NACM Statistics","text":"

        NACM tracks denied operations. If you suspect permission issues, check the statistics:

        admin@example:/> show nacm\n...\n  denied operations      : 5\n  denied data writes     : 12\n...\n

        Increasing counters indicate permission denials are occurring.

        "},{"location":"nacm/#best-practices","title":"Best Practices","text":"
        1. Leverage permit-by-default - The factory configuration uses write-default: \"permit\" with targeted denials. This is \"future proof\" - new features work immediately without NACM updates.

        2. Protect sensitive items globally - Use group: [\"*\"] rule-list to deny access to passwords, cryptographic keys, and similar sensitive data. Admin's permit-all rule (evaluated first) bypasses these denials.

        3. Leverage YANG annotations - Many sensitive operations are already protected by nacm:default-deny-all in YANG modules (factory-reset, software upgrades, etc.). Only add explicit permit rules when needed.

        4. Order matters - Rule-lists are evaluated in order. Place admin's permit-all rule first so it bypasses global denials.

        5. Use path-based denials - For protecting specific data (like password hashes), use path rules. For protecting entire modules (like keystore), use module-name rules.

        6. Test thoroughly - Always test user permissions after changes. NACM errors can be subtle (nodes may be silently omitted from read operations).

        7. Keep it simple - The factory configuration uses only 6 rules for three user levels. Fewer rules are easier to understand and maintain.

        8. Document rules - Use the \"comment\" field to explain why specific permissions are granted or denied.

        "},{"location":"nacm/#references","title":"References","text":"
        • RFC 8341: Network Configuration Access Control Model (NACM)
        • RFC 7317: A YANG Data Model for System Management (ietf-system)
        • RFC 8808: A YANG Data Model for Factory Default Settings (ietf-factory-default)
        • RFC 8177: YANG Key Chain (ietf-key-chain)
        • System Configuration - Multiple Users
        "},{"location":"netboot/","title":"Netboot HowTo","text":""},{"location":"netboot/#netboot-howto","title":"Netboot HowTo","text":"

        This document describes how to set up network booting U-Boot devices on a LAN, e.g., when working with an evaluation board or other embedded system. The most secure way to do this is with a local LAN between a PC and the device.

        Instead of setting up everything in U-Boot to download the Linux Image, device tree, and initramfs, we will let U-Boot download a script with instructions to run. When you have multiple systems (boards) this quickly becomes a lot easier to manage.

        Note

        Instructions in this HowTo assume a Debian based development system, e.g., Ubuntu or Linux Mint.

        "},{"location":"netboot/#network-interface-setup","title":"Network Interface Setup","text":"

        For two dedicated network interfaces, here eth2 and eth3 (ymmv), we create an old-style interfaces config1 with the following content:

        # /etc/network/interfaces.d/gimli\nauto eth2\niface eth2 inet static\n        address 192.168.0.1\n        netmask 255.255.255.0\n\nauto eth3\niface eth3 inet manual\n

        Tip

        Use configuration file names in .d/ directories that make sense and can easily be remembered. Here we use the hostname of the PC.

        "},{"location":"netboot/#dhcptftp-server-setup","title":"DHCP/TFTP Server Setup","text":"

        The examples given here use dnsmasq, which provides both DHCP and TFTP server support. The same can be achieved with other implementations.

        Similar to interfaces.d, dnsmasq has an /etc/dnsmasq.d directory so we can use \"snippets\" instead of modifying /etc/dnsmasq.conf directly. Add a file called /etc/dnsmasq.d/gimli.

        Initial content:

        # Remember IP address handed out to BOOTP clients\nbootp-dynamic\n# Disable check-if-ip-address-is-already-used\nno-ping\n\n# Enable TFTP server, use /srv/ftp, same as any FTP server, useful\n# when using the same images for system upgrade as for netbooting.\nenable-tftp\ntftp-root=/srv/ftp\n

        Caution

        First of all, make sure you DO NOT accidentally set up dnsmasq so that it starts acting as a DHCP server also on your office LAN! Follow the instructions below for more details.

        If you have many interfaces used for lab equipment and only one office LAN interface, then use something like this:

        # Disable DHCP server on loopback and office LAN (eth0)\nexcept-interface=lo\nexcept-interface=eth0\n

        To further lock this down, we only run the DHCP server on each of the interfaces used for lab equipment, with a dedicated IP range as well:

        # Currently I have an imx8mp-evk on eth2, so on my system I have a\n# symlink bootfile-eth2 -> netboot.scr\ninterface=eth2\ndhcp-range=192.168.0.100,192.168.0.199,1h\ndhcp-boot=tag:eth2,bootfile-eth2\n
        "},{"location":"netboot/#bootfile-netbootscr","title":"Bootfile netboot.scr","text":"

        The bootfile U-Boot retrieves from the TFTP server is a script that looks like this, netboot.sh:

        setenv ramdisk_addr_r 0x58000000\nsetenv fdt_addr_r     0x50400000\n\nsetenv autoboot off\ntftp ${fdt_addr_r}     imx8mp-evk/imx8mp-evk.dtb\ntftp ${kernel_addr_r}  imx8mp-evk/Image\ntftp ${ramdisk_addr_r} imx8mp-evk/rootfs.squashfs\n\nsetenv bootargs console=ttymxc1,115200 root=/dev/ram0 brd.rd_size=500000 rauc.slot=net\nbooti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r}\n

        U-Boot cannot read script files directly, so we need to wrap it with a FIT format header, this is done by first converting it on the PC:

        $ mkimage -T script -d netboot.sh netboot.scr\n

        The output is netboot.scr which we symlink to above in the dnsmasq setup step.

        "},{"location":"netboot/#u-boot-commands","title":"U-Boot Commands","text":"

        U-Boot is a maze of environment variables, some with values, some wrap commands, and most are undocumented. We will use a prefix for our variables to ensure we do not overwrite anything you may want to use later.

        ==> setenv ixboot 'dhcp && source \\${fileaddr}'\n==> saveenv\n
        1. To prevent NetworkManager from automatically managing the interfaces.\u00a0\u21a9

        "},{"location":"networking/","title":"Overview","text":""},{"location":"networking/#network-configuration","title":"Network Configuration","text":"

        Infix aims to support all Linux Networking constructs. The YANG models used to describe the system are chosen to fit well and leverage the underlying Linux kernel's capabilities. The ietf-interfaces.yang model forms the base, extended with ietf-ip.yang and other layer-3 IETF models. The layer-2 bridge and aggregate models are defined by Infix to exploit the unique features not available in IEEE models.

        Important

        When issuing leave to activate your changes, remember to also save your settings, copy running-config startup-config. See the CLI Introduction for a background.

        "},{"location":"networking/#interface-lego","title":"Interface LEGO\u00ae","text":"

        The network building blocks available in Linux are akin to the popular LEGO\u00ae bricks.

        There are two types of relationships that can link two blocks together:

        1. Lower-to-upper: Visually represented by an extruding square connected upwards to a square socket. An interface can only have a single lower-to-upper relationship, i.e., it can be attached to a single upper interface like a bridge or a LAG. In iproute2 parlance, this corresponds to the interface's master setting
        2. Upper-to-lower: Visually represented by an extruding semicircle connected downwards to a semicircle socket. The lower interface in these relationships accepts multiple upper-to-lower relationships from different upper blocks. E.g., multiple VLANs and IP address blocks can be connected to the same lower interface

        An interface may simultaneously have a lower-to-upper relation to some other interface, and be the target of one or more upper-to-lower relationships. It is valid, for example, for a physical port to be attached to a bridge, but also have a VLAN interface stacked on top of it. In this example, traffic assigned to the VLAN in question would be diverted to the VLAN interface before entering the bridge, while all other traffic would be bridged as usual.

        Type Yang Model Description bridge infix-if-bridge SW implementation of an IEEE 802.1Q bridge ip ietf-ip, infix-ip IP address to the subordinate interface vlan infix-if-vlan Capture all traffic belonging to a specific 802.1Q VID lag infix-if-lag Link aggregation, static and IEEE 802.3ad (LACP) lo ietf-interfaces Software loopback interface eth ieee802-ethernet-interface Physical Ethernet device/port infix-ethernet-interface veth infix-if-veth Virtual Ethernet pair, typically one end is in a container common ietf-interfaces, Properties common to all interface types infix-interfaces"},{"location":"networking/#data-plane","title":"Data Plane","text":"

        The blocks you choose, and how you connect them, defines your data plane. Here we see an example of how to bridge a virtual port with a physical LAN.

        Depending on the (optional) VLAN filtering of the bridge, the container may have full or limited connectivity with outside ports, as well as the internal CPU.

        In fact the virtual port connected to the bridge can be member of several VLANs, with each VLAN being an interface with an IP address inside the container.

        Thanks to Linux, and technologies like switchdev, that allow you to split a switching fabric into unique (isolated) ports, the full separation and virtualization of all Ethernet layer properties are possible to share with a container. Meaning, all the building blocks used on the left hand side can also be used freely on the right hand side as well.

        "},{"location":"ntp/","title":"NTP Server","text":""},{"location":"ntp/#ntp-server","title":"NTP Server","text":"

        The NTP (Network Time Protocol) server provides accurate time synchronization for network clients. It supports both standalone operation with a local reference clock and hybrid mode where it synchronizes with upstream servers while serving time to downstream clients.

        Note

        The NTP server is mutually exclusive with the NTP client in system configuration context.

        "},{"location":"ntp/#standalone-mode","title":"Standalone Mode","text":"

        Configure a standalone NTP server using only a local reference clock:

        admin@example:/> configure\nadmin@example:/config/> edit ntp\nadmin@example:/config/ntp/> leave\n

        When setting up NTP via the CLI the system automatically configures a local reference clock. The default stratum is 16 (unsynchronized), which is suitable for isolated networks. For production use, configure a specific stratum level:

        admin@example:/config/> edit ntp\nadmin@example:/config/ntp/> set refclock-master master-stratum 10\nadmin@example:/config/ntp/> leave\n
        "},{"location":"ntp/#gps-reference-clock","title":"GPS Reference Clock","text":"

        A GPS/GNSS receiver can be used as an NTP reference clock source, providing stratum 1 time derived from the GPS satellite constellation. This requires a GPS hardware component to be configured first, see Hardware \u2014 GPS/GNSS Receivers.

        "},{"location":"ntp/#basic-setup","title":"Basic setup","text":"

        Add a GPS receiver as a reference clock source:

        admin@example:/config/> edit ntp\nadmin@example:/config/ntp/> edit refclock-master source gps0\nadmin@example:/config/ntp/refclock-master/source/gps0/> set poll 2\nadmin@example:/config/ntp/refclock-master/source/gps0/> set precision 0.1\nadmin@example:/config/ntp/refclock-master/source/gps0/> end\nadmin@example:/config/ntp/> leave\n

        Tunable parameters:

        Parameter Default Description poll 2 Polling interval in log2 seconds (2 = 4s) precision 0.1 Assumed precision in seconds (0.1 = 100ms) refid \"GPS\" Reference identifier (e.g., GPS, GNSS, GLO) prefer false Prefer this source over other reference clocks pps false Enable PPS for microsecond-level accuracy offset 0.0 Constant offset correction in seconds delay 0.0 Assumed maximum delay from the receiver"},{"location":"ntp/#pps-pulse-per-second","title":"PPS (Pulse Per Second)","text":"

        When the GPS receiver provides a PPS signal, enable the pps option for microsecond-level accuracy. With PPS, the GPS time provides the initial lock and the PPS edges discipline the clock:

        admin@example:/config/ntp/> edit refclock-master source gps0\nadmin@example:/config/ntp/refclock-master/source/gps0/> set pps true\nadmin@example:/config/ntp/refclock-master/source/gps0/> set precision 0.000001\nadmin@example:/config/ntp/refclock-master/source/gps0/> end\nadmin@example:/config/ntp/> leave\n
        "},{"location":"ntp/#monitoring","title":"Monitoring","text":"

        The show ntp command shows the GPS receiver as the reference clock source:

        admin@example:/> show ntp\nMode                : Server (GPS reference clock: gps0)\nPort                : 123\nStratum             : 1\nRef time (UTC)      : Sun Feb 08 19:44:36 2026\n

        Use show ntp source to see GPS reference clock details:

        admin@example:/> show ntp source\nReference Clock     : gps0 (u-blox)\nStatus              : selected\nFix Mode            : 3D\nSatellites          : 9/17 (used/visible)\n
        "},{"location":"ntp/#server-mode","title":"Server Mode","text":"

        Synchronize from upstream NTP servers while serving time to clients:

        admin@example:/config/> edit ntp\nadmin@example:/config/ntp/> edit unicast-configuration 0.pool.ntp.org type uc-server\nadmin@example:/config/ntp/\u2026/0.pool.ntp.org/type/uc-server/> set iburst true\nadmin@example:/config/ntp/\u2026/0.pool.ntp.org/type/uc-server/> end\nadmin@example:/config/ntp/> edit unicast-configuration 1.pool.ntp.org type uc-server\nadmin@example:/config/ntp/\u2026/1.pool.ntp.org/type/uc-server/> set iburst true\nadmin@example:/config/ntp/\u2026/1.pool.ntp.org/type/uc-server/> end\nadmin@example:/config/ntp/> leave\n

        The unicast-configuration uses a composite key with both address and type. Both hostnames and IP addresses are supported. The iburst option enables fast initial synchronization.

        "},{"location":"ntp/#peer-mode","title":"Peer Mode","text":"

        In peer mode, two NTP servers synchronize with each other bidirectionally. Each server acts as both client and server to the other:

        First peer:

        admin@peer1:/config/> edit ntp\nadmin@peer1:/config/ntp/> edit unicast-configuration 192.168.1.2 type uc-peer\nadmin@peer1:/config/ntp/\u2026/192.168.1.2/type/uc-peer/> end\nadmin@peer1:/config/ntp/> set refclock-master master-stratum 8\nadmin@peer1:/config/ntp/> leave\n

        Second peer:

        admin@peer2:/config/> edit ntp\nadmin@peer2:/config/ntp/> edit unicast-configuration 192.168.1.1 type uc-peer\nadmin@peer2:/config/ntp/\u2026/192.168.1.1/type/uc-peer/> end\nadmin@peer2:/config/ntp/> set refclock-master master-stratum 8\nadmin@peer2:/config/ntp/> leave\n

        This configuration provides mutual synchronization between peers. If one peer fails, the other continues to serve time to clients.

        Note

        The iburst and burst options are not supported in peer mode.

        "},{"location":"ntp/#peer-selection-in-symmetric-mode","title":"Peer Selection in Symmetric Mode","text":"

        When both peers have the same stratum (as in the example above where both are stratum 8), NTP's clock selection algorithm uses the Reference ID as the tie-breaker. The Reference ID is typically derived from the peer's IP address when using a local reference clock.

        This means the peer with the numerically lower IP address will be selected as the sync source by the other peer. In the example above:

        • peer1 (192.168.1.1) has a lower Reference ID
        • peer2 (192.168.1.2) will select peer1 as sync source

        This behavior is deterministic and ensures stable clock selection. If you need a specific peer to be selected, configure it with a lower stratum level than the other peer.

        "},{"location":"ntp/#timing-configuration","title":"Timing Configuration","text":""},{"location":"ntp/#poll-intervals","title":"Poll Intervals","text":"

        Control how often the NTP server polls upstream sources:

        admin@example:/config/ntp/> edit unicast-configuration 0.pool.ntp.org type uc-server\nadmin@example:/config/ntp/\u2026/0.pool.ntp.org/type/uc-server/> set minpoll 4\nadmin@example:/config/ntp/\u2026/0.pool.ntp.org/type/uc-server/> set maxpoll 10\nadmin@example:/config/ntp/\u2026/0.pool.ntp.org/type/uc-server/> end\n

        Poll intervals are specified as powers of 2: - minpoll 4 = poll every 2^4 = 16 seconds (minimum polling rate) - maxpoll 10 = poll every 2^10 = 1024 seconds (maximum polling rate) - Defaults: minpoll 6 (64 seconds), maxpoll 10 (1024 seconds)

        Use shorter intervals (minpoll 2-4) for faster convergence in test environments or peer configurations. Use defaults for production servers.

        "},{"location":"ntp/#fast-initial-synchronization","title":"Fast Initial Synchronization","text":"

        The makestep directive is automatically configured with safe defaults (1.0 seconds threshold, 3 updates limit) when creating an NTP server. This is critical for embedded systems without RTC that boot with epoch time.

        To customize the values:

        admin@example:/config/ntp/> edit makestep\nadmin@example:/config/ntp/makestep/> set threshold 2.0\nadmin@example:/config/ntp/makestep/> set limit 1\nadmin@example:/config/ntp/makestep/> end\n
        • threshold - If clock offset exceeds this (in seconds), step immediately instead of slewing slowly
        • limit - Number of updates during which stepping is allowed. After this, only gradual slewing is used for security

        With these defaults, a device booting at epoch time (1970-01-01) will sync to correct time within seconds instead of hours.

        "},{"location":"ntp/#monitoring_1","title":"Monitoring","text":"

        For a quick overview:

        To view the sources being used by the NTP client, run:

        admin@example:/> show ntp\nMode                : Client\nStratum             : 3\nRef time (UTC)      : Sat Jan 24 23:41:42 2026\n\nADDRESS         MODE    STATE     STRATUM  POLL\n147.78.228.41   server  outlier         2   64s\n192.168.0.1     server  unusable        0  128s\n176.126.86.247  server  selected        2   64s\n

        Check NTP source status:

        admin@example:/> show ntp source\nMS  Name/IP address  Stratum  Poll  Reach  LastRx          Last sample\n^+  147.78.228.41          2     6    007      15  -431us +/- 33.573ms\n^*  176.126.86.247         2     6    007      14   -389us +/- 4.307ms\n

        For detailed information about a specific source:

        admin@example:/> show ntp source 176.126.86.247\nAddress             : 176.126.86.247\nMode                : Server (client mode) [^]\nState               : Selected sync source [*]\nConfigured          : Yes\nStratum             : 2\nPoll interval       : 7 (2^7 seconds = 128s)\nReachability        : 377 (octal) = 11111111b\nLast RX             : 75s ago\nOffset              : +2.0us (+0.002000ms)\nDelay               : 4.270ms (0.004270s)\nDispersion          : 205.0us (0.205000ms)\n
        "},{"location":"ntp/#ntp-stratum-levels","title":"NTP Stratum Levels","text":"

        NTP uses a hierarchical system called stratum to indicate distance from authoritative time sources:

        • Stratum 0: Reference clocks (atomic clocks)
        • Stratum 1: Servers directly connected to stratum 0 (e.g., GPS receivers)
        • Stratum 2-15: Servers that sync from lower stratum (each hop adds one)
        • Stratum 16: Unsynchronized (invalid)

        The default stratum (16) is not suitable for distributing time in isolated networks, so when setting up an NTP server remember to adjust this value. Use, e.g., 10, this is a safe, low-priority value that ensures clients will prefer upstream-synchronized servers (stratum 1-9) while still having a fallback time source in isolated networks.

        "},{"location":"override-package/","title":"Developing with Buildroot","text":""},{"location":"override-package/#package-override","title":"Package Override","text":"

        This guide demonstrates how the local.mk file is utilized to override a Buildroot package, which can be very useful when fixing bugs, adding features, or evaluating new releases of packages. As an example we use tcpdump to illustrate this process.

        Tip

        For a comprehensive guide to utilizing Buildroot during development, including the <pkg>_OVERRIDE_SRCDIR mechanism, shown below, please see Using Buildroot during development in the official docs.

        "},{"location":"override-package/#setup","title":"Setup","text":"

        Since the output/ directory is often wiped for rebuilds, make sure you keep the file local.mk in the top directory and only symlink it to your build directory:

        ~$ cd infix/\n~/infix(main)$ touch local.mk\n~/infix/output(main)$ ln -s ../local.mk .\n~/infix/output(main)$ cd ..\n
        "},{"location":"override-package/#override","title":"Override","text":"

        Now edit the file:

        ~/infix(main)$ editor local.mk\n

        Add an override for tcpdump. The file is a Makefile snippet so you can add a lot of things. Comment out lines with the UNIX comment # character if needed:

        TCPDUMP_OVERRIDE_SRCDIR = /path/to/tcpdump/repo\n
        "},{"location":"override-package/#building","title":"Building","text":"

        The execution of make tcpdump-rebuild all triggers a process where Buildroot synchronizes the tcpdump source code from the specified override directory to output/build/tcpdump-custom, followed by the rebuilding of the entire project.

        ~/infix$(main)$ make tcpdump-rebuild all\n

        Buildroot follows a process of downloading and processing tarballs: extraction, configuration, compilation, and installation. The source for each package is extracted to a temporary build directory:

        output/build/<package>-<version>    # e.g., tcpdump-4.99.4\n

        Let's have a look at what we got:

        ~/infix$(main)$ ll /output/build/ | grep tcpdump\ndrwxr-xr-x   7 group user 20480 Nov 10 18:26 tcpdump-4.99.4/\ndrwxr-xr-x   7 group user 12288 Nov 10 18:28 tcpdump-custom/\n

        As long as your local override is in place, Buildroot will use your custom version.

        Note

        Remember, the build directory is ephemeral, so be careful to change any of the files therein. It can be useful though during debugging, but just make sure to learn the difference between the various Buildroot commands to build, clean, reconfigure, etc.

        "},{"location":"ptp/","title":"PTP (IEEE 1588/802.1AS)","text":""},{"location":"ptp/#ptp-precision-time-protocol","title":"PTP \u2014 Precision Time Protocol","text":"

        The Precision Time Protocol (PTP), defined in IEEE 1588-2019, synchronises clocks across a network to sub-microsecond accuracy. Where NTP (Network Time Protocol) aims at millisecond accuracy over wide-area networks, PTP is designed for local-area networks and relies on hardware timestamping in the network interface to eliminate software-induced jitter.

        PTP works by exchanging timestamped messages between devices. A grandmaster clock \u2014 elected by the Best TimeTransmitter Clock Algorithm (BTCA) based on priority, clock class, and accuracy \u2014 distributes time to the rest of the network. Each synchronising device measures the one-way message delay to its time-transmitter and continuously adjusts its local clock to compensate.

        Note

        The IEEE 1588g-2022 amendment to IEEE 1588-2019 introduced the terms timeTransmitter and timeReceiver as replacements for the former master and slave terminology, and Best TimeTransmitter Clock Algorithm (BTCA) in place of BMCA. This document uses the updated terms throughout. You may even see the short forms transmitter and receiver here and in online documentation.

        "},{"location":"ptp/#clock-roles","title":"Clock roles","text":"

        Every device in a PTP network takes one of the following roles:

        Role Description Grandmaster (GM) Network-wide time source; elected by BTCA Time-transmitter Sends Sync messages downstream on a port Time-receiver Synchronises to a time-transmitter on a port Boundary Clock (BC) Terminates PTP on each port; acts as time-receiver upstream and time-transmitter downstream Transparent Clock (TC) Passes PTP messages while correcting the residence-time delay accumulated in the device

        An Ordinary Clock (OC) has a single PTP port and is either a time-transmitter (acting as a grandmaster candidate) or a time-receiver (a leaf node synchronising to the network).

        "},{"location":"ptp/#ptp-profiles","title":"PTP profiles","text":"

        A PTP profile (as defined in IEEE 1588-2019 \u00a73.1) is a document that specifies a consistent set of required, permitted, and prohibited PTP options for a particular application domain \u2014 much like a dialect of the protocol. Examples from the standards world include profiles for power utilities (IEC/IEEE C37.238), telecom (ITU-T G.8265.1), and Time-Sensitive Networks.

        Each profile sets a unique value in the majorSdoId field of PTP message headers \u2014 a 4-bit identifier that lets devices distinguish traffic belonging to different profiles on the same link. Profile also determines the network transport (UDP or Ethernet) and the delay measurement mechanism.

        Currently, two profiles are supported via the profile leaf in default-ds:

        profile Standard majorSdoId Transport Delay ieee1588 (default) IEEE 1588-2019 0x0 UDP/IPv4 e2e or p2p ieee802-dot1as IEEE 802.1AS-2020 0x1 L2 p2p

        The gPTP (generalized Precision Time Protocol) profile from IEEE 802.1AS-2020 is used in TSN (Time-Sensitive Networking) and AVB (Audio/Video Bridging) applications. Setting profile ieee802-dot1as applies all protocol-mandatory settings automatically \u2014 Layer 2 transport, P2P delay measurement, 802.1AS multicast addressing, path trace, follow-up information, and neighbour propagation delay thresholds. The user still configures priority1, priority2, domain-number, time-receiver-only, and timer interval leaves.

        The ieee1588 profile leaves transport and delay mechanism user-configurable per port.

        "},{"location":"ptp/#delay-mechanisms","title":"Delay mechanisms","text":"

        PTP measures the link delay between neighbours using one of two mechanisms:

        • End-to-End (E2E): Each time-receiver measures the delay to the grandmaster by sending a DELAY_REQ message upstream. Simple to configure; works with any network topology.
        • Peer-to-Peer (P2P): Each port measures its delay to its immediate neighbour independently using PDELAY_REQ messages. Enables faster path-delay updates and is required by the gPTP profile.
        "},{"location":"ptp/#data-sets","title":"Data Sets","text":"

        IEEE 1588 organises protocol state into named Data Sets (DS) \u2014 each a collection of related attributes for one aspect of a PTP instance. You will encounter these directly in the CLI and in the show ptp output:

        Data Set CLI node Contents Default DS default-ds Instance identity, clock class, priority, domain number Current DS current-ds Live offset-from-GM, mean path delay, steps-removed Parent DS parent-ds Grandmaster identity and quality attributes Time Properties DS time-properties-ds UTC offset, leap-second flags, time source Port DS port-ds Per-port state, delay mechanism, message intervals"},{"location":"ptp/#domains","title":"Domains","text":"

        A PTP domain (0\u2013255) is a logical partition of the network. Devices only synchronise with others in the same domain. Running multiple instances on the same device \u2014 one per domain, or one per profile \u2014 is fully supported; each instance is independent.

        Each PTP instance is identified on the network by its (domain-number, profile) pair, which must be unique across all instances on a device.

        Note

        The show ptp offset values reflect PHC (PTP Hardware Clock) synchronisation only. A PHC is the hardware clock exposed by the network interface; it tracks the PTP grandmaster but is independent of the Linux system clock, which currently is not automatically adjusted.

        "},{"location":"ptp/#ordinary-clock-time-receiver","title":"Ordinary Clock (time-receiver)","text":"

        A typical time-receiver Ordinary Clock, synchronising on interface eth0 using the default IEEE 1588 profile:

        admin@example:/> configure\nadmin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> set default-ds time-receiver-only true\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> leave\n
        "},{"location":"ptp/#ordinary-clock-time-transmitter-grandmaster","title":"Ordinary Clock (time-transmitter / grandmaster)","text":"

        A grandmaster clock with high priority, domain 0:

        admin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> set default-ds priority1 1\nadmin@example:/config/ptp/instance/0/> set default-ds priority2 1\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> leave\n

        Lower priority1 values win in the BTCA. A clock with priority1 1 will be preferred over the default 128 in any compliant network.

        "},{"location":"ptp/#boundary-clock","title":"Boundary Clock","text":"

        A Boundary Clock terminates PTP on each port and re-originates it. Add one port per interface:

        admin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds instance-type bc\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> end\nadmin@example:/config/ptp/instance/0/> edit port 2\nadmin@example:/config/ptp/\u2026/0/port/2/> set underlying-interface eth1\nadmin@example:/config/ptp/\u2026/0/port/2/> leave\n

        Tip

        PTP port numbers are assigned sorted by port-index, so port-index 1 becomes PTP port 1, port-index 2 becomes PTP port 2, and so on.

        "},{"location":"ptp/#transparent-clock","title":"Transparent Clock","text":"

        Transparent Clocks correct timestamps end-to-end without terminating PTP. Use instance-type p2p-tc for a P2P TC (preferred in TSN networks) or instance-type e2e-tc for an E2E TC:

        admin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds instance-type p2p-tc\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> end\nadmin@example:/config/ptp/instance/0/> edit port 2\nadmin@example:/config/ptp/\u2026/0/port/2/> set underlying-interface eth1\nadmin@example:/config/ptp/\u2026/0/port/2/> leave\n

        Note

        For Transparent Clocks the delay mechanism is determined globally by the instance-type (p2p-tc \u2192 P2P, e2e-tc \u2192 E2E). Per-port delay-mechanism settings have no effect for TC instances.

        "},{"location":"ptp/#gptp-ieee-8021as","title":"gPTP / IEEE 802.1AS","text":"

        The gPTP profile is used in TSN and AVB applications. Setting profile ieee802-dot1as applies all protocol-mandatory options from IEEE 802.1AS-2020 automatically \u2014 Layer 2 transport, P2P delay measurement, 802.1AS multicast addressing, and related protocol features.

        admin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds profile ieee802-dot1as\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> set default-ds time-receiver-only true\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> leave\n

        Note

        The ieee802-dot1as profile enforces Layer 2 transport and P2P delay measurement globally, as required by IEEE 802.1AS-2020. Per-port delay-mechanism settings have no effect for 802.1AS instances.

        "},{"location":"ptp/#multiple-instances","title":"Multiple Instances","text":"

        Multiple PTP instances can run simultaneously, one per domain or profile combination. Each instance must have a unique (domain-number, profile) pair and an independent set of ports:

        admin@example:/config/> edit ptp instance 0\nadmin@example:/config/ptp/instance/0/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/0/> set default-ds profile ieee1588\nadmin@example:/config/ptp/instance/0/> edit port 1\nadmin@example:/config/ptp/\u2026/0/port/1/> set underlying-interface eth0\nadmin@example:/config/ptp/\u2026/0/port/1/> end\nadmin@example:/config/ptp/instance/0/> end\nadmin@example:/config/ptp/> edit instance 1\nadmin@example:/config/ptp/instance/1/> set default-ds domain-number 0\nadmin@example:/config/ptp/instance/1/> set default-ds profile ieee802-dot1as\nadmin@example:/config/ptp/instance/1/> edit port 1\nadmin@example:/config/ptp/\u2026/1/port/1/> set underlying-interface eth1\nadmin@example:/config/ptp/\u2026/1/port/1/> leave\n
        "},{"location":"ptp/#port-states","title":"Port states","text":"

        Each PTP port progresses through a state machine. The current state is shown in the show ptp port table:

        State Meaning initializing Port is starting up, not yet ready to exchange messages faulty A fault condition has been detected on this port disabled Port is administratively disabled listening Awaiting ANNOUNCE messages; BTCA has not yet resolved pre-time-transmitter Transitioning towards time-transmitter state time-transmitter Port is acting as time-transmitter on this link passive Another port on this device is already time-transmitter uncalibrated Receiving sync; local clock not yet locked to time-transmitter time-receiver Port is locked and tracking its time-transmitter

        A port in uncalibrated will typically transition to time-receiver within a few seconds once the clock servo has converged.

        "},{"location":"ptp/#monitoring","title":"Monitoring","text":"

        Use the ? key in the CLI

        The show ptp command has sub-commands \u2014 tap ? after show ptp to see them, or use Tab to complete.

        "},{"location":"ptp/#show-all-ptp-instances","title":"Show all PTP instances","text":"
        admin@example:/> show ptp\nPTP Instance 0                          Ordinary Clock \u00b7 domain 0\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  Clock identity          : AA-BB-CC-FF-FE-00-11-22\n  Grandmaster             : DD-EE-FF-FF-FE-33-44-55\n  Priority1/Priority2     : 128 / 128\n  GM Priority1/Priority2  : 1 / 1\n  Clock class             : cc-time-receiver-only\n  GM clock class          : cc-primary-sync\n  Time source             : gnss\n  PTP timescale           : yes\n  UTC offset              : 37 s\n  Time traceable          : yes\n  Freq. traceable         : yes\n  Offset from GM          : -42 ns\n  Mean path delay         : 1250 ns\n  Steps removed           : 1\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPorts\nPORT  INTERFACE          STATE                DELAY  LINK DELAY (ns)\n   1  eth0               time-receiver        E2E                  0\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nMessage Statistics  (\u25bc rx  \u25b2 tx)\nPORT  INTERFACE             SYNC \u25bc  SYNC \u25b2  ANN \u25bc  ANN \u25b2  PD \u25bc  PD \u25b2\n   1  eth0                      42       0     15      0     0     0\n\n

        Port state is colour-coded: green for time-transmitter and time-receiver (actively synchronising), yellow for transient states (listening, uncalibrated, pre-time-transmitter), and red for fault states (faulty, disabled). The Message Statistics section is omitted when no counts are available.

        "},{"location":"ptp/#show-a-specific-instance","title":"Show a specific instance","text":"
        admin@example:/> show ptp 0\n
        "},{"location":"ptp/#tuning-port-intervals","title":"Tuning port intervals","text":"

        Adjust announcement, sync, and delay-request intervals per port. Values are expressed as log\u2082 of the interval in seconds (e.g. -3 = 125 ms, 0 = 1 s, 1 = 2 s):

        admin@example:/config/ptp/\u2026/0/port/1/> set port-ds log-announce-interval 0\nadmin@example:/config/ptp/\u2026/0/port/1/> set port-ds log-sync-interval -3\nadmin@example:/config/ptp/\u2026/0/port/1/> set port-ds log-min-delay-req-interval 0\nadmin@example:/config/ptp/\u2026/0/port/1/> set announce-receipt-timeout 3\n

        announce-receipt-timeout is a count of announce intervals, not a duration in seconds. With log-announce-interval 0 (1 s) and announce-receipt-timeout 3, a port waits 3 s without receiving an ANNOUNCE before declaring the time-transmitter lost and returning to listening.

        "},{"location":"ptp/#message-exchange","title":"Message exchange","text":"

        PTP distributes time using a small set of messages, all of which carry hardware timestamps at the network interface:

        Message Timestamped Purpose ANNOUNCE No Advertises clock quality for BTCA election SYNC Yes Carries transmitter timestamp to receivers FOLLOW_UP No Carries precise t1 in two-step mode DELAY_REQ Yes Receiver-initiated E2E delay measurement DELAY_RESP No Time-transmitter reply to DELAY_REQ PDELAY_REQ Yes Initiates P2P neighbour-delay measurement PDELAY_RESP Yes Neighbour reply to PDELAY_REQ PDELAY_RESP_FOLLOW_UP No Carries precise PDELAY_RESP t3 in two-step mode

        In one-step mode the timestamp is embedded directly into each SYNC message as it leaves the wire, eliminating the need for FOLLOW_UP. In two-step mode the SYNC carries a placeholder and the precise transmit timestamp arrives in a subsequent FOLLOW_UP. Hardware timestamping gives high accuracy in both modes; one-step reduces message overhead at the cost of more demanding hardware support.

        "},{"location":"ptp/#message-format","title":"Message format","text":"

        Every PTP message begins with a common 34-octet header, regardless of type. The structure below follows the traditional IETF bit-field layout: each row is four octets wide, bit 7 (MSB) is on the left and bit 0 (LSB) on the right within each octet.

                 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  0-3   |trSpec |msgType|  rsv  |  ver  |         messageLength         |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  4-7   |  domainNumber |  minorSdoId   |             flags             |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n 8-15   |                                                               |\n        +                        correctionField                        +\n        |                                                               |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n16-19   |                      messageTypeSpecific                      |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n20-27   |                                                               |\n        +                         clockIdentity                         +\n        |                                                               |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n28-31   |          portNumber           |           sequenceId          |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n32-33   |  controlField | logMsgIntvl   |\n        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n
        • trSpec (transportSpecific, bits 7\u20134 of octet 0): 4-bit profile identifier. 0x0 = IEEE 1588, 0x1 = gPTP (802.1AS). Set implicitly by the profile configuration leaf.
        • msgType (messageType, bits 3\u20130 of octet 0): 0x0 SYNC \u00b7 0x1 DELAY_REQ \u00b7 0x2 PDELAY_REQ \u00b7 0x3 PDELAY_RESP \u00b7 0x8 FOLLOW_UP \u00b7 0x9 DELAY_RESP \u00b7 0xA PDELAY_RESP_FOLLOW_UP \u00b7 0xB ANNOUNCE.
        • rsv (reserved, bits 7\u20134 of octet 1): Set to zero; ignored on receipt.
        • ver (versionPTP, bits 3\u20130 of octet 1): PTP version; 2 for IEEE 1588-2008 and IEEE 1588-2019.
        • messageLength (octets 2\u20133): Total message length in octets, including the header.
        • domainNumber (octet 4): PTP domain; receivers silently discard messages that do not match their configured domain.
        • minorSdoId (octet 5): Reserved in IEEE 1588-2008; carries a profile sub-identifier in IEEE 1588-2019.
        • flags (octets 6\u20137): Per-message flags \u2014 includes the two-step flag (set when a FOLLOW_UP will follow a SYNC), UTC offset valid, and leap-second indicators.
        • correctionField (octets 8\u201315): Accumulated path correction in nanoseconds \u00d7 2\u00b9\u2076. Transparent Clocks add their measured residence time and link delay here as they forward each message, so the final time-receiver can subtract the total accumulated delay.
        • messageTypeSpecific (octets 16\u201319): Reserved in IEEE 1588-2008; carries message-type-specific data in IEEE 1588-2019.
        • clockIdentity (octets 20\u201327): EUI-64 identity of the sending clock \u2014 the value shown as \"Clock identity\" in show ptp.
        • portNumber (octets 28\u201329): Port number of the sender within its clock; together with clockIdentity it forms the unique sourcePortIdentity.
        • sequenceId (octets 30\u201331): Increments with each message; used to match a DELAY_REQ to its DELAY_RESP.
        • controlField (octet 32): Deprecated in PTPv2; set to fixed values per message type for backward compatibility with PTPv1.
        • logMsgIntvl (logMessageInterval, octet 33): Log\u2082 of the expected interval between messages of this type; 0x7F means not applicable.

        The transportSpecific and domainNumber fields are the quickest way to verify on the wire that a device is using the profile and domain you configured.

        "},{"location":"ptp/#decoding-with-wireshark","title":"Decoding with Wireshark","text":"

        Wireshark decodes PTP messages automatically, expanding every header field and message-type-specific payload in the packet tree. PTP travels over two UDP ports \u2014 319 for event messages (SYNC, DELAY_REQ, PDELAY_REQ and their responses) and 320 for general messages (ANNOUNCE, FOLLOW_UP) \u2014 as well as directly over Ethernet (EtherType 0x88F7) when layer-2 transport is in use.

        Use the display filter ptp to isolate PTP traffic:

        ptp\n

        To narrow down to a specific domain or profile (exact field names can be verified in Wireshark via View \u2192 Internals \u2192 Supported Protocols, filtering for ptp):

        ptp.v2.domainnumber == 0\nptp.v2.transportspecific == 1\n

        This makes it straightforward to confirm which grandmaster a port is tracking, verify that correctionField is being updated by a Transparent Clock, or diagnose why the BTCA is not electing the expected grandmaster.

        "},{"location":"ptp/#glossary","title":"Glossary","text":"Abbreviation Expansion Notes AVB Audio/Video Bridging IEEE 802.1 precursor to TSN; real-time AV over Ethernet IETF Internet Engineering Task Force Standards body; defines RFC for layer-3 and up UDP User Datagram Protocol IP transport used by PTP; port 319 (event) and 320 (general) EUI-64 Extended Unique Identifier (64-bit) IEEE identifier format used as clockIdentity in PTP EtherType Ethernet frame type field 0x88F7 identifies PTP over layer-2 Ethernet BC Boundary Clock Terminates and re-originates PTP on each port BTCA Best TimeTransmitter Clock Algorithm Elects the GM; replaces BMCA from IEEE 1588-2008 CMLDS Common Mean Link Delay Service IEEE 1588-2019 \u00a716.6; shared delay service for multiple instances DS Data Set Named attribute collection in IEEE 1588 (default-ds, port-ds, \u2026) E2E End-to-End Delay mechanism: measures path from GM to time-receiver GM Grandmaster PTP network-wide time source, elected by BTCA gPTP generalized Precision Time Protocol IEEE 802.1AS profile; used in TSN and AVB NTP Network Time Protocol Millisecond-accuracy time protocol for wide-area use OC Ordinary Clock Single-port PTP clock; time-transmitter or time-receiver P2P Peer-to-Peer Delay mechanism: measures delay to immediate neighbour PHC PTP Hardware Clock Hardware clock in the NIC used for PTP timestamping PTP Precision Time Protocol IEEE 1588 sub-microsecond clock synchronisation protocol SDO Standards Development Organization Body that defines a PTP profile; encoded in sdo-id TC Transparent Clock Forwards PTP messages, correcting for residence-time delay TSN Time-Sensitive Networking IEEE 802.1 standard set for deterministic Ethernet"},{"location":"qos/","title":"Quality of Service","text":""},{"location":"qos/#quality-of-service","title":"Quality of Service","text":"

        On occasion, most networks will experience congestion due to some extraordinary load being placed upon it. If the load is transient, switches and routers may be able to absorb such bursts of traffic by queuing packets in internal memories. However, if the load is sustained over long periods of time, queues will fill up and packets will start to be dropped. When such situations arise, it is the job of the network's Quality of Service (QoS) policy to define which packets to drop and which ones to prioritize, such that critical services remain operational.

        "},{"location":"qos/#hardware-forwarded-traffic","title":"Hardware Forwarded Traffic","text":"

        The default QoS policy for flows which are offloaded to a switching ASIC is defined by the hardware defaults of the device in question.

        "},{"location":"qos/#marvell-linkstreet","title":"Marvell LinkStreet","text":"

        This family of devices, sometimes also referred to as SOHO, are managed by the mv88e6xxx driver in the Linux kernel. While older chips in this family where limited to 4 output queues per port, this documentation is only valid for newer generations with 8 output queues per port.

        The picture illustrates packets having their priority determined at ingress, here interface e1 and e3. In this example, both packets are forwarded to the same outgoing interface (e2), subject to output queueing. The sections below provides more information on these topics.

        "},{"location":"qos/#default-policy","title":"Default Policy","text":""},{"location":"qos/#queueing","title":"Queueing","text":"

        Both layer 2 (VLAN PCP) and layer 3 (IP DSCP) priority marks are considered when selecting the output queue of an incoming frame. PCP to queue mapping is done 1:1. For IP packets, the 3 most significant bits of the DSCP is used to select the queue:

        PCP DSCP \u21d2 Queue Weight 0 0-7 \u21d2 0 1 1 8-15 \u21d2 1 2 2 16-23 \u21d2 2 3 3 24-31 \u21d2 3 6 4 32-39 \u21d2 4 12 5 40-47 \u21d2 5 17 6 48-55 \u21d2 6 25 7 56-63 \u21d2 7 33

        For packets containing both a VLAN tag and an IP header, PCP priority takes precedence over DSCP priority. In cases where neither are available, packets are always assigned to queue 0.

        Each port's set of 8 egress queues operate on a Weighted Round Robin (WRR) schedule, using the weights listed in the table above. The sum of all weights adds up to 99, meaning that the weight of any given queue is roughly equivalent to the percentage of the available bandwidth reserved for it.

        "},{"location":"qos/#marking","title":"Marking","text":"

        Any priority marks available on ingress are left unmodified when the frame egresses an output port. In the case when an IP packet ingresses without a VLAN tag, and is to egress with a VLAN tag, its PCP is set to the 3 most significant bits of it. If no priority information is available in the frame on ingress (i.e. untagged non-IP), then packets will egress out of tagged ports with PCP set to 0.

        "},{"location":"qos/#software-forwarded-traffic","title":"Software Forwarded Traffic","text":"

        For packets which are processed by a CPU, i.e. typically routed traffic, and bridged traffic between interfaces that do not belong to the same hardware switching domain, an nftables container can be used to define a QoS policy.

        For VLAN interfaces, Infix provides support for mapping the Priority Code Point (PCP) to internal priority on ingress, and the reverse on egress.

        These ingress-qos and egress-qos settings are done per VLAN, both defaulting to '0'. The example below shows how to keep the PCP priority for packets being routed between two VLAN interfaces.

        admin@example:/config/> edit interface e1.10\nadmin@example:/config/interface/e1.10/> set vlan ingress-qos priority from-pcp\nadmin@example:/config/interface/e1.10/> up\nadmin@example:/config/> edit interface e1.20\nadmin@example:/config/interface/e1.20/> set vlan egress-qos pcp from-priority\nadmin@example:/config/interface/e1.20/> leave\nadmin@example:/>\n
        "},{"location":"qos/#a-complex-example","title":"A complex example","text":"

        The picture below shows a packet flow being subject both to software forwarding and hardware offloading.

        "},{"location":"routing/","title":"Routing","text":""},{"location":"routing/#routing","title":"Routing","text":"

        Currently supported YANG models:

        YANG Model Description ietf-routing Base model for all other models ietf-ipv4-unicast-routing Static IPv4 unicast routing ietf-ipv6-unicast-routing Static IPv6 unicast routing ietf-ospf OSPF routing ietf-rip RIP routing infix-routing Infix deviations and extensions

        The base model, ietf-routing, is where all the other models hook in. It is used to set configuration and read operational status (RIB tables) in the other models.

        Note

        The standard IETF routing models allows multiple instances, but Infix currently only support one instance per routing protocol! In the examples presented here, the instance name default is used.

        "},{"location":"routing/#ipv4-static-routes","title":"IPv4 Static routes","text":"

        The standard IETF model for static routes reside under the static control plane protocol. For our examples we use the instance name default, you can use any name.

        The most common case when using a static IP setup is adding a default route (i.e., the default gateway):

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol static name default ipv4\nadmin@example:/config/routing/\u2026/ipv4/> set route 0.0.0.0/0 next-hop next-hop-address 192.168.1.1\nadmin@example:/config/routing/\u2026/ipv4/> leave\nadmin@example:/>\n

        For a route with destination 192.168.200.0/24 via 192.168.1.1:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol static name default ipv4\nadmin@example:/config/routing/\u2026/ipv4/> set route 192.168.200.0/24 next-hop next-hop-address 192.168.1.1\nadmin@example:/config/routing/\u2026/ipv4/> leave\nadmin@example:/>\n

        For a \"floating\" static route with destination 10.0.0.0/16 via a backup router 192.168.1.1, using the highest possible distance:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol static name default ipv4\nadmin@example:/config/routing/\u2026/ipv4/> set route 10.0.0.0/16 next-hop next-hop-address 192.168.1.1 route-preference 254\nadmin@example:/config/routing/\u2026/ipv4/> leave\nadmin@example:/>\n

        Tip

        Remember to enable IPv4 forwarding for the interfaces you want to route between.

        "},{"location":"routing/#ipv6-static-routes","title":"IPv6 Static routes","text":"

        Default route via an IPv6 gateway:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol static name default ipv6\nadmin@example:/config/routing/\u2026/ipv6/> set route ::/0 next-hop next-hop-address 2001:db8:3c4d:1::1\nadmin@example:/config/routing/\u2026/ipv6/> leave\nadmin@example:/>\n

        For a route with destination 2001:db8:3c4d:200::/64 via 2001:db8:3c4d:1::1:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol static name default ipv6\nadmin@example:/config/routing/\u2026/ipv6/> set route 2001:db8:3c4d:200::/64 next-hop next-hop-address 2001:db8:3c4d:1::1\nadmin@example:/config/routing/\u2026/ipv6/> leave\nadmin@example:/>\n
        "},{"location":"routing/#ospfv2-routing","title":"OSPFv2 Routing","text":"

        The system supports OSPF dynamic routing for IPv4, i.e., OSPFv2. To enable OSPF and set one active interface in area 0:

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 enabled\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n

        Tip

        Remember to enable IPv4 forwarding for all the interfaces you want to route between.

        "},{"location":"routing/#ospf-area-types","title":"OSPF area types","text":"

        In addition to regular OSPF areas, area types NSSA and Stub are also supported. To configure an NSSA area with summary routes:

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.1 area-type nssa-area\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.1 summary true\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n
        "},{"location":"routing/#bidirectional-forwarding-detection-bfd","title":"Bidirectional Forwarding Detection (BFD)","text":"

        It is possible to enable BFD per OSPF interface to speed up detection of link loss.

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 bfd enabled true\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n
        "},{"location":"routing/#ospf-interface-settings","title":"OSPF interface settings","text":"

        We have already seen how to enable OSPF per interface (enabled true) and BFD for OSPF per interface (bfd enabled true). These and other OSPF interface settings are done in context of an OSFP area, e.g., area 0.0.0.0. Available commands can be listed using the ? mark.

        admin@example:/config/routing/\u2026/> edit ospf area 0.0.0.0\nadmin@example:/config/routing/\u2026/ospf/area/0.0.0.0/> edit interface e0\nadmin@example:/config/routing/\u2026/ospf/area/0.0.0.0/interface/e0/> set ?\n  bfd                  BFD interface configuration.\n  cost                 Interface's cost.\n  dead-interval        Interval after which a neighbor is declared down\n  enabled              Enables/disables the OSPF protocol on the interface.\n  hello-interval       Interval between Hello packets (seconds).  It must\n  interface-type       Interface type.\n  passive              Enables/disables a passive interface.  A passive\n  retransmit-interval  Interval between retransmitting unacknowledged Link\n  transmit-delay       Estimated time needed to transmit Link State Update\nadmin@example:/config/routing/\u2026/ospf/area/0.0.0.0/interface/e0/> set\n

        For example, setting the OSPF interface type to point-to-point for an Ethernet interface can be done as follows.

        admin@example:/config/routing/\u2026/ospf/area/0.0.0.0/interface/e0/> set interface-type point-to-point\nadmin@example:/config/routing/\u2026/ospf/area/0.0.0.0/interface/e0/>\n
        "},{"location":"routing/#point-to-multipoint","title":"Point-to-Multipoint","text":"

        Point-to-Multipoint (P2MP) is used when multiple OSPF routers share a common network segment but should form individual adjacencies rather than electing a Designated Router (DR). This is common in NBMA-like environments, DMVPN, or hub-and-spoke topologies.

        Infix supports two P2MP variants via the interface-type setting:

        Interface Type Behavior hybrid P2MP with multicast Hellos (broadcast-capable) point-to-multipoint P2MP with unicast Hellos (non-broadcast)"},{"location":"routing/#hybrid-broadcast-capable-p2mp","title":"Hybrid (broadcast-capable P2MP)","text":"

        Use hybrid when all neighbors on the segment can receive multicast. Hello packets are sent to the standard OSPF multicast address (224.0.0.5) and neighbors are discovered automatically \u2014 no manual neighbor configuration is needed.

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 interface-type hybrid\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n
        "},{"location":"routing/#non-broadcast-p2mp","title":"Non-broadcast P2MP","text":"

        Use point-to-multipoint when the network does not support multicast. Hello packets are sent as unicast directly to each configured neighbor. Since neighbors cannot be discovered automatically, they must be configured explicitly using static neighbors (see below).

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 interface-type point-to-multipoint\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n
        "},{"location":"routing/#static-neighbors","title":"Static Neighbors","text":"

        When using non-broadcast interface types (such as point-to-multipoint), OSPF cannot discover neighbors via multicast. Static neighbors must be configured so the router knows where to send unicast Hello packets.

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 static-neighbors neighbor 10.0.123.2\nadmin@example:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface e0 static-neighbors neighbor 10.0.123.3\nadmin@example:/config/routing/\u2026/ospf/> leave\nadmin@example:/>\n

        Note

        Static neighbors are only needed for non-broadcast interface types. With hybrid (or broadcast), neighbors are discovered automatically via multicast.

        "},{"location":"routing/#ospf-global-settings","title":"OSPF global settings","text":"

        In addition to area and interface specific settings, OSPF provides global settings for route redistribution and OSPF router identifier.

        admin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@example:/config/routing/\u2026/ospf/> set ?\n  area                     List of OSPF areas.\n  default-route-advertise  Distribute default route to network\n  explicit-router-id       Defined in RFC 2328.  A 32-bit number\n  redistribute             Redistribute protocols into OSPF\nadmin@example:/config/routing/\u2026/ospf/> set\n
        • Explicit router ID: By default the router will pick an IP address from one of its OSPF interfaces as OSPF router ID. An explicit ID is used to get a deterministic behavior, e.g., set explicit-router-id 1.1.1.1.
        • Redistribution: set redistribute static and set redistribute connected can be used to include static or connected routes into the OSPF routing domain. These routes are redistributed as external type-2 (E2) routes.
        • Advertising default route: An OSPF router can be made to distribute a default route into the OSPF domain by command set default-route-advertise enabled. This route is distributed as long as the router itself has an active default route in its routing table. By adding command set default-route-advertise always the router will distribute a default route even when it lacks a default route. The default route will be distributed as an external type-2 (E2) route.
        "},{"location":"routing/#debug-ospfv2","title":"Debug OSPFv2","text":"

        Using NETCONF and the YANG model ietf-routing it is possible to read the OSPF routing table, neighbors and more, that may be useful for debugging the OSPFv2 setup. The CLI has various OSPF status commands such as show ospf neighbor, show ospf interface and show ospf routes.

        admin@example:/> show ospf neighbor\nNeighbor ID     Pri State           Up Time         Dead Time Address         Interface                        RXmtL RqstL DBsmL\n10.1.1.2          1 Full/-          3h46m59s          30.177s 10.1.1.2        e0:10.1.1.1                          0     0     0\n10.1.1.3          1 Full/-          3h46m55s          34.665s 10.1.1.3        e1:10.1.1.1                          0     0     0\nadmin@example:/>\n

        For more detailed troubleshooting, OSPF debug logging can be enabled to capture specific protocol events. Debug messages are written to the routing log file (/var/log/routing).

        Caution

        Debug logging significantly increases log output and may impact performance. Only enable debug categories needed for troubleshooting, and disable them when done.

        To enable specific OSPF debug categories:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol ospfv2 name default ospf debug\nadmin@example:/config/routing/\u2026/ospf/debug/> set bfd true\nadmin@example:/config/routing/\u2026/ospf/debug/> set nsm true\nadmin@example:/config/routing/\u2026/ospf/debug/> leave\nadmin@example:/>\n

        Available debug categories include:

        • bfd: BFD (Bidirectional Forwarding Detection) events
        • packet: Detailed packet debugging (all OSPF packets)
        • ism: Interface State Machine events
        • nsm: Neighbor State Machine events
        • default-information: Default route origination
        • nssa: Not-So-Stubby Area events

        All debug options are disabled by default. Refer to the infix-routing YANG model for the complete list of available debug options.

        To view current debug settings:

        admin@example:/> show running-config routing control-plane-protocol\n

        To disable all debug logging, simply delete the debug settings or set all options back to false:

        admin@example:/> configure\nadmin@example:/config/> delete routing control-plane-protocol ospfv2 name default ospf debug\nadmin@example:/config/> leave\nadmin@example:/>\n
        "},{"location":"routing/#rip-routing","title":"RIP Routing","text":"

        The system supports RIP dynamic routing for IPv4, i.e., RIPv2. To enable RIP and set active interfaces:

        admin@example:/config/> edit routing control-plane-protocol ripv2 name default rip\nadmin@example:/config/routing/\u2026/rip/> set interfaces interface e0\nadmin@example:/config/routing/\u2026/rip/> set interfaces interface e1\nadmin@example:/config/routing/\u2026/rip/> leave\nadmin@example:/>\n

        Tip

        Remember to enable IPv4 forwarding for all the interfaces you want to route between.

        "},{"location":"routing/#rip-interface-settings","title":"RIP interface settings","text":"

        By default, interfaces send and receive RIPv2 packets. To control the RIP version per interface:

        admin@example:/config/routing/\u2026/rip/> edit interfaces interface e0\nadmin@example:/config/routing/\u2026/rip/interfaces/interface/e0/> set send-version 1\nadmin@example:/config/routing/\u2026/rip/interfaces/interface/e0/> set receive-version 1-2\nadmin@example:/config/routing/\u2026/rip/interfaces/interface/e0/> leave\nadmin@example:/>\n

        Valid version values are 1, 2, or 1-2 (both versions).

        To configure a passive interface (advertise network but don't send/receive RIP updates):

        admin@example:/config/routing/\u2026/rip/> edit interfaces interface e0\nadmin@example:/config/routing/\u2026/rip/interfaces/interface/e0/> set passive\nadmin@example:/config/routing/\u2026/rip/interfaces/interface/e0/> leave\nadmin@example:/>\n
        "},{"location":"routing/#rip-global-settings","title":"RIP global settings","text":"

        RIP supports redistribution of connected and static routes:

        admin@example:/config/routing/\u2026/rip/> set redistribute connected\nadmin@example:/config/routing/\u2026/rip/> set redistribute static\nadmin@example:/config/routing/\u2026/rip/> leave\nadmin@example:/>\n
        "},{"location":"routing/#debug-ripv2","title":"Debug RIPv2","text":"

        The CLI provides various RIP status commands:

        admin@example:/> show ip rip\nDefault version control: send version 2, receive version 2\n  Interface        Send  Recv   Key-chain                    \n  e0               2     2\n  e1               2     2\n\nRouting for Networks:\n  e0\n  e1\n\nRouting Information Sources:\n  Gateway          BadPackets BadRoutes  Distance Last Update\n  10.0.1.2                  0         0       120    00:00:16\nDistance: (default is 120)\n\nadmin@example:/> show ip rip neighbor\nADDRESS          BAD-PACKETS    BAD-ROUTES                   \n10.0.1.2         0              0\nadmin@example:/>\n

        For more detailed troubleshooting, RIP debug logging can be enabled to capture specific protocol events. Debug messages are written to the routing log file (/var/log/routing).

        Caution

        Debug logging significantly increases log output and may impact performance. Only enable debug categories needed for troubleshooting, and disable them when done.

        To enable specific RIP debug categories:

        admin@example:/> configure\nadmin@example:/config/> edit routing control-plane-protocol ripv2 name default rip debug\nadmin@example:/config/routing/\u2026/rip/debug/> set events true\nadmin@example:/config/routing/\u2026/rip/debug/> set packet true\nadmin@example:/config/routing/\u2026/rip/debug/> leave\nadmin@example:/>\n

        Available debug categories include:

        • events: RIP events (sending/receiving packets, timers, interface changes)
        • packet: Detailed packet debugging (packet dumps with origin and port)
        • kernel: Kernel routing table updates (route add/delete, interface updates)

        All debug options are disabled by default. Refer to the infix-routing YANG model for the complete list of available debug options.

        To view current debug settings:

        admin@example:/> show running-config routing control-plane-protocol\n

        To disable all debug logging, simply delete the debug settings or set all options back to false:

        admin@example:/> configure\nadmin@example:/config/> delete routing control-plane-protocol ripv2 name default rip debug\nadmin@example:/config/> leave\nadmin@example:/>\n
        "},{"location":"routing/#view-routing-table","title":"View routing table","text":"

        The routing table can be inspected from the operational datastore, XPath /routing/ribs, using sysrepocfg, NETCONF/RESTCONF, or using the CLI.

        "},{"location":"routing/#ipv4-routing-table","title":"IPv4 routing table","text":"

        This CLI example shows the IPv4 routing table with a few connected routes and some routes learned from OSPF. See the next section for an explanation of route preferences (PREF).

        The > at the start of a line marks a selected route (in the IETF YANG model referred to as active), if there are more than one route with the same destination the * marks the next-hop used and installed in the kernel FIB (the YANG model refers to this as installed).

        admin@example:/> show ip route\n   DESTINATION            PREF NEXT-HOP         PROTO     UPTIME\n>* 0.0.0.0/0             110/2 10.0.23.1        ospfv2   4h2m43s\n>* 10.0.0.1/32        110/4000 10.0.13.1        ospfv2   4h2m43s\n   10.0.0.3/32           110/0 lo               ospfv2   4h2m57s\n>* 10.0.0.3/32             0/0 lo               direct   4h2m58s\n   10.0.13.0/30       110/2000 e5               ospfv2   4h2m57s\n>* 10.0.13.0/30            0/0 e5               direct   4h2m58s\n   10.0.23.0/30          110/1 e6               ospfv2   4h2m57s\n>* 10.0.23.0/30            0/0 e6               direct   4h2m58s\n   192.168.3.0/24        110/1 e2               ospfv2   4h2m57s\n>* 192.168.3.0/24          0/0 e2               direct   4h2m58s\nadmin@example:/>\n
        "},{"location":"routing/#ipv6-routing-table","title":"IPv6 routing table","text":"

        This CLI example show the IPv6 routing table.

        admin@example:/> show ipv6 route\n   DESTINATION                      PREF NEXT-HOP              PROTO     UPTIME\n>* ::/0                              1/0 2001:db8:3c4d:50::1   static   0h1m20s\n>* 2001:db8:3c4d:50::/64             0/0 e6                    direct   0h1m20s\n>* 2001:db8:3c4d:200::1/128          0/0 lo                    direct   0h1m20s\n * fe80::/64                         0/0 e7                    direct   0h1m20s\n * fe80::/64                         0/0 e6                    direct   0h1m20s\n * fe80::/64                         0/0 e5                    direct   0h1m20s\n * fe80::/64                         0/0 e4                    direct   0h1m20s\n * fe80::/64                         0/0 e3                    direct   0h1m20s\n * fe80::/64                         0/0 e2                    direct   0h1m20s\n>* fe80::/64                         0/0 e1                    direct   0h1m20s\nadmin@example:/>\n
        "},{"location":"routing/#route-preference","title":"Route Preference","text":"

        The operating system leverages FRRouting (Frr) as routing engine for both static and dynamic routing. Even routes injected from a DHCP client, and IPv4 link-local (IPv4) routes, are injected into Frr to let it weigh all routes before installing them into the kernel routing table (sometimes referred to as FIB).

        Routes have different weights made up from a distance and a metric. The kernel routing table only talks about metric, which unfortunately is not the same -- this is one of the reasons why the term route preference is used instead. It is recommended to use the CLI, or any of the other previously mentioned YANG based front-ends, to inspect the routing table.

        Default distances used (lower numeric value wins):

        Distance Protocol 0 Kernel routes, i.e., connected routes 1 Static routes 5 DHCP routes 110 OSPF 254 IPv4LL (ZeroConf) device routes 255 Route will not be used or redistributed

        Hence, a route learned from OSPF may be overridden by a static route set locally. By default, even a route to the same destination, but with a different next-hop, learned from a DHCP server wins over an OSPF route.

        The distance used for static routes and DHCP routes can be changed by setting a different routing preference value.

        Note

        The kernel metric is an unsigned 32-bit value, which is read by Frr as (upper) 8 bits distance and 24 bits metric. But it does not write it back to the kernel FIB this way, only selected routes are candidates to be installed in the FIB by Frr.

        "},{"location":"routing/#source-protocol","title":"Source protocol","text":"

        The source protocol describes the origin of the route.

        Protocol Description kernel Added when setting a subnet address on an interface static User created, learned from DHCP, or IPv4LL ospfv2 Routes learned from OSPFv2

        The YANG model ietf-routing support multiple ribs but only two are currently supported, namely ipv4 and ipv6.

        "},{"location":"scripting-netconf/","title":"NETCONF Scripting","text":""},{"location":"scripting-netconf/#scripting-with-netconf","title":"Scripting with NETCONF","text":"

        NETCONF (Network Configuration Protocol) provides a standardized mechanism for managing network devices using XML-based RPC operations over SSH (port 830). This guide shows practical examples for interacting with Infix using NETCONF.

        NETCONF offers robust capabilities for network automation:

        • Transactional operations: Validate before commit, rollback on error
        • Fine-grained locking: Prevent concurrent configuration conflicts
        • Structured data: XML with YANG schema validation
        • Standardized operations: Get, edit-config, copy-config, etc.
        "},{"location":"scripting-netconf/#netconf-vs-restconf","title":"NETCONF vs RESTCONF","text":"

        Both protocols use the same YANG data models, but differ in approach:

        Feature NETCONF RESTCONF Transport SSH HTTPS Encoding XML JSON/XML Operations RPC-based REST/HTTP methods Best for Automation scripts Web integration, simple queries

        Choose NETCONF when you need:

        • Transactional configuration changes
        • Configuration validation before commit
        • Locking to prevent concurrent changes
        • Integration with existing NETCONF tooling

        Choose RESTCONF for:

        • Simple queries and updates
        • Web-based applications
        • When you prefer JSON over XML
        • RESTful API patterns
        "},{"location":"scripting-netconf/#quick-start-with-netopeer2-cli","title":"Quick Start with netopeer2-cli","text":"

        netopeer2-cli is an interactive NETCONF client, useful for learning and testing. Install it:

        ~$ sudo apt install netopeer2-cli\n

        Connect to your Infix device:

        ~$ netopeer2-cli\n> connect --host example.local --login admin\nadmin@example.local password:\n> status\nCurrent NETCONF session:\n  ID          : 1\n  Host        : example.local\n  Port        : 830\n  Transport   : SSH\n  Capabilities: 35\n
        "},{"location":"scripting-netconf/#basic-operations-in-netopeer2-cli","title":"Basic Operations in netopeer2-cli","text":"

        Get entire configuration:

        > get-config --source running\n

        Get specific subtree (hostname):

        > get-config --source running --filter-xpath /system/hostname\n<data xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n    <hostname>example</hostname>\n  </system>\n</data>\n

        Get operational state:

        > get --filter-xpath /interfaces\n

        Edit configuration:

        > edit-config --target candidate --config=/tmp/config.xml\n> commit\n

        Disconnect:

        > disconnect\n> quit\n
        "},{"location":"scripting-netconf/#discovery-common-patterns","title":"Discovery & Common Patterns","text":"

        Before working with specific configuration items, you often need to discover what exists on the system. This section shows common discovery patterns and practical workflows.

        "},{"location":"scripting-netconf/#discovering-available-interfaces","title":"Discovering Available Interfaces","text":"

        List all interface names:

        <rpc message-id=\"1\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <get>\n    <filter type=\"subtree\">\n      <interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">\n        <interface>\n          <name/>\n        </interface>\n      </interfaces>\n    </filter>\n  </get>\n</rpc>\n

        Using netopeer2-cli:

        > get --filter-xpath /interfaces/interface/name\n

        This returns all interface names, useful for iterating through interfaces in scripts.

        "},{"location":"scripting-netconf/#get-all-yang-capabilities","title":"Get All YANG Capabilities","text":"

        Discover which YANG modules and features are available:

        > status\n

        Or programmatically via the <hello> message capabilities received during connection establishment.

        "},{"location":"scripting-netconf/#get-entire-running-configuration","title":"Get Entire Running Configuration","text":"

        Useful for exploration or backup:

        <rpc message-id=\"2\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <get-config>\n    <source>\n      <running/>\n    </source>\n  </get-config>\n</rpc>\n

        Using netopeer2-cli:

        > get-config --source running\n
        "},{"location":"scripting-netconf/#common-workflow-patterns","title":"Common Workflow Patterns","text":""},{"location":"scripting-netconf/#pattern-1-find-interface-by-ip-address","title":"Pattern 1: Find interface by IP address","text":"

        Get all interfaces with their IPs, then filter:

        > get --filter-xpath /interfaces\n

        Parse the XML output to find which interface has the desired IP.

        "},{"location":"scripting-netconf/#pattern-2-check-which-interfaces-are-down","title":"Pattern 2: Check which interfaces are down","text":"
        > get --filter-xpath /interfaces/interface/oper-status\n

        Look for interfaces with <oper-status>down</oper-status>.

        "},{"location":"scripting-netconf/#pattern-3-get-interface-statistics-for-monitoring","title":"Pattern 3: Get interface statistics for monitoring","text":"
        <rpc message-id=\"3\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <get>\n    <filter type=\"subtree\">\n      <interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">\n        <interface>\n          <statistics/>\n        </interface>\n      </interfaces>\n    </filter>\n  </get>\n</rpc>\n

        Returns in-octets, out-octets, in-errors, out-errors for all interfaces.

        "},{"location":"scripting-netconf/#scripting-with-netconf-client","title":"Scripting with netconf-client","text":"

        netconf-client is a lightweight Python-based NETCONF client designed for scripting and automation.

        "},{"location":"scripting-netconf/#installation","title":"Installation","text":"
        ~$ pip install netconf-client\n

        Or clone and install from source:

        ~$ git clone https://github.com/wires-se/netconf-client.git\n~$ cd netconf-client\n~$ pip install .\n
        "},{"location":"scripting-netconf/#basic-usage","title":"Basic Usage","text":"

        The client provides a simple command-line interface:

        ~$ netconf-client --host example.local --user admin --password admin <operation>\n

        Common operations:

        • get-config - Retrieve configuration
        • edit-config - Modify configuration
        • get - Retrieve operational state
        • copy-config - Copy between datastores
        • lock/unlock - Lock datastores
        • commit - Commit candidate configuration
        "},{"location":"scripting-netconf/#python-api","title":"Python API","text":"

        You can also use netconf-client as a Python library:

        from netconf_client.client import NetconfClient\n\n# Connect\nclient = NetconfClient(\n    host='example.local',\n    username='admin',\n    password='admin'\n)\n\n# Get configuration\nconfig = client.get_config(source='running')\nprint(config)\n\n# Edit configuration\nxml_config = \"\"\"\n<config>\n  <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n    <hostname>newhostname</hostname>\n  </system>\n</config>\n\"\"\"\nclient.edit_config(target='candidate', config=xml_config)\nclient.commit()\n\n# Close connection\nclient.close()\n
        "},{"location":"scripting-netconf/#configuration-examples","title":"Configuration Examples","text":""},{"location":"scripting-netconf/#read-hostname","title":"Read Hostname","text":"

        XML request:

        <rpc message-id=\"1\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <get-config>\n    <source>\n      <running/>\n    </source>\n    <filter type=\"subtree\">\n      <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n        <hostname/>\n      </system>\n    </filter>\n  </get-config>\n</rpc>\n

        Using netopeer2-cli:

        > get-config --source running --filter-xpath /system/hostname\n

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     get-config --source running --xpath /system/hostname\n

        Response:

        <rpc-reply message-id=\"1\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <data>\n    <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n      <hostname>example</hostname>\n    </system>\n  </data>\n</rpc-reply>\n
        "},{"location":"scripting-netconf/#set-hostname","title":"Set Hostname","text":"

        XML request:

        <rpc message-id=\"2\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <edit-config>\n    <target>\n      <candidate/>\n    </target>\n    <config>\n      <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n        <hostname>newhostname</hostname>\n      </system>\n    </config>\n  </edit-config>\n</rpc>\n

        Using netopeer2-cli:

        Save the config to /tmp/hostname.xml:

        <config>\n  <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n    <hostname>newhostname</hostname>\n  </system>\n</config>\n

        Then apply:

        > edit-config --target candidate --config=/tmp/hostname.xml\n> commit\n

        Using netconf-client:

        ~$ cat > hostname.xml <<EOF\n<config>\n  <system xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">\n    <hostname>newhostname</hostname>\n  </system>\n</config>\nEOF\n\n~$ netconf-client --host example.local --user admin \\\n     edit-config --target candidate --config hostname.xml\n\n~$ netconf-client --host example.local --user admin commit\n
        "},{"location":"scripting-netconf/#add-ip-address-to-interface","title":"Add IP Address to Interface","text":"

        Save the config to ip-config.xml:

        <config>\n  <interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">\n    <interface>\n      <name>eth0</name>\n      <ipv4 xmlns=\"urn:ietf:params:xml:ns:yang:ietf-ip\">\n        <address>\n          <ip>192.168.1.100</ip>\n          <prefix-length>24</prefix-length>\n        </address>\n      </ipv4>\n    </interface>\n  </interfaces>\n</config>\n

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     edit-config --target candidate --config ip-config.xml\n~$ netconf-client --host example.local --user admin commit\n
        "},{"location":"scripting-netconf/#copy-running-to-startup","title":"Copy Running to Startup","text":"

        XML request:

        <rpc message-id=\"3\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <copy-config>\n    <source>\n      <running/>\n    </source>\n    <target>\n      <startup/>\n    </target>\n  </copy-config>\n</rpc>\n

        Using netopeer2-cli:

        > copy-config --source running --target startup\n

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     copy-config --source running --target startup\n
        "},{"location":"scripting-netconf/#operational-data-examples","title":"Operational Data Examples","text":""},{"location":"scripting-netconf/#read-interface-state","title":"Read Interface State","text":"

        XML request:

        <rpc message-id=\"4\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n  <get>\n    <filter type=\"subtree\">\n      <interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">\n        <interface>\n          <name>eth0</name>\n        </interface>\n      </interfaces>\n    </filter>\n  </get>\n</rpc>\n

        Using netopeer2-cli:

        > get --filter-xpath /interfaces/interface[name='eth0']\n

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     get --xpath \"/interfaces/interface[name='eth0']\"\n

        This returns operational state including admin/oper status, statistics, MAC address, MTU, and IP addresses.

        "},{"location":"scripting-netconf/#read-all-interfaces","title":"Read All Interfaces","text":"

        Using netopeer2-cli:

        > get --filter-xpath /interfaces\n

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     get --xpath /interfaces\n
        "},{"location":"scripting-netconf/#read-routing-table","title":"Read Routing Table","text":"

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     get --xpath \"/routing/ribs/rib[name='ipv4-default']\"\n
        "},{"location":"scripting-netconf/#read-ospf-neighbors","title":"Read OSPF Neighbors","text":"

        Using netconf-client:

        ~$ netconf-client --host example.local --user admin \\\n     get --xpath \"/routing/control-plane-protocols/control-plane-protocol[type='ietf-ospf:ospfv2'][name='default']/ietf-ospf:ospf\"\n
        "},{"location":"scripting-netconf/#advanced-scripting","title":"Advanced Scripting","text":""},{"location":"scripting-netconf/#python-script-backup-configuration","title":"Python Script: Backup Configuration","text":"
        #!/usr/bin/env python3\nfrom netconf_client.client import NetconfClient\nfrom datetime import datetime\nimport sys\n\ndef backup_config(host, user, password, output_file):\n    try:\n        # Connect\n        client = NetconfClient(host=host, username=user, password=password)\n\n        # Get running config\n        config = client.get_config(source='running')\n\n        # Save to file with timestamp\n        timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')\n        filename = f\"{output_file}-{timestamp}.xml\"\n\n        with open(filename, 'w') as f:\n            f.write(config)\n\n        print(f\"Configuration backed up to {filename}\")\n\n        client.close()\n        return 0\n\n    except Exception as e:\n        print(f\"Error: {e}\", file=sys.stderr)\n        return 1\n\nif __name__ == '__main__':\n    if len(sys.argv) != 5:\n        print(f\"Usage: {sys.argv[0]} <host> <user> <password> <output_prefix>\")\n        sys.exit(1)\n\n    sys.exit(backup_config(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]))\n

        Usage:

        ~$ chmod +x backup.py\n~$ ./backup.py example.local admin admin config-backup\nConfiguration backed up to config-backup-20250102-143022.xml\n
        "},{"location":"scripting-netconf/#python-script-monitor-interface-statistics","title":"Python Script: Monitor Interface Statistics","text":"
        #!/usr/bin/env python3\nfrom netconf_client.client import NetconfClient\nimport xml.etree.ElementTree as ET\nimport time\nimport sys\n\ndef get_interface_stats(client, interface):\n    \"\"\"Get interface statistics\"\"\"\n    xpath = f\"/interfaces/interface[name='{interface}']/statistics\"\n    data = client.get(filter_xpath=xpath)\n\n    # Parse XML to extract counters\n    root = ET.fromstring(data)\n    ns = {'if': 'urn:ietf:params:xml:ns:yang:ietf-interfaces'}\n\n    stats = {}\n    for stat in root.findall('.//if:statistics/*', ns):\n        stats[stat.tag.split('}')[1]] = int(stat.text)\n\n    return stats\n\ndef monitor_interface(host, user, password, interface, interval=5):\n    \"\"\"Monitor interface statistics\"\"\"\n    client = NetconfClient(host=host, username=user, password=password)\n\n    print(f\"Monitoring {interface} on {host} (Ctrl-C to stop)\")\n    print(f\"{'Time':<20} {'RX Packets':<15} {'TX Packets':<15} {'RX Bytes':<15} {'TX Bytes':<15}\")\n    print(\"-\" * 80)\n\n    try:\n        while True:\n            stats = get_interface_stats(client, interface)\n            timestamp = time.strftime('%Y-%m-%d %H:%M:%S')\n\n            print(f\"{timestamp:<20} \"\n                  f\"{stats.get('in-unicast-pkts', 0):<15} \"\n                  f\"{stats.get('out-unicast-pkts', 0):<15} \"\n                  f\"{stats.get('in-octets', 0):<15} \"\n                  f\"{stats.get('out-octets', 0):<15}\")\n\n            time.sleep(interval)\n\n    except KeyboardInterrupt:\n        print(\"\\nMonitoring stopped\")\n    finally:\n        client.close()\n\nif __name__ == '__main__':\n    if len(sys.argv) < 5:\n        print(f\"Usage: {sys.argv[0]} <host> <user> <password> <interface> [interval]\")\n        sys.exit(1)\n\n    interval = int(sys.argv[5]) if len(sys.argv) > 5 else 5\n    monitor_interface(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], interval)\n

        Usage:

        ~$ chmod +x monitor.py\n~$ ./monitor.py example.local admin admin eth0 2\nMonitoring eth0 on example.local (Ctrl-C to stop)\nTime                 RX Packets      TX Packets      RX Bytes        TX Bytes\n--------------------------------------------------------------------------------\n2025-01-02 14:35:10  12453          8932           1847392        892341\n2025-01-02 14:35:12  12489          8967           1851204        895673\n...\n
        "},{"location":"scripting-netconf/#shell-script-batch-configuration","title":"Shell Script: Batch Configuration","text":"
        #!/bin/bash\n# Apply configuration to multiple devices\n\nDEVICES=\"device1.local device2.local device3.local\"\nUSER=\"admin\"\nPASSWORD=\"admin\"\nCONFIG_FILE=\"$1\"\n\nif [ -z \"$CONFIG_FILE\" ]; then\n    echo \"Usage: $0 <config.xml>\"\n    exit 1\nfi\n\nfor device in $DEVICES; do\n    echo \"Configuring $device...\"\n\n    # Edit candidate\n    netconf-client --host \"$device\" --user \"$USER\" --password \"$PASSWORD\" \\\n        edit-config --target candidate --config \"$CONFIG_FILE\"\n\n    if [ $? -eq 0 ]; then\n        # Commit if edit succeeded\n        netconf-client --host \"$device\" --user \"$USER\" --password \"$PASSWORD\" commit\n        echo \"  \u2713 $device configured successfully\"\n    else\n        echo \"  \u2717 $device configuration failed\"\n    fi\ndone\n
        "},{"location":"scripting-netconf/#other-netconf-tools","title":"Other NETCONF Tools","text":""},{"location":"scripting-netconf/#ncclient-python","title":"ncclient (Python)","text":"

        Popular Python library for NETCONF:

        ~$ pip install ncclient\n

        Example:

        from ncclient import manager\n\nwith manager.connect(host='example.local', port=830,\n                     username='admin', password='admin',\n                     hostkey_verify=False) as m:\n    # Get config\n    c = m.get_config(source='running')\n    print(c)\n
        "},{"location":"scripting-netconf/#ansible","title":"Ansible","text":"

        Ansible includes NETCONF modules for automation:

        - name: Get interface config\n  netconf_get:\n    source: running\n    filter: <interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\"/>\n
        "},{"location":"scripting-netconf/#cisco-tools","title":"Cisco Tools","text":"
        • NSO (Network Services Orchestrator): Enterprise orchestration platform
        • YANG Suite: Web-based YANG exploration and testing tool
        • Cisco pyATS: Network test automation
        "},{"location":"scripting-netconf/#troubleshooting","title":"Troubleshooting","text":""},{"location":"scripting-netconf/#enable-netconf-debugging","title":"Enable NETCONF Debugging","text":"

        For netconf-client, use verbose mode:

        ~$ netconf-client --host example.local --user admin --verbose get-config\n

        For netopeer2-cli, enable debug output:

        > debug 1\n> get-config --source running\n
        "},{"location":"scripting-netconf/#common-issues","title":"Common Issues","text":"

        Connection refused:

        • Verify SSH is running on port 830: ssh -p 830 admin@example.local
        • Check firewall rules

        Authentication failed:

        • Verify credentials
        • Check user has NETCONF access permissions

        Operation not supported:

        • Verify NETCONF capability: netopeer2-cli \u2192 status \u2192 check capabilities
        • Some operations require specific YANG modules
        "},{"location":"scripting-netconf/#references","title":"References","text":"
        • NETCONF Protocol (RFC 6241)
        • netconf-client on GitHub
        • netopeer2 Documentation
        • YANG Data Modeling Language (RFC 7950)
        • ietf-interfaces YANG module
        • ietf-system YANG module
        • ncclient Documentation
        "},{"location":"scripting-prod/","title":"Production Testing","text":""},{"location":"scripting-prod/#scripting-for-production-tests","title":"Scripting for Production Tests","text":"

        This document shows how to set up and remotely script devices with a focus on production testing.

        "},{"location":"scripting-prod/#vlan-snake","title":"VLAN Snake","text":"

        As part of production tests, verification of Ethernet ports is usually expected. A common way for devices with multiple bridged Ethernet ports is to connect a test PC to two ports and send a ping traversing all ports. This can be achieved by using VLANs, on the switch, as described in this section. The resulting configuration file can be applied to the running configuration of the produced unit, e.g, use config file restore as described previously.

        In this example we assume a 10 port switch, with ports e1-e10. The following VLAN configuration and cable connections will be used:

        VLAN & Ports Connect VLAN 10: e1 & e2 e2 <=> e3 VLAN 20: e3 & e4 e4 <=> e5 VLAN 30: e5 & e6 e6 <=> e7 VLAN 40: e7 & e8 e8 <=> e9 VLAN 50: e9 & e10

        The test PC is connected to e1 and e10 via different interfaces (alternatively, two different PCs are used).

        Tip

        Configuration here is done via console. When configuring remotely over SSH, remember to keep one IP address (the one used for the SSH connection)! I.e., set a static IP address first, then perform the VLAN configuration step.

        "},{"location":"scripting-prod/#configuration-at-start","title":"Configuration at Start","text":"

        Starting out, we assume a configuration where all ports are network interfaces (possibly with IPv6 enabled).

        admin@example:/> show interfaces\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\ne1              ethernet   LOWER-DOWN  00:53:00:06:11:01\ne2              ethernet   LOWER-DOWN  00:53:00:06:11:02\ne3              ethernet   LOWER-DOWN  00:53:00:06:11:03\ne4              ethernet   LOWER-DOWN  00:53:00:06:11:04\ne5              ethernet   LOWER-DOWN  00:53:00:06:11:05\ne6              ethernet   LOWER-DOWN  00:53:00:06:11:06\ne7              ethernet   LOWER-DOWN  00:53:00:06:11:07\ne8              ethernet   LOWER-DOWN  00:53:00:06:11:08\ne9              ethernet   LOWER-DOWN  00:53:00:06:11:09\ne10             ethernet   UP          00:53:00:06:11:0a\n                ipv6                   fe80::0053:00ff:fe06:110a/64 (link-layer)\nadmin@example:/>\n
        "},{"location":"scripting-prod/#creating-bridge-and-adding-ports","title":"Creating Bridge and Adding Ports","text":"

        The following example creates a bridge and adds all Ethernet ports to it. On a device with layer-2 offloading (switch fabric), this sets all ports in the same VLAN. The next section sets up VLAN isolation.

        admin@example:/> configure\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> end\nadmin@example:/config/> set interface e1 bridge-port bridge br0\nadmin@example:/config/> set interface e2 bridge-port bridge br0\nadmin@example:/config/> set interface e3 bridge-port bridge br0\nadmin@example:/config/> set interface e4 bridge-port bridge br0\nadmin@example:/config/> set interface e5 bridge-port bridge br0\nadmin@example:/config/> set interface e6 bridge-port bridge br0\nadmin@example:/config/> set interface e7 bridge-port bridge br0\nadmin@example:/config/> set interface e8 bridge-port bridge br0\nadmin@example:/config/> set interface e9 bridge-port bridge br0\nadmin@example:/config/> set interface e10 bridge-port bridge br0\nadmin@example:/config/>\n

        The interface status can be viewed using show interfaces after leaving configuration context. When configuring via SSH, first assign an IP address to br0 before leaving configuration context, e.g.

        admin@example:/config/> set interface br0 ipv6 enabled\n

        This enables IPv6 SLAAC, auto-configured address. Or skip leave and stay in configuration context until you have completed all the device setup, including setting IP address.

        admin@example:/config/> leave\nadmin@example:/>\nadmin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\nbr0             bridge\n\u2502               ethernet   UP          00:53:00:06:11:01\n\u251c e1            bridge     LOWER-DOWN\n\u251c e2            bridge     LOWER-DOWN\n\u251c e3            bridge     LOWER-DOWN\n\u251c e4            bridge     LOWER-DOWN\n\u251c e5            bridge     LOWER-DOWN\n\u251c e6            bridge     LOWER-DOWN\n\u251c e7            bridge     LOWER-DOWN\n\u251c e8            bridge     LOWER-DOWN\n\u251c e9            bridge     LOWER-DOWN\n\u2514 e10           bridge     FORWARDING\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n
        "},{"location":"scripting-prod/#assign-vlans-to-ports","title":"Assign VLANs to Ports","text":"

        Now, configure VLANs as outlined previously: default VID for ingress (PVID), which is done per port, and egress mode (untagged), which is done at the bridge level. See the VLAN bridges section for more information.

        admin@example:/>\nadmin@example:/> configure\nadmin@example:/config/> set interface e1 bridge-port pvid 10\nadmin@example:/config/> set interface e2 bridge-port pvid 10\nadmin@example:/config/> set interface e3 bridge-port pvid 20\nadmin@example:/config/> set interface e4 bridge-port pvid 20\nadmin@example:/config/> set interface e5 bridge-port pvid 30\nadmin@example:/config/> set interface e6 bridge-port pvid 30\nadmin@example:/config/> set interface e7 bridge-port pvid 40\nadmin@example:/config/> set interface e8 bridge-port pvid 40\nadmin@example:/config/> set interface e9 bridge-port pvid 50\nadmin@example:/config/> set interface e10 bridge-port pvid 50\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> edit bridge vlans\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 10 untagged e1\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 10 untagged e2\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 20 untagged e3\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 20 untagged e4\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 30 untagged e5\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 30 untagged e6\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 40 untagged e7\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 40 untagged e8\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 50 untagged e9\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 50 untagged e10\nadmin@example:/config/interface/br0/bridge/vlans/> leave\nadmin@example:/>\n

        Interface status would now should something like the following

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\nbr0             bridge\n\u2502               ethernet   UP          00:53:00:06:11:01\n\u251c e1            bridge     LOWER-DOWN  vlan:10u pvid:10\n\u251c e2            bridge     LOWER-DOWN  vlan:10u pvid:10\n\u251c e3            bridge     LOWER-DOWN  vlan:20u pvid:20\n\u251c e4            bridge     LOWER-DOWN  vlan:20u pvid:20\n\u251c e5            bridge     LOWER-DOWN  vlan:30u pvid:30\n\u251c e6            bridge     LOWER-DOWN  vlan:30u pvid:30\n\u251c e7            bridge     LOWER-DOWN  vlan:40u pvid:40\n\u251c e8            bridge     LOWER-DOWN  vlan:40u pvid:40\n\u251c e9            bridge     LOWER-DOWN  vlan:50u pvid:50\n\u2514 e10           bridge     FORWARDING  vlan:50u pvid:50\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n
        "},{"location":"scripting-prod/#connect-cables-and-test","title":"Connect Cables and Test","text":"

        We can now connect the PC to e1 and e10, while the other ports are patched according to above. We should see link up and FORWARDING on all ports in the bridge.

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\nbr0             bridge\n\u2502               ethernet   UP          00:53:00:06:11:01\n\u251c e1            bridge     FORWARDING  vlan:10u pvid:10\n\u251c e2            bridge     FORWARDING  vlan:10u pvid:10\n\u251c e3            bridge     FORWARDING  vlan:20u pvid:20\n\u251c e4            bridge     FORWARDING  vlan:20u pvid:20\n\u251c e5            bridge     FORWARDING  vlan:30u pvid:30\n\u251c e6            bridge     FORWARDING  vlan:30u pvid:30\n\u251c e7            bridge     FORWARDING  vlan:40u pvid:40\n\u251c e8            bridge     FORWARDING  vlan:40u pvid:40\n\u251c e9            bridge     FORWARDING  vlan:50u pvid:50\n\u2514 e10           bridge     FORWARDING  vlan:50u pvid:50\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nadmin@example:/>\n

        Here we use IPv6 ping all hosts (ff02::1) on PC interface eth1 to check reachability to the other interface of the PC.

        Tip

        We recommend using network namespaces (Linux only) on the PC to ensure that traffic is actually sent out to the switch, rather than being looped back internally. Alternatively, use two separate PCs.

        ~ $ ping -L ff02::1%eth1\nPING ff02::1%eth1(ff02::1%eth1) 56 data bytes\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=1 ttl=64 time=0.496 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=2 ttl=64 time=0.514 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=3 ttl=64 time=0.473 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=4 ttl=64 time=0.736 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=5 ttl=64 time=0.563 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=6 ttl=64 time=0.507 ms\n^C\n--- ff02::1%eth1 ping statistics ---\n6 packets transmitted, 6 received, 0% packet loss, time 5108ms\nrtt min/avg/max/mdev = 0.473/0.548/0.736/0.088 ms\n~ $\n

        We can verify that traffic goes through the switch by disconnecting one of the patch cables, e.g., between e4 and e5

        ~ $ ping -L ff02::1%eth1\nPING ff02::1%eth1(ff02::1%eth1) 56 data bytes\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=1 ttl=64 time=0.510 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=2 ttl=64 time=0.448 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=3 ttl=64 time=0.583 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=4 ttl=64 time=0.515 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=5 ttl=64 time=0.521 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=6 ttl=64 time=0.495 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=7 ttl=64 time=0.743 ms\n... Disconnecting patch cable, thus losing packets\n... and reconnecting again. Connectivity resumes.\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=16 ttl=64 time=0.961 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=17 ttl=64 time=0.513 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=18 ttl=64 time=0.794 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=19 ttl=64 time=0.755 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=20 ttl=64 time=0.779 ms\n^C\n--- ff02::1%eth1 ping statistics ---\n20 packets transmitted, 12 received, 40% packet loss, time 19432ms\nrtt min/avg/max/mdev = 0.448/0.634/0.961/0.156 ms\n~ $\n
        "},{"location":"scripting-prod/#set-ip-address","title":"Set IP Address","text":"

        The configuration so far does not provide a means to connect to the switch management via SSH or NETCONF, as the switch has no IP address. The example below shows how to add the switch to VLAN 10 (as used for ports e1 and e2) and enables IPv6.

        admin@example:/config/> edit interface vlan10\nadmin@example:/config/interface/vlan10/> set vlan lower-layer-if br0\nadmin@example:/config/interface/vlan10/> set ipv6 enabled\nadmin@example:/config/interface/vlan10/> show\ntype vlan;\nipv6 {\n  enabled true;\n}\nvlan {\n  tag-type c-vlan;\n  id 10;\n  lower-layer-if br0;\n}\nadmin@example:/config/interface/vlan10/>\nadmin@example:/config/interface/vlan10/> end\nadmin@example:/config/> edit interface br0 bridge vlans\nadmin@example:/config/interface/br0/bridge/vlans/> set vlan 10 tagged br0\nadmin@example:/config/interface/br0/bridge/vlans/> leave\nadmin@example:/>\n

        Interface vlan10 with an auto-configured IPv6 address should appear.

        admin@example:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\nbr0             bridge                 vlan:10t\n\u2502               ethernet   UP          00:53:00:06:11:01\n\u251c e1            bridge     FORWARDING  vlan:10u pvid:10\n\u251c e2            bridge     FORWARDING  vlan:10u pvid:10\n\u251c e3            bridge     FORWARDING  vlan:20u pvid:20\n\u251c e4            bridge     FORWARDING  vlan:20u pvid:20\n\u251c e5            bridge     FORWARDING  vlan:30u pvid:30\n\u251c e6            bridge     FORWARDING  vlan:30u pvid:30\n\u251c e7            bridge     FORWARDING  vlan:40u pvid:40\n\u251c e8            bridge     FORWARDING  vlan:40u pvid:40\n\u251c e9            bridge     FORWARDING  vlan:50u pvid:50\n\u2514 e10           bridge     FORWARDING  vlan:50u pvid:50\nlo              ethernet   UP          00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\nvlan10          ethernet   UP          00:53:00:06:11:01\n\u2502               ipv6                   fe80::0053:00ff:fe06:1101/64 (link-layer)\n\u2514 br0           ethernet   UP          00:53:00:06:11:01\nadmin@example:/>\n

        When pinging \"IPv6 all hosts\" from the PC, there should be two responses for every ping, one from the switch and one from the PC attached to e10.

        ~ $ ping -L ff02::1%eth1\nPING ff02::1%eth1(ff02::1%eth1) 56 data bytes\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=1 ttl=64 time=0.508 ms\n64 bytes from fe80::0053:00ff:fe06:1101%eth1: icmp_seq=1 ttl=64 time=0.968 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=2 ttl=64 time=0.866 ms\n64 bytes from fe80::0053:00ff:fe06:1101%eth1: icmp_seq=2 ttl=64 time=0.867 ms\n64 bytes from fe80::0053:00ff:fe06:1101%eth1: icmp_seq=3 ttl=64 time=0.467 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=3 ttl=64 time=0.469 ms\n64 bytes from fe80::488a:a35f:9d41:ac9c%eth1: icmp_seq=4 ttl=64 time=0.452 ms\n64 bytes from fe80::0053:00ff:fe06:1101%eth1: icmp_seq=4 ttl=64 time=0.453 ms\n^C\n--- ff02::1%eth1 ping statistics ---\n4 packets transmitted, 4 received, +4 duplicates, 0% packet loss, time 3031ms\nrtt min/avg/max/mdev = 0.452/0.631/0.968/0.211 ms\n~ $\n

        It should now be possible to access the switch from the PC via SSH (or NETCONF).

        ~ $ ssh admin@fe80::0053:00ff:fe06:1101%eth1\nadmin@fe80::0053:00ff:fe06:1101%eth1's password:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-'\n\nRun the command 'cli' for interactive OAM\n\nadmin@example:~$ exit\n~ $\n

        See previous sections on backup and restore of the created configuration.

        "},{"location":"scripting-restconf/","title":"RESTCONF Scripting","text":""},{"location":"scripting-restconf/#scripting-with-restconf","title":"Scripting with RESTCONF","text":"

        RESTCONF provides a programmatic interface to both configuration and operational data over HTTPS. This guide shows practical examples using curl to interact with the RESTCONF API.

        All examples use the following conventions:

        • Host: example.local (replace with your device hostname/IP)
        • Credentials: admin:admin (default username:password)
        • HTTPS: Self-signed certificates require -k flag in curl
        "},{"location":"scripting-restconf/#helper-script","title":"Helper Script","text":"

        To simplify RESTCONF operations, create a curl.sh wrapper script:

        #!/bin/sh\n# RESTCONF CLI wrapper for curl\n\nHOST=${HOST:-infix.local}\nDATASTORE=running\nAUTH=admin:admin\n\nusage()\n{\n    cat <<EOF >&2\nUsage: $0 [-v] [-h HOST] [-d DATASTORE] [-u USER:PASS] METHOD XPATH [CURL_ARGS...]\n\nOptions:\n  -h HOST    Target host (default: infix.local)\n  -d DS      Datastore: running, operational, startup (default: running)\n  -u CREDS   Credentials as user:pass (default: admin:admin)\n  -v         Verbose mode, use it to display variables and curl command\n\nMethods: GET, POST, PUT, PATCH, DELETE\nEOF\n    exit \"$1\"\n}\n\ncheck()\n{\n    hint=\"Note: URL may be missing a module prefix (e.g., ietf-system:system)\"\n    resp=\"$1\"\n    url=\"$2\"\n\n    if ! command -v jq >/dev/null 2>&1; then\n        printf \"%s\\n\" \"$resp\"\n        return\n    fi\n\n    case \"$resp\" in\n        *\"Syntax error\"*)\n            path_only=\"${url#*//}\"\n\n            # Check for common URL error(s), e.g., missing module prefix\n            case \"$path_only\" in\n                *\":\"*)\n                    printf \"%s\\n\" \"$resp\" | jq .\n                    ;;\n                *)\n                    printf \"%s\\n\" \"$resp\" | jq --arg hint \"$hint\" \\\n                           '.[\"ietf-restconf:errors\"].error[] |= . + {comment: $hint}'\n                    ;;\n            esac\n            ;;\n        *)\n            printf \"%s\\n\" \"$resp\" | jq .\n            ;;\n    esac\n}\n\nwhile getopts \"h:d:u:v\" opt; do\n    case $opt in\n        h) HOST=\"$OPTARG\"      ;;\n        d) DATASTORE=\"$OPTARG\" ;;\n        u) AUTH=\"$OPTARG\"      ;;\n        v) VERBOSE=1           ;;\n        *) usage 1 ;;\n    esac\ndone\nshift $((OPTIND - 1))\n\nif [ $# -lt 2 ]; then\n    echo \"Error: METHOD and XPATH are required\" >&2\n    usage 1\nfi\n\nMETHOD=$1\nXPATH=$2\nshift 2\n\n# Ensure XPATH starts with /\ncase \"$XPATH\" in\n    /*) ;;\n    *) XPATH=\"/$XPATH\" ;;\nesac\n\ncase \"$DATASTORE\" in\n    running|startup)\n        URL=\"https://${HOST}/restconf/data${XPATH}\"\n        ;;\n    operational)\n        URL=\"https://${HOST}/restconf/data${XPATH}\"\n        ;;\n    *)\n        echo \"Error: Invalid datastore '$DATASTORE'. Use: running, operational, or startup\" >&2\n        exit 1\n        ;;\nesac\n\nif [ \"$VERBOSE\" ]; then\n    echo \"DS     : $DATASTORE\"\n    echo \"OP     : $METHOD\"\n    echo \"XPATH  : $XPATH\"\n    echo \"AUTH   : $AUTH\"\n    echo \"=> URL : $URL\"\n    set -x\nfi\n\nRESP=$(/usr/bin/curl --silent                           \\\n    --insecure                                          \\\n    --user \"${AUTH}\"                                    \\\n    --request \"${METHOD}\"                               \\\n    --header \"Content-Type: application/yang-data+json\" \\\n    --header \"Accept: application/yang-data+json\"       \\\n    \"$@\"                                                \\\n    \"${URL}\")\n\ncheck \"$RESP\" \"$URL\"\n

        Make it executable:

        ~$ chmod +x curl.sh\n

        This wrapper handles authentication, headers, SSL certificates, and URL construction, making commands much cleaner. You can override defaults with command-line options or environment variables:

        # Using command-line options\n~$ ./curl.sh -h 192.168.1.10 -d operational -u admin:secret GET /ietf-interfaces:interfaces\n\n# Using environment variables\n~$ HOST=192.168.1.10 ./curl.sh GET /ietf-system:system\n

        The examples below show both raw curl commands and the equivalent using curl.sh where applicable.

        "},{"location":"scripting-restconf/#http-methods-in-restconf","title":"HTTP Methods in RESTCONF","text":"

        RESTCONF uses standard HTTP methods to perform different operations on configuration and operational data. Understanding when to use each method is crucial for correct and safe operations.

        "},{"location":"scripting-restconf/#get-read-data","title":"GET - Read Data","text":"

        Retrieve configuration or operational data without making changes.

        When to use:

        • Reading current configuration
        • Querying operational state (interface statistics, routing tables, etc.)
        • Discovering what exists before making changes

        Characteristics:

        • Safe operation (no side effects)
        • Can be repeated without consequences
        • Works on both configuration and operational datastores

        Example:

        ~$ ./curl.sh -h example.local GET /ietf-interfaces:interfaces\n

        "},{"location":"scripting-restconf/#put-replace-resource","title":"PUT - Replace Resource","text":"

        Heads-up!

        PUT replaces everything - missing fields will be deleted!

        Replace an entire resource with new content.

        When to use:

        • Replacing entire datastore (backup/restore scenarios)
        • Complete reconfiguration of a container
        • When you want to ensure the resource matches exactly what you provide

        Characteristics:

        • Replaces all content at the target path
        • Any existing data not in the PUT request is removed
        • Requires complete, valid configuration
        • Dangerous if you don't include all necessary fields

        Example:

        ~$ ./curl.sh -h example.local PUT /ietf-system:system \\\n     -d '{\"ietf-system:system\":{\"hostname\":\"newhost\",\"contact\":\"admin@example.com\"}}'\n

        "},{"location":"scripting-restconf/#patch-mergeupdate-resource","title":"PATCH - Merge/Update Resource","text":"

        Partially update a resource by merging changes with existing data.

        When to use:

        • Modifying specific fields while preserving others
        • Working with path-based NACM permissions
        • Incremental configuration changes
        • Safer alternative to PUT for most use cases

        Characteristics:

        • Merges changes with existing configuration
        • Only specified fields are modified
        • Unspecified fields remain unchanged
        • Works with partial data structures
        • NACM-friendly (respects path-based permissions)

        Example:

        ~$ ./curl.sh -h example.local PATCH /ietf-interfaces:interfaces \\\n     -d '{\"ietf-interfaces:interfaces\":{\"interface\":[{\"name\":\"e0\",\"description\":\"WAN\"}]}}'\n

        Best practice

        Use PATCH instead of PUT for most configuration updates.

        "},{"location":"scripting-restconf/#post-create-new-resource","title":"POST - Create New Resource","text":"

        Create a new resource within a collection or invoke an RPC.

        When to use:

        • Adding new list entries (interfaces, users, routes, etc.)
        • Creating resources at specific paths
        • Invoking RPC operations (reboot, factory-reset, etc.)

        Characteristics:

        • Creates new resources
        • For lists: adds a new element
        • For RPCs: executes the operation
        • Returns error if resource already exists (for configuration)

        Example - Add IP address:

        ~$ ./curl.sh -h example.local POST \\\n     /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254 \\\n     -d '{\"prefix-length\": 32}'\n

        Example - Invoke RPC:

        ~$ curl -kX POST -u admin:admin \\\n     -H \"Content-Type: application/yang-data+json\" \\\n     https://example.local/restconf/operations/ietf-system:system-restart\n

        "},{"location":"scripting-restconf/#delete-remove-resource","title":"DELETE - Remove Resource","text":"

        Delete a resource or configuration element.

        When to use:

        • Removing interfaces, routes, users, etc.
        • Deleting specific configuration items
        • Cleaning up unwanted configuration

        Characteristics:

        • Removes the resource completely
        • Cannot be undone (except by reconfiguration)
        • Returns error if resource doesn't exist

        Example:

        ~$ ./curl.sh -h example.local DELETE \\\n     /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254\n

        "},{"location":"scripting-restconf/#quick-reference","title":"Quick Reference","text":"Method Use Case Safe? Idempotent? NACM Impact GET Read data \u2713 \u2713 Read permissions PUT Replace entire resource \u2717 \u2713 Full write access needed PATCH Merge/update fields \u2713 \u2713 Path-specific write POST Create new/invoke RPC \u2717 \u2717 Create/exec permissions DELETE Remove resource \u2717 \u2713 Delete permissions

        Key takeaways:

        • Use PATCH for updates - Safer than PUT, works with NACM
        • Use PUT sparingly - Only when you need complete replacement
        • GET is always safe - Read as much as you need
        • POST for creation/RPCs - Creating new items or executing operations
        • DELETE with care - Cannot be undone
        "},{"location":"scripting-restconf/#discovery-common-patterns","title":"Discovery & Common Patterns","text":"

        Before working with specific configuration items, you often need to discover what exists on the system. This section shows common discovery patterns and practical workflows.

        "},{"location":"scripting-restconf/#discovering-available-interfaces","title":"Discovering Available Interfaces","text":"

        List all interface names:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces 2>/dev/null | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][].name'\nlo\ne0\ne1\n

        This is essential for automation - interface names vary by platform (eth0, e1, enp0s3, etc.), so scripts should discover them rather than hardcode.

        "},{"location":"scripting-restconf/#get-api-capabilities","title":"Get API Capabilities","text":"

        Discover what YANG modules are available:

        ~$ curl -kX GET -u admin:admin \\\n        -H 'Accept: application/yang-data+json' \\\n        https://example.local/restconf/data/ietf-yang-library:yang-library\n

        This returns all supported YANG modules, revisions, and features.

        "},{"location":"scripting-restconf/#get-entire-running-configuration","title":"Get Entire Running Configuration","text":"

        Useful for exploration or backup:

        ~$ ./curl.sh -h example.local GET / -o backup.json\n
        "},{"location":"scripting-restconf/#common-workflow-patterns","title":"Common Workflow Patterns","text":""},{"location":"scripting-restconf/#pattern-1-find-interface-by-ip-address","title":"Pattern 1: Find interface by IP address","text":"

        Get all interfaces with IPs and search:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces 2>/dev/null \\\n     | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][] | select(.[\"ietf-ip:ipv4\"][\"address\"][]?.ip == \"192.168.1.100\") | .name'\n
        "},{"location":"scripting-restconf/#pattern-2-list-all-interfaces-that-are-down","title":"Pattern 2: List all interfaces that are down","text":"
        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces 2>/dev/null \\\n     | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][] | select(.[\"oper-status\"] == \"down\") | .name'\n
        "},{"location":"scripting-restconf/#pattern-3-get-statistics-for-all-interfaces","title":"Pattern 3: Get statistics for all interfaces","text":"
        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces 2>/dev/null \\\n     | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][] | \"\\(.name): RX \\(.statistics[\"in-octets\"]) TX \\(.statistics[\"out-octets\"])\"'\n

        Output:

        lo: RX 29320 TX 29320\ne0: RX 1847392 TX 892341\ne1: RX 0 TX 0\n
        "},{"location":"scripting-restconf/#pattern-4-check-if-interface-exists-before-configuring","title":"Pattern 4: Check if interface exists before configuring","text":"
        ~$ if ./curl.sh -h example.local GET /ietf-interfaces:interfaces/interface=eth0 2>/dev/null | grep -q \"ietf-interfaces:interface\"; then\n     echo \"Interface eth0 exists\"\n   else\n     echo \"Interface eth0 not found\"\n   fi\n
        "},{"location":"scripting-restconf/#configuration-operations","title":"Configuration Operations","text":""},{"location":"scripting-restconf/#read-hostname","title":"Read Hostname","text":"

        Example of fetching JSON configuration data:

        Using curl directly:

        ~$ curl -kX GET -u admin:admin \\\n        -H 'Accept: application/yang-data+json' \\\n        https://example.local/restconf/data/ietf-system:system/hostname\n{\n  \"ietf-system:system\": {\n    \"hostname\": \"foo\"\n  }\n}\n

        Using curl.sh:

        ~$ ./curl.sh -h example.local GET /ietf-system:system/hostname\n{\n  \"ietf-system:system\": {\n    \"hostname\": \"foo\"\n  }\n}\n
        "},{"location":"scripting-restconf/#set-hostname","title":"Set Hostname","text":"

        Example of updating configuration with inline JSON data:

        Using curl directly:

        ~$ curl -kX PATCH -u admin:admin \\\n     -H 'Content-Type: application/yang-data+json' \\\n     -d '{\"ietf-system:system\":{\"hostname\":\"bar\"}}' \\\n     https://example.local/restconf/data/ietf-system:system\n

        Using curl.sh:

        ~$ ./curl.sh -h example.local PATCH /ietf-system:system \\\n     -d '{\"ietf-system:system\":{\"hostname\":\"bar\"}}'\n
        "},{"location":"scripting-restconf/#update-interface-description","title":"Update Interface Description","text":"

        PATCH allows you to modify specific parts of the configuration without replacing the entire container. This is particularly useful when working with NACM (Network Access Control Model) permissions that grant access to specific paths.

        Why use PATCH instead of PUT:

        • Partial updates: Only changes specified fields, preserves others
        • NACM-friendly: Works with path-based access control rules
        • Safer: Reduces risk of accidentally removing unrelated configuration

        Example - Modify an interface description:

        ~$ curl -kX PATCH -u admin:admin \\\n     -H 'Content-Type: application/yang-data+json' \\\n     -d '{\"ietf-interfaces:interfaces\":{\"interface\":[{\"name\":\"e0\",\"description\":\"WAN Port\"}]}}' \\\n     https://example.local/restconf/data/ietf-interfaces:interfaces\n

        Using curl.sh:

        ~$ ./curl.sh -h example.local PATCH /ietf-interfaces:interfaces \\\n     -d '{\"ietf-interfaces:interfaces\":{\"interface\":[{\"name\":\"e0\",\"description\":\"WAN Port\"}]}}'\n

        Formatted for readability:

        ~$ curl -kX PATCH -u admin:admin \\\n     -H 'Content-Type: application/yang-data+json' \\\n     -d '{\n       \"ietf-interfaces:interfaces\": {\n         \"interface\": [\n           {\n             \"name\": \"e0\",\n             \"description\": \"WAN Port\"\n           }\n         ]\n       }\n     }' \\\n     https://example.local/restconf/data/ietf-interfaces:interfaces\n

        Key points:

        • PATCH URL targets the container (/interfaces), not a specific interface
        • JSON body includes the full structure with the interface array
        • Only specified fields are modified; other interface settings remain unchanged
        • The name field identifies which interface to update

        Verify the change:

        ~$ ./curl.sh -h example.local GET /ietf-interfaces:interfaces/interface=e0 2>/dev/null \\\n     | jq '.[\"ietf-interfaces:interface\"][0].description'\n\"WAN Port\"\n
        "},{"location":"scripting-restconf/#add-ip-address-to-interface","title":"Add IP Address to Interface","text":"

        Add an IP address to the loopback interface:

        ~$ ./curl.sh -h example.local POST \\\n     /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254 \\\n     -d '{ \"prefix-length\": 32 }'\n
        "},{"location":"scripting-restconf/#delete-ip-address-from-interface","title":"Delete IP Address from Interface","text":"

        Remove an IP address from the loopback interface:

        ~$ ./curl.sh -h example.local DELETE \\\n     /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254\n
        "},{"location":"scripting-restconf/#copy-running-to-startup","title":"Copy Running to Startup","text":"

        No copy command available yet to copy between datastores, and the Rousette back-end also does not support \"write-through\" to the startup datastore.

        To save running-config to startup-config, fetch running to a local file and then update startup with it:

        Using curl directly:

        ~$ curl -kX GET -u admin:admin -o running-config.json \\\n        -H 'Accept: application/yang-data+json'       \\\n         https://example.local/restconf/ds/ietf-datastores:running\n\n~$ curl -kX PUT -u admin:admin -d @running-config.json \\\n        -H 'Content-Type: application/yang-data+json'  \\\n        https://example.local/restconf/ds/ietf-datastores:startup\n

        Using curl.sh:

        ~$ ./curl.sh -h example.local GET / -o running-config.json\n~$ ./curl.sh -h example.local -d startup PUT / -d @running-config.json\n
        "},{"location":"scripting-restconf/#operational-data","title":"Operational Data","text":""},{"location":"scripting-restconf/#read-interface-configuration","title":"Read Interface Configuration","text":"

        Get the running configuration for the loopback interface:

        ~$ ./curl.sh -h example.local GET /ietf-interfaces:interfaces/interface=lo\n
        "},{"location":"scripting-restconf/#read-interface-operational-state","title":"Read Interface Operational State","text":"

        Get operational data (state, statistics, etc.) for an interface:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces/interface=lo\n

        This includes administrative and operational state, MAC address, MTU, and statistics counters.

        "},{"location":"scripting-restconf/#read-interface-statistics","title":"Read Interface Statistics","text":"

        Extract specific statistics using jq:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces/interface=eth0 2>/dev/null \\\n     | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][0][\"statistics\"][\"in-octets\"]'\n
        "},{"location":"scripting-restconf/#list-all-interfaces","title":"List All Interfaces","text":"

        Get operational data for all interfaces:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-interfaces:interfaces\n
        "},{"location":"scripting-restconf/#read-routing-table","title":"Read Routing Table","text":"

        Get the IPv4 routing table:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-routing:routing/ribs/rib=ipv4-default\n
        "},{"location":"scripting-restconf/#read-ospf-state","title":"Read OSPF State","text":"

        Get OSPF operational data (neighbors, routes, etc.):

        ~$ ./curl.sh -h example.local -d operational GET /ietf-routing:routing/control-plane-protocols/control-plane-protocol=ietf-ospf:ospfv2,default\n

        Or get just the neighbor information:

        ~$ ./curl.sh -h example.local -d operational GET /ietf-routing:routing/control-plane-protocols/control-plane-protocol=ietf-ospf:ospfv2,default/ietf-ospf:ospf/areas/area=0.0.0.0/interfaces\n
        "},{"location":"scripting-restconf/#system-operations-rpcs","title":"System Operations (RPCs)","text":""},{"location":"scripting-restconf/#factory-reset","title":"Factory Reset","text":"

        Reset the system to factory defaults:

        ~$ curl -kX POST -u admin:admin \\\n        -H \"Content-Type: application/yang-data+json\" \\\n        https://example.local/restconf/operations/ietf-factory-default:factory-reset\ncurl: (56) OpenSSL SSL_read: error:0A000126:SSL routines::unexpected eof while reading, errno 0\n

        Note: The connection error is expected - the device resets immediately.

        "},{"location":"scripting-restconf/#system-reboot","title":"System Reboot","text":"

        Reboot the system:

        ~$ curl -kX POST -u admin:admin \\\n        -H \"Content-Type: application/yang-data+json\" \\\n        https://example.local/restconf/operations/ietf-system:system-restart\n
        "},{"location":"scripting-restconf/#set-date-and-time","title":"Set Date and Time","text":"

        Example of an RPC that takes input/arguments:

        ~$ curl -kX POST -u admin:admin \\\n        -H \"Content-Type: application/yang-data+json\" \\\n        -d '{\"ietf-system:input\": {\"current-datetime\": \"2024-04-17T13:48:02-01:00\"}}' \\\n        https://example.local/restconf/operations/ietf-system:set-current-datetime\n

        Verify the change with SSH:

        ~$ ssh admin@example.local 'date'\nWed Apr 17 14:48:12 UTC 2024\n
        "},{"location":"scripting-restconf/#advanced-examples","title":"Advanced Examples","text":""},{"location":"scripting-restconf/#makefile-for-common-operations","title":"Makefile for Common Operations","text":"

        Create a Makefile to simplify common operations:

        HOST ?= infix.local\n\nlo-running:\n    ./curl.sh -h $(HOST) GET /ietf-interfaces:interfaces/interface=lo\n\nlo-operational:\n    ./curl.sh -h $(HOST) -d operational GET /ietf-interfaces:interfaces/interface=lo\n\nlo-add-ip:\n    ./curl.sh -h $(HOST) POST \\\n        /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254 \\\n        -d '{ \"prefix-length\": 32 }'\n\nlo-del-ip:\n    ./curl.sh -h $(HOST) DELETE \\\n        /ietf-interfaces:interfaces/interface=lo/ietf-ip:ipv4/address=192.168.254.254\n\n%-stats:\n    @./curl.sh -h $(HOST) -d operational GET /ietf-interfaces:interfaces/interface=$* 2>/dev/null \\\n        | jq -r '.[\"ietf-interfaces:interfaces\"][\"interface\"][0][\"statistics\"][\"in-octets\"]'\n\n%-monitor:\n    while sleep 0.2; do make -s HOST=$(HOST) $*-stats; done \\\n        | ttyplot -t \"$(HOST):$* in-octets\" -r\n

        Usage examples:

        # Get loopback operational state\n~$ make lo-operational\n\n# Add IP to loopback\n~$ make lo-add-ip\n\n# Get eth0 statistics\n~$ make eth0-stats\n\n# Monitor eth0 traffic in real-time (requires ttyplot)\n~$ make eth0-monitor\n

        You can override the host:

        ~$ make HOST=192.168.1.10 lo-operational\n
        "},{"location":"scripting-restconf/#monitoring-interface-traffic","title":"Monitoring Interface Traffic","text":"

        The %-monitor target demonstrates real-time monitoring by polling interface statistics and piping to ttyplot for visualization. Install ttyplot with:

        ~$ sudo apt install ttyplot\n

        Then monitor any interface:

        ~$ make eth0-monitor\n

        This creates a live ASCII graph of incoming octets on eth0.

        "},{"location":"scripting-restconf/#references","title":"References","text":"
        • RESTCONF Protocol (RFC 8040)
        • YANG Data Modeling Language (RFC 7950)
        • ietf-interfaces YANG module
        • ietf-routing YANG module
        • ietf-system YANG module
        "},{"location":"scripting-sysrepocfg/","title":"Legacy Scripting","text":"

        Deprecated - Use RESTCONF Instead

        This legacy interface requires elevated privileges (sudo sysrepocfg) even for admin users and is no longer intended for production use. All scripting should use RESTCONF as the standard management interface. There is however an NACM compliant replacement copy that can be used without sudo.

        "},{"location":"scripting-sysrepocfg/#legacy-scripting","title":"Legacy Scripting","text":"

        Although not the primary interface for Infix, it is possible to interact with the system using raw sysrepocfg commands. This way you get to interact directly with the YANG models when logged in to Infix. Thus, a set config, read config, read status and an RPC can be conducted using sysrepocfg for any supported YANG model.

        See sysrepocfg for more information. Examples below will utilize:

        • sysrepocfg -I FILE -fjson -d DATABASE to import/write a JSON formatted configuration file to the specified database.
        • sysrepocfg -E FILE -fjson -d DATABASE to edit/merge JSON formatted configuration in FILE with the specified database.
        • sysrepocfg -R FILE -fjson to execute remote procedure call (RPC) defined in FILE (JSON formatted).
        • sysrepocfg -X -fjson -d DATABASE -x xpath to read configuration or status from specified database.

        For importing (-I) and editing (-E), -d running is typically used in examples below. Specify -d startup to apply changes to startup configuration. Exporting (-X) could operate on configuration (e.g., -d running) or status (-d operational).

        Some commands require a file as input. In the examples below we assume it has been transferred to the device in advance, e.g. using scp:

        ~$ cat file.json\n{\n   \"ietf-factory-default:factory-reset\": {\n   }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$\n
        "},{"location":"scripting-sysrepocfg/#factory-reset","title":"Factory Reset","text":"
        ~$ cat file.json\n{\n   \"ietf-factory-default:factory-reset\": {\n   }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -fjson -R /tmp/file.json'\n^C\n~$\n

        See Factory Reset for another (simpler) alternative.

        If it is only wished to copy factory config to running config the following RPC is available

        ~$ cat file.json\n{\n   \"infix-factory-default:factory-default\": {\n   }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -fjson -R /tmp/file.json'\n^C\n~$\n
        "},{"location":"scripting-sysrepocfg/#system-reboot","title":"System Reboot","text":"
        ~$ cat /tmp/file.json\n{\n   \"ietf-system:system-restart\": {\n   }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -fjson -R /tmp/file.json'\n~$\n

        See System Reboot for another (simpler) alternative.

        "},{"location":"scripting-sysrepocfg/#set-date-and-time","title":"Set Date and Time","text":"
        ~$ ssh admin@example.local 'date'\nSun Nov 20 10:20:23 UTC 2005\n~$ cat file.json\n{\n   \"ietf-system:set-current-datetime\": {\n      \"current-datetime\": \"2024-04-17T13:48:02-01:00\"\n   }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -fjson -R /tmp/file.json'\n~$ ssh admin@example.local 'date'\nWed Apr 17 14:48:12 UTC 2024\n~$\n

        See Set Date and Time for another (simpler) alternative.

        "},{"location":"scripting-sysrepocfg/#remote-control-of-ethernet-ports","title":"Remote Control of Ethernet Ports","text":"

        Reading administrative status of interface e0 of running configuration.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d running -e report-all \\\n       -x \\\"/ietf-interfaces:interfaces/interface[name='e0']/enabled\\\"'\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\",\n        \"enabled\": true\n      }\n    ]\n  }\n}\n~$\n

        Note

        Without -e report-all argument the line \"enabled: true would not be shown as true is default.

        ~$ ssh admin@example.local \"sysrepocfg -X -fjson -d running \\\n       -x \\\"/ietf-interfaces:interfaces/interface[name='e0']/enabled\\\"\"\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\"\n      }\n    ]\n  }\n}\n~$\n

        Setting the administrative status of interface e0 of running configuration.

        $ cat file.json\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\",\n        \"enabled\": false\n      }\n    ]\n  }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -E /tmp/file.json -fjson -d running'\n~$\n
        "},{"location":"scripting-sysrepocfg/#enabledisable-dhcpv4-client","title":"Enable/Disable DHCPv4 client","text":"

        Enabling DHCPv4 client on interface e0, with current default options.

        ~$ cat /tmp/file.json\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\",\n        \"ietf-ip:ipv4\": {\n          \"infix-dhcp-client:dhcp\": {}\n        }\n      }\n    ]\n  }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -E /tmp/file.json -fjson -d running'\n~$\n

        Disabling DHCPv4 client on interface e0 (remove the dhcp container).

        ~$ cat /tmp/file.json\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\",\n        \"ietf-ip:ipv4\": {}\n      }\n    ]\n  }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -E /tmp/file.json -fjson -d running'\n~$\n

        To fully remove the DHCPv4 client configuration, remove the infix-dhcp-client:dhcp container from the interface's ipv4 configuration.

        "},{"location":"scripting-sysrepocfg/#enabledisable-ipv6","title":"Enable/Disable IPv6","text":"

        IPv6 is typically enabled on all interfaces by default. The example below shows IPv4 and IPv6 addresses assigned on e0.

        ~$ ssh admin@example.local 'ip addr show dev e0'\n2: e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000\n    link/ether 02:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff\n    inet 10.0.2.15/24 scope global proto dhcp e0\n       valid_lft forever preferred_lft forever\n    inet6 fec0::ff:fe00:0/64 scope site dynamic mngtmpaddr proto kernel_ra\n       valid_lft 86380sec preferred_lft 14380sec\n    inet6 fe80::ff:fe00:0/64 scope link proto kernel_ll\n       valid_lft forever preferred_lft forever\n~$\n

        IPv6 is enabled/disabled per interface. The example below disables IPv6 on interface e0.

        ~$ cat /tmp/file.json\n{\n  \"ietf-interfaces:interfaces\": {\n    \"interface\": [\n      {\n        \"name\": \"e0\",\n        \"ietf-ip:ipv6\": {\n          \"enabled\": false\n        }\n      }\n    ]\n  }\n}\n~$ scp file.json admin@example.local:/tmp/file.json\n~$ ssh admin@example.local 'sysrepocfg -E /tmp/file.json -fjson -d running'\n~$ ssh admin@example.local 'ip addr show dev e0'\n2: e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000\n    link/ether 02:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff\n    inet 10.0.2.15/24 scope global proto dhcp e0\n       valid_lft forever preferred_lft forever\n~$\n
        "},{"location":"scripting-sysrepocfg/#change-a-binary-setting","title":"Change a Binary Setting","text":"

        A YANG binary type setting is Base64 encoded and requires a little bit more tricks. We take the opportunity to showcase a shell script helper: /usr/bin/text-editor, which works just like the text-editor command in the CLI, but this one takes an XPath argument to the binary leaf to edit.

        Stripped down, it looks something like this:

        if tmp=$(sysrepocfg -G \"$xpath\"); then\n    file=$(mktemp)\n\n    echo \"$tmp\" | base64 -d > \"$file\"\n    if edit \"$file\"; then\n        tmp=$(base64 -w0 < \"$file\")\n        sysrepocfg -S \"$xpath\" -u \"$tmp\"\n    fi\n\n    rm -f \"$file\"\nelse\n    echo \"Failed to retrieve value for $xpath\"\n    exit 1\nfi\n

        An example container configuration, with an embedded file that is mounted to /var/www/index.html can look like this:

          \"infix-containers:containers\": {\n    \"container\": [\n      {\n        \"name\": \"web\",\n        \"image\": \"oci-archive:/lib/oci/curios-httpd-latest.tar.gz\",\n        \"hostname\": \"web\",\n        \"network\": {\n          \"interface\": [\n            {\n              \"name\": \"veth-sys0\"\n            }\n          ]\n        },\n        \"mount\": [\n          {\n            \"name\": \"index.html\",\n            \"content\": \"PCFET0NUWVBFIGh0bWwjibberish.shortened.down==\",\n            \"target\": \"/var/www/index.html\"\n          }\n        ]\n      }\n    ]\n  }\n

        The command to edit this file, and restart the container with the new contents, look like this:

        admin@infix:~$ cfg edit \"/infix-containers:containers/container[name='web']/mount[name='index.html']/content\"\n
        "},{"location":"scripting-sysrepocfg/#backup-configuration","title":"Backup Configuration","text":"

        Displaying running or startup configuration is possible with sysrepocfg -X, as shown below.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d running'\n{\n  \"ieee802-dot1ab-lldp:lldp\": {\n    \"infix-lldp:enabled\": true\n...\n~$\n

        An example for backing up startup configuration from remote PC.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d startup > /tmp/backup.json'\n~$ scp admin@example.local:/tmp/backup.json .\n~$\n

        Or possibly skip intermediate storage of file

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d startup' > backup.json\n~$\n

        A final example is to only use scp. This is simpler, but only works to backup the startup configuration (not running).

        ~$ scp admin@example.local:/cfg/startup-config.cfg backup.json\n~$\n
        "},{"location":"scripting-sysrepocfg/#restore-configuration","title":"Restore Configuration","text":"

        To restore a backup configuration to startup, the simplest way is to use scp and reboot as shown below

        ~$ scp admin@example.local:/cfg/startup-config.cfg backup.json\n~$ ssh admin@example.local 'reboot'\nConnection to switch.local closed by remote host.\n~$\n

        An alternative method to restore a backup configuration is to use the sysrepocfg -I FILE (import) command.

        The example below imports the backup configuration to startup, and reboots the unit.

        ~$ scp backup.json admin@example.local:/tmp/\n~$ ssh admin@example.local 'sudo sysrepocfg -I /tmp/backup.json -fjson -d startup'\n~$ ssh admin@example.local 'reboot'\nConnection to switch.local closed by remote host.\n~$\n

        Note

        The login credentials (hash) for the admin user are stored as part of the configuration file. When replacing a switch and applying the backed up configuration from the former switch, the password on the replacement unit will also change.

        "},{"location":"scripting-sysrepocfg/#copy-running-to-startup","title":"Copy Running to Startup","text":"

        The following command reads out the running config via sysrepocfg -X and writes the result to the startup configuration.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d running > /cfg/startup-config.cfg'\n~$\n

        An alternative is to write it to a temporary file, and use sysrepocfg -I to import it to startup.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d running > /tmp/running.json'\n~$ ssh admin@example.local 'sysrepocfg -I /tmp/running.json -fjson -d startup'\n~$\n
        "},{"location":"scripting-sysrepocfg/#read-hardware-information","title":"Read Hardware Information","text":"

        The IETF Hardware YANG model has been augmented for ONIE formatted production data stored in EEPROMs, if available. For details, see the VPD documentation and the ietf-hardware and infix-hardware YANG models.

        ~$ ssh admin@example.local 'sysrepocfg -X -fjson -d operational -x /ietf-hardware:hardware'\n{\n  \"ietf-hardware:hardware\": {\n    \"component\": [\n      {\n        \"name\": \"product\",\n        \"class\": \"infix-hardware:vpd\",\n        \"serial-num\": \"12345\",\n        \"model-name\": \"Switch2010\",\n        \"mfg-date\": \"2024-01-30T16:42:37+00:00\",\n        \"infix-hardware:vpd-data\": {\n          \"product-name\": \"Switch2010\",\n          \"part-number\": \"ABC123-001\",\n          \"serial-number\": \"007\",\n          \"mac-address\": \"00:53:00:01:23:45\",\n          \"manufacture-date\": \"01/30/2024 16:42:37\",\n          \"num-macs\": 11,\n          \"manufacturer\": \"ACME Production\",\n          \"vendor\": \"SanFran Networks\"\n        }\n      },\n      {\n        \"name\": \"USB\",\n        \"class\": \"infix-hardware:usb\",\n        \"state\": {\n          \"admin-state\": \"unlocked\",\n          \"oper-state\": \"enabled\"\n        }\n      }\n    ]\n  }\n}\n~$\n
        "},{"location":"scripting/","title":"Introduction","text":""},{"location":"scripting/#scripting-infix","title":"Scripting Infix","text":"

        In some situations a user cannot, or does not want to, use the NETCONF API for interacting with Infix. Examples include production tasks and simpler remote scripting jobs to one or more remote devices.

        This document assumes you have the password for the admin user, and that you can connect to the device. Please see Finding my Device for help on locating it.

        Furthermore, the example commands shown here that are execute from a PC to a remote device over SSH, use Linux/UNIX. With advances lately in both Windows and macOS, many of the user friendly tools previously only available in Linux are now available there too.

        The shell prompt for the PC laptop side:

        ~$\n

        The shell prompt when logged in to an Infix device:

        admin@example:~$\n

        Note

        The shell script commands used here are the raw variants which the CLI usually wraps in a warm and snugly blanket. Meaning they may change over time, while the CLI wrappers do not. That being said, please let us know if you find any inconsistencies.

        "},{"location":"scripting/#tips","title":"Tips","text":"
        • Ensure the admin user does not have clish as login shell
        • Enable SSH key authentication
        • Deploy same SSH public key to many Infix devices
        • Secure your private SSH key using, e.g., ssh-agent

        The ssh-keygen command, used to create the private/public key pair, asks for a passphrase and although this is technically optional it is highly recommended to set one. For ease of use, in particular when scripting, use ssh-agent to avoid retyping the passphrase for every command.

        Useful links on SSH, keys, and using ssh-agent:

        • https://en.wikipedia.org/wiki/Ssh-agent
        • https://www.cyberciti.biz/faq/how-to-use-ssh-agent-for-authentication-on-linux-unix/
        • https://goteleport.com/blog/how-to-use-ssh-agent-safely/
        "},{"location":"scripting/#admin-user-not-authorized","title":"Admin User Not Authorized","text":"

        All system services and critical configuration files are owned by the locked-down root user. It is not possible to activate the root user account for remote logins. Instead, use admin user and the sudo command prefix.

        Here we are logged in to an example device:

        admin@example:~$ cp /cfg/startup-config.cfg /cfg/backup-config.cfg\ncp: can't create '/cfg/backup-config.cfg': Permission denied\nadmin@example:~$ sudo cp /cfg/startup-config.cfg /cfg/backup-config.cfg\n
        "},{"location":"scripting/#examples","title":"Examples","text":"

        The following example commands are run from the PC over SSH. The following is a very brief introduction.

        The notation is ssh username@address, where address can be an IPv4 or IPv6 address, a DNS name, or an mDNS name, e.g. infix.local. In the case of IPv6: address%interface, where interface differs between operating systems. On Linux and macOS the interface name is used, but on Windows the interface index1 is used.

        "},{"location":"scripting/#logging-in-to-a-device","title":"Logging in to a device","text":"
        ~$ ssh admin@fe80::ff:fe00:0%eth0\nThe authenticity of host 'fe80::ff:fe00:0%eth0 (fe80::ff:fe00:0%eth0)' can't be established.\nED25519 key fingerprint is SHA256:5/9mw64jhmYyD8MD+SwrsG3RXMBbP48pDe2T8bg14RQ.\nThis key is not known by any other names\nAre you sure you want to continue connecting (yes/no/[fingerprint])? yes\nWarning: Permanently added 'fe80::ff:fe00:0%eth0' (ED25519) to the list of known hosts.\nadmin@fe80::ff:fe00:0%eth0's password: *****\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-'\n\nRun the command 'cli' for interactive OAM\n\nadmin@infix-c0-ff-ee:/>\n
        "},{"location":"scripting/#running-a-remote-command","title":"Running a remote command","text":"
        ~$ ssh admin@fe80::ff:fe00:0%eth0 echo hej\nadmin@fe80::ff:fe00:0%eth0's password: *****\nhej\n
        "},{"location":"scripting/#made-easy-with-mdns","title":"Made Easy with mDNS","text":"

        Connecting to networked devices using IP addresses is the way many people are used to. The above example with IPv6 tend to scare off people, so for the rest of this document we'll use the mDNS name instead:

        ~$ ssh admin@infix.local%eth0\nThe authenticity of host 'infix.local%eth0' can't be established.\nAre you sure you want to continue connecting (yes/no/[fingerprint])? yes\n...\nadmin@infix:/>\n
        "},{"location":"scripting/#factory-reset","title":"Factory Reset","text":"

        The command option -y disables any \"are you sure?\" interaction and immediately triggers a factory reset and reboot of the device. It is when the device boots up it erases all writable storage.

        ~$ ssh admin@infix.local%eth0 factory -y\nadmin@infix.local%eth0's password: *****\n
        "},{"location":"scripting/#system-reboot","title":"System Reboot","text":"
        ~$ ssh admin@infix.local%eth0 reboot\nadmin@infix.local%eth0's password: *****\n
        "},{"location":"scripting/#set-date-and-time","title":"Set Date and Time","text":"

        Devices running Infix may have their system time completely off and this can cause problems for upgrading and when accessing the web interface over HTTPS (certificate validation looks at start and end dates).

        To set the device's system time, and sync that with the RTC: use the PCs current time as argument:

        ~$ ssh admin@infix.local%eth0 \"sudo date -s '2024-03-20 18:14+01:00' && \\\n                               sudo hwclock -w -u\"\nadmin@infix.local%eth0's password: *****\n

        Tip

        The -u option ensures saving system time to the RTC in UTC time.

        Verify that the change took:

        ~$ ssh admin@infix.local%eth0 date\nadmin@infix.local%eth0's password: *****\nWed Mar 20 17:14:47 UTC 2024\n
        "},{"location":"scripting/#remote-control-of-ethernet-ports","title":"Remote Control of Ethernet Ports","text":"

        There are two ways to do it:

        1. Change the configuration without saving it to startup-config
        2. Change the operational state

        The first involves sending a NETCONF command/config in XML. The second we will cover here. We start by querying available interfaces (ports) on the remote system:

        ~$ ssh admin@infix.local%qtap0 ip -br a\nadmin@infix.local%qtap0's password:\nlo               UP             127.0.0.1/8 ::1/128\ne0               UP             fe80::ff:fe00:0/64\ne1               UP\ne2               UP\ne3               UP\ne4               UP\ne5               UP             fe80::ff:fe00:5/64\ne6               UP             fe80::ff:fe00:6/64\ne7               UP             fe80::ff:fe00:7/64\ne8               UP             fe80::ff:fe00:8/64\ne9               UP             192.168.2.200/24 fe80::ff:fe00:9/64\nbr0              UP\n

        Here we see a loopback interface (lo), ten Ethernet ports (e0-e9) and a bridge (br0). From this quick glance we can guess that the ports e1-e4 are bridged (you can verify this with the remote command bridge link) because they do not have a link-local IPv6 address.

        I know it's port e6 that I want to take down:

        ~$ ssh admin@infix.local%qtap0 ip link set e6 down\nadmin@infix.local%qtap0's password:\nRTNETLINK answers: Operation not permitted\n~$ ssh admin@infix.local%qtap0 sudo ip link set e6 down\nadmin@infix.local%qtap0's password:\n

        Changing the operational link state of a port is a privileged command, so we have to prefix our command with sudo.

        Inspecting the link state again show the port is now down:

        ~$ ssh admin@infix.local%qtap0 ip -br a\nadmin@infix.local%qtap0's password:\nlo               UP             127.0.0.1/8 ::1/128\ne0               UP             fe80::ff:fe00:0/64\ne1               UP\ne2               UP\ne3               UP\ne4               UP\ne5               UP             fe80::ff:fe00:5/64\ne6               DOWN\ne7               UP             fe80::ff:fe00:7/64\ne8               UP             fe80::ff:fe00:8/64\ne9               UP             192.168.2.200/24 fe80::ff:fe00:9/64\nbr0              UP\n
        "},{"location":"scripting/#check-network-connectivity","title":"Check Network Connectivity","text":"

        Say you want to perform a [System Upgrade][#system-upgrade] and it just does not work, a good place to start troubleshooting is ensuring the device can actually reach the upgrade server.

        We run the following ping command remotely on the device:

        ~$ ssh admin@infix.local%eth0 ping -c 3 server.local\nadmin@infix.local%qtap0's password: *****\nPING server.local (192.168.2.42) 56(84) bytes of data.\n64 bytes from server.local: icmp_seq=1 ttl=64 time=0.201 ms\n64 bytes from server.local: icmp_seq=2 ttl=64 time=0.432 ms\n64 bytes from server.local: icmp_seq=3 ttl=64 time=0.427 ms\n\n--- server.local ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2050ms\nrtt min/avg/max/mdev = 0.201/0.353/0.432/0.107 ms\n

        Here we get a reply, so whatever is the issue with the upgrade was not hiding behind a connectivity issue at least.

        "},{"location":"scripting/#system-upgrade","title":"System Upgrade","text":"

        The underlying software that handles upgrades is called RAUC. To trigger an upgrade you (currently) need an FTP/TFTP or HTTP/HTTPS server where RAUC can fetch the upgrade from. In this example we use an FTP server to upgrade the currently inactive \"slot\":

        ~$ ssh admin@infix.local%eth0 rauc install ftp://server.local/infix-aarch64-24.06.0.pkg\nadmin@infix.local%eth0's password: *****\ninstalling\n  0% Installing\n  0% Determining slot states\n 20% Determining slot states done.\n 20% Checking bundle\n 20% Verifying signature\n 40% Verifying signature done.\n 40% Checking bundle done.\n 40% Checking manifest contents\n 60% Checking manifest contents done.\n 60% Determining target install group\n 80% Determining target install group done.\n 80% Updating slots\n 80% Checking slot rootfs.1\n 90% Checking slot rootfs.1 done.\n 90% Copying image to rootfs.1\n 99% Copying image to rootfs.1 done.\n 99% Updating slots done.\n100% Installing done.\nidle\nInstalling `ftp://server.local/infix-aarch64-24.06.0.pkg` succeeded\n~$\n

        The inactive slot is now marked active and will be used on the next boot. To upgrade the partition we booted from, we must first reboot.

        For more information, see the Boot Procedure document.

        Alternative:

        If you know your device has sufficient storage, eMMC or RAM disk (check with the remote command df -h), you can also copy the .pkg file to the device instead of having to set up an FTP/TFTP or HTTP/HTTPS server.

        Create an upload directory where admin has write permission:

        ~$ ssh admin@infix.local%eth0 \"sudo mkdir /var/tmp/upload; \\\n                               sudo chown admin /var/tmp/upload\"\nadmin@infix.local%eth0's password:\n

        Copy the file with secure copy, first we show the nasty IPv6 version of the command:

        ~$ scp infix-aarch64-24.06.0.pkg admin@\\[fe80::ff:fe00:0%eth0\\]:/var/tmp/upload/\nadmin@fe80::ff:fe00:0%eth0's password:\ninfix-aarch64-24.06.0.pkg                              100%  296   601.4KB/s   00:00\n

        And the upgrade command itself:

        ~$ ssh admin@infix.local%eth0 rauc install /var/tmp/upload/infix-aarch64-24.06.0.pkg\nadmin@infix.local%eth0's password: *****\n.\n.\n.\n

        Remember to remove the file from the upload directory when you are done, this can be done before or after the reboot to activate the upgrade. If you want to upgrade both \"slots\", then you can of course keep the file until you are done (provided the upload directory was created on persistent storage).

        ~$ ssh admin@infix.local%eth0 rm /var/tmp/upload/infix-aarch64-24.06.0.pkg\nadmin@infix.local%eth0's password: *****\n~$\n
        "},{"location":"scripting/#controlling-leds","title":"Controlling LEDs","text":"

        As part of production testing, LED verification is often expected to be performed. Infix uses standard Linux support for LED management, where LEDs appear in the file system under /sys/class/leds and can be controlled using echo command. sudo privileges are required.

        When interacting with LEDs this way, first disable the Infix iitod daemon to avoid conflicting LED control.

        ~$ ssh admin@example.local 'initctl stop iitod'\n

        Then run the test, e.g., visually control that a red LED labeled 'LAN' is working.

        ~$ ssh admin@example.local 'echo none | sudo tee /sys/class/leds/red\\:lan/trigger'\n~$ ssh admin@example.local 'echo 1    | sudo tee /sys/class/leds/red\\:lan/brightness'\n

        To turn off the same LED, run the following commands.

        ~$ ssh admin@example.local 'echo none | sudo tee /sys/class/leds/red\\:lan/trigger'\n~$ ssh admin@example.local 'echo 0    | sudo tee /sys/class/leds/red\\:lan/brightness'\n

        When done with LED testing, enable Infix iitod daemon again.

        ~$ ssh admin@example.local 'initctl start iitod'\n
        "},{"location":"scripting/#reading-power-feed-status","title":"Reading Power Feed Status","text":"

        As part of production tests, verification of Power Feed sensors is often expected to be performed. Infix uses standard Linux support for Power management, where power sources appear in the file system under /sys/class/power_supply. The following example reads status of two power supplies named pwr1 and pwr2.

        ~$ ssh admin@example 'cat /sys/class/power_supply/pwr1/online'\n1\n~$ ssh admin@example 'cat /sys/class/power_supply/pwr2/online'\n0\n~$\n

        Here, only pwr1 have power.

        1. Press Win-r to bring up the Run dialog, enter cmd.exe and press enter. Then type in ipconfig /all to list all interfaces, their status, as well as interface index.\u00a0\u21a9

        "},{"location":"support/","title":"Support Data","text":""},{"location":"support/#support-data-collection","title":"Support Data Collection","text":"

        When troubleshooting issues or seeking support, the support command provides a convenient way to collect comprehensive system diagnostics. This command gathers configuration files, logs, network state, and other system information into a single compressed archive.

        Note

        The support collect command should be run with sudo to collect complete system information (kernel logs, hardware details, etc.). Use the --unprivileged option to run as a regular user in degraded data collection mode.

        "},{"location":"support/#collecting-support-data","title":"Collecting Support Data","text":"

        To collect support data and save it to a file:

        admin@host:~$ sudo support collect > support-data.tar.gz\nStarting support data collection from host...\nCollecting to: /var/lib/support\nThis may take up to a minute. Please wait...\nTailing /var/log/messages for 30 seconds (please wait)...\nLog tail complete.\nCollection complete. Creating archive...\nadmin@host:~$ ls -l support-data.tar.gz\n-rw-rw-r-- 1 admin admin 508362 nov 30 13:05 support-data.tar.gz\n

        The command can also be run remotely via SSH from your workstation:

        $ ssh admin@host 'sudo support collect' > support-data.tar.gz\n...\n

        The collection process may take up to a minute depending on system load and the amount of logging data. Progress messages are shown during the collection process.

        "},{"location":"support/#encrypted-collection","title":"Encrypted Collection","text":"

        For secure transmission of support data, the archive can be encrypted with GPG using a password:

        admin@host:~$ sudo support collect -p mypassword > support-data.tar.gz.gpg\nStarting support data collection from host...\nCollecting to: /var/lib/support\nThis may take up to a minute. Please wait...\n...\nCollection complete. Creating archive...\nEncrypting with GPG...\n

        The support collect command even supports omitting mypassword and will then prompt interactively for the password. This works over SSH too, but the local ssh client may then echo the password.

        Tip

        To hide the encryption password for an SSH session, the script supports reading from stdin: echo \"$MYSECRET\" | ssh user@device 'sudo support collect -p' > file.tar.gz.gpg

        After transferring the resulting file to your workstation, decrypt it with the password:

        $ gpg -d support-data.tar.gz.gpg > support-data.tar.gz\n$ tar xzf support-data.tar.gz\n...\n

        or

        $ gpg -d support-data.tar.gz.gpg | tar xz\n...\n

        Important

        Make sure to share mypassword out-of-band from the encrypted data with the recipient of the data. I.e., avoid sending both in the same plain-text email for example.

        "},{"location":"support/#what-is-collected","title":"What is Collected","text":"

        The support archive includes:

        • System identification (hostname, uptime, kernel version)
        • Running and operational configuration (sysrepo datastores)
        • System logs (/var/log directory and live tail of messages log)
        • Network configuration and state (interfaces, routes, neighbors, bridges)
        • FRRouting information (OSPF, BFD status)
        • Container information (podman containers and their configuration)
        • System resource usage (CPU, memory, disk, processes)
        • Hardware information (PCI, USB devices, network interfaces)
        "},{"location":"syslog/","title":"Syslog Support","text":""},{"location":"syslog/#syslog-support","title":"Syslog Support","text":"

        The system comes with native logging to internal storage, /var/log/, or /log for a shortcut. Depending on the device, this may be a RAM disk, meaning logs are not retained across reboots. This document details how to log to external media or remote syslog servers.

        It is also possible to set up the device to act as a syslog server (log sink), this is covered briefly at the very end of this document.

        Note

        The default logging setup in the system cannot be modified, only the log file rotation. Please see the dir admin-exec command for a listing of existing log files.

        "},{"location":"syslog/#log-to-file","title":"Log to File","text":"

        Logging to a local file is useful when combined with an external media. E.g., a USB stick with a log partition (named/labeled: \"log\"). Below is an example.

        For a list of available log facilities, see the table in a later section.

        admin@example:/> configure\nadmin@example:/config/> edit syslog actions log-file file:/media/log/mylog\nadmin@example:/config/syslog/\u2026/file:/media/log/mylog/> set facility-list\n      all    audit     auth authpriv  console     cron    cron2   daemon      ftp     kern\n   local0   local1   local2   local3   local4   local5   local6   local7      lpr     mail\n     news      ntp   syslog     user     uucp\nadmin@example:/config/syslog/\u2026/file:/media/log/mylog/> set facility-list all severity\n     alert       all  critical     debug emergency     error      info      none    notice   warning\nadmin@example:/config/syslog/\u2026/file:/media/log/mylog/> set facility-list all severity critical\nadmin@example:/config/syslog/\u2026/file:/media/log/mylog/> set facility-list mail severity warning\nadmin@example:/config/syslog/\u2026/file:/media/log/mylog/> leave\nadmin@example:/>\n

        Important

        The log-file syntax requires the leading prefix file:. If the path is not absolute, e.g., file:mylog, the file is saved to the system default path, i.e., /log/mylog. In this case, verify that the filename is not already in use.

        "},{"location":"syslog/#log-rotation","title":"Log Rotation","text":"

        By default log files are allowed to grow to a size of 1 MiB after which they are \"rotated\". The whole reason for this is to not fill up the disk with outdated logs. A rotated file is saved in stages and older ones are also compressed (using gzip). Use the show log command in admin-exec context to start the log file viewer:

        admin@example:/config/syslog/> do show log\nlog  log.0  log.1.gz  log.2.gz  log.3.gz  log.4.gz  log.5.gz\nadmin@example:/config/syslog/> do show log log.1.gz\n

        Tip

        Use the Tab key on your keyboard list available log files. The do prefix is also very useful in configure context to access commands from admin-exec context.

        By default 10 compressed older files are saved. Here the oldest is log.5.gz and the most recently rotated one is log.0.

        Log file rotation can be configured both globally and per log file. Here we show the global settings, the set up is the same for per log file, which if unset inherit the global settings:

        admin@example:/> configure\nadmin@example:/config/> edit syslog file-rotation\nadmin@example:/config/syslog/file-rotation/> show\nadmin@example:/config/syslog/file-rotation/>\n

        The defaults are not shown. We can inspect them by asking the YANG model for the help texts:

        admin@example:/config/syslog/file-rotation/> help\n   max-file-size    number-of-files\nadmin@example:/config/syslog/file-rotation/> help max-file-size\nNAME\n        max-file-size kilobytes\n\nDESCRIPTION\n        Maximum log file size (kiB), before rotation.\n\nDEFAULT\n        1024\nadmin@example:/config/syslog/file-rotation/> help number-of-files\nNAME\n        number-of-files [0..4294967295]\n\nDESCRIPTION\n        Maximum number of log files retained.\n\nDEFAULT\n        10\n

        To change the defaults to something smaller, 512 kiB and 20 (remember everything after .0 is compressed, and text compresses well):

        admin@example:/config/syslog/file-rotation/> set max-file-size 512\nadmin@example:/config/syslog/file-rotation/> set number-of-files 20\nadmin@example:/config/syslog/file-rotation/> show\nnumber-of-files 20;\nmax-file-size 512;\nadmin@example:/config/syslog/file-rotation/> leave\nadmin@example:/>\n
        "},{"location":"syslog/#log-format","title":"Log Format","text":"

        There are three major syslog log formats, the default is RFC3164 for log files and BSD for remote logging. Depending on time synchronization and remote log server capabilities, or policies, the RFC5424 format is often preferred since it not only has better time resolution but also supports structured logging:

        BSD     : myproc[8710]: Kilroy was here.\nRFC3164 : Aug 24 05:14:15 192.0.2.1 myproc[8710]: Kilroy was here.\nRFC5424 : 2003-08-24T05:14:15.000003-07:00 192.0.2.1 myproc 8710 - - Kilroy was here.\n

        The BSD format is only applicable to remote logging. It remains the default for compatibility reasons, and is recommended since the device may not have proper time, making it better for the remote log server to perform time stamping at the time of arrival.

        Configuring the log format is the same for log files and remotes:

        admin@example:/config/> edit syslog actions log-file file:foobar\nadmin@example:/config/syslog/\u2026/file:foobar/> set log-format\n                  bsd               rfc3164              rfc5424\nadmin@example:/config/syslog/\u2026/file:foobar/> set log-format rfc5424\nadmin@example:/config/syslog/\u2026/file:foobar/> leave\nadmin@example:/>\n
        "},{"location":"syslog/#log-to-remote-server","title":"Log to Remote Server","text":"

        Logging to a remote syslog server is the recommended way of supervising the system. This way all login attempts (console, SSH, or web) and any configuration changes can be traced, even in cases of a remote attacker tries to cover their traces by deleting logs.

        The recommended setup involves using a fixed IP address, default BSD log format, and the default Internet port (514). This is the most reliable, because your device may not have DNS set up or even available, and some remote syslog servers do not support receiving time stamped log messages -- this is of course entirely dependent on how the remote server is set up, as well as local policy.

        admin@example:/config/> edit syslog\n       actions file-rotation        server\nadmin@example:/config/> edit syslog actions destination moon\nadmin@example:/config/syslog/\u2026/moon/> set\n facility-list    log-format           udp\nadmin@example:/config/syslog/\u2026/moon/> set udp\n address    port\nadmin@example:/config/syslog/\u2026/moon/> set udp address 192.168.0.12\nadmin@example:/config/syslog/\u2026/moon/> set facility-list container severity all\nadmin@example:/config/syslog/\u2026/moon/> leave\nadmin@example:/>\n

        Tip

        The alternatives shown below each prompt in the example above can be found by tapping the Tab key.

        "},{"location":"syslog/#acting-as-a-log-server","title":"Acting as a Log Server","text":"

        The syslog server can act as a log sink for other devices on a LAN. For this to work you need a static IP address, here we use 10.0.0.1/24.

        admin@example:/> configure\nadmin@example:/config/> edit syslog server\nadmin@example:/config/syslog/server/> set enabled true\nadmin@example:/config/syslog/server/> set listen udp 514 address 10.0.0.1\nadmin@example:/config/syslog/server/> leave\nadmin@example:/>\n

        See the above Log to File section on how to set up filtering of received logs to local files. Advanced filtering based on hostname and message properties is also available, see the next section for details.

        "},{"location":"syslog/#advanced-filtering","title":"Advanced Filtering","text":"

        The syslog subsystem supports several advanced filtering options that allow fine-grained control over which messages are logged. These can be combined with facility and severity filters to create sophisticated logging rules.

        "},{"location":"syslog/#pattern-matching","title":"Pattern Matching","text":"

        Messages can be filtered using regular expressions (POSIX extended regex) on the message content. This is useful when you want to log only messages containing specific keywords or patterns:

        admin@example:/config/> edit syslog actions log-file file:errors\nadmin@example:/config/syslog/\u2026/file:errors/> set pattern-match \"ERROR|CRITICAL|FATAL\"\nadmin@example:/config/syslog/\u2026/file:errors/> set facility-list all severity info\nadmin@example:/config/syslog/\u2026/file:errors/> leave\nadmin@example:/>\n

        This will log all messages containing ERROR, CRITICAL, or FATAL.

        "},{"location":"syslog/#advanced-severity-comparison","title":"Advanced Severity Comparison","text":"

        By default, severity filtering uses \"equals-or-higher\" comparison, meaning a severity of error will match error, critical, alert, and emergency messages. You can change this behavior:

        admin@example:/config/> edit syslog actions log-file file:daemon-errors\nadmin@example:/config/syslog/\u2026/file:daemon-errors/> set facility-list daemon\nadmin@example:/config/syslog/\u2026/daemon/> set severity error\nadmin@example:/config/syslog/\u2026/daemon/> set advanced-compare compare equals\nadmin@example:/config/syslog/\u2026/daemon/> leave\nadmin@example:/>\n

        This will log only error severity messages, not higher severities.

        You can also block specific severities:

        admin@example:/config/syslog/\u2026/daemon/> set advanced-compare action block\n

        This will exclude error messages from the log.

        "},{"location":"syslog/#hostname-filtering","title":"Hostname Filtering","text":"

        When acting as a log server, you can filter messages by hostname. This is useful for directing logs from different devices to separate files:

        admin@example:/config/> edit syslog actions log-file file:router1\nadmin@example:/config/syslog/\u2026/file:router1/> set hostname-filter router1\nadmin@example:/config/syslog/\u2026/file:router1/> set facility-list all severity info\nadmin@example:/config/syslog/\u2026/file:router1/> leave\nadmin@example:/>\n

        Multiple hostnames can be added to the filter list.

        "},{"location":"syslog/#property-based-filtering","title":"Property-Based Filtering","text":"

        For more advanced filtering, you can match on specific message properties using various comparison operators:

        admin@example:/config/> edit syslog actions log-file file:myapp\nadmin@example:/config/syslog/\u2026/file:myapp/> edit property-filter\nadmin@example:/config/syslog/\u2026/property-filter/> set property programname\nadmin@example:/config/syslog/\u2026/property-filter/> set operator isequal\nadmin@example:/config/syslog/\u2026/property-filter/> set value myapp\nadmin@example:/config/syslog/\u2026/property-filter/> leave\nadmin@example:/>\n

        Available properties: - msg: Message body - msgid: RFC5424 message identifier - programname: Program/tag name - hostname: Source hostname - source: Alias for hostname - data: RFC5424 structured data

        Available operators: - contains: Substring match - isequal: Exact equality - startswith: Prefix match - regex: Basic regular expression - ereregex: Extended regular expression (POSIX ERE)

        The comparison can be made case-insensitive:

        admin@example:/config/syslog/\u2026/property-filter/> set case-insensitive true\n

        Or negated to exclude matching messages:

        admin@example:/config/syslog/\u2026/property-filter/> set negate true\n
        "},{"location":"syslog/#facilities","title":"Facilities","text":"Code Facility Description 0 kern Kernel log messages 1 user User-level messages 2 mail Mail system 3 daemon General system daemons 4 auth Security/authorization messages 5 syslog Messages generated by syslogd 6 lpr Line printer subsystem 7 news Network news subsystem 8 uucp UNIX-to-UNIX copy 9 cron Clock/cron daemon (BSD, Linux) 10 authpriv Security/authorization messages (private) 11 ftp FTP daemon 12 ntp NTP subsystem 13 audit Log audit (security) 14 console Log alert 15 cron2 Clock/cron daemon (Solaris) 16 rauc* local0, reserved for RAUC 17 container* local1, reserved for containers 18 local2 Currently unused 19 local3 Currently unused 20 local4 Currently unused 21 local5 Currently unused 22 reserved* local6, reserved for industrial Ethernet 23 web* local7, reserved for nginx web server

        Facilities marked * are local augments to the model.

        "},{"location":"system/","title":"Configuration","text":""},{"location":"system/#system-configuration","title":"System Configuration","text":"

        System settings in Infix are provided by the ietf-system YANG model, augmented with Linux specific extensions in infix-system, like Message of the Day (login message) and user login shell. More on this later on in this document.

        For the sake of brevity, the hostname in the following examples has been shortened to example. The default hostname is composed from a product specific string followed by the last three octets of the system base MAC address, e.g., switch-12-34-56. An example of how to change the hostname is included below.

        Note

        When issuing leave to activate your changes, remember to also save your settings, copy running-config startup-config. See the CLI Introduction for a background.

        "},{"location":"system/#changing-password","title":"Changing Password","text":"

        User management, including passwords, SSH keys, remote authentication is available in the system authentication configuration context.

        admin@example:/config/> edit system authentication user admin\nadmin@example:/config/system/\u2026/user/admin/> change password\nNew password:\nRetype password:\nadmin@example:/config/system/\u2026/user/admin/> leave\n

        The change password command starts an interactive dialogue that asks for the new password, with a confirmation, and then salts and encrypts the password with sha512crypt.

        It is also possible to use the set password ... command. This allows setting an already hashed password. To manually hash a password, use the do password encrypt command. This launches the admin-exec command to hash, and optionally salt, your password. This encrypted string can then be used with set password ....

        Tip

        If you are having trouble thinking of a password, there is a nifty password generate command in admin-exec context which generates random passwords using the UNIX command pwgen. Use the do prefix when inside any configuration context to access admin-exec commands.

        "},{"location":"system/#ssh-authorized-key","title":"SSH Authorized Key","text":"

        Logging in remotely with SSH is possible by adding a public key to a user. Here we add the authorized key to the admin user, multiple keys are supported.

        With SSH keys in place it is possible to disable password login, just remember to verify SSH login and network connectivity before doing so.

        admin@example:/config/> edit system authentication user admin\nadmin@example:/config/system/\u2026/user/admin/> edit authorized-key admin@example\nadmin@example:/config/system/\u2026/example@host/> set algorithm ssh-rsa\nadmin@example:/config/system/\u2026/example@host/> set key-data AAAAB3NzaC1yc2EAAAADAQABAAABgQC8iBL42yeMBioFay7lty1C4ZDTHcHyo739gc91rTTH8SKvAE4g8Rr97KOz/8PFtOObBrE9G21K7d6UBuPqmd0RUF2CkXXN/eN2PBSHJ50YprRFt/z/304bsBYkDdflKlPDjuSmZ/+OMp4pTsq0R0eNFlX9wcwxEzooIb7VPEdvWE7AYoBRUdf41u3KBHuvjGd1M6QYJtbFLQMMTiVe5IUfyVSZ1RCxEyAB9fR9CBhtVheTVsY3iG0fZc9eCEo89ErDgtGUTJK4Hxt5yCNwI88YaVmkE85cNtw8YwubWQL3/tGZHfbbQ0fynfB4kWNloyRHFr7E1kDxuX5+pbv26EqRdcOVGucNn7hnGU6C1+ejLWdBD7vgsoilFrEaBWF41elJEPKDzpszEijQ9gTrrWeYOQ+x++lvmOdssDu4KvGmj2K/MQTL2jJYrMJ7GDzsUu3XikChRL7zNfS2jYYQLzovboUCgqfPUsVba9hqeX3U67GsJo+hy5MG9RSry4+ucHs=\nadmin@example:/config/system/\u2026/example@host/> show\nalgorithm ssh-rsa;\nkey-data AAAAB3NzaC1yc2EAAAADAQABAAABgQC8iBL42yeMBioFay7lty1C4ZDTHcHyo739gc91rTTH8SKvAE4g8Rr97KOz/8PFtOObBrE9G21K7d6UBuPqmd0RUF2CkXXN/eN2PBSHJ50YprRFt/z/304bsBYkDdflKlPDjuSmZ/+OMp4pTsq0R0eNFlX9wcwxEzooIb7VPEdvWE7AYoBRUdf41u3KBHuvjGd1M6QYJtbFLQMMTiVe5IUfyVSZ1RCxEyAB9fR9CBhtVheTVsY3iG0fZc9eCEo89ErDgtGUTJK4Hxt5yCNwI88YaVmkE85cNtw8YwubWQL3/tGZHfbbQ0fynfB4kWNloyRHFr7E1kDxuX5+pbv26EqRdcOVGucNn7hnGU6C1+ejLWdBD7vgsoilFrEaBWF41elJEPKDzpszEijQ9gTrrWeYOQ+x++lvmOdssDu4KvGmj2K/MQTL2jJYrMJ7GDzsUu3XikChRL7zNfS2jYYQLzovboUCgqfPUsVba9hqeX3U67GsJo+hy5MG9RSry4+ucHs=;\nadmin@example:/config/system/\u2026/example@host/> leave\n

        Note

        The ssh-keygen program already base64 encodes the public key data, so there is no need to use the text-editor command, set does the job.

        "},{"location":"system/#multiple-users","title":"Multiple Users","text":"

        The factory configuration provides three hierarchical user group levels by default: guest \u2282 operator \u2282 admin. These levels work out-of-the-box with sensible permissions - operators can configure the system immediately, while sensitive items (passwords, cryptographic keys) remain protected.

        The default levels provide different access to system resources and configuration:

        • Admin: Full system access - can manage users, upgrade software, restart the system, and modify all configuration including network settings, routing, and firewall rules.

        • Operator: Configuration access - can modify most system settings including network interfaces, routing, firewall, hostname, and more. Cannot access password hashes, cryptographic keys, or perform sensitive operations (factory reset, software upgrade).

        • Guest: Read-only access - can view operational state and configuration but cannot modify anything or execute operations.

        System access control is handled by the ietf-netconf-acm YANG model, usually referred to as NACM, which provides granular access to configuration, data, and RPC commands. The hierarchical levels in the system are determined by:

        1. NACM permissions - what the user can access
        2. Shell setting - which command-line interface the user can use

        By default the system ships with a single user, admin, in the admin group. There are no restrictions on the number of users with admin privileges, nor is the admin user reserved or protected -- it can be removed from the configuration. However, it is strongly recommended to keep at least one user with administrator privileges, otherwise the only way to regain full access is to perform a factory reset.

        For an overview of users and groups on the system, there is an admin-exec command:

        admin@example:/> show nacm\nenabled              : yes\ndefault read access  : permit\ndefault write access : permit\ndefault exec access  : permit\ndenied operations    : 0\ndenied data writes   : 0\ndenied notifications : 0\n\n          \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n          \u2502 GROUP    \u2502  READ   \u2502  WRITE  \u2502  EXEC   \u2502\n          \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n          \u2502 admin    \u2502    \u2713    \u2502    \u2713    \u2502    \u2713    \u2502\n          \u2502 operator \u2502    \u26a0    \u2502    \u26a0    \u2502    \u26a0    \u2502\n          \u2502 guest    \u2502    \u26a0    \u2502    \u2717    \u2502    \u2717    \u2502\n          \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n              \u2713 Full    \u26a0 Restricted    \u2717 Denied\n\nUSER                   SHELL   LOGIN                            \nadmin                  bash    password+key\njacky                  clish   password\nmonitor                false   key\n\nGROUP                  USERS                                    \nadmin                  admin\noperator               jacky\nguest                  monitor\n

        The permissions matrix shows effective access for each NACM group:

        • \u2713 Full (green) - unrestricted access
        • \u26a0 Restricted (yellow) - access with exceptions, use show nacm group for details
        • \u2717 Denied (red) - no access

        For detailed information about a specific group's rules:

        admin@example:/> show nacm group operator\nmembers          : jacky\nread permission  : restricted\nwrite permission : restricted\nexec permission  : restricted\napplicable rules : 4\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\npermit-system-rpcs\n  action     : permit\n  operations : exec\n  target     : ietf-system (rpc: *)\n  comment    : Operators can reboot, shutdown, and set system time.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-password-access (via '*')\n  action     : deny\n  operations : *\n  target     : /ietf-system:system/authentication/user/password\n  comment    : No user except admins can access password hashes.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-keystore-access (via '*')\n  action     : deny\n  operations : *\n  target     : ietf-keystore\n  comment    : No user except admins can access cryptographic keys.\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndeny-truststore-access (via '*')\n  action     : deny\n  operations : *\n  target     : ietf-truststore\n  comment    : No user except admins can access trust store.\n

        For user details:

        admin@example:/> show nacm user jacky\nshell            : clish\nlogin            : password\nnacm group       : operator\nread permission  : restricted\nwrite permission : restricted\nexec permission  : restricted\n\nFor detailed rules, use: show nacm group <name>\n
        "},{"location":"system/#adding-a-user","title":"Adding a User","text":"

        Creating a new user starts with defining the user account in the system:

        admin@example:/config/> edit system authentication user jacky\nadmin@example:/config/system/\u2026/user/jacky/> change password\nNew password:\nRetype password:\nadmin@example:/config/system/\u2026/user/jacky/> leave\n

        Tip

        It is also possible to use set password ... if you have the fully crypted and salted string ready. This can be created offline with the mkpasswd(1) tool, or the built-in CLI version do password encrypt [OPTS]. The do prefix is handy for reaching all top-level commands when in configure context.

        An authorized SSH key can be added the same way as described in the previous sections.

        By default, shell access is disabled (shell false). To allow CLI/SSH access, set the shell:

        admin@example:/config/> edit system authentication user jacky\nadmin@example:/config/system/\u2026/user/jacky/> set shell clish\nadmin@example:/config/system/\u2026/user/jacky/> leave\n

        Available shells:

        • bash - Full Bourne-again shell (recommended for admins only)
        • sh - POSIX shell (recommended for admins only)
        • clish - Limited CLI-only shell (recommended for operators and guests)
        • false - No shell access (default)

        Security Notice

        For security reasons, it is strongly recommended to limit non-admin users to the clish shell, which provides CLI access without exposing the underlying UNIX system. Reserve bash and sh for administrators who need full system access for debugging and maintenance.

        Note that shell and CLI access is not always necessary - the system supports NETCONF and RESTCONF for remote management and automation. Setting shell false for users who only need programmatic access minimizes the attack surface and improves overall system security.

        "},{"location":"system/#adding-a-user-to-a-group","title":"Adding a User to a Group","text":"

        To assign a user to a specific privilege level, add them to the corresponding NACM group:

        Operator user:

        admin@example:/config/> edit nacm group operator\nadmin@example:/config/nacm/group/operator/> set user-name jacky\nadmin@example:/config/nacm/group/operator/> leave\n

        Adding another admin:

        admin@example:/config/> edit nacm group admin\nadmin@example:/config/nacm/group/admin/> set user-name alice\nadmin@example:/config/nacm/group/admin/> leave\n

        Guest user:

        admin@example:/config/> edit nacm group guest\nadmin@example:/config/nacm/group/guest/> set user-name monitor\nadmin@example:/config/nacm/group/guest/> leave\n

        Tip

        For technical details about NACM rule evaluation, module-name vs path matching, and creating custom access control policies, see the NACM Technical Guide.

        "},{"location":"system/#access-control-matrix","title":"Access Control Matrix","text":"

        The following table shows what each user level can do based on the NACM rules and shell access configured for each user:

        • Admin: bash \u2014 full system access
        • Operator: clish \u2014 CLI-only access without UNIX system exposure
        • Guest: false \u2014 no shell access
        Feature Admin Operator Guest Network interfaces \u2713 \u2713 Read only Routing (FRR) \u2713 \u2713 Read only Firewall rules \u2713 \u2713 Read only VLANs/bridges \u2713 \u2713 Read only Containers \u2713 \u2713 Read only Hostname/system config \u2713 \u2713 Read only CLI/SSH access \u2713 \u2713 \u2717 System restart \u2713 \u2713 \u2717 Set date/time \u2713 \u2713 \u2717 System reboot \u2713 \u2713 \u2717 System shutdown \u2713 \u2713 \u2717 User management \u2713 \u2717 Read only Keystore (certs/keys) \u2713 \u2717 \u2717 Truststore \u2713 \u2717 \u2717 Read passwords/secrets \u2713 \u2717 \u2717 NACM rules \u2713 \u2717 \u2717 Factory reset \u2713 \u2717 \u2717 Software upgrade \u2713 \u2717 \u2717"},{"location":"system/#security-aspects","title":"Security Aspects","text":"

        The three default user levels are implemented through a combination of NACM rules and UNIX group membership. Access control is permission-based, not name-based - the system detects user levels by examining their NACM permissions and shell settings.

        Admin users have unrestricted NACM access with the following rule:

           \"module-name\": \"*\",\n   \"access-operations\": \"*\",\n   \"action\": \"permit\"\n

        Admin users are automatically added to the UNIX wheel and frrvty groups, granting them sudo privileges and access to FRR routing protocols. This makes it possible to use all the underlying UNIX tooling, which can be very useful for debugging, but please use with care -- the system is designed to be managed through the CLI and NETCONF, not directly via shell commands.

        Operator users use the permit-by-default NACM model (write-default: \"permit\"), which means they can configure most system settings without explicit permit rules. This design is \"future proof\" - when new features are added, operators can immediately use them.

        The following are explicitly denied to operators through global NACM rules:

        • Password hashes (/ietf-system:system/authentication/user/password)
        • Cryptographic keys (ietf-keystore module)
        • Trust store certificates (ietf-truststore module)

        Additionally, sensitive operations like factory reset, software upgrades, and system shutdown are protected by YANG-level nacm:default-deny-all annotations and remain restricted to administrators.

        Operators are automatically added to the UNIX operator and frrvty groups, granting them sudo privileges for network operations and FRR access.

        Guest users have read-only NACM access through an explicit deny rule that blocks all write and exec operations (create update delete exec), while read-default: \"permit\" allows viewing configuration and state. Guests receive no special UNIX group memberships. The shell setting determines whether guests can access the CLI (clish) or are restricted from shell access entirely (false).

        All users, regardless of level, are denied access to password hashes and cryptographic key material through global NACM rules.

        "},{"location":"system/#changing-hostname","title":"Changing Hostname","text":"

        Notice how the hostname in the prompt does not change until the change is committed by issuing the leave command.

        admin@example:/config/> edit system\nadmin@example:/config/system/> set hostname myrouter\nadmin@example:/config/system/> leave\nadmin@myrouter:/>\n

        The hostname is advertised over mDNS-SD in the .local domain. If another device already has claimed the myrouter.local CNAME, in our case, mDNS will advertise a \"uniqified\" variant, usually suffixing with an index, e.g., myrouter-1.local. Use an mDNS browser to scan for available devices on your LAN.

        In some cases you may want to set the device's domain name as well. This is handled the same way:

        admin@example:/config/> edit system\nadmin@example:/config/system/> set hostname foo.example.com\nadmin@example:/config/system/> leave\nadmin@foo:/>\n

        Both host and domain name are stored in the system files /etc/hosts and /etc/hostname. The latter is exclusively for the host name. The domain may be used by the system DHCP server when handing out leases to clients, it is up to the clients to request the domain name option.

        Note

        Critical services like syslog, mDNS, LLDP, and similar that advertise the hostname, are restarted when the hostname is changed.

        "},{"location":"system/#changing-login-banner","title":"Changing Login Banner","text":"

        The motd-banner setting is an Infix augment and an example of a binary type setting that can be changed interactively with the built-in text-editor command.

        Tip

        See the next section for how to change the editor used to something you may be more familiar with.

        admin@example:/config/> edit system\nadmin@example:/config/system/> text-editor motd-banner\nadmin@example:/config/system/> leave\nadmin@example:/>\n

        Log out and log back in again to inspect the changes.

        "},{"location":"system/#changing-the-editor","title":"Changing the Editor","text":"

        The system has three different built-in editors that can be used as the text-editor command:

        • emacs (Micro Emacs)
        • nano (GNU Nano)
        • vi (Visual Editor)

        To change the editor to GNU Nano:

        admin@example:/> configure\nadmin@example:/config/> edit system\nadmin@example:/config/system/> set text-editor nano\nadmin@example:/config/system/> leave\nadmin@example:/>\n

        Important

        Configuration changes only take effect after issuing the leave command. I.e., you must change the editor first, and then re-enter configure context to use your editor of choice.

        "},{"location":"system/#dns-resolver-configuration","title":"DNS Resolver Configuration","text":"

        The system supports both static and dynamic (DHCP) DNS setup. The locally configured (static) server is preferred over any acquired from a DHCP client.

        admin@example:/> configure\nadmin@example:/config/> edit system dns-resolver\nadmin@example:/config/system/dns-resolver/> set server google udp-and-tcp address 8.8.8.8\nadmin@example:/config/system/dns-resolver/> show\nserver google {\n  udp-and-tcp {\n    address 8.8.8.8;\n  }\n}\nadmin@example:/config/system/dns-resolver/> leave\n

        It is also possible to configure resolver options like timeout and retry attempts. See the YANG model for details, or use the built-in help system in the CLI.

        Note

        When acting as a DHCP server and DNS proxy for other devices, any local DNS server configured here is automatically used as upstream DNS server.

        "},{"location":"system/#ntp-client-configuration","title":"NTP Client Configuration","text":"

        Below is an example configuration for enabling NTP with a specific server and the iburst option for faster initial synchronization.

        admin@example:/> configure\nadmin@example:/config/> edit system ntp\nadmin@example:/config/system/ntp/> set enabled\nadmin@example:/config/system/ntp/> set server ntp-pool\nadmin@example:/config/system/ntp/> set server ntp-pool udp address pool.ntp.org\nadmin@example:/config/system/ntp/> set server ntp-pool iburst\nadmin@example:/config/system/ntp/> set server ntp-pool prefer\nadmin@example:/config/system/ntp/> leave\n

        This configuration enables the NTP client and sets the NTP server to pool.ntp.org with the iburst and prefer options. The iburst option ensures faster initial synchronization, and the prefer option designates this server as preferred.

        • prefer false: The NTP client will choose the best available source based on several factors, such as network delay, stratum, and other metrics (default config).
        • prefer true: The NTP client will try to use the preferred server as the primary source unless it becomes unreachable or unusable.
        "},{"location":"system/#show-ntp-sources","title":"Show NTP Sources","text":"

        The status for NTP sources is availble in YANG and accessable with CLI/NETCONF/RESTCONF.

        To view the sources being used by the NTP client, run:

        admin@example:/> show ntp\nMode                : Client\nStratum             : 3\nRef time (UTC)      : Sat Jan 24 23:41:42 2026\n\nADDRESS         MODE    STATE     STRATUM  POLL\n147.78.228.41   server  outlier         2   64s\n192.168.0.1     server  unusable        0  128s\n176.126.86.247  server  selected        2   64s\n
        "},{"location":"system/#show-ntp-status","title":"Show NTP Status","text":"

        To check the status of NTP synchronization (only availble in CLI), use the following command:

        admin@example:/> show ntp tracking\nReference ID        : 176.126.86.247\nStratum             : 3\nRef time (UTC)      : Sat Jan 24 23:41:42 2026\nSystem time         : 0.000000 seconds slow of NTP time\nLast offset         : -454779.375000000 seconds\nRMS offset          : 454779.375000000 seconds\nFrequency           : 0.000 ppm slow\nResidual freq       : -26.383 ppm\nSkew                : 1000000.000 ppm\nRoot delay          : 0.007395 seconds\nRoot dispersion     : 39.181149 seconds\nUpdate interval     : 0.0 seconds\nLeap status         : Normal\n

        This output provides detailed information about the NTP status, including reference ID, stratum, time offsets, frequency, and root delay.

        Tip

        The system uses chronyd Network Time Protocol (NTP) daemon. The output shown here is best explained in the Chrony documentation.

        "},{"location":"test-arch/","title":"Test System Architecture","text":""},{"location":"test-arch/#test-system-architecture","title":"Test System Architecture","text":""},{"location":"test-arch/#tenets","title":"Tenets","text":"
        • Keep overhead to a minimum. Tests should be fast to both write and run. Ideally, the developer should want to add tests early in the development cycle because they instinctively feel that that is the quickest route to arrive at a correct and robust implementation.

        • Both physical and virtual hardware matters. Infix is primarily deployed on physical hardware, so being able to run the test suite on real devices is crucial to guarantee a high quality product. At the same time, there is much value in running the same suite on virtual hardware, as it makes it easy to catch regressions early. It is also much more practical and economical to build large virtual networks than physical ones.

        • Avoid CLI scripting & scraping. Reliably interacting with a DUT over a serial line in a robust way is very hard to get right. Given that we have a proper API (RESTCONF), we should leverage that when testing. Front-ends can be tested by other means.

        "},{"location":"test-arch/#overview","title":"Overview","text":"

        The test system is made up of several independent components, which are typically used in concert to run a full test suite.

        "},{"location":"test-arch/#test-cases","title":"Test Cases","text":"

        A test case is an executable, receiving the physical topology as a positional argument, which produces TAP compliant output on its stdout. I.e., it is executed in the following manner:

        test-case [OPTS] <physical-topology>\n

        Test cases are typically written in Python, using the Infamy library. Ultimately though, it can be implemented in any language, as long as it matches the calling convention above.

        "},{"location":"test-arch/#infamy","title":"Infamy","text":"

        Rather than having each test case come up with its own implementation of how to map topologies, how to push NETCONF data to a device, etc., we provide a library of functions to take care of all that, dubbed \"Infamy\". When adding a new test case, ask yourself if any parts of it might belong in Infamy as a generalized component that can be reused by other tests.

        Some of the core functions provided by Infamy are:

        • Mapping a logical topology to a physical one
        • Finding and attaching to a device over an Ethernet interface, using NETCONF
        • Pushing/pulling NETCONF data to/from a device
        • Generating TAP compliant output
        "},{"location":"test-arch/#9pm","title":"9PM","text":"

        To run multiple tests, we employ 9PM. It let's us define test suites as simple YAML files. Suites can also be hierarchically structured, with a suite being made up of other suites, etc.

        It also validates the TAP output, making sure to catch early exits from a case, and produces a nice summary report.

        "},{"location":"test-arch/#testenv","title":"/test/env","text":"

        A good way to ensure that nobody ever runs the test suite is to make it really hard to do so. /test/env's job is instead to make it very easy to create a reproducible environment in which tests can be executed.

        Several technologies are leveraged to accomplish this:

        • Containers: The entire execution is optionally done inside a standardized docker container environment. This ensures that the software needed to run the test suite is always available, no matter which distribution the user is running on their machine.

        • Python Virtual Environments: To make sure that the expected versions of all Python packages are available, the execution is wrapped inside a venv. This is true for containerized executions, where the container comes with a pre-installed environment, but it can also be sourced from the host system when running outside of the container.

        • Virtual Test Topology: Using Qeneth, the environment can optionally be started with a virtual topology of DUTs to run the tests on.

        docker is the only supported container environment when running tests in the host's network namespace. When running on a virtual Qeneth topology, podman may also be used by installing the podman-docker package from your host system's distro.

        "},{"location":"test-arch/#physical-and-logical-topologies","title":"Physical and Logical Topologies","text":"

        Imagine that we want to create a test with three DUTs; one acting as a DHCP server, and the other two as DHCP clients - with all three having a management connection to the host PC running the test. In other words, the test requires a logical topology like the one below.

        graph \"dhcp-client-server\" {\n    host [\n        label=\"host | { <c1> c1 | <srv> srv | <c2> c2 }\",\n        kind=\"controller\",\n    ];\n\n    server [\n        label=\"{ <mgmt> mgmt } | server | { <c1> c1 | <c2> c2 }\",\n        kind=\"infix\",\n    ];\n    client1 [\n        label=\"{ <mgmt> mgmt } | client1 | { <srv> srv }\",\n        kind=\"infix\",\n    ];\n    client2 [\n        label=\"{ <mgmt> mgmt } | client2 | { <srv> srv }\",\n        kind=\"infix\",\n    ];\n\n    host:srv -- server:mgmt\n    host:c1  -- client1:mgmt\n    host:c2  -- client2:mgmt\n\n    server:c1 -- client1:srv;\n    server:c2 -- client2:srv;\n}\n

        When running in a virtualized environment, one could simply create a setup that matches the test's logical topology. But in scenarios when devices are physical systems, connected by real copper cables, this is not possible (unless you have some wicked L1 relay matrix thingy).

        Instead, the test implementation does not concern itself with the exact nodes used to run the test, only that the logical topology can be mapped to some subset of the physical topology. In mathematical terms, the physical topology must contain a subgraph that is isomorphic to the logical topology.

        Standing on the shoulders of giants (i.e. people with mathematics degrees), we can deploy well-known algorithms to find such subgraphs. Continuing our example, let's say we want to run our DHCP test on the physical topology below.

        graph \"quad-ring\" {\n    host [\n        label=\"host | { <d1a> d1a | <d1b> d1b | <d1c> d1c | <d2a> d2a | <d2b> d2b | <d2c> d2c | <d3a> d3a | <d3b> d3b | <d3c> d3c | <d4a> d4a | <d4b> d4b | <d4c> d4c }\",\n        kind=\"controller\",\n    ];\n\n    dut1 [\n        label=\"{ <e1> e1 | <e2> e2 | <e3> e3 } | dut1 | { <e4> e4 | <e5> e5 }\",\n        kind=\"infix\",\n    ];\n    dut2 [\n        label=\"{ <e1> e1 | <e2> e2 | <e3> e3 } | dut2 | { <e4> e4 | <e5> e5 }\",\n        kind=\"infix\",\n    ];\n    dut3 [\n        label=\"{ <e1> e1 | <e2> e2 | <e3> e3 } | dut3 | { <e4> e4 | <e5> e5 }\",\n        kind=\"infix\",\n    ];\n    dut4 [\n        label=\"{ <e1> e1 | <e2> e2 | <e3> e3 } | dut4 | { <e4> e4 | <e5> e5 }\",\n        kind=\"infix\",\n    ];\n\n    host:d1a -- dut1:e1\n    host:d1b -- dut1:e2\n    host:d1c -- dut1:e3\n\n    host:d2a -- dut2:e1\n    host:d2b -- dut2:e2\n    host:d2c -- dut2:e3\n\n    host:d3a -- dut3:e1\n    host:d3b -- dut3:e2\n    host:d3c -- dut3:e3\n\n    host:d4a -- dut4:e1\n    host:d4b -- dut4:e2\n    host:d4c -- dut4:e3\n\n    dut1:e5 -- dut2:e4\n    dut2:e5 -- dut3:e4\n    dut3:e5 -- dut4:e4\n    dut4:e5 -- dut1:e4\n}\n

        Our test (in fact, all tests) receives the physical topology as an input parameter, and then maps the desired logical topology onto it, producing a mapping from logical nodes and ports to their physical counterparts.

        {\n  \"client1\": \"dut1\",\n  \"client1:mgmt\": \"dut1:e1\",\n  \"client1:srv\": \"dut1:e4\",\n  \"client2\": \"dut3\",\n  \"client2:mgmt\": \"dut3:e2\",\n  \"client2:srv\": \"dut3:e5\",\n  \"host\": \"host\",\n  \"host:c1\": \"host:d1a\",\n  \"host:c2\": \"host:d3b\",\n  \"host:srv\": \"host:d4c\",\n  \"server\": \"dut4\",\n  \"server:c1\": \"dut4:e5\",\n  \"server:c2\": \"dut4:e4\",\n  \"server:mgmt\": \"dut4:e3\"\n}\n

        With this information, the test knows that, in this particular environment, the server should be managed via the port called d4c on the node called host; that the port connected to the server on client1 is e4 on dut1, etc. Thereby separating the implementation of the test from any specific physical setup.

        Testcases are not required to use a logical topology; they may choose to accept whatever physical topology its given, and dynamically determine the DUTs to use for testing. As an example, an STP test could accept an arbitrary physical topology, run the STP algorithm on it offline, enable STP on all DUTs, and then verify that the resulting spanning tree matches the expected one.

        "},{"location":"test-arch/#integration-to-infix","title":"Integration to Infix","text":""},{"location":"test-arch/#test-mode","title":"Test Mode","text":"

        Infix supports a test mode that makes devices safe and predictable to test against. When active, each boot starts from a known-good test-config (equivalent to the failure/secure config) instead of the stored startup or factory configuration. This prevents any persistent configuration from interfering with tests and avoids L2 loops that a factory config might create. It also enables two extra RPCs used by Infamy:

        • infix-test:test/reset \u2014 reload the test-config into the running datastore (called by env.attach() at the start of each test)
        • infix-test:test/override-startup \u2014 boot from the startup datastore once, instead of the test-config (used by tests that verify save-and-reboot behaviour)

        Test mode is activated by the presence of an empty file named test-mode on the device's aux partition (/mnt/aux/test-mode).

        "},{"location":"test-arch/#test-mode-on-virtual-devices","title":"Test Mode on Virtual Devices","text":"

        When the test environment is started with Qeneth, the inject-test-mode script (test/inject-test-mode) is run automatically. It copies the base disk image and writes the test-mode marker into the aux partition of that copy, which is then used as the Qeneth backing image.

        "},{"location":"test-arch/#test-mode-on-physical-devices","title":"Test Mode on Physical Devices","text":"

        For a physical device, log in as root (or admin) and create the marker file on the aux partition, then reboot:

        # touch /mnt/aux/test-mode\n# reboot\n

        Alternatively, if you want to prepare the disk image before flashing, use the inject-test-mode script:

        $ test/inject-test-mode -b infix-<board>.img -o infix-<board>-test.img\n

        Flash infix-<board>-test.img to the device instead of the plain image. The device will boot into test mode on first power-on.

        Note

        Test mode persists across reboots until the marker file is removed (rm /mnt/aux/test-mode). A device in test mode always starts from the test-config, so any configuration changes are lost on reboot unless startup_override() is called first (or the marker is removed and a normal startup config is saved).

        "},{"location":"testing/","title":"Regression Testing","text":""},{"location":"testing/#regression-testing-with-infamy","title":"Regression Testing with Infamy","text":"

        Infix comes with a test suite that is intended to provide end-to-end verification of supported features. Generally speaking, this means that one or more DUTs are configured over NETCONF or RESTCONF; the resulting network is then black-box tested by injecting and inspecting network traffic at various points.

        This document is intended to be a practical guide on how to run, develop and debug tests. There is a separate document describing the Test System Architecture.

        "},{"location":"testing/#modes-of-testing","title":"Modes of Testing","text":""},{"location":"testing/#virtual-devices","title":"Virtual Devices","text":"

        By default, tests are run on a topology made up of virtual Infix nodes using Qeneth. To run the full regression test suite: build Infix for x86_64:

        $ make x86_64_defconfig\n...\n$ make\n...\n$ make test\n...\n
        "},{"location":"testing/#physical-devices","title":"Physical Devices","text":"

        To run the tests on a preexisting topology from the host's network namespace, specify the host TEST_MODE:

        $ make TEST_MODE=host test\n...\n

        This is typically used when testing on physical hardware. By default the topology will be sourced from /etc/infamy.dot, but this can be overwritten by setting the TOPOLOGY variable:

        $ make TEST_MODE=host TOPOLOGY=~/my-topology.dot test\n...\n

        Important

        Physical devices must be put into test mode before running tests. See Test Mode in the architecture document for details. The short version: log in to the device and run touch /mnt/aux/test-mode && reboot.

        "},{"location":"testing/#make-run-devices","title":"make run Devices","text":"

        Some tests only require a single DUT. These can therefore be run against an Infix image started from make run. This requires that the instance is configured to use TAP networking.

        When the instance is running, you can open a separate terminal and run the subset of the test suite that can be mapped to it:

        $ make TEST_MODE=run test\n...\n
        "},{"location":"testing/#interactive-usage","title":"Interactive Usage","text":"

        When developing and debugging tests, the overhead of repeatedly setting up and tearing down the test environment can quickly start to weigh you down. In these situation, you can start an interactive test environment:

        $ make test-sh\nInfo: Generating topology\nInfo: Generating node YAML\nInfo: Generating executables\nInfo: Launching dut1\nInfo: Launching dut2\nInfo: Launching dut3\nInfo: Launching dut4\n11:42:52 infamy0:test #\n

        The example above uses the default mode (qeneth), but the host and run modes also support the test-sh target.

        It takes a little while to start up, but then we have a shell prompt inside the container running Infamy. It's a very limited environment, but it has enough to easily run single tests, connect to the virtual devices, and step your code.

        "},{"location":"testing/#running-subsets-of-tests","title":"Running Subsets of Tests","text":"

        Each test case is a separate executable, which can be run without arguments:

        11:42:53 infamy0:test # ./case/dhcp/dhcp_basic.py\n

        To run a suite of tests, e.g., only the DHCP client tests, pass the suite as an argument to 9PM:

        11:42:53 infamy0:test # ./9pm/9pm.py case/dhcp/dhcp.yaml\n

        To run the suite of all tests:

        11:42:53 infamy0:test # ./9pm/9pm.py case/all.yaml\n
        "},{"location":"testing/#connecting-to-infamy","title":"Connecting to Infamy","text":"

        The test system runs in a Docker container, so to get a shell prompt in another terminal you need to connect to that container. Infamy comes with a helper script for this:

        $ ./test/shell\n11:42:53 infamy0:test #\n

        By default it connect to the latest started Infamy instance. If you for some reason run multiple instances of Infamy the shell script takes an optional argument \"system\", which is the hostname of the container you want to connect to:

        $ ./test/shell infamy2\n11:42:53 infamy2:test #\n
        "},{"location":"testing/#connecting-to-a-dut","title":"Connecting to a DUT","text":"

        Tip

        The console script uses telnet to connect to a port forwarded to localhost by the Docker container. To exit Telnet, use Ctrl-] and then 'q' followed by enter. This can be customized in ~/.telnetrc

        All DUTs in a virtual Infamy topology are emulated in Qemu instances managed by qeneth. If you want to watch what's happening on one of the target systems, e.g., tail a log file or run tcpdump during the test, there is another helper script in Infamy for this:

        $ ./test/console 1\nTrying 127.0.0.1...\nConnected to 127.0.0.1.\n\nInfix OS \u2014 Immutable.Friendly.Secure v23.11.0-226-g0c144da (console)\ninfix-00-00-00 login: admin\nPassword:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-'\n\nRun the command 'cli' for interactive OAM\n\nadmin@infix-00-00-00:~$\n

        From here we can observe dut1 freely while running tests.

        Like the shell script, console takes an optional \"system\" argument in case you run multiple instances of Infamy:

        $ ./test/console 1 infamy2\n...\n

        You can also connect to the console of a DUT from within a shell:

        $ ./test/shell\n11:42:54 infamy0:test # qeneth status\n11:42:54 infamy0:test # qeneth console dut1\nlogin: admin\npassword: *****\nadmin@infix-00-00-00:~$\n

        Tip

        Disconnect from the qeneth console by bringing up the old Telnet \"menu\" using Ctrl-], compared to standard Telnet this is the BusyBox version so you press 'e' + enter instead of 'q' to quit.

        "},{"location":"testing/#attaching-to-macvlans","title":"Attaching to MACVLANs","text":"

        To fully isolate the host's interfaces from one another, many tests will stack a MACVLAN on an interface, which is then placed in a separate network namespace.

        It is often useful to attach to those namespaces, so that you can interactively inject traffic into the test setup.

        First, connect to the Infamy instance in question:

        $ ./test/shell\n11:43:19 infamy0:test #\n

        Then, attach to the MACVLAN namespace by running the nsenter helper script from the test directory, supplying the base interface name as the first argument:

        11:43:19 infamy0:test # ./nsenter d1b\n11:43:20 infamy0(d1b):test #\n

        By default, an interactive shell is started, but you can also supply another command:

        11:43:19 infamy0:test # ./nsenter d1b ip -br addr\nlo               UNKNOWN        127.0.0.1/8 ::1/128\niface@if7        UP             10.0.0.1/24 fe80::38d0:88ff:fe77:b7cd/64\n

        You can now freely debug the network activity of your test and the responses from the DUT.

        "},{"location":"testing/#using-the-python-debugger","title":"Using the Python Debugger","text":"

        The built in breakpoint() function in Python is very useful when you want to run a test case to a certain point at which you might want to interactively inspect either the test's or the device's state.

        Simply insert a call to breakpoint() at the point of interest in your test and run it as normal. Once Python executes the call, it will drop you into the Python debugger:

        11:42:58 infamy0:test # ./case/dhcp/dhcp_basic.py\n# Starting (2024-02-10 11:42:59)\n# Probing dut1 on port d1a for IPv6LL mgmt address ...\n# Connecting to mgmt IP fe80::ff:fe00:0%d1a:830 ...\nok 1 - Initialize\n> /home/jocke/src/infix/test/case/dhcp/dhcp_basic.py(44)<module>()\n(Pdb)\n

        At this point you have full access to the test's state, but it is also an opportunity to inspect the state of the DUTs (e.g. via their console or over SSH).

        It is also possible to run a test under Pdb from the get-go, if you want to setup breakpoints without modifying the source, or simply step through the code:

        11:42:58 infamy0:test # python -m pdb case/dhcp/dhcp_basic.py\n
        "},{"location":"testing/#deterministic-topology-mappings","title":"Deterministic Topology Mappings","text":"

        By default, mappings from logical to physical topologies are not stable across test case executions. This can be very frustrating when debugging a failing test, since logical nodes are suffled around between phyical nodes. In such cases, supplying a PYTHONHASHSEED variable (set to any 32-bit unsigned integer) when launching the test environment will make sure that topology mappings are deterministic:

        $ make PYTHONHASHSEED=0 test-sh\n

        If a seed is not supplied, a random value is chosen. This seed is logged by the meta/reproducible.py test case when running a test suite:

        $ make test\nInfo: Generating topology\nInfo: Generating node YAML\nInfo: Generating executables\nInfo: Launching dut1\nInfo: Launching dut2\nInfo: Launching dut3\nInfo: Launching dut4\n9PM - Simplicity is the ultimate sophistication\n\nStarting test 0002-reproducible.py\n2024-05-03 10:40:30 # Starting (2024-05-03 10:40:30)\n2024-05-03 10:40:30 # Specify PYTHONHASHSEED=3773822171 to reproduce this test environment\n2024-05-03 10:40:30 ok 1 - $PYTHONHASHSEED is set\n2024-05-03 10:40:30 # Exiting (2024-05-03 10:40:30)\n2024-05-03 10:40:30 1..1\n...\n

        This is useful because this value can then be used to rerun a test (or the whole suite) with identical topology mappings:

        $ make PYTHONHASHSEED=3773822171 TESTS=case/system/hostname.py test\n
        "},{"location":"testing/#deterministic-transport-protocol","title":"Deterministic Transport Protocol","text":"

        By default, the communication transport protocol (NETCONF/RESTCONF) is chosen randomly. If you supply a PYTHONHASHSEED as described above, you get the same protocol used for that hash. But if you want to choose the protocol, add extra arguments to Infamy:

        $ make INFAMY_EXTRA_ARGS=\"--transport=restconf\" TESTS=case/system/hostname.py test\n

        or, when running interactively:

        $ make test-sh\n09:08:17 infamy0:test # ./9pm/9pm.py -o\"--transport=restconf\" case/system/hostname.py\n
        "},{"location":"testing/#test-specification","title":"Test specification","text":"

        The test specification is automaticly generated from the test cases, this puts some constraints on the test cases:

        All test cases must be written so that it can be understandable with only the steps so the result can be reproduced manually. E.g., instead of set ip address on the interface it should be more human readable, for example:

        test.step(\"Set IPv4 address on the interface dut:cross\")\ntest.step(\"Verify the IP address is set on dut:cross\")\n

        where dut, cross and the IPv4 address could be found inside the logical topology for the test.

        Each test case must begin with the test name followed by a empty line and the description of the test. For example:

        \"\"\"\nSet hostname\n\nVerify that it it possible to change hostname\n\"\"\"\n

        The test specifaction can be genererated with:

        $ make test-spec\n...\n
        "},{"location":"testing/#node-and-link-capabilities","title":"Node and Link Capabilities","text":"

        Logical topology files (topology.dot) declare what each node and link requires; physical topology files declare what each node and link provides. When mapping a logical topology to physical hardware, infamy only assigns a physical node to a logical node when the physical node's provides set is a superset of the logical node's requires set. Tests are skipped if no matching physical topology can be found.

        "},{"location":"testing/#declaring-requirements-logical-topology","title":"Declaring requirements (logical topology)","text":"
        dut [\n    requires=\"infix\",\n];\n\nhost:data -- dut:data [requires=\"ptp-hwts\"]\n
        "},{"location":"testing/#declaring-capabilities-physical-topology","title":"Declaring capabilities (physical topology)","text":"
        switch1 [\n    provides=\"infix\",\n];\n\nswitch1:eth0 -- switch2:eth0 [provides=\"ptp-hwts\"]\n
        "},{"location":"testing/#node-capabilities","title":"Node capabilities","text":"Capability Meaning controller Reserved for the host/controller node; never assigned to a DUT infix Node runs Infix OS \u2014 required by virtually all DUT nodes gps Node has a GPS receiver available as a time reference watchdog Node has a hardware watchdog device"},{"location":"testing/#link-capabilities","title":"Link capabilities","text":"Capability Meaning mgmt Link is a management path (typically coloured grey in diagrams) ieee-mc Link carries IEEE multicast traffic (required by LAG and some L2 tests) link-ctrl copper Link supports copper speed/duplex control ptp-hwts Both ends of this link support PTP hardware timestamping (PHC); required for sub-microsecond accuracy"},{"location":"testing/#test-development","title":"Test Development","text":"

        For adding a new test to the automated regression test suite, it's best to start by reviewing an existing test case.

        All tests are located in the infix/test/case repository and are grouped by the features they verify. For example, infix/test/case/services contains tests for various Infix services, such as LLDP and mDNS.

        While test grouping is flexible, each test should be placed in a logically relevant category.

        When creating a new test group, add it to infix/test/case/all.yaml, to enable it to run as a subset of the test suite:

        - name:  infix-services\n  suite: services/services.yaml\n

        A new test (e.g., lldp_enable_disable) should be added to the corresponding test group .yaml file, such as infix/test/cases/services.yaml:

        - name: lldp_enable_disable\n  case: lldp_enable_disable/test.py\n

        It is necessary to include the test in infix/test/case/services/Readme.adoc to ensure proper test specification generation:

        include::lldp_enable_disable/Readme.adoc[]\n

        Each test case should have its own directory under, infix/test/case/services, containing:

        • test.py - the test script
        • topology.dot - the logical topology definition.

        When the test specification is generated, topology.svg and Readme.adoc should also be created.

        "},{"location":"tunnels/","title":"Tunneling (L2/L3)","text":""},{"location":"tunnels/#tunnel-configuration","title":"Tunnel Configuration","text":"

        Infix supports multiple tunnel encapsulation protocols for connecting remote networks or devices across an IP backbone. Tunnels encapsulate packets within IP datagrams, allowing traffic to traverse intermediate networks transparently.

        Important

        When issuing leave to activate your changes, remember to also save your settings, copy running-config startup-config. See the CLI Introduction for a background.

        "},{"location":"tunnels/#generic-routing-encapsulation-gre","title":"Generic Routing Encapsulation (GRE)","text":"

        GRE tunnels provide a simple and efficient method to encapsulate various network layer protocols over IP networks. Infix supports both IPv4 and IPv6 tunnels in two modes:

        • GRE (Layer 3): Point-to-point IP tunnel for routing protocols and routed traffic
        • GRETAP (Layer 2): Ethernet tunnel for bridging Layer 2 networks

        Tip

        If you name your tunnel interface greN or gretapN, where N is a number, the CLI infers the interface type automatically.

        "},{"location":"tunnels/#basic-gre-configuration","title":"Basic GRE Configuration","text":"

        A basic GRE tunnel for routing between two sites:

        admin@example:/> configure\nadmin@example:/config/> edit interface gre0\nadmin@example:/config/interface/gre0/> set gre local 192.168.3.1 remote 192.168.3.2\nadmin@example:/config/interface/gre0/> set ipv4 address 10.255.0.1 prefix-length 30\nadmin@example:/config/interface/gre0/> leave\nadmin@example:/>\n

        This creates a Layer 3 tunnel between 192.168.3.1 and 192.168.3.2 using the outer IP addresses, with the tunnel itself using 10.255.0.0/30 for the inner IP addressing.

        "},{"location":"tunnels/#gretap-configuration","title":"GRETAP Configuration","text":"

        GRETAP tunnels operate at Layer 2, allowing bridging across the tunnel:

        admin@example:/> configure\nadmin@example:/config/> edit interface gretap0\nadmin@example:/config/interface/gretap0/> set type gretap\nadmin@example:/config/interface/gretap0/> set gre local 192.168.3.1 remote 192.168.3.2\nadmin@example:/config/interface/gretap0/> leave\nadmin@example:/>\n

        GRETAP interfaces can be added to a bridge, bridging local and remote Ethernet segments. See the Bridge Configuration for more on bridges.

        "},{"location":"tunnels/#ospf-over-gre","title":"OSPF over GRE","text":"

        GRE tunnels are commonly used to carry dynamic routing protocols like OSPF across networks that don't support multicast or where you want to create a virtual topology different from the physical network.

        Example topology: Two sites connected via a GRE tunnel, running OSPF to exchange routes.

        Site A configuration:

        admin@siteA:/> configure\nadmin@siteA:/config/> edit interface gre0\nadmin@siteA:/config/interface/gre0/> set gre local 203.0.113.1 remote 203.0.113.2\nadmin@siteA:/config/interface/gre0/> set ipv4 address 10.255.0.1 prefix-length 30\nadmin@siteA:/config/interface/gre0/> set ipv4 forwarding\nadmin@siteA:/config/interface/gre0/> end\nadmin@siteA:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@siteA:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface gre0\nadmin@siteA:/config/routing/\u2026/ospf/> leave\nadmin@siteA:/>\n

        Site B configuration:

        admin@siteB:/> configure\nadmin@siteB:/config/> edit interface gre0\nadmin@siteB:/config/interface/gre0/> set gre local 203.0.113.2 remote 203.0.113.1\nadmin@siteB:/config/interface/gre0/> set ipv4 address 10.255.0.2 prefix-length 30\nadmin@siteB:/config/interface/gre0/> set ipv4 forwarding\nadmin@siteB:/config/interface/gre0/> end\nadmin@siteB:/config/> edit routing control-plane-protocol ospfv2 name default ospf\nadmin@siteB:/config/routing/\u2026/ospf/> set area 0.0.0.0 interface gre0\nadmin@siteB:/config/routing/\u2026/ospf/> leave\nadmin@siteB:/>\n

        Once configured, OSPF will establish a neighbor relationship through the tunnel and exchange routes between the sites. For more info on OSPF configuration, see OSPFv2 Routing.

        Note

        Consider adjusting MTU on the tunnel interface to account for GRE overhead (typically 24 bytes for IPv4, 44 bytes for IPv6) to avoid fragmentation issues.

        "},{"location":"tunnels/#advanced-tunnel-settings","title":"Advanced Tunnel Settings","text":"

        All tunnel types support common parameters for controlling tunnel behavior and performance.

        "},{"location":"tunnels/#time-to-live-ttl","title":"Time To Live (TTL)","text":"

        The TTL setting controls the Time To Live value for the outer tunnel packets. By default, tunnels use a fixed TTL of 64, which allows packets to traverse multiple hops between tunnel endpoints.

        admin@example:/config/> edit interface gre0\nadmin@example:/config/interface/gre0/> set gre ttl 255\nadmin@example:/config/interface/gre0/> leave\n

        Valid values are 1-255, or the special value inherit which copies the TTL from the encapsulated packet.

        Important

        The inherit mode can cause problems with routing protocols like OSPF that use TTL=1 for their packets. For tunnels carrying routing protocols, always use a fixed TTL value (typically 64 or 255).

        "},{"location":"tunnels/#type-of-service-tos","title":"Type of Service (ToS)","text":"

        The ToS setting controls QoS marking for tunnel traffic:

        admin@example:/config/> edit interface gre0\nadmin@example:/config/interface/gre0/> set gre tos 0x10\nadmin@example:/config/interface/gre0/> leave\n

        Valid values are 0-255 for fixed ToS/DSCP marking, or inherit (default) to copy the ToS value from the encapsulated packet.

        "},{"location":"tunnels/#path-mtu-discovery-gre-only","title":"Path MTU Discovery (GRE only)","text":"

        The pmtu-discovery setting can be used to control the Path MTU Discovery on GRE tunnels. When enabled (default), the tunnel respects the Don't Fragment (DF) bit and performs PMTU discovery:

        admin@example:/config/> edit interface gre0\nadmin@example:/config/interface/gre0/> set gre pmtudisc false\nadmin@example:/config/interface/gre0/> leave\n

        Disabling PMTU discovery may be necessary in networks with broken ICMP filtering but can lead to suboptimal performance and fragmentation.

        "},{"location":"tunnels/#virtual-extensible-local-area-network-vxlan","title":"Virtual eXtensible Local Area Network (VXLAN)","text":"

        VXLAN is a network virtualization technology that encapsulates Layer 2 Ethernet frames within Layer 4 UDP datagrams. It uses a 24-bit segment ID, termed VXLAN Network Identifier (VNI), allowing up to 16 million isolated networks.

        Infix supports both IPv4 and IPv6 for VXLAN tunnel endpoints.

        "},{"location":"tunnels/#basic-vxlan-configuration","title":"Basic VXLAN Configuration","text":"

        Tip

        If you name your VXLAN interface vxlanN, where N is a number, the CLI infers the interface type automatically.

        admin@example:/> configure\nadmin@example:/config/> edit interface vxlan100\nadmin@example:/config/interface/vxlan100/> set vxlan local 192.168.3.1\nadmin@example:/config/interface/vxlan100/> set vxlan remote 192.168.3.2\nadmin@example:/config/interface/vxlan100/> set vxlan vni 100\nadmin@example:/config/interface/vxlan100/> leave\nadmin@example:/>\n

        The VNI uniquely identifies the VXLAN segment and must match on both tunnel endpoints.

        "},{"location":"tunnels/#vxlan-with-custom-udp-port","title":"VXLAN with Custom UDP Port","text":"

        The default VXLAN UDP destination port is 4789 (IANA assigned). In some cases you may need to use a different port:

        admin@example:/> configure\nadmin@example:/config/> edit interface vxlan100\nadmin@example:/config/interface/vxlan100/> set vxlan local 192.168.3.1\nadmin@example:/config/interface/vxlan100/> set vxlan remote 192.168.3.2\nadmin@example:/config/interface/vxlan100/> set vxlan vni 100\nadmin@example:/config/interface/vxlan100/> set vxlan remote-port 8472\nadmin@example:/config/interface/vxlan100/> leave\nadmin@example:/>\n

        The remote-port setting allows interoperability with systems using non-standard VXLAN ports.

        Note

        VXLAN tunnels also support the ttl and tos settings described in the Advanced Tunnel Settings section above.

        "},{"location":"upgrade/","title":"Upgrade","text":""},{"location":"upgrade/#upgrading-boot-order","title":"Upgrading & Boot Order","text":"

        For resilience purposes, Infix maintains two software images referred to as the primary and secondary partition image. In addition, some bootloaders support netbooting.

        The boot order defines which image is tried first, and is listed with the CLI show software command. It also shows Infix version installed per partition, and which image was used when booting (STATE booted).

        admin@example:/> show software\nBoot order : primary secondary net\n\nNAME       STATE     VERSION                DATE                     \nprimary    booted    v25.01.0               2025-04-25T10:15:00+00:00\nsecondary  inactive  v25.01.0               2025-04-25T10:07:20+00:00\nadmin@example:/>\n

        YANG support for upgrading Infix, inspecting and modifying the boot-order, is defined in infix-system-software.

        "},{"location":"upgrade/#changing-boot-order","title":"Changing Boot Order","text":"

        The boot order can be manually changed using the set boot-order command. This is useful for rolling back to a previous version or changing the preferred boot source.

        The command accepts one to three boot targets as separate arguments, in the desired boot order. Valid boot targets are:

        • primary - The primary partition
        • secondary - The secondary partition
        • net - Network boot (if supported by bootloader)

        The CLI provides tab-completion for boot targets, making it easy to enter valid values.

        Example: View current boot order and change it:

        admin@example:/> show boot-order\nprimary secondary net\nadmin@example:/> set boot-order secondary primary net\nadmin@example:/> show boot-order\nsecondary primary net\nadmin@example:/>\n

        Example: Set boot order to only try primary partition:

        admin@example:/> show boot-order\nsecondary primary net\nadmin@example:/> set boot-order primary\nadmin@example:/> show boot-order\nprimary\nadmin@example:/>\n

        Example: Using tab-completion (press TAB to see available options):

        admin@example:/> set boot-order TAB\nnet        primary    secondary\nadmin@example:/> set boot-order secondary TAB\nnet        primary    secondary\nadmin@example:/> set boot-order secondary primary\nadmin@example:/> show boot-order\nsecondary primary\nadmin@example:/>\n

        The new boot order takes effect on the next reboot and can be verified with show boot-order or show software:

        admin@example:/> show software\nBoot order : secondary primary\n\nNAME       STATE     VERSION                DATE                     \nprimary    booted    v25.01.0               2025-04-25T10:15:00+00:00\nsecondary  inactive  v25.01.0               2025-04-25T10:07:20+00:00\nadmin@example:/>\n

        Note

        The boot order is automatically updated when performing an upgrade. The newly installed image will be set as the first boot target.

        Duplicate boot targets are not allowed. The CLI will reject attempts to specify the same target multiple times.

        "},{"location":"upgrade/#upgrading","title":"Upgrading","text":"

        Upgrading Infix is done one partition at a time. If the system has booted from one partition, an upgrade will apply to the other (inactive) partition.

        1. Download and unpack the release to install. Make the image pkg bundle available at some URL2
        2. (Optional) Backup the startup configuration
        3. Assume the unit has booted the primary image. Then running the upgrade command installs a new image on the secondary partition
        4. As part of a successful upgrade, the boot-order is implictly changed to boot the newly installed image
        5. Reboot the unit
        6. The unit now runs the new image. To upgrade the remaining partition (primary), run the same upgrade command again, and (optionally) reboot to verify the upgrade

        Caution

        During boot (step 5), the unit may migrate the startup configuration for any syntax changes. It is therefore important that you make sure to upgrade the other partition as well after reboot, of course after having verified your setup.

        The CLI example below shows steps 2-5.

        Backup startup configuration: It is recommended to backup the startup configuration before performing an upgrade. The backup is useful if the upgrade fails, and makes a later downgrade a smoother process.

        admin@example:/> dir /cfg\n/cfg directory\nbackup/             ssl/                startup-config.cfg\n\nadmin@example:/> copy /cfg/startup-config.cfg /cfg/v25.01.0-startup-config.cfg\nadmin@example:/> dir /cfg\n/cfg directory\nbackup/             ssl/                startup-config.cfg           v25.01.0-startup-config.cfg\n\nadmin@example:/>\n

        Upgrade: Here the image pkg bundle was made available via TFTP.

        admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.03.1.pkg\ninstalling\n  0% Installing\n  0% Determining slot states\n 10% Determining slot states done.\n...\n 40% Checking slot rootfs.1 (secondary)\n 46% Checking slot rootfs.1 (secondary) done.\n...\n 98% Copying image to rootfs.1\n 99% Copying image to rootfs.1\n 99% Copying image to rootfs.1 done.\n 99% Updating slots done.\n100% Installing done.\nInstalling `tftp://198.18.117.1/infix-aarch64-25.03.1.pkg` succeeded\nadmin@example:/>\n

        Reboot: The unit will boot on the other partition, with the newly installed image. The Loading startup-config step conducts migration of startup configuration if applicable.

        admin@example:/> reboot\n[ OK ] Stopping Static routing daemon\n[ OK ] Stopping Zebra routing daemon\n...\n[ OK ] Loading startup-config\n[ OK ] Verifying self-signed https certificate\n[ OK ] Update DNS configuration\n[ OK ] Starting Status daemon\n\nInfix OS \u2014 Immutable.Friendly.Secure v25.03.1 (ttyS0)\nexample login: admin\nPassword:\n.-------.\n|  . .  | Infix OS \u2014 Immutable.Friendly.Secure\n|-. v .-| https://www.kernelkit.org\n'-'---'-'\n\nRun the command 'cli' for interactive OAM\n\nadmin@example:~$ cli\n\nSee the 'help' command for an introduction to the system\n\nadmin@example:/> show software\nBoot order : secondary primary net\n\nNAME       STATE     VERSION                DATE                     \nprimary    inactive  v25.01.0               2025-04-25T10:15:00+00:00\nsecondary  booted    v25.03.1               2025-04-25T10:24:31+00:00\nadmin@example:/>\n

        As shown, the boot order has been updated, so that secondary is now the preferred boot source.

        To upgrade the remaining partition (primary), run the upgrade URL command again, and (optionally) reboot.

        "},{"location":"upgrade/#configuration-migration","title":"Configuration Migration","text":"

        The example above illustrated an upgrade from Infix v25.01.0 to v25.03.1. Inbetween these versions, YANG configuration definitions changed slightly (more details given below).

        During boot, Infix inspects the version meta information within the startup configuration file to determine if configuration migration is needed. In this specific case, the configuration file has version 1.4 while the booted software expects version 1.5 (the configuration version numbering differs from the Infix image version numbering). The startup configuration is migrated to 1.5 definitions and stored, while a backup previous startup configuration is stored in directory /cfg/backup/.

        admin@example:/> dir /cfg/backup/\n/cfg/backup/ directory\nstartup-config-1.4.cfg\n\nadmin@example:/>\n

        The modifications made to the startup configuration can be viewed by comparing the files from the shell. An example is shown below.

        admin@example:/> exit\nadmin@example:~$ diff /cfg/backup/startup-config-1.4.cfg /cfg/startup-config.cfg\n--- /cfg/backup/startup-config-1.4.cfg\n+++ /cfg/startup-config.cfg\n...\n-          \"public-key-format\": \"ietf-crypto-types:ssh-public-key-format\",\n+          \"public-key-format\": \"infix-crypto-types:ssh-public-key-format\",\n...\n-          \"private-key-format\": \"ietf-crypto-types:rsa-private-key-format\",\n+          \"private-key-format\": \"infix-crypto-types:rsa-private-key-format\",\n...\n-    \"version\": \"1.4\"\n+    \"version\": \"1.5\"\n...\nadmin@example:~$\n
        "},{"location":"upgrade/#downgrading","title":"Downgrading","text":"

        Downgrading to an earlier version is possible, however, downgrading is not guaranteed to work smoothly. In particular, when the unit boots up with the downgraded version, it may fail to apply the startup config, and instead apply its failure config.

        We consider two cases: downgrading with and without applying a backup startup configuration before rebooting.

        In both cases we start out with a unit running Infix v25.03.1, and wish to downgrade to v25.01.0.

        admin@example:/> show software\nBoot order : primary secondary net\n\nNAME       STATE     VERSION                DATE                     \nprimary    booted    v25.03.1               2025-04-25T11:36:26+00:00\nsecondary  inactive  v25.03.1               2025-04-25T10:24:31+00:00\nadmin@example:/>\n
        "},{"location":"upgrade/#with-backup-startup-config","title":"With Backup startup-config","text":"

        This is the recommended approach to downgrade, given that you have a backup configuration available. The objective is to avoid ending up with the unit in failure config.

        1. Find the backup configuration file
        2. Run upgrade URL to install Infix image to downgrade to
        3. Copy backup startup configuration to current startup configuration (from shell)
        4. Reboot

        Find the backup configuration file:

        Assume you have a backup startup config for the Infix version to downgrade to (here Infix v25.01.0, config version 1.4).

        The preferred approach is to use a startup configuration backed up when running Infix v25.01.0 on the unit. See section Upgrading above for more information. In the following example, there is a backup file available named v25.01.0-startup-config.cfg:

        admin@example:/> dir /cfg\n/cfg directory\nbackup/       ssl/       startup-config.cfg    v25.01.0-startup-config.cfg\n\nadmin@example:/>\n

        The alternative is to use a startup config implicitly backed up by the system as part of Configuration Migration.

        admin@example:/> dir /cfg/backup/\n/cfg/backup/ directory\nstartup-config-1.4.cfg\n\nadmin@example:/>\n

        Caution

        Using a backup configuration file stored when the unit was running the old version (e.g., v25.01.0-startup-config.cfg) is preferred. Although backup files stored due to configuration migration (e.g., startup-config-1.4.cfg) usually works too if the configuration file version (1.4) matches, there are situations when the system may fail to apply it as described below.

        The configuration file version (1.4) is only incremented when changes in YANG configuration syntax mandates it to handle upgrading. Say the next Infix version includes a new feature setting, it can still have version 1.4, as upgrading to it would not need migration. If a user then enables the new feature setting, the new configuration will no longer be compatible with the previous Infix version. A downgrade after enabling new features risks ending up with the unit in failure config.

        Use upgrade command to downgrade:

        admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.01.0.pkg\ninstalling\n  0% Installing\n  0% Determining slot states\n 10% Determining slot states done.\n ...\n 99% Copying image to rootfs.1 done.\n 99% Updating slots done.\n100% Installing done.\nInstalling `tftp://198.18.117.1/infix-aarch64-25.01.0.pkg` succeeded\nadmin@example:/>\n

        Apply the backup configuration file:

        It is recommended to use a backup configuration file for the Infix version to downgrade to, if there is one available.

        admin@example:/> copy /cfg/v25.01.0-startup-config.cfg /cfg/startup-config.cfg\nOverwrite existing file /cfg/startup-config.cfg (y/N)? y\nadmin@example:/>\n

        An alternative is to use a backup file stored when the system conducted a configuration migration. See the caution note above.

        admin@example:/> copy /cfg/backup/startup-config-1.4.cfg /cfg/startup-config.cfg\nOverwrite existing file /cfg/startup-config.cfg (y/N)? y\nadmin@example:/>\n

        Reboot:

        The unit will come up with the applied backup configuration.

        admin@example:/> reboot\n[ OK ] Saving system clock to file\n[ OK ] Stopping Software update service\n[ OK ] Stopping Status daemon\n...\n[ OK ] Bootstrapping YANG datastore\n[ OK ] Starting Configuration daemon\n[ OK ] Loading startup-config\n[ OK ] Update DNS configuration\n[ OK ] Verifying self-signed https certificate\n[ OK ] Starting Status daemon\n\nInfix OS \u2014 Immutable.Friendly.Secure v25.01.0 (ttyS0)\nexample login:\n

        Note

        If the unit despite these measures ends up in failure config, see the next section for more information on how to recover.

        "},{"location":"upgrade/#without-a-backup-startup-config","title":"Without a Backup startup-config","text":"

        This procedure assumes you have access to the unit's console port and its default login credentials1.

        1. Downgrade
        2. Reboot
        3. Login with unit's default credentials
        4. Conduct factory reset
        5. (Then go on configure the unit as you wish)

        Use upgrade command to downgrade:

        admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.01.0.pkg\ninstalling\n  0% Installing\n  0% Determining slot states\n 10% Determining slot states done.\n ...\n 99% Copying image to rootfs.1 done.\n 99% Updating slots done.\n100% Installing done.\nInstalling `tftp://198.18.117.1/infix-aarch64-25.01.0.pkg` succeeded\nadmin@example:/>\n

        Reboot:

        Conduct a reboot. During boot, the unit fails to apply the existing startup configuration (config version 1.5 while software expects version 1.4 or earlier), and instead applies its failure config. This is what is seen on the console when this situation occurs. Note that the login prompt displays failed as part of the hostname.

        admin@example:/> reboot\n[ OK ] Saving system clock to file\n[ OK ] Stopping Software update service\n[ OK ] Stopping Status daemon\n...\n[ OK ] Verifying SSH host keys\n[ OK ] Bootstrapping YANG datastore\n[ OK ] Starting Configuration daemon\n[FAIL] Loading startup-config\n[ OK ] Loading failure-config\n[ OK ] Verifying self-signed https certificate\n[ OK ] Starting Status daemon\n\nInfix OS \u2014 Immutable.Friendly.Secure v25.01.0 (ttyS0)\n\nERROR: Corrupt startup-config, system has reverted to default login credentials\nfailed-00-00-00 login:\n

        To remedy a situation like this, you can login with the unit's default login credentials, preferrably via a console port. The unit's default credentials are typically printed on a sticker on the unit.

        failed-00-00-00 login: admin\nPassword:\n\nRun the command 'cli' for interactive OAM\n\nadmin@failed-00-00-00:~$\n

        When it is safe from a network operations perspective, you can conduct a factory reset and reboot. It is recommended to remove the unit from any production network before doing this, as a factory reset may enable undesired connectivity between the unit's ports.

        admin@failed-00-00-00:~$ factory\nFactory reset device (y/N)? y\nfactory: scheduled factory reset on next boot.\nReboot now to perform reset, (y/N)? y\n[ OK ] Saving system time (UTC) to RTC\n[ OK ] Stopping mDNS alias advertiser\n...\n[ OK ] Starting Configuration daemon\n[ OK ] Loading startup-config\n[ OK ] Update DNS configuration\n[ OK ] Verifying self-signed https certificate\n[ OK ] Starting Status daemon\n[ OK ] Starting Status daemon\n\n\nPlease press Enter to activate this console.\n\nInfix OS \u2014 Immutable.Friendly.Secure v25.01.0 (ttyS0)\nexample login:\n

        Continued configuration is done as with any unit after factory reset.

        1. In failure config, Infix puts all Ethernet ports as individual interfaces. With direct access, one can connect with e.g., SSH, using link local IPv6 addresses. This as an alternative to connecting via a console port.\u00a0\u21a9

        2. Set up an FTP/TFTP/SFTP or HTTP/HTTPS server on the same LAN.\u00a0\u21a9

        "},{"location":"virtual/","title":"Virtual Environments","text":""},{"location":"virtual/#virtual-environments","title":"Virtual Environments","text":"

        Infix primarily targets real hardware, deployment to the cloud is not a priority at the moment. However, for development and testing purposes there is an x86_64 build that runs in Qemu.

        These images also work with the Graphical Network Simulator (GNS3), which is a very user-friendly tool for playing around with simple to complex network setups, verifying interoperability between vendors, etc.

        "},{"location":"virtual/#qemu","title":"QEMU","text":"

        Tip

        Installation for Debian/Ubuntu based systems can be done by \"simply\": sudo apt install virt-manager -- dependencies ensure the relevant Qemu packages are pulled in as well. This trick, installing virt-manager, helps set up Qemu networking on your system.

        A virtualized Infix x86_64 instance can be launched from a Linux system, with Qemu installed, by issuing:

        $ ./qemu/run.sh\n...\n

        from an unpacked release tarball. From a built source tree of Infix the same functionality is bundled as:

        $ make run\n...\n

        To change settings, e.g. networking, make run-menuconfig, or from a pre-built Infix release tarball, using ./qemu/run.sh -c

        The Infix test suite is built around Qemu and Qeneth, see:

        • Regression Testing with Infamy
        • Infamy Docker Image
        "},{"location":"virtual/#gns3","title":"GNS3","text":"

        Download the latest build of the x86_64, or aarch64 if your host machine is Arm. Unpack the tarball in a dedicated directory and use \"Import Appliance\" to install the .gns3a file into GNS3.

        Infix is in the \"Router\" category, it comes with 10 interfaces available by default for use as switch ports or routing.

        "},{"location":"vpd/","title":"Vital Product Data (VPD)","text":""},{"location":"vpd/#vital-product-data","title":"Vital Product Data","text":"

        Infix can source Vital Product Data (VPD) from EEPROMs containing an ONIE TLV. The load bearing information from VPDs, used by Infix by default, is:

        1. Base (chassis) MAC address of the system
        2. Factory default admin password hash

        Additionally, every VPD is listed as a separate component in the ietf-hardware model, in which Infix exports all available attributes to the user. This is useful to read out exact hardware revisions of each board making up a system, figure out if a system belongs to a particular production batch, etc.

        "},{"location":"vpd/#json-encoding","title":"JSON Encoding","text":"

        To make EEPROM binary generation less cumbersome, Infix defines a JSON encoding of an ONIE TLV, and provides a tool, onieprom, to translate from one encoding to another. I.e. a JSON object can be translated to its binary equivalent, and vice versa.

        A mapping between TLV attribute IDs and JSON keys is defined, using the Linux kernel driver as a reference:

        TLV ID Description Key Type 0x21 Product Name \"product-name\" String 0x22 Part Number \"part-number\" String 0x23 Serial Number \"serial-number\" String 0x24 MAC #1 Base \"mac-address\" String (\"c0:ff:ee:00:00:00\") 0x25 Manufacture Date \"manufacture-date\" String (\"MM/DD/YYYY HH:NN:SS\") 0x26 Device Version \"device-version\" Integer (u8) 0x27 Label Revision \"label-revision\" String 0x28 Platform Name \"platform-name\" String 0x29 ONIE Version \"onie-version\" String 0x2a Num MACs \"num-macs\" Integer (u16) 0x2b Manufacturer \"manufacturer\" String 0x2c Country Code \"country-code\" String (ISO 3166-1 2-byte code) 0x2d Vendor \"vendor\" String 0x2e Diag Version \"diag-version\" String 0x2f Service Tag \"service-tag\" String 0xfd Vendor Extension \"vendor-extension\" List of extensions (see below)

        The JSON encoding of a TLV is a single JSON object with an arbitrary subset of the attributes from the list above.

        Example:

        {\n    \"product-name\": \"Wacky Widget\",\n    \"serial-number\": \"#1\",\n    \"manufacture-date\": \"02/13/2024 11:29:52\"\n}\n

        "},{"location":"vpd/#vendor-extensions","title":"Vendor Extensions","text":"

        As the specification explicitly mentions the option of supplying multiple vendor extensions, possibly multiple ones of the same type, the JSON chosen encoding is a list, where each element is itself a list of exactly two elements:

        Element # Type 1 Integer (u32, IANA enterprise number) 2 String (UTF-8 encoded extension data)

        The format of the extension data is defined by the entity indicated by the IANA enterprise number. Restricting extension data to UTF-8 encoded strings is a decision imposed by Infix; the ONIE specification allows for arbitrary binary data.

        Example:

        {\n    ...\n    \"vendor-extension\": [\n        [ 12345, \"my extension data\" ]\n    ]\n}\n
        "},{"location":"vpd/#infix-specific-extensions","title":"Infix Specific Extensions","text":"

        Kernelkit's IANA enterprise number is 61046, under which any extensions required by Infix are stored. The only valid extension data that may be stored under this number is documented in this section. If other device specific data needs to be stored in a VPD, you must associate that with an enterprise number under our control.

        Every Kernelkit extension must be a valid JSON object containing an arbitrary subset of the following attributes:

        Key Value Description \"pwhash\" String (output of mkpasswd(1)) Factory default password for admin

        Since the extension is itself stored in a JSON document, it has to be appropriately quoted.

        Example:

        {\n    ...\n    \"vendor-extension\": [\n        [ 61046, \"{\\\"pwhash\\\":\\\"$6$9rufAxdqCrxrwfQR$G0l9cTVlu/vOhxgo/uMKfRDOmZRd5XWF3vKr5da6qYoxuTJBS/Pl9K.5lrabWoWFFc.71yFMaSlZz0O8FtAtl.\\\"}\" ]\n    ]\n}\n
        "},{"location":"vpd/#creating-and-parsing-onie-eeprom-binaries","title":"Creating and Parsing ONIE EEPROM Binaries","text":"
        usage: onieprom [-h] [-e] [-d] [infile] [outfile]\n\npositional arguments:\n  infile\n  outfile\n\noptions:\n  -h, --help    show this help message and exit\n  -e, --encode  Encode JSON input to binary output\n  -d, --decode  Decode binary input to JSON output\n

        To convert a compatible JSON document (using the first example above) to its binary equivalent, we ask onieprom to encode it for us:

        ~$ onieprom -e example.json >example.bin\n~$ hexdump -C example.bin\n00000000  54 6c 76 49 6e 66 6f 00  01 00 2d 25 13 30 32 2f  |TlvInfo...-%.02/|\n00000010  31 33 2f 32 30 32 34 20  31 31 3a 32 39 3a 35 32  |13/2024 11:29:52|\n00000020  21 0c 57 61 63 6b 79 20  57 69 64 67 65 74 23 02  |!.Wacky Widget#.|\n00000030  23 31 fe 04 dd 69 88 97                           |#1...i..|\n00000038\n

        We can also run the process in reverse, to inspect the contents of a binary in its equivalent JSON representation:

        ~$ onieprom -d example.bin >example-again.json\n~$ jq . example-again.json\n{\n  \"manufacture-date\": \"02/13/2024 11:29:52\",\n  \"product-name\": \"Wacky Widget\",\n  \"serial-number\": \"#1\"\n}\n
        "},{"location":"vpn-wireguard/","title":"WireGuard","text":""},{"location":"vpn-wireguard/#wireguard-vpn","title":"WireGuard VPN","text":"

        Note

        For a general introduction to VPN concepts and deployment models, see VPN Configuration.

        WireGuard is a modern, high-performance VPN protocol that uses state-of-the-art cryptography. It is significantly simpler and faster than traditional VPN solutions like IPsec or OpenVPN, while maintaining strong security guarantees.

        Key features of WireGuard:

        • Simple Configuration: Minimal settings required compared to IPsec
        • High Performance: Runs in kernel space with efficient cryptography
        • Strong Cryptography: Uses Curve25519, ChaCha20, Poly1305, and BLAKE2
        • Roaming Support: Seamlessly handles endpoint IP address changes
        • Dual-Stack: Supports IPv4 and IPv6 for both tunnel endpoints and traffic

        Important

        When issuing leave to activate your changes, remember to also save your settings, copy running-config startup-config. See the CLI Introduction for a background.

        "},{"location":"vpn-wireguard/#key-management","title":"Key Management","text":"

        WireGuard uses public-key cryptography similar to SSH. Each WireGuard interface requires a private key, and each peer is identified by its public key.

        Import the key pair into the keystore:

        admin@example:/> configure\nadmin@example:/config/> edit keystore asymmetric-key wg-site-a\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> do wireguard genkey\nPrivate: aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=\nPublic:  bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> set public-key-format x25519-public-key-format\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> set private-key-format x25519-private-key-format\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> set public-key bN1CwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP6KsrCwZ1lTP=\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> set cleartext-private-key aMqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP5JrqBvZqkSP=\nadmin@example:/config/keystore/asymmetric-key/wg-site-a/> leave\nadmin@example:/>\n

        Tip

        The do prefix allows running admin-exec commands from configure context. Use the base for wireguard genkey when in admin-exec context.

        Import peer public keys into the truststore:

        admin@example:/> configure\nadmin@example:/config/> edit truststore public-key-bag wg-peers public-key peer-b\nadmin@example:/config/truststore/\u2026/peer-b/> set public-key-format x25519-public-key-format\nadmin@example:/config/truststore/\u2026/peer-b/> set public-key PEER_PUBLIC_KEY_HERE\nadmin@example:/config/truststore/\u2026/peer-b/> leave\nadmin@example:/>\n

        Important

        Keep private keys secure! Never share your private key. Only exchange public keys with peers.

        "},{"location":"vpn-wireguard/#point-to-point-configuration","title":"Point-to-Point Configuration","text":"

        Tip

        If you name your WireGuard interface wgN, where N is a number, the CLI infers the interface type automatically.

        A basic WireGuard tunnel between two sites:

        Site A configuration:

        admin@siteA:/> configure\nadmin@siteA:/config/> edit interface wg0\nadmin@siteA:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@siteA:/config/interface/wg0/> set wireguard private-key wg-site-a\nadmin@siteA:/config/interface/wg0/> set ipv4 address 10.0.0.1 prefix-length 24\nadmin@siteA:/config/interface/wg0/> edit wireguard peer wg-peers peer-b\nadmin@siteA:/config/interface/\u2026/wg-peers/peer/peer-b/> set endpoint 203.0.113.2\nadmin@siteA:/config/interface/\u2026/wg-peers/peer/peer-b/> set endpoint-port 51820\nadmin@siteA:/config/interface/\u2026/wg-peers/peer/peer-b/> set allowed-ips 10.0.0.2/32\nadmin@siteA:/config/interface/\u2026/wg-peers/peer/peer-b/> set persistent-keepalive 25\nadmin@siteA:/config/interface/\u2026/wg-peers/peer/peer-b/> leave\nadmin@siteA:/>\n

        Site B configuration:

        admin@siteB:/> configure\nadmin@siteB:/config/> edit interface wg0\nadmin@siteB:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@siteB:/config/interface/wg0/> set wireguard private-key wg-site-b\nadmin@siteB:/config/interface/wg0/> set ipv4 address 10.0.0.2 prefix-length 24\nadmin@siteB:/config/interface/wg0/> edit wireguard peer wg-peers peer-a\nadmin@siteB:/config/interface/\u2026/wg-peers/peer/peer-a/> set endpoint 203.0.113.1\nadmin@siteB:/config/interface/\u2026/wg-peers/peer/peer-a/> set endpoint-port 51820\nadmin@siteB:/config/interface/\u2026/wg-peers/peer/peer-a/> set allowed-ips 10.0.0.1/32\nadmin@siteB:/config/interface/\u2026/wg-peers/peer/peer-a/> set persistent-keepalive 25\nadmin@siteB:/config/interface/\u2026/wg-peers/peer/peer-a/> leave\nadmin@siteB:/>\n

        This creates an encrypted tunnel with Site A at 10.0.0.1 and Site B at 10.0.0.2.

        "},{"location":"vpn-wireguard/#understanding-allowed-ips","title":"Understanding Allowed IPs","text":"

        The allowed-ips setting in WireGuard serves two critical purposes:

        1. Ingress Filtering: Only packets with source IPs in the allowed list are accepted from the peer
        2. Cryptokey Routing: Determines which peer receives outbound packets for a given destination

        Think of allowed-ips as a combination of firewall rules and routing table.

        Figure: WireGuard key exchange and allowed-ips configuration between two peers

        For a simple point-to-point tunnel, you typically allow only the peer's tunnel IP address (e.g., 10.0.0.2/32). For site-to-site VPNs connecting entire networks, include the remote network prefixes:

        admin@example:/config/interface/\u2026/wg-peers/peer/peer-a/> set allowed-ips 10.0.0.2/32\nadmin@example:/config/interface/\u2026/wg-peers/peer/peer-a/> set allowed-ips 192.168.2.0/24\n

        This allows traffic to/from the peer at 10.0.0.2 and routes traffic destined for 192.168.2.0/24 through this peer.

        Note

        When routing traffic to networks behind WireGuard peers, you also need to configure static routes pointing to the WireGuard interface. See Static Routes for more information.

        "},{"location":"vpn-wireguard/#peer-configuration-and-key-bags","title":"Peer Configuration and Key Bags","text":"

        WireGuard peer configuration supports a two-level hierarchy that allows efficient management of multiple peers with shared settings.

        Public Key Bags group related peers together (e.g., all mobile clients, all branch offices) and allow you to configure default settings that apply to all peers in the bag. Individual peers can then override these defaults when needed.

        Settings that support bag-level defaults and per-peer overrides:

        • endpoint - Remote peer's IP address
        • endpoint-port - Remote peer's UDP port (defaults to 51820 at bag level)
        • persistent-keepalive - Keepalive interval in seconds
        • preshared-key - Optional pre-shared key for additional quantum resistance
        • allowed-ips - IP addresses allowed to/from this peer

        Important

        Key Bag Configuration Rules:

        • Single key in bag: You can use bag-level settings (endpoint, allowed-ips, etc.) without specifying individual peer configurations. All settings apply to that one peer.

        • Multiple keys in bag: You MUST provide individual peer configuration for each key in the bag. Settings like endpoint and allowed-ips must be unique per peer and cannot be shared at the bag level when multiple peers exist.

        This prevents configuration errors where multiple peers would incorrectly share the same endpoint address or allowed-ips ranges.

        Example with bag-level defaults:

        admin@example:/> configure\nadmin@example:/config/> edit interface wg0\nadmin@example:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@example:/config/interface/wg0/> set wireguard private-key wg-key\nadmin@example:/config/interface/wg0/> set ipv4 address 10.0.0.1 prefix-length 24\n\n# Configure defaults for all peers in the 'branch-offices' bag\nadmin@example:/config/interface/wg0/> edit wireguard peers branch-offices\nadmin@example:/config/interface/\u2026/wireguard/peers/branch-offices/> set endpoint-port 51820\nadmin@example:/config/interface/\u2026/wireguard/peers/branch-offices/> set persistent-keepalive 25\nadmin@example:/config/interface/\u2026/wireguard/peers/branch-offices/> end\n\n# Configure peer-specific settings (inherits endpoint-port and keepalive from bag)\nadmin@example:/config/interface/\u2026/wireguard/peers/branch-offices/> edit peer office-east\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-east/> set endpoint 203.0.113.10\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-east/> set allowed-ips 10.0.0.10/32\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-east/> set allowed-ips 192.168.10.0/24\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-east/> end\n\n# Another peer with an override for persistent-keepalive\nadmin@example:/config/interface/\u2026/wireguard/peers/branch-offices/> edit peer office-west\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-west/> set endpoint 203.0.113.20\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-west/> set allowed-ips 10.0.0.20/32\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-west/> set allowed-ips 192.168.20.0/24\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-west/> set persistent-keepalive 10\nadmin@example:/config/interface/\u2026/branch-offices/peer/office-west/> leave\nadmin@example:/>\n

        In this example: - Both peers inherit endpoint-port 51820 and persistent-keepalive 25 from the bag - office-west overrides the keepalive to 10 seconds while office-east uses the default 25 - Each peer has its own endpoint and allowed-ips configuration

        This approach simplifies management when you have many peers with similar configurations - set the common defaults once at the bag level, then only specify per-peer differences.

        "},{"location":"vpn-wireguard/#site-to-site-vpn","title":"Site-to-Site VPN","text":"

        Figure: Site-to-Site VPN connecting two office networks

        A site-to-site VPN connects entire networks across locations, creating a unified private network over the internet. This allows devices in one location to seamlessly access resources in another as if they were on the same local network.

        This is the point-to-point configuration shown earlier, extended with routing to allow access to networks behind each peer. Configure the WireGuard tunnel as shown in Point-to-Point Configuration, then add the remote network to allowed-ips and configure static routes.

        "},{"location":"vpn-wireguard/#road-warrior-vpn","title":"Road Warrior VPN","text":"

        Figure: Mobile clients connecting to corporate network

        For mobile clients or peers without fixed IPs, omit the endpoint setting. WireGuard learns the peer's endpoint from authenticated incoming packets:

        admin@hub:/> configure\nadmin@hub:/config/> edit interface wg0 wireguard peers wg-peers peer mobile-client\nadmin@hub:/config/interface/\u2026/wg-peers/peer/mobile-client/> set allowed-ips 10.0.0.10/32\nadmin@hub:/config/interface/\u2026/wg-peers/peer/mobile-client/> leave\nadmin@hub:/>\n

        The mobile client configures the hub's endpoint normally. The hub learns and tracks the mobile client's changing IP address automatically.

        "},{"location":"vpn-wireguard/#hub-and-spoke-topology","title":"Hub-and-Spoke Topology","text":"

        Figure: Hub-and-Spoke topology with central hub routing traffic between spokes

        WireGuard excels at hub-and-spoke (star) topologies where multiple remote sites connect to a central hub.

        Hub configuration:

        admin@hub:/> configure\nadmin@hub:/config/> edit interface wg0\nadmin@hub:/config/interface/wg0/> set ipv4 address 10.0.0.1 prefix-length 24\nadmin@hub:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@hub:/config/interface/wg0/> set wireguard private-key wg-hub\nadmin@hub:/config/interface/wg0/> edit wireguard peers wg-peers\n\n# Spoke 1\nadmin@hub:/config/interface/\u2026/wireguard/peers/wg-peers/> edit peer spoke1\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke1/> set allowed-ips 10.0.0.2/32\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke1/> set allowed-ips 192.168.1.0/24\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke1/> end\n\n# Spoke 2\nadmin@hub:/config/interface/\u2026/wireguard/peers/wg-peers/> edit peer spoke2\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke2/> set allowed-ips 10.0.0.3/32\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke2/> set allowed-ips 192.168.2.0/24\nadmin@hub:/config/interface/\u2026/wg-peers/peer/spoke2/> leave\n\n# Add routes for spoke networks\nadmin@hub:/> configure\nadmin@hub:/config/> edit routing control-plane-protocol static name default\nadmin@hub:/config/routing/\u2026/static/name/default/> set ipv4 route 192.168.1.0/24 wg0\nadmin@hub:/config/routing/\u2026/static/name/default/> set ipv4 route 192.168.2.0/24 wg0\nadmin@hub:/config/routing/\u2026/static/name/default/> leave\nadmin@hub:/>\n

        Spoke 1 configuration:

        admin@spoke1:/> configure\nadmin@spoke1:/config/> edit interface wg0\nadmin@spoke1:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@spoke1:/config/interface/wg0/> set wireguard private-key wg-spoke1\nadmin@spoke1:/config/interface/wg0/> set ipv4 address 10.0.0.2 prefix-length 24\nadmin@spoke1:/config/interface/wg0/> edit wireguard peers wg-peers peer hub\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set endpoint 203.0.113.1\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set endpoint-port 51820\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set allowed-ips 10.0.0.1/32\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set allowed-ips 10.0.0.3/32\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set allowed-ips 192.168.0.0/24\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set allowed-ips 192.168.2.0/24\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> set persistent-keepalive 25\nadmin@spoke1:/config/interface/\u2026/wg-peers/peer/hub/> leave\nadmin@spoke1:/> configure\nadmin@spoke1:/config/> edit routing control-plane-protocol static name default\nadmin@spoke1:/config/routing/\u2026/static/name/default/> set ipv4 route 192.168.0.0/24 wg0\nadmin@spoke1:/config/routing/\u2026/static/name/default/> set ipv4 route 192.168.2.0/24 wg0\nadmin@spoke1:/config/routing/\u2026/static/name/default/> leave\nadmin@spoke1:/>\n

        This configuration allows Spoke 1 to reach both the hub network (192.168.0.0/24) and Spoke 2's network (192.168.2.0/24) via the hub, enabling spoke-to-spoke communication through the central hub.

        "},{"location":"vpn-wireguard/#persistent-keepalive","title":"Persistent Keepalive","text":"

        The persistent-keepalive setting sends periodic packets to keep the tunnel active through NAT devices and firewalls:

        admin@example:/config/interface/\u2026/wg-peers/peer/hub/> set persistent-keepalive 25\n

        This is particularly important when:

        • The peer is behind NAT
        • Intermediate firewalls have connection timeouts
        • You need the tunnel to remain ready for bidirectional traffic

        A value of 25 seconds is recommended for most scenarios. Omit this setting for peers with public static IPs that initiate connections.

        Note

        Only the peer behind NAT needs persistent-keepalive configured. The peer with a public IP learns the NAT endpoint from incoming packets.

        "},{"location":"vpn-wireguard/#ipv6-endpoints","title":"IPv6 Endpoints","text":"

        WireGuard fully supports IPv6 for tunnel endpoints:

        admin@example:/> configure\nadmin@example:/config/> edit interface wg0\nadmin@example:/config/interface/wg0/> set wireguard listen-port 51820\nadmin@example:/config/interface/wg0/> set wireguard private-key wg-key\nadmin@example:/config/interface/wg0/> set ipv4 address 10.0.0.1 prefix-length 24\nadmin@example:/config/interface/wg0/> set ipv6 address fd00::1 prefix-length 64\nadmin@example:/config/interface/wg0/> edit wireguard peers wg-peers peer remote\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> set endpoint 2001:db8::2\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> set endpoint-port 51820\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> set allowed-ips 10.0.0.2/32\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> set allowed-ips fd00::2/128\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> leave\nadmin@example:/>\n

        WireGuard can carry both IPv4 and IPv6 traffic regardless of whether the tunnel endpoints use IPv4 or IPv6.

        "},{"location":"vpn-wireguard/#monitoring-wireguard-status","title":"Monitoring WireGuard Status","text":"

        Check WireGuard interface status and peer connections:

        admin@example:/> show interfaces\nwg0             wireguard  UP          2 peers (1 up)\n                ipv4                   10.0.0.1/24 (static)\n                ipv6                   fd00::1/64 (static)\n\nadmin@example:/> show interfaces wg0\nname                : wg0\ntype                : wireguard\nindex               : 12\noperational status  : up\npeers               : 2\n\n  Peer 1:\n    status            : UP\n    endpoint          : 203.0.113.2:51820\n    latest handshake  : 2025-12-09T10:23:45+0000\n    transfer tx       : 125648 bytes\n    transfer rx       : 98432 bytes\n\n  Peer 2:\n    status            : DOWN\n    endpoint          : 203.0.113.3:51820\n    latest handshake  : 2025-12-09T09:15:22+0000\n    transfer tx       : 45120 bytes\n    transfer rx       : 32768 bytes\n

        The connection status shows UP if a handshake occurred within the last 3 minutes, indicating an active tunnel. The latest handshake timestamp shows when the peers last successfully authenticated and exchanged keys.

        "},{"location":"vpn-wireguard/#post-quantum-security-preshared-keys","title":"Post-Quantum Security (Preshared Keys)","text":"

        WireGuard supports optional preshared keys (PSK) that add an extra layer of symmetric encryption alongside Curve25519. This provides defense-in-depth against future quantum computers that might break elliptic curve cryptography.

        PSKs protect your data from \"harvest now, decrypt later\" attacks - adversaries recording traffic today would still need the PSK even if they break Curve25519 later. However, peer authentication still relies on Curve25519, so PSKs don't provide complete post-quantum security.

        Generate a preshared key:

        admin@example:/config/> do wireguard genpsk\ncO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=\n

        Import the preshared key into the keystore:

        admin@example:/config/> edit keystore symmetric-key wg-psk\nadmin@example:/config/keystore/symmetric-key/wg-psk/> set key-format octet-string-key-format\nadmin@example:/config/keystore/symmetric-key/wg-psk/> set cleartext-symmetric-key cO2DxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2mUQ7LtsrDxZ2m=\nadmin@example:/config/keystore/symmetric-key/wg-psk/> end\nadmin@example:/config/interface/wg0/> edit wireguard peers wg-peers peer remote\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> set preshared-key wg-psk\nadmin@example:/config/interface/\u2026/wg-peers/peer/remote/> leave\nadmin@example:/>\n

        The preshared key must be securely shared between both peers and configured on both sides.

        Important

        Preshared keys must be kept secret and exchanged through a secure channel, just like passwords.

        "},{"location":"vpn/","title":"Overview","text":""},{"location":"vpn/#vpn-configuration","title":"VPN Configuration","text":"

        A Virtual Private Network (VPN) creates encrypted tunnels over public networks, enabling secure communication between remote locations or users. Unlike plain tunnels (GRE, VXLAN) that only provide encapsulation, VPNs add authentication and encryption to protect data confidentiality and integrity.

        "},{"location":"vpn/#configuring-vpn","title":"Configuring VPN","text":"

        For detailed configuration instructions and examples, see:

        • WireGuard VPN - Complete guide to configuring WireGuard tunnels, including site-to-site, road warrior, and hub-and-spoke topologies.
        "},{"location":"vpn/#understanding-vpn-tunnels","title":"Understanding VPN Tunnels","text":"

        VPN tunnels establish secure connections across untrusted networks by:

        • Authentication: Verifying the identity of tunnel endpoints using cryptographic keys or certificates
        • Encryption: Protecting data confidentiality with strong ciphers
        • Integrity: Detecting tampering through message authentication codes

        This makes VPNs essential for connecting sites over the internet, enabling remote access for mobile users, and securing traffic in untrusted environments.

        "},{"location":"vpn/#vpn-deployment-models","title":"VPN Deployment Models","text":"

        VPNs are typically deployed in one of several models:

        Site-to-Site VPN

        Figure: Site-to-Site VPN connecting two office networks

        Connects entire networks across locations, creating a unified private network over the internet. Routers or firewalls at each site maintain persistent tunnels, allowing seamless access between locations.

        • Use case: Connecting branch offices to headquarters
        • Characteristics: Always-on, connects networks not individual devices
        • Example: Main office (192.168.1.0/24) \u2194 Branch office (192.168.2.0/24)

        Remote Access VPN (Road Warrior)

        Figure: Mobile clients connecting to corporate network

        Enables individual users to securely access a private network from remote locations. Clients initiate connections as needed from dynamic IP addresses.

        • Use case: Remote employees accessing corporate resources
        • Characteristics: On-demand, handles dynamic endpoints and roaming
        • Example: Mobile laptop \u2194 Corporate network

        Hub-and-Spoke VPN

        Figure: Hub-and-Spoke topology with central hub routing traffic between spokes

        A central hub connects to multiple remote sites (spokes), routing traffic between them. Spokes don't connect directly to each other but communicate through the hub.

        • Use case: Central office connecting multiple remote locations
        • Characteristics: Centralized control, simplified management
        • Example: HQ \u2194 (Branch A, Branch B, Branch C)
        "},{"location":"vpn/#vpn-protocol-comparison","title":"VPN Protocol Comparison","text":"

        Different VPN protocols offer varying trade-offs between security, performance, and complexity:

        Protocol Complexity Performance Use Case WireGuard Simple Very High Modern deployments, all models IPsec Complex High Legacy systems, compliance reqs OpenVPN Moderate Moderate Maximum compatibility

        Infix supports WireGuard as its primary VPN solution, offering the best balance of simplicity, security, and performance for modern networks.

        "},{"location":"wifi/","title":"Wireless LAN (WiFi)","text":""},{"location":"wifi/#wi-fi-wireless-lan","title":"Wi-Fi (Wireless LAN)","text":"

        Infix includes comprehensive Wi-Fi support for both client (Station) and Access Point modes. When a compatible Wi-Fi adapter is detected, the system automatically creates a WiFi radio (PHY) in factory-config, that can host virtual interfaces.

        "},{"location":"wifi/#architecture-overview","title":"Architecture Overview","text":"

        Infix uses a two-layer WiFi architecture:

        1. WiFi Radio (PHY layer): Represents the physical wireless hardware

          • Configured via ietf-hardware module
          • Controls channel, transmit power, regulatory domain
          • One radio can host multiple virtual interfaces
        2. WiFi Interface (Network layer): Virtual interface on a radio

          • Configured via infix-interfaces module
          • Can operate in Station (client) or Access Point mode
          • Each interface references a parent radio
        "},{"location":"wifi/#naming-conventions","title":"Naming Conventions","text":"

        Like other interface types in Infix, WiFi components follow naming conventions that allow the CLI to automatically infer types:

        Name Pattern Type Description radioN WiFi Radio Hardware component for WiFi PHY wifiN WiFi Interface Virtual WiFi interface on a radio

        Where N is a number (0, 1, 2, ...).

        Tip

        Using these naming conventions simplifies configuration since type/class settings are automatically inferred. For example, creating a hardware component named radio0 automatically sets its class to wifi, enabling the wifi-radio configuration container.

        Note: This inference only works with the CLI. Configuring WiFi over NETCONF or RESTCONF requires setting the class/type explicitly.

        "},{"location":"wifi/#current-limitations","title":"Current Limitations","text":"
        • USB hotplug is not supported - adapters must be present at boot
        • Interface naming may be inconsistent with multiple USB Wi-Fi adapters
        • AP and Station modes cannot be mixed on the same radio
        "},{"location":"wifi/#supported-wi-fi-adapters","title":"Supported Wi-Fi Adapters","text":"

        Wi-Fi support is primarily tested with Realtek chipset-based adapters.

        "},{"location":"wifi/#known-working-chipsets","title":"Known Working Chipsets","text":"
        • Built-in Wi-Fi on Banana Pi BPi-R3
        • Built-in Wi-Fi on Raspberry Pi 4/CM4
        • Realtek:
          • RTL8188CU
          • RTL8188FU
          • RTL8821CU

        Other Realtek chipsets may work but are not tested.

        Note

        Some Realtek chipsets require proprietary drivers not included in the standard kernel.

        • Firmware requirements vary by chipset
        • Check kernel logs if your adapter is not detected
        "},{"location":"wifi/#usb-wifi-dongles","title":"USB WiFi Dongles","text":"

        USB WiFi dongles may be slow to initialize at boot due to firmware loading. If your USB dongle is not detected reliably, configure a probe-timeout on the radio to wait for the PHY:

        admin@example:/> configure\nadmin@example:/config/> edit hardware component radio0 wifi-radio\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set probe-timeout 30\nadmin@example:/config/hardware/component/radio0/wifi-radio/> leave\n

        This waits up to 30 seconds for the radio PHY to appear before creating WiFi interfaces. If the PHY is not detected within the timeout, a dummy interface is created as a placeholder, allowing IP configuration to proceed. Reboot when the radio becomes available.

        "},{"location":"wifi/#radio-configuration","title":"Radio Configuration","text":"

        Before configuring WiFi interfaces, you must first configure the WiFi radio. Radios are automatically discovered and named radio0, radio1, etc.

        "},{"location":"wifi/#country-code","title":"Country Code \u26a0","text":"

        The radio defaults to \"00\" for World domain, but some systems may ship with a factory default country code (typically \"DE\" for the BPi-R3).

        Legal notice!

        The country-code setting is legally required and determines which WiFi channels and power levels are permitted in your location. Using an incorrect country code may violate local wireless regulations.

        Common country codes, see ISO 3166-1 alpha-2 for the complete list:

        • Europe:
          • DE: Germany
          • SE: Sweden
          • GB: UK
          • FR: France
          • ES: Spain
        • Americas:
          • US: United States
          • CA: Canada
          • BR: Brazil
        • Asia-Pacific:
          • JP: Japan
          • AU: Australia
          • CN: China
        "},{"location":"wifi/#basic-radio-setup","title":"Basic Radio Setup","text":"

        Configure the radio with channel, power, and regulatory domain.

        For Station (client) mode:

        admin@example:/> configure\nadmin@example:/config/> edit hardware component radio0 wifi-radio\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE\nadmin@example:/config/hardware/component/radio0/wifi-radio/> leave\n

        For Access Point mode:

        admin@example:/> configure\nadmin@example:/config/> edit hardware component radio0 wifi-radio\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set band 5GHz\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set channel 36\nadmin@example:/config/hardware/component/radio0/wifi-radio/> leave\n

        Key radio parameters:

        • country-code: Two-letter ISO 3166-1 alpha-2 code, determines allowed channels and maximum power. Examples: US, DE, GB, SE, FR, JP. \u26a0 Must match your physical location for legal compliance! \u26a0
        • band: 2.4GHz, 5GHz, or 6GHz (required for AP mode). Automatically enables appropriate WiFi standards:
          • 2.4GHz: 802.11n/ax
          • 5GHz: 802.11n/ac/ax
          • 6GHz: 802.11ax
        • channel: Channel number (1-196) or \"auto\". When set to \"auto\", defaults to channel 6 for 2.4GHz, channel 36 for 5GHz, or channel 109 for 6GHz
        • probe-timeout: Seconds to wait for PHY detection at boot (default: 0). Set to a non-zero value (e.g., 30) for USB WiFi dongles that are slow to initialize due to firmware loading

        Note

        TX power and channel width are automatically determined by the driver based on regulatory constraints, PHY mode, and hardware capabilities.

        "},{"location":"wifi/#wifi-6-support","title":"WiFi 6 Support","text":"

        WiFi 6 (802.11ax) is always enabled in AP mode on all bands, providing improved performance through features like OFDMA, BSS Coloring, and beamforming.

        WiFi 6 Features (always enabled):

        • OFDMA: Better multi-user efficiency in dense environments
        • BSS Coloring: Reduced interference from neighboring networks
        • Beamforming: Improved signal quality and range

        Requirements:

        • Hardware must support 802.11ax
        • Client devices must support WiFi 6 for full benefits
        • Older WiFi 5/4 clients can still connect but won't use WiFi 6 features
        "},{"location":"wifi/#discovering-available-networks","title":"Discovering Available Networks","text":"

        Before connecting to a WiFi network, you need to discover which networks are available. Infix automatically scans for networks when a WiFi interface is created with a radio reference.

        "},{"location":"wifi/#enable-background-scanning","title":"Enable Background Scanning","text":"

        To enable scanning without connecting, configure the radio and create a WiFi interface referencing it:

        Step 1: Configure the radio

        admin@example:/> configure\nadmin@example:/config/> edit hardware component radio0 wifi-radio\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE\nadmin@example:/config/hardware/component/radio0/wifi-radio/> leave\n

        Step 2: Create WiFi interface with radio reference only

        admin@example:/> configure\nadmin@example:/config/> edit interface wifi0\nadmin@example:/config/interface/wifi0/> set wifi radio radio0\nadmin@example:/config/interface/wifi0/> leave\n

        The system will now start scanning in the background. The interface will operate in scan-only mode until you configure a specific mode (station or access-point).

        "},{"location":"wifi/#view-available-networks","title":"View Available Networks","text":"

        Use show interface to see discovered networks and their signal strength:

        admin@example:/> show interface wifi0\nname                : wifi0\ntype                : wifi\nindex               : 3\nmtu                 : 1500\noperational status  : up\nip forwarding       : enabled\nphysical address    : f0:09:0d:36:5f:86\nipv4 addresses      : 192.168.1.100/24 (dhcp)\nipv6 addresses      :\nin-octets           : 148388\nout-octets          : 24555\nmode                : station\nssid                : MyNetwork\nsignal              : -45 dBm (good)\nrx bitrate          : 72.2 Mbps\ntx bitrate          : 86.6 Mbps\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nAvailable Networks\nSSID                 BSSID              SECURITY       SIGNAL  CHANNEL\nMyNetwork            b4:fb:e4:17:b6:a7  WPA2-Personal  good          6\nGuestWiFi            c8:3a:35:12:34:56  WPA2-Personal  fair         11\nCoffeeShop           00:1a:2b:3c:4d:5e  Open           bad           1\n

        In the CLI, signal strength is reported as: excellent, good, fair or bad. For precise signal strength values in dBm, use NETCONF or RESTCONF to access the signal-strength leaf in the operational datastore.

        "},{"location":"wifi/#passphrase-requirements","title":"Passphrase Requirements","text":"

        To ensure your connection is secure and compatible with all network hardware, your passphrase must meet the following criteria:

        • Length: Between 8 and 63 characters
        • Characters: Use only standard English keyboard characters
          • Allowed: Letters (A-Z, a-z), numbers (0-9), and common symbols (e.g., ! @ # $ % ^ & * ( ) _ + - = [ ] { } | ; : ' \" , . < > / ? ~)
          • Spaces: Spaces are allowed, but not at the very beginning or very end of the passphrase
          • Prohibited: Emojis, accented characters (like \u00e1 or \u00f1), and special \"control\" characters

        Why the limit?

        Standard WiFi security (WPA2/WPA3) requires a minimum of 8 characters to prevent \"brute-force\" hacking. The character limit ensures your password works on older routers and various operating systems. Tips for password strength, see XKCD #936.

        "},{"location":"wifi/#station-mode-client","title":"Station Mode (Client)","text":"

        Station mode connects to an existing Wi-Fi network. Before configuring station mode, follow the \"Discovering Available Networks (Scanning)\" section above to scan for available networks and identify the SSID you want to connect to.

        "},{"location":"wifi/#step-1-configure-password","title":"Step 1: Configure Password","text":"

        Create a keystore entry for your WiFi password (8-63 characters):

        admin@example:/> configure\nadmin@example:/config/> edit keystore symmetric-key my-wifi-key\nadmin@example:/config/keystore/\u2026/my-wifi-key/> set key-format passphrase-key-format\nadmin@example:/config/keystore/\u2026/my-wifi-key/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\nadmin@example:/config/keystore/\u2026/my-wifi-key/> leave\n

        The change command prompts for the passphrase interactively and handles the base64 encoding required by the keystore automatically.

        "},{"location":"wifi/#step-2-connect-to-network","title":"Step 2: Connect to Network","text":"

        Configure station mode with the SSID and password to connect:

        admin@example:/> configure\nadmin@example:/config/> edit interface wifi0\nadmin@example:/config/interface/wifi0/> set wifi station ssid MyHomeNetwork\nadmin@example:/config/interface/wifi0/> set wifi station security secret my-wifi-key\nadmin@example:/config/interface/wifi0/> leave\n

        The connection attempt will start immediately. You can verify the connection status:

        admin@example:/> show interface wifi0\nname                : wifi0\ntype                : wifi\noperational status  : up\nphysical address    : f0:09:0d:36:5f:86\nmode                : station\nssid                : MyHomeNetwork\nsignal              : -52 dBm (good)\n

        Station configuration parameters:

        • radio: Reference to the WiFi radio (mandatory) - already set during scanning
        • station ssid: Network name to connect to (mandatory)
        • station security mode:
          • auto: default, WPA2/WPA3 auto-negotiation
          • disabled: open network
        • station security secret: Reference to keystore entry, required unless mode is disabled

        Note

        The auto security mode automatically selects WPA3-SAE or WPA2-PSK based on what the access point supports, prioritizing WPA3 for better security. Certificate-based authentication (802.1X/EAP) is not yet supported.

        "},{"location":"wifi/#access-point-mode","title":"Access Point Mode","text":"

        Access Point (AP) mode allows your device to create a WiFi network that other devices can connect to. APs are configured as virtual interfaces on a WiFi radio.

        "},{"location":"wifi/#basic-ap-configuration","title":"Basic AP Configuration","text":"

        First, ensure the radio is configured (see Radio Configuration above). Then create a keystore entry for your WiFi password and configure the AP interface:

        Step 1: Create keystore entry for the WiFi password

        admin@example:/> configure\nadmin@example:/config/> edit keystore symmetric-key my-wifi-secret\nadmin@example:/config/keystore/\u2026/my-wifi-secret/> set key-format passphrase-key-format\nadmin@example:/config/keystore/\u2026/my-wifi-secret/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\nadmin@example:/config/keystore/\u2026/my-wifi-secret/> end\n

        Step 2: Create the AP interface

        admin@example:/config/> edit interface wifi0\nadmin@example:/config/interface/wifi0/> set wifi radio radio0\nadmin@example:/config/interface/wifi0/> set wifi access-point ssid MyNetwork\nadmin@example:/config/interface/wifi0/> set wifi access-point security mode wpa2-personal\nadmin@example:/config/interface/wifi0/> set wifi access-point security secret my-wifi-secret\nadmin@example:/config/interface/wifi0/> leave\n

        Note

        Using wifiN as the interface name automatically sets the type to WiFi. Alternatively, you can use any name and explicitly set type wifi.

        Access Point configuration parameters:

        • radio: Reference to the WiFi radio (mandatory)
        • access-point ssid: Network name (SSID) to broadcast
        • access-point hidden: Set to true to hide SSID (optional, default: false)
        • access-point security mode: Security mode (see below)
        • access-point security secret: Reference to keystore entry (for secured networks)

        Security modes:

        • open: No encryption (not recommended)
        • wpa2-personal: WPA2-PSK (most compatible)
        • wpa3-personal: WPA3-SAE (more secure, requires WPA3-capable clients)
        • wpa2-wpa3-personal: Mixed mode (maximum compatibility)
        "},{"location":"wifi/#ssid-hiding","title":"SSID Hiding","text":"

        To create a hidden network that doesn't broadcast its SSID:

        admin@example:/config/interface/wifi0/> set wifi access-point hidden true\n
        "},{"location":"wifi/#multi-ssid-configuration","title":"Multi-SSID Configuration","text":"

        Multiple AP interfaces on the same radio allow broadcasting multiple SSIDs, each with independent security settings. This is useful for guest networks, IoT devices, or segregating traffic into different VLANs.

        Step 1: Configure the radio (shared by all APs)

        admin@example:/> configure\nadmin@example:/config/> edit hardware component radio0 wifi-radio\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set band 5GHz\nadmin@example:/config/hardware/component/radio0/wifi-radio/> set channel 36\nadmin@example:/config/hardware/component/radio0/wifi-radio/> leave\n

        Step 2: Configure keystore secrets

        admin@example:/> configure\nadmin@example:/config/> edit keystore symmetric-key main-secret\nadmin@example:/config/keystore/\u2026/main-secret/> set key-format passphrase-key-format\nadmin@example:/config/keystore/\u2026/main-secret/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\nadmin@example:/config/> edit keystore symmetric-key guest-secret\nadmin@example:/config/keystore/\u2026/guest-secret/> set key-format passphrase-key-format\nadmin@example:/config/keystore/\u2026/guest-secret/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\nadmin@example:/config/> edit keystore symmetric-key iot-secret\nadmin@example:/config/keystore/\u2026/iot-secret/> set key-format passphrase-key-format\nadmin@example:/config/keystore/\u2026/iot-secret/> change cleartext-symmetric-key\nPassphrase: ************\nRetype passphrase: ************\nadmin@example:/config/keystore/\u2026/iot-secret/> leave\n

        Step 3: Create multiple AP interfaces (all on radio0)

        admin@example:/> configure\n# Primary AP - Main network (WPA3 for maximum security)\nadmin@example:/config/> edit interface wifi0\nadmin@example:/config/interface/wifi0/> set wifi radio radio0\nadmin@example:/config/interface/wifi0/> set wifi access-point ssid MainNetwork\nadmin@example:/config/interface/wifi0/> set wifi access-point security mode wpa3-personal\nadmin@example:/config/interface/wifi0/> set wifi access-point security secret main-secret\n\n# Guest AP - Guest network (WPA2/WPA3 mixed for compatibility)\nadmin@example:/config/> edit interface wifi1\nadmin@example:/config/interface/wifi1/> set wifi radio radio0\nadmin@example:/config/interface/wifi1/> set wifi access-point ssid GuestNetwork\nadmin@example:/config/interface/wifi1/> set wifi access-point security mode wpa2-wpa3-personal\nadmin@example:/config/interface/wifi1/> set wifi access-point security secret guest-secret\nadmin@example:/config/interface/wifi1/> set custom-phys-address static 00:0c:43:26:60:01\n\n# IoT AP - IoT devices (WPA2 for older device compatibility)\nadmin@example:/config/> edit interface wifi2\nadmin@example:/config/interface/wifi2/> set wifi radio radio0\nadmin@example:/config/interface/wifi2/> set wifi access-point ssid IoT-Devices\nadmin@example:/config/interface/wifi2/> set wifi access-point security mode wpa2-personal\nadmin@example:/config/interface/wifi2/> set wifi access-point security secret iot-secret\nadmin@example:/config/interface/wifi2/> set custom-phys-address static 00:0c:43:26:60:02\nadmin@example:/config/interface/wifi2/> leave\n

        Important

        MAC Address Requirement for Multi-SSID: When creating multiple AP interfaces on the same radio, you must configure a unique MAC address for each secondary interface (wifi1, wifi2, etc.) using set custom-phys-address static <MAC>. All interfaces on the same radio inherit the radio's hardware MAC address by default, which causes network conflicts. Only the primary interface (alphabetically first, e.g., wifi0) should use the default hardware MAC address.

        Choose MAC addresses from the same locally-administered range:

        • Primary (wifi0): Uses hardware MAC (e.g., 00:0c:43:26:60:00)
        • Secondary (wifi1): 00:0c:43:26:60:01 (increment last octet)
        • Tertiary (wifi2): 00:0c:43:26:60:02 (increment last octet)

        Result: Three SSIDs broadcasting simultaneously on radio0:

        • MainNetwork (WPA3, most secure)
        • GuestNetwork (WPA2/WPA3 mixed mode)
        • IoT-Devices (WPA2 for compatibility)

        All APs on the same radio share the same channel and physical layer settings (configured at the radio level). Each AP can have its own:

        • SSID (network name)
        • Security mode and passphrase
        • Hidden/visible SSID setting
        • Bridge membership

        You can verify the configuration with show hardware component radio0 to see radio settings, and show interface to see all active AP interfaces.

        Important

        AP and Station modes cannot be mixed on the same radio. All virtual interfaces on a radio must be the same mode (all APs or all Stations).

        "},{"location":"wifi/#ap-as-bridge-port","title":"AP as Bridge Port","text":"

        WiFi AP interfaces can be added to bridges to integrate wireless devices into your LAN:

        admin@example:/> configure\nadmin@example:/config/> edit interface br0\nadmin@example:/config/interface/br0/> set type bridge\n\nadmin@example:/config/> edit interface wifi0\nadmin@example:/config/interface/wifi0/> set bridge-port bridge br0\nadmin@example:/config/interface/wifi0/> leave\n
        "},{"location":"wifi/#troubleshooting","title":"Troubleshooting","text":"

        Use show interface wifi0 to verify signal strength and connection status. If issues arise, try the following troubleshooting steps:

        1. Verify signal strength: Check that the target network shows \"good\" or \"excellent\" signal in scan results
        2. Check credentials: Use show keystore symmetric <name> to verify the passphrase matches the network password
        3. Review logs: Check system logs with show log for Wi-Fi related errors
        4. Regulatory compliance: Ensure the country-code on the radio matches your location
        5. Hardware detection: Confirm the WiFi radio appears in show hardware

        If issues persist, check the system log for specific error messages that can help identify the root cause.

        "},{"location":"cli/configure/","title":"Configuration","text":""},{"location":"cli/configure/#cli-configure-context","title":"CLI Configure Context","text":"

        Enter the configure context from admin-exec by typing configure followed by Enter. Available commands, press ? at the prompt:

        admin@host:/> configure\nadmin@host:/config/>\n  abort        Abandon candidate\n  change       Interactively change setting, e.g. password\n  check        Validate candidate\n  commit       Commit current candidate to running-config\n  delete       Delete configuration setting(s)\n  diff         Summarize uncommitted changes\n  do           Execute operational mode command\n  edit         Descend to the specified configuration node\n  end          Alias to up, leave this subsection/node\n  exit         Ascend to the parent configuration node, or abort (from top)\n  leave        Finalize candidate and apply to running-config\n  no           Alias for delete\n  rollback     Restore candidate to running-config\n  set          Set configuration setting\n  show         Show configuration\n  text-editor  Modify binary content in a text editor\n  top          Ascend to the configuration root\n  up           Ascend to the parent configuration node\n

        The edit command lets you change to a sub-configure context, e.g.:

        admin@host:/config/> edit interface eth0\nadmin@host:/config/interface/eth0/>\n

        Use up to go up one level.

        admin@host:/config/interface/eth0/> up\nadmin@host:/config/>\n

        Note

        Commands in configure context are automatically generated from the system's YANG models, hence different products likely have a different set of commands. However, both the ietf-interfaces.yang and ietf-ip.yang models, for instance, that provide the networking support are common to all systems.

        "},{"location":"cli/configure/#set-ip-address-on-an-interface","title":"Set IP Address on an Interface","text":"
        admin@host:/config/> edit interface eth0\nadmin@host:/config/interface/eth0/>\nadmin@host:/config/interface/eth0/> set ipv4 address 192.168.2.200 prefix-length 24\n

        From anywhere in configure context you can see the changes you have made by typing diff:

        admin@host:/config/interface/eth0/> diff\ninterfaces {\n  interface eth0 {\n+    ipv4 {\n+      address 192.168.2.200 {\n+        prefix-length 24;\n+      }\n+    }\n  }\n}\n
        "},{"location":"cli/configure/#saving-changes","title":"Saving Changes","text":"

        Apply the changes (from candidate to running-config):

        admin@host:/config/interface/eth0/> leave\nadmin@host:/> show running-config\n...\ninterfaces {\n  interface eth0 {\n    type ethernetCsmacd;\n    ipv4 {\n      address 192.168.2.200 {\n        prefix-length 24;\n      }\n    }\n  }\n...\n

        Since we did not get any warnings we can save the running (RAM only) configuration to startup, so the changes are made persistent across reboots:

        admin@host:/> copy running-config startup-config\n

        The startup-config can also be inspected with the show command to verify the changes are saved.

        Important

        All commands need to be spelled out, no short forms are allowed in the CLI. Use the TAB key to make your life easier.

        "},{"location":"cli/configure/#changing-hostname","title":"Changing Hostname","text":"

        Settings like hostname are located in the ietf-system.yang model. Here is how it can be set.

        admin@host:/config/> edit system\nadmin@host:/config/system/> set hostname example\nadmin@host:/config/system/> leave\nadmin@example:/> \n

        Notice how the hostname in the prompt does not change until the change is committed.

        Note

        Critical services like syslog, mDNS, LLDP, and similar that advertise the hostname, are restarted when the hostname is changed.

        "},{"location":"cli/configure/#changing-password","title":"Changing Password","text":"

        User management, including passwords, is also a part of ietf-system.

        admin@host:/config/> edit system authentication user admin\nadmin@host:/config/system/authentication/user/admin/> change password\nNew password:\nRetype password:\nadmin@host:/config/system/authentication/user/admin/> leave\n

        The change password command starts an interactive dialogue that asks for the new password, with a confirmation, and then salts and encrypts the password with sha512crypt.

        It is also possible to use the set password ... command. This allows setting an already hashed password. To manually hash a password, use the do password encrypt command. This launches the admin-exec command to hash, and optionally salt, your password. This encrypted string can then be used with set password ....

        Tip

        If you are having trouble thinking of a password, there is also do password generate, which generates random but readable strings using the UNIX command pwgen.

        "},{"location":"cli/configure/#ssh-authorized-key","title":"SSH Authorized Key","text":"

        Logging in remotely with SSH is possible by adding a public key to a user. Here we add the authorized key to the admin user, multiple keys are supported.

        With SSH keys in place it is possible to disable password login, just remember to verify SSH login and network connectivity before doing so.

        admin@host:/config/> edit system authentication user admin \nadmin@host:/config/system/authentication/user/admin/> edit authorized-key example@host\nadmin@host:/config/system/authentication/user/admin/authorized-key/example@host/> set algorithm ssh-rsa\nadmin@host:/config/system/authentication/user/admin/authorized-key/example@host/> set key-data AAAAB3NzaC1yc2EAAAADAQABAAABgQC8iBL42yeMBioFay7lty1C4ZDTHcHyo739gc91rTTH8SKvAE4g8Rr97KOz/8PFtOObBrE9G21K7d6UBuPqmd0RUF2CkXXN/eN2PBSHJ50YprRFt/z/304bsBYkDdflKlPDjuSmZ/+OMp4pTsq0R0eNFlX9wcwxEzooIb7VPEdvWE7AYoBRUdf41u3KBHuvjGd1M6QYJtbFLQMMTiVe5IUfyVSZ1RCxEyAB9fR9CBhtVheTVsY3iG0fZc9eCEo89ErDgtGUTJK4Hxt5yCNwI88YaVmkE85cNtw8YwubWQL3/tGZHfbbQ0fynfB4kWNloyRHFr7E1kDxuX5+pbv26EqRdcOVGucNn7hnGU6C1+ejLWdBD7vgsoilFrEaBWF41elJEPKDzpszEijQ9gTrrWeYOQ+x++lvmOdssDu4KvGmj2K/MQTL2jJYrMJ7GDzsUu3XikChRL7zNfS2jYYQLzovboUCgqfPUsVba9hqeX3U67GsJo+hy5MG9RSry4+ucHs=\nadmin@host:/config/system/authentication/user/admin/authorized-key/example@host/> show\nalgorithm ssh-rsa;\nkey-data AAAAB3NzaC1yc2EAAAADAQABAAABgQC8iBL42yeMBioFay7lty1C4ZDTHcHyo739gc91rTTH8SKvAE4g8Rr97KOz/8PFtOObBrE9G21K7d6UBuPqmd0RUF2CkXXN/eN2PBSHJ50YprRFt/z/304bsBYkDdflKlPDjuSmZ/+OMp4pTsq0R0eNFlX9wcwxEzooIb7VPEdvWE7AYoBRUdf41u3KBHuvjGd1M6QYJtbFLQMMTiVe5IUfyVSZ1RCxEyAB9fR9CBhtVheTVsY3iG0fZc9eCEo89ErDgtGUTJK4Hxt5yCNwI88YaVmkE85cNtw8YwubWQL3/tGZHfbbQ0fynfB4kWNloyRHFr7E1kDxuX5+pbv26EqRdcOVGucNn7hnGU6C1+ejLWdBD7vgsoilFrEaBWF41elJEPKDzpszEijQ9gTrrWeYOQ+x++lvmOdssDu4KvGmj2K/MQTL2jJYrMJ7GDzsUu3XikChRL7zNfS2jYYQLzovboUCgqfPUsVba9hqeX3U67GsJo+hy5MG9RSry4+ucHs=;\nadmin@host:/config/system/authentication/user/admin/authorized-key/example@host/> leave\n

        Note

        The ssh-keygen program already base64 encodes the public key data, so there is no need to use the text-editor command, set does the job.

        "},{"location":"cli/configure/#creating-a-veth-pair","title":"Creating a VETH Pair","text":"

        The following example creates a veth0a <--> veth0b virtual Ethernet pair which is useful for connecting, e.g., a container to the physical world. Here we also add an IPv4 address to one end of the pair.

        admin@host:/config/> edit interface veth0a\nadmin@host:/config/interface/veth0a/> set veth peer veth0b\nadmin@host:/config/interface/veth0a/> set ipv4 address 192.168.0.1 prefix-length 24\nadmin@host:/config/interface/veth0a/> up\nadmin@host:/config/> diff\ninterfaces {\n+  interface veth0a {\n+    type veth;\n+    ipv4 {\n+      address 192.168.0.1 {\n+        prefix-length 24;\n+      }\n+    }\n+    veth {\n+      peer veth0b;\n+    }\n+  }\n+  interface veth0b {\n+    type veth;\n+    veth {\n+      peer veth0a;\n+    }\n+  }\n}\nadmin@host:/config/> leave\n

        See the bridging example below for more.

        Tip

        In the CLI you do not have to create the veth0b interface. The system infers this for you. This does not apply when setting up a VETH pair using NETCONF or RESTCONF, then you must submit a complete configuration.

        "},{"location":"cli/configure/#creating-a-bridge","title":"Creating a Bridge","text":"

        Building on the previous example, we now create a non-VLAN filtering bridge (br0) that forwards any, normally link-local, LLDP traffic between both its bridge ports: eth0 and vet0b.

        admin@host:/> configure\nadmin@host:/config/> edit interface br0\nadmin@host:/config/interface/br0/> set bridge ieee-group-forward lldp\nadmin@host:/config/interface/br0/> up\nadmin@host:/config/> set interface eth0 bridge-port bridge br0\nadmin@host:/config/> set interface veth0b bridge-port bridge br0\nadmin@host:/config/> diff\ninterfaces {\n+  interface br0 {\n+    type bridge;\n+    bridge {\n+      ieee-group-forward lldp;\n+    }\n+  }\n  interface eth0 {\n+    bridge-port {\n+      bridge br0;\n+    }\n  }\n+  interface veth0a {\n+    type veth;\n+    ipv4 {\n+      address 192.168.0.1 {\n+        prefix-length 24;\n+      }\n+    }\n+    veth {\n+      peer veth0b;\n+    }\n+  }\n+  interface veth0b {\n+    type veth;\n+    veth {\n+      peer veth0a;\n+    }\n+    bridge-port {\n+      bridge br0;\n+    }\n+  }\n}\n

        Both a physical port eth0 and a virtual port veth0b (bridge side of the VETH pair from the previous example) are now bridged. Any traffic ingressing one port will egress the other. Only reserved IEEE multicast is filtered, except LLDP frames as shown above.

        Important

        The bridge can be named anything, provided the interface name is not already taken. However, for any name outside the pattern br[0-9]+, you have to set the interface type manually to bridge.

        "},{"location":"cli/introduction/","title":"Introduction","text":""},{"location":"cli/introduction/#cli-introduction","title":"CLI Introduction","text":"

        The command line interface (CLI, see-ell-aye) implements a CISCO-like, or Juniper Networks JunOS-like, CLI. It is the traditional way of interacting with single network equipment like switches and routers. Today more advanced graphical NETCONF-based tools are available that allows for managing entire fleets of installed equipment.

        Nevertheless, when it comes to initial deployment and debugging, it is very useful to know how to navigate and use the CLI. This very short guide intends to help you with that.

        New users usually get the CLI as the default \"shell\" when logging in, but the default admin user logs in to bash. To access the CLI, type:

        admin@host-12-34-56:~$ cli\n\nSee the 'help' command for an introduction to the system\n\nadmin@host-12-34-56:/>\n

        The prompt (beginning of the line) changes slightly. Key commands available in any context are:

        admin@host-12-34-56:/> help                   # Try: Tab or ?\n...\nadmin@host-12-34-56:/> show                   # Try: Tab or ?\nadmin@host-12-34-56:/>                        # Try: Tab or ?\n

        Tip

        Even on an empty command line, you can tap the Tab or ? keys. See help keybindings for more tips!

        "},{"location":"cli/introduction/#key-concepts","title":"Key Concepts","text":"

        The two modes in the CLI are the admin-exec and the configure context.

        The top-level context after logging in and starting the CLI is the admin-exec or \"main\" context. It is used for querying system status, managing configuration files/profiles and doing advanced debugging.

        Available commands can be seen by pressing ? at the prompt:

        admin@host:/>\n  configure      Create new candidate-config based on running-config\n  copy           Copy file or configuration, e.g., copy running-config startup-config\n  dir            List available configuration files\n  exit           Exit from CLI (log out)\n  factory-reset  Restore the system to factory default state\n  follow         Monitor a log file, use Ctrl-C to abort\n  help           Help system (try also the '?' key)\n  logout         Alias to exit\n  netcalc        IP subnet calculator, with subnetting\n  password       Password tools\n  ping           Ping a network host or multicast group\n  poweroff       Poweroff system (system policy may yield reboot)\n  reboot         Reboot system\n  remove         Remove a configuration file\n  set            Set operations, e.g., current date/time\n  show           Show system status and configuration files\n  tcpdump        Capture network traffic\n  upgrade        Install a software update bundle from remote or local file\n

        The system has three main datastores (or files): factory, startup, and running that can be managed and inspected using the copy, show, and configure commands. The traditional names used in the CLI for these are listed below:

        • factory-config the default configuration from factory for the device, i.e., what the system returns to after a factory-reset
        • startup-config created from factory-config at first boot after factory reset. Loaded as the system configuration on each boot
        • running-config what is actively running on the system. If no changes have been made since boot, it is the same as startup-config
        • candidate-config is created from running-config when entering the configure context. Any changes made here can be discarded (abort, rollback) or committed (commit, leave) to running-config

        Edit the running configuration using the configure command. This copies running to candidate, a temporary datastore, where changes are made:

        admin@host-12-34-56:/> configure\nadmin@host-12-34-56:/config/> ...             # Try: Tab or ?\nadmin@host-12-34-56:/config/> leave\n

        The leave command activates the changes by issuing a transaction to, essentially, copy the candidate back to running. Depending on the changes made, this can take a few seconds. If the changes are invalid, i.e., not correct according to the underlying YANG models, a warning is shown and the session remains in configure context. Use the abort command to cancel your changes, or investigate further with the diff command (see more below).

        To save configuration changes made to the running-config to persistent store, so the system will use them for consecutive reboots, use the copy command:

        admin@host-12-34-56:/> copy running-config startup-config\n

        In configure context the following commands are available:

        Command Description set foo bar val Set bar leaf node in foo subcontext to val no foo bar Clear/delete configuration made to bar in foo edit foo baz Enter baz sub-sub-context in foo subcontext change password Start password dialogue to change a user's password text-editor foo Open a text editor to edit binary setting foo abort Abort changes in configuration, return to admin-exec exit Exit one level sub-context, or abort from top-level leave Save changes to running-config, return to admin-exec show [foo] Show configured values (optionally in subcontext) diff [foo] Show uncommitted changes in candidate do command Call admin-exec command: do show log commit"},{"location":"cli/introduction/#example-session","title":"Example Session","text":"

        Tip

        Remember to use the TAB and ? keys to speed up your navigation. See help keybindings for more tips!

        In this example we enter configure context to add an IPv4 address to interface eth0, then we apply the changes using the leave command.

        We inspect the system status to ensure the change took effect. Then we save the changes for the next reboot.

        admin@host-12-34-56:/> configure\nadmin@host-12-34-56:/config/> edit interface eth0\nadmin@host-12-34-56:/config/interface/eth0/> set ipv4 <TAB>\n      address     autoconf      bind-ni-name     dhcp \n      enabled     forwarding    mtu              neighbor\nadmin@host-12-34-56:/config/interface/eth0/> set ipv4 address 192.168.2.200 prefix-length 24\nadmin@host-12-34-56:/config/interface/eth0/> show\ntype ethernetCsmacd;\nipv4 address 192.168.2.200 prefix-length 24;\nipv6 enabled true;\nadmin@host-12-34-56:/config/interface/eth0/> diff\ninterfaces {\n  interface eth0 {\n+    ipv4 {\n+      address 192.168.2.200 {\n+        prefix-length 24;\n+      }\n+    }\n  }\n}\nadmin@host-12-34-56:/config/interface/eth0/> leave\nadmin@host-12-34-56:/> show interfaces\nINTERFACE       PROTOCOL   STATE       DATA\nlo              loopback   UNKNOWN     00:00:00:00:00:00\n                ipv4                   127.0.0.1/8 (static)\n                ipv6                   ::1/128 (static)\neth0            ethernet   UP          52:54:00:12:34:56\n                ipv4                   192.168.2.200/24 (static)\n                ipv6                   fe80::5054:ff:fe12:3456/64 (link-layer)\nadmin@host-12-34-56:/> show ip brief\nlo               UNKNOWN        127.0.0.1/8 ::1/128\neth0             UP             192.168.2.200/24 fe80::5054:ff:fe12:3456/64\nadmin@host-12-34-56:/> copy running-config startup-config\n

        One of the ideas behind a separate running and startup configuration is to be able to verify a configuration change. In case of an inadvertent change that, e.g., breaks networking, it is trivial to revert back by:

        admin@host-12-34-56:/> copy startup-config running-config\n

        Or restart the device, for example if the change to the configuration caused you to lose contact with the system (it happens to the best of us). The system will start up from the last \"save game\".

        Tip: Restoring Backups

        When restoring a backup of a configuration, or having manually edited a config file, you can validate it using the system's YANG models, it is not applied if validation is successful:

        copy /media/backup/old.cfg running-config validate

        "},{"location":"cli/keybindings/","title":"Keybindings","text":""},{"location":"cli/keybindings/#cli-keybindings","title":"CLI Keybindings","text":"

        Writing CLI commands by hand is very tedious. To make things easier the CLI has several keybindings, most significant first:

        Key Cmd/Key Description TAB Complete current command, see below for example ? Show available commands, or arguments, with help Ctrl-c Cancel everything on current line Ctrl-d abort/exit Delete character, or abort/exit on empty line Ctrl-z leave Leave and activate changes in configure context Ctrl-f Right arrow Move cursor forward one character Ctrl-b Left arrow Move cursor back one character Meta-f Ctrl-Right Move cursor forward one word Meta-b Ctrl-Left Move cursor back one word Ctrl-e End Move cursor to end of line Ctrl-a Home Move cursor to beginning of line Ctrl-@ Ctrl-Space Mark current position for region operations Meta w Copy region to kill buffer without deleting Ctrl-k Kill (cut) text from cursor to end of line Ctrl-u Delete (cut) entire line Ctrl-y Yank (paste) from kill buffer to cursor Meta-. Yank (paste) last argument from previous line Ctrl-w Kill region if mark set, else kill word backward Meta-Backspace Delete (cut) word to the left Meta-Delete Delete (cut) word to the right Ctrl-l Clear screen and refresh current line Ctrl-p Up arrow History, previous command Ctrl-n Down arrow History, next command Ctrl-q Ctrl-v Insert next character literally Ctrl-r History, reversed interactive search (i-search) Ctrl-t Transpose/Swap characters before and at cursor Meta-# Alt-Shift-3 Prepend # to current line and submit to history"},{"location":"cli/keybindings/#what-is-meta","title":"What is Meta","text":"

        The Meta key is called Alt on most modern keyboards. If you have neither, first tap the Esc key instead of holding down Alt/Meta.

        "},{"location":"cli/keybindings/#usage","title":"Usage","text":"

        Complete a word. Start by typing a few characters, then tap the TAB key on your keyboard:

        conf<TAB> --> configure\n

        See possible arguments, with brief help text, to a command:

        show ?\nbridge          Show bridge (ports/fdb/mdb/vlans)\ndatetime        Show current date and time, default RFC2822 format\n...\n

        Type the command, then tap the ? key.

        "},{"location":"cli/netcalc/","title":"Network Calculator","text":""},{"location":"cli/netcalc/#network-calculator","title":"Network Calculator","text":"
        netcalc <ADDRESS/LEN | NETWORK NETMASK> [split <1-32 | 64-128>]\n
        "},{"location":"cli/netcalc/#description","title":"Description","text":"

        netcalc is a network calculator that takes an IP address and a subnet mask, or an IP address and prefix mask in CIDR notation, and then returns information about the subnet. Both IPv4 and IPv6 is supported.

        A subnet can be entered in two ways:

        • 192.168.2.0 255.255.255.0: traditional IPv4 'address netmask' style
        • 192.168.2.0/24: modern prefix length, same also for IPv6

        An optional split LEN can be given as argument, the new length value must be bigger than the current prefix length. See example below.

        "},{"location":"cli/netcalc/#examples","title":"Examples","text":"

        Its most commonly used features are to understand how many addresses an IP subnet has, what the broadcast address is, first and last usable address.

        admin@example:/> netcalc 192.168.2.0/24\nAddress  : 192.168.2.0          11000000.10101000.00000010. 00000000\nNetmask  : 255.255.255.0 = 24   11111111.11111111.11111111. 00000000\nWildcard : 0.0.0.255            00000000.00000000.00000000. 11111111\n=>\nNetwork  : 192.168.2.0/24       11000000.10101000.00000010. 00000000\nHostMin  : 192.168.2.1          11000000.10101000.00000010. 00000001\nHostMax  : 192.168.2.254        11000000.10101000.00000010. 11111110\nBroadcast: 192.168.2.255        11000000.10101000.00000010. 11111111\nHosts/Net: 254                   Class C, Private network (RFC1918)\n

        Another common use-case is for IP subnetting, i.e., using only as many addresses for an IP subnet as needed. Example, to split the above /24 in four:

        admin@example:/> netcalc 192.168.2.0/24 split 26\nAddress  : 192.168.2.0          11000000.10101000.00000010. 00000000\nNetmask  : 255.255.255.0 = 24   11111111.11111111.11111111. 00000000\nWildcard : 0.0.0.255            00000000.00000000.00000000. 11111111\n=>\nNetwork  : 192.168.2.0/24       11000000.10101000.00000010. 00000000\nHostMin  : 192.168.2.1          11000000.10101000.00000010. 00000001\nHostMax  : 192.168.2.254        11000000.10101000.00000010. 11111110\nBroadcast: 192.168.2.255        11000000.10101000.00000010. 11111111\nHosts/Net: 254                   Class C, Private network (RFC1918)\n\n[Split network/26]\nNetwork  : 192.168.2.0   - 192.168.2.63     Netmask  : 255.255.255.192\nNetwork  : 192.168.2.64  - 192.168.2.127    Netmask  : 255.255.255.192\nNetwork  : 192.168.2.128 - 192.168.2.191    Netmask  : 255.255.255.192\nNetwork  : 192.168.2.192 - 192.168.2.255    Netmask  : 255.255.255.192\n
        "},{"location":"cli/quick/","title":"Quickstart Guide","text":""},{"location":"cli/quick/#cli-quickstart-guide","title":"CLI Quickstart Guide","text":"

        The question mark ? key along with the Tab key are your best friends in the command line interface. They provide context help and completion of commands you input. See the table below for a handful of examples.

        Command Description help Overview of topics/settings in the current context ? Lists commands + brief help in the current context <Tab> Lists commands available in the current context partial<Tab> Completes a partial command name command <Tab> Lists keywords/arguments associated with a command command keyword ? Lists arguments associated with a keyword + help

        Explore the following topics for more information. Note, the keybindings are really useful to learn!

        Command Description help introduction An introduction to the CLI help configure How to use configure context help text-editor Help with the built-in text-editor command help keybindings Lists available keybindings & other helpful tricks

        Tip: Online Help

        In configure context the help <setting> command shows the YANG description text for each node and container. To reach the admin exec help from configure context, e.g., do help text-editor

        "},{"location":"cli/tcpdump/","title":"Network Monitoring","text":""},{"location":"cli/tcpdump/#network-monitoring","title":"Network Monitoring","text":"

        tcpdump is useful tool for analyzing and diagnosing network problems. This document presents the limited feature set that exposed is in the CLI. Administrator level users with shell access can use the full feature set, and is not described here.

        The following section is useful for understanding how to use the tool. A section called Examples follows that, which may be what you want to scroll down to.

        "},{"location":"cli/tcpdump/#hardware-overview","title":"Hardware Overview","text":"

        Using tcpdump effectively requires an understanding of how the underlying hardware works. For a standard PC, or common single-board computers (SBC), the network cards (NICs) are usually connected directly to the CPU.

                              .---------.\n                      |         +-- eth0\n                      |   CPU   |\n                      |         +-- eth1\n                      '---------'\n

        In this setup it is evident that traffic coming in on either eth0 or eth1 reach the CPU, i.e., running tcpdump eth0 captures all traffic.

        However, on other types of networking hardware, e.g., dedicated switch core setups depicted below, the flow of network traffic will likely not pass through the CPU. This depends of course on how the switch is set up, for instance if routing between all ports is enabled, each flow will reach the CPU, but in a plain switching setup it will not.

                              .---------.\n                      |         |\n                      |   CPU   |\n                      |         |\n                      '----+----'\n                           |\n                           |\n                .----------+---------.\n                |                    |\n             ---+                    +---\n            E1  |         SC         |  E3\n             ---+                    +---\n            E2  |                    |  E4\n                '--+--+--+--+--+--+--'\n                   |  |  |  |  |  |\n                  E5 E6 E7 E8 E9 E10\n

        So, running tcpdump e1 in a switching setup, inside the CPU, the only traffic that will be captured is traffic ingressing port E1 destined for the CPU itself. To analyze traffic going through the switch, you need something called port mirroring, or port monitoring, i.e., setting up the switch core to mirror traffic ingressing and/or egressing a set of ports to another port. On this monitor port you can then run your tcpdump, which means you need an external device (laptop).

        A planned feature is to support mirroring traffic to the CPU port, which would be an effective way to log and monitor traffic over a longer period of time. Highly effective for diagnosing intermittent and other rare network issues.

        If only \"proof of life\" is required, then sometimes port counters, also called RMON counters, can be very useful too. Seeing counters of a particular type increment means traffic is ingressing or egressing.

        "},{"location":"cli/tcpdump/#examples","title":"Examples","text":"

        Listen to all traffic on an interface:

        admin@example:/> tcpdump e1\n

        Listen to only ping traffic:

        admin@example:/> tcpdump e1 expression icmp\n

        Listen to traffic on a given port:

        admin@example:/> tcpdump e1 expression \"port 80\"\n

        Wait for one ping only:

        admin@example:/> tcpdump e1 count 1 expression icmp\n

        Very verbose output:

        admin@example:/> tcpdump e1 verbose\n
        "},{"location":"cli/text-editor/","title":"Text Editor","text":""},{"location":"cli/text-editor/#text-editor","title":"Text Editor","text":"

        The CLI built-in text-editor command can be used to edit type binary settings in configure context.

        The default editor is a Micro Emacs clone. Users not familiar with terminal based editors may benefit from this introduction.

        "},{"location":"cli/text-editor/#escape-metaalt-control-shift","title":"Escape Meta/Alt Control Shift","text":"

        When starting up, the editor status field at the bottom shows the following shorthand:

        C-h q  quick help | C-h t  tutorial | C-h b  key bindings | C = Ctrl | M = Alt\n

        Key combinations with a - (dash) mean holding down the modifier key. Combinations without a - (dash) mean without any modifier key.

        "},{"location":"cli/text-editor/#quick-help-c-h-q","title":"Quick help C-h q","text":"
        • hold down the Ctrl key on
        • tap the h key
        • release Ctrl
        • tap the q key

        The bottom part of the terminal now shows a \"buffer\" called *quick*:

        FILE              BUFFER           WINDOW            MARK/KILL        MISC\nC-x C-c exit      C-x b   switch   C-x 0 only other  C-space mark     C-_ undo\nC-x C-f find      C-x k   close    C-x 1 only this   C-w     kill-rg  C-s search\nC-x C-s save      C-x C-b list     C-x 2 split two   C-k     kill-ln  C-r r-search\nC-x s   save-all  C-x h   mark     C-x ^ enlarge     C-y     yank     M-% replace\nC-x i   insert    C-x g   goto-ln  C-x o other win   C-x C-x swap     M-q reformat\n
        "},{"location":"cli/text-editor/#save-exit-c-x-c-c","title":"Save & Exit C-x C-c","text":"
        • Hold down the Ctrl key
        • tap X
        • tap c
        • release Ctrl

        The status field at the bottom asks if you are really sure, and/or if you want to add a final Enter/newline to the file. For binary content that final newline may be important.

        "},{"location":"cli/text-editor/#changing-the-editor","title":"Changing the Editor","text":"

        The system has three different built-in editors:

        • emacs (Micro Emacs)
        • nano (GNU Nano)
        • vi (Visual Editor)

        Changing editor is done in configure context, in the system container:

        admin@host:/> configure\nadmin@host:/config/> edit system\nadmin@host:/config/system/> set text-editor <TAB>\nemacs   nano    vi\nadmin@host:/config/system/> set text-editor nano\nadmin@host:/config/system/> leave\nadmin@host:/>\n
        "},{"location":"cli/upgrade/","title":"Upgrading","text":""},{"location":"cli/upgrade/#upgrading-the-system","title":"Upgrading the System","text":"

        The admin-exec command upgrade can be used to install software images, or bundles. A bundle is a signed and self-contained package that carries all the information necessary to determine if it holds a bootloader, a Linux image, or even both.

        To install a new software image to the currently inactive partition1, we use the upgrade command and a URI to a ftp/tftp/sftp or http/https server that hosts the file:

        admin@example:/> upgrade tftp://192.168.122.1/firmware-x86_64-v23.11.pkg\ninstalling\n  0% Installing\n  0% Determining slot states\n 20% Determining slot states done.\n 20% Checking bundle\n 20% Verifying signature\n 40% Verifying signature done.\n 40% Checking bundle done.\n 40% Checking manifest contents\n 60% Checking manifest contents done.\n 60% Determining target install group\n 80% Determining target install group done.\n 80% Updating slots\n 80% Checking slot rootfs.1\n 90% Checking slot rootfs.1 done.\n 90% Copying image to rootfs.1\n 99% Copying image to rootfs.1 done.\n 99% Updating slots done.\n100% Installing done.\nInstalling `tftp://192.168.122.1/firmware-x86_64-v23.11.pkg` succeeded\nadmin@example:/>\n

        The secondary partition (rootfs.1) has now been upgraded and will be used as the active partition on the next boot. Leaving the primary partition, with the version we are currently running, intact in case of trouble.

        See Upgrade & Boot Order for more information on upgrading.

        1. It is not possible to upgrade the partition we booted from. Thankfully the underlying \"rauc\" subsystem keeps track of this. Hence, to upgrade both partitions you must reboot to the new version (to verify it works) and then repeat the same command.\u00a0\u21a9

        "}]} \ No newline at end of file diff --git a/dev/sitemap.xml b/dev/sitemap.xml index 854e9bb4..9f587f68 100644 --- a/dev/sitemap.xml +++ b/dev/sitemap.xml @@ -2,206 +2,206 @@ https://kernelkit.github.io/infix/dev/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/boot/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/branding/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/bridging/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/container/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/developers-guide/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/dhcp/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/discovery/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/eth-counters/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/ethernet/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/firewall/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/hardware/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/iface/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/ip/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/keystore/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/lag/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/license/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/management/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/nacm/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/netboot/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/networking/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/ntp/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/override-package/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/ptp/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/qos/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/routing/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/scripting-netconf/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/scripting-prod/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/scripting-restconf/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/scripting-sysrepocfg/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/scripting/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/support/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/syslog/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/system/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/test-arch/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/testing/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/tunnels/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/upgrade/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/virtual/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/vpd/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/vpn-wireguard/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/vpn/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/wifi/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/cli/configure/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/cli/introduction/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/cli/keybindings/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/cli/netcalc/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/cli/quick/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/cli/tcpdump/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/cli/text-editor/ - 2026-04-28 + 2026-04-30 https://kernelkit.github.io/infix/dev/cli/upgrade/ - 2026-04-28 + 2026-04-30 \ No newline at end of file diff --git a/dev/sitemap.xml.gz b/dev/sitemap.xml.gz index 03499e4d3109a2b7afc552b2df5c8bf28d623ebd..66e54d048ea8e1776371ff97d5615d344e6d0b71 100644 GIT binary patch literal 524 zcmV+n0`vVJiwFpSoAPM_|8r?{Wo=<_E_iKh0L_@ej+-zLhVT0n5qCqnt*W+4*jt~V zeE`P{Fv57e_BbSOUz1%`J-3IyNC+^-|A+1I%=q+jTm9imDYJxg^PxR92MSIG4%4~$ z`Qy9!(!BIfr;>aoJ=Dq}pPN}Ve(Snz+uF=ZtBtvpG-_gn1mWiKKM(@r+*1Ax=#VFKzq32He|LIKp1yu@l1W-2@ojAM2Yrp?(&T^Q~t z0MgkwDM9Nn02%i8u$5rsM4R;=H?f2{`WZ0jt zHrR<)3NVuo)~zsYQe@myA7Pk*@<)p>JvcPK`aMHnSH3^0YkFKV OY5xnFUl;=n8UO$Srv--q literal 524 zcmV+n0`vVJiwFpS`0r@~|8r?{Wo=<_E_iKh0L_@ej+-zLhVT0n5qBVss%l#$*;}7r z_W>MxfDy*ywZ|cO`k>bX7qMM8iv{y%JwXU4~u+v0auN|_~`nlIhH*-^0KIE<&} z?e%B-+`OEhjwSg_x~r8#J~fkS{L%O8x^|hB787$PY3#F|>O<~73;TWdy*WSa>ajKO zarZcmUhH{mCD&$VN`d^$+KsGJxpXW&hk7G?^-R=!+?e}acWZ0iC zCYX^H3NVuo#x5{zQe@myXzX0)Ac`a6y5L=4jy_3I;JnK= zaohmfY6CU=9q=GAiNjMQ%Ku4!&Q?xDmn8Qea=m6H|&%L1>_;iN;m)+Bf=H4_CwprltG7`6@PdE1H`cr3V7 zbc5iaH5e&YSpt%yrsWrY?965)#ug$PZ;=S^h3$+4P#axYA{2pc+LB|Hgv9t~*Yvn# O((^AaJ?@GO8UO$}+XSru