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 — elected by the Best TimeTransmitter Clock Algorithm (BTCA) based
+on priority, clock class, and accuracy — 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.
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).
A PTP profile (as defined in IEEE 1588-2019 §3.1) is a document that
+specifies a consistent set of required, permitted, and prohibited PTP
+options for a particular application domain — 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 — 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 — 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.
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.
IEEE 1588 organises protocol state into named Data Sets (DS) — 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
A PTP domain (0–255) is a logical partition of the network. Devices
+only synchronise with others in the same domain. Running multiple
+instances on the same device — one per domain, or one per profile — 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.
A Boundary Clock terminates PTP on each port and re-originates it. Add one
+port per interface:
+
admin@example:/config/> edit ptp instance 0
+admin@example:/config/ptp/instance/0/> set default-ds instance-type bc
+admin@example:/config/ptp/instance/0/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/0/> edit port 1
+admin@example:/config/ptp/…/0/port/1/> set underlying-interface eth0
+admin@example:/config/ptp/…/0/port/1/> end
+admin@example:/config/ptp/instance/0/> edit port 2
+admin@example:/config/ptp/…/0/port/2/> set underlying-interface eth1
+admin@example:/config/ptp/…/0/port/2/> leave
+
+
+
+
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.
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
+admin@example:/config/ptp/instance/0/> set default-ds instance-type p2p-tc
+admin@example:/config/ptp/instance/0/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/0/> edit port 1
+admin@example:/config/ptp/…/0/port/1/> set underlying-interface eth0
+admin@example:/config/ptp/…/0/port/1/> end
+admin@example:/config/ptp/instance/0/> edit port 2
+admin@example:/config/ptp/…/0/port/2/> set underlying-interface eth1
+admin@example:/config/ptp/…/0/port/2/> leave
+
+
+
+
Note
+
For Transparent Clocks the delay mechanism is determined globally by the
+instance-type (p2p-tc → P2P, e2e-tc → E2E). Per-port
+delay-mechanism settings have no effect for TC instances.
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 — Layer 2 transport, P2P delay
+measurement, 802.1AS multicast addressing, and related protocol features.
+
admin@example:/config/> edit ptp instance 0
+admin@example:/config/ptp/instance/0/> set default-ds profile ieee802-dot1as
+admin@example:/config/ptp/instance/0/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/0/> set default-ds time-receiver-only true
+admin@example:/config/ptp/instance/0/> edit port 1
+admin@example:/config/ptp/…/0/port/1/> set underlying-interface eth0
+admin@example:/config/ptp/…/0/port/1/> leave
+
+
+
+
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.
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
+admin@example:/config/ptp/instance/0/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/0/> set default-ds profile ieee1588
+admin@example:/config/ptp/instance/0/> edit port 1
+admin@example:/config/ptp/…/0/port/1/> set underlying-interface eth0
+admin@example:/config/ptp/…/0/port/1/> end
+admin@example:/config/ptp/instance/0/> end
+admin@example:/config/ptp/> edit instance 1
+admin@example:/config/ptp/instance/1/> set default-ds domain-number 0
+admin@example:/config/ptp/instance/1/> set default-ds profile ieee802-dot1as
+admin@example:/config/ptp/instance/1/> edit port 1
+admin@example:/config/ptp/…/1/port/1/> set underlying-interface eth1
+admin@example:/config/ptp/…/1/port/1/> leave
+
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.
Adjust announcement, sync, and delay-request intervals per port. Values
+are expressed as log₂ of the interval in seconds (e.g. -3 = 125 ms,
+0 = 1 s, 1 = 2 s):
+
admin@example:/config/ptp/…/0/port/1/> set port-ds log-announce-interval 0
+admin@example:/config/ptp/…/0/port/1/> set port-ds log-sync-interval -3
+admin@example:/config/ptp/…/0/port/1/> set port-ds log-min-delay-req-interval 0
+admin@example:/config/ptp/…/0/port/1/> set announce-receipt-timeout 3
+
+
+
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.
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_RESPt3 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.
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.
rsv (reserved, bits 7–4 of octet 1): Set to zero; ignored on
+ receipt.
+
ver (versionPTP, bits 3–0 of octet 1): PTP version; 2 for
+ IEEE 1588-2008 and IEEE 1588-2019.
+
messageLength (octets 2–3): 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–7): Per-message flags — includes the two-step
+ flag (set when a FOLLOW_UP will follow a SYNC), UTC offset valid, and
+ leap-second indicators.
+
correctionField (octets 8–15): Accumulated path correction in
+ nanoseconds × 2¹⁶. 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–19): Reserved in IEEE 1588-2008;
+ carries message-type-specific data in IEEE 1588-2019.
+
clockIdentity (octets 20–27): EUI-64 identity of the sending
+ clock — the value shown as "Clock identity" in show ptp.
+
portNumber (octets 28–29): Port number of the sender within its
+ clock; together with clockIdentity it forms the unique
+ sourcePortIdentity.
+
sequenceId (octets 30–31): 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₂ 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.
Wireshark decodes PTP messages automatically, expanding every header field
+and message-type-specific payload in the packet tree. PTP travels over
+two UDP ports — 319 for event messages (SYNC, DELAY_REQ, PDELAY_REQ and
+their responses) and 320 for general messages (ANNOUNCE, FOLLOW_UP) — 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
+
+
To narrow down to a specific domain or profile (exact field names can be
+verified in Wireshark via View → Internals → Supported Protocols,
+filtering for ptp):
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.
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.
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.
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.
Systems running Infix will typically boot in multiple phases, forming a boot chain. Each link in the chain has three main responsibilities:
Ensuring the integrity of the next link before passing control to it. This avoids silent failures stemming from data corruption.
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.
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.
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
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
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.
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.
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.
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
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)
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
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
Provided the integrity of the startup-config is OK, a system service loads and activates the configuration
The per-device password cannot be read, or is corrupt, so the system factory-config and failure-config are not generated:
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
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.
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.
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.
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.
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.
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.
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.
"},{"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.
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.
See Upgrade & Boot Order for more information.\u00a0\u21a9
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
Return Merchandise Authorization (RMA), i.e., broken beyond repair by end-user and eligible for return to manufacturer.\u00a0\u21a9
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
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.
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:
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:
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:
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:
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.
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.
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.
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:
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.
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.
Two popular scheme for versioning a product derived from Infix:
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).
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.
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.
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.
Used for VERSION and VERSION_ID in /etc/os-release and generated file names like disk images, etc.
Default: ${INFIX_BUILD_ID}
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
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!
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.
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:/> 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.
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
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
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
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.
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.
In the CLI, containers can be run in one of two ways:
container run IMAGE [COMMAND], or
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.
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.
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
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.
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
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.
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.
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:
Podman pulls the new image in the background (if not already present)
Your container is automatically stopped
The container is recreated with the new image
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
For images using mutable tags like :latest or :edge, use the container upgrade command:
admin@example:/> container upgrade NAME\n
This command:
Stops the running container
Pulls the latest version of the image from the registry
Recreates the container with the new image
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.
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.
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:
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.
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.
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
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.
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
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.
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.
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.
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.
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:
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.
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):
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.
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.
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:
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.
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.
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).
Something which the container bridge network type does behind the scenes with one end of an automatically created VETH pair.\u00a0\u21a9
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
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
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.
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
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.
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):
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:
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.
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.
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.
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.
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.
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:
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.
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.
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
Rebase the new branch onto the corresponding upstream release
git rebase 2025.02.2\n
Push the new branch and tags
git push origin 2025.02.2-kkit --tags\n
In Infix, checkout new branch of Buildroot
cd buildroot\ngit fetch\ngit checkout 2025.02.2-kkit\n
Commit and push the changes. Remember to update the ChangeLog!
Create a pull request.
Note
Remember to set the pull request label to ci:main to ensure full CI coverage.
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.
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.
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:
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.
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:
Fork Infix to your own user or organization1
Fork all the Infix submodules, e.g., kernelkit/buildroot to your own user or organization as well
Clone your fork of Infix to your laptop/workstation
Deactivate the Actions you don't want in your fork
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.
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
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.
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.
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.
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.
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.
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
This requires the system DNS resolver to be configured.\u00a0\u21a9
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:
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:
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.
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.
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.
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:
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.
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
E.g., lldpd which includes the lldpcli too, handy to sniff and display LLDP packets.\u00a0\u21a9
mdns-scan: a tool for scanning for mDNS/DNS-SD services on the local network.\u00a0\u21a9
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.
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'.
"},{"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.
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.
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.
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
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
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.
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.
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.
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.
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.
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
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
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.
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.
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)
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:
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.
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
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.
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.
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
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.
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.
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.
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.
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
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:
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.
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.
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
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.
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
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
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
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.)
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
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.
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.
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):
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
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.
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
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
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
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.
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.
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:
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:
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.
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:
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:
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.
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.
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.
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:
static: Active members selected based on link status (carrier)
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.
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.
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
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
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
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
(source MAC XOR destination MAC XOR EtherType) MODULO num_links \u21a9
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.
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
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.
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":"
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.
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
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
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.
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.
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.
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.
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.
Permit by default - write-default: permit allows operators to configure any module
Minimal operator rules - Only one rule to permit system RPCs (reboot, set time)
Future proof - New YANG modules automatically configurable by operators
Targeted denials - Only sensitive items (passwords, keys) are explicitly denied
Global denials - Password/keystore/truststore denied for everyone via group \"*\"
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:
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.
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
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.
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.
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.
Order matters - Rule-lists are evaluated in order. Place admin's permit-all rule first so it bypasses global denials.
Use path-based denials - For protecting specific data (like password hashes), use path rules. For protecting entire modules (like keystore), use module-name rules.
Test thoroughly - Always test user permissions after changes. NACM errors can be subtle (nodes may be silently omitted from read operations).
Keep it simple - The factory configuration uses only 6 rules for three user levels. Fewer rules are easier to understand and maintain.
Document rules - Use the \"comment\" field to explain why specific permissions are granted or denied.
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.
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
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.
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.
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:
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
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.
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.
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
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.
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 poll2 Polling interval in log2 seconds (2 = 4s) precision0.1 Assumed precision in seconds (0.1 = 100ms) refid\"GPS\" Reference identifier (e.g., GPS, GNSS, GLO) preferfalse Prefer this source over other reference clocks ppsfalse Enable PPS for microsecond-level accuracy offset0.0 Constant offset correction in seconds delay0.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
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.
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.
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.
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.
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
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.
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
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:
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:
~/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":"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.
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.
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:
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.
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.
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
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.
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.
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
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
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
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
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.
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
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
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
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
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.
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
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":"
"},{"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":"
"},{"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":"
"},{"location":"scripting-netconf/#operational-data-examples","title":"Operational Data Examples","text":""},{"location":"scripting-netconf/#read-interface-state","title":"Read Interface State","text":"
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-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.
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:
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).
"},{"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.
"},{"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
"},{"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.
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.
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)
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/#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":"
~$ ./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":"
"},{"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
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
~$ 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
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.
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:
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:
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.
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.
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.
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.
"},{"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.
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":"
"},{"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
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.
"},{"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:
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:
Change the configuration without saving it to startup-config
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
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:
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\":
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:
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).
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.
"},{"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.
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.
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)
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.
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:
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
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.
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.
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.
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
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.
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.
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.
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.
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.
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:
NACM permissions - what the user can access
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:
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:
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:
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.
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.
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.
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.
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.
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.
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
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.
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.
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
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.
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.
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.
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.
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:
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
To run the tests on a preexisting topology from the host's network namespace, specify the hostTEST_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:
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:
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:
"},{"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:
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:
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:
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
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
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:
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.
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.
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.
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.
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.
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.
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.
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.
Download and unpack the release to install. Make the image pkg bundle available at some URL2
(Optional) Backup the startup configuration
Assume the unit has booted the primary image. Then running the upgrade command installs a new image on the secondary partition
As part of a successful upgrade, the boot-order is implictly changed to boot the newly installed image
Reboot the unit
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.
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.
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.
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
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.
Find the backup configuration file
Run upgrade URL to install Infix image to downgrade to
Copy backup startup configuration to current startup configuration (from shell)
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.
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.
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.
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
Set up an FTP/TFTP/SFTP or HTTP/HTTPS server on the same LAN.\u00a0\u21a9
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.
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:
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:
Base (chassis) MAC address of the system
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.
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.
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.
"},{"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.
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.
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.
The allowed-ips setting in WireGuard serves two critical purposes:
Ingress Filtering: Only packets with source IPs in the allowed list are accepted from the peer
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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:
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.
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.
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.
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.
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:
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.
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.
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.
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:
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
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 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
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.:
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:
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.
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.
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.
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.
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!
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:
In configure context the following commands are available:
Command Description set foo bar val Set bar leaf node in foo subcontext to valno foo bar Clear/delete configuration made to bar in fooedit 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 fooabort 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 logcommit"},{"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:
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:
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.
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.
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
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.
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.
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.
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.
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
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:
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.
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.
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.
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.
Systems running Infix will typically boot in multiple phases, forming a boot chain. Each link in the chain has three main responsibilities:
Ensuring the integrity of the next link before passing control to it. This avoids silent failures stemming from data corruption.
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.
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.
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
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
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.
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.
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.
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
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)
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
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
Provided the integrity of the startup-config is OK, a system service loads and activates the configuration
The per-device password cannot be read, or is corrupt, so the system factory-config and failure-config are not generated:
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
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.
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.
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.
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.
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.
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.
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.
"},{"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.
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.
See Upgrade & Boot Order for more information.\u00a0\u21a9
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
Return Merchandise Authorization (RMA), i.e., broken beyond repair by end-user and eligible for return to manufacturer.\u00a0\u21a9
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
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.
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:
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:
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:
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:
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.
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.
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.
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:
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.
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.
Two popular scheme for versioning a product derived from Infix:
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).
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.
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.
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.
Used for VERSION and VERSION_ID in /etc/os-release and generated file names like disk images, etc.
Default: ${INFIX_BUILD_ID}
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
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!
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.
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:/> 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.
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
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
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
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.
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.
In the CLI, containers can be run in one of two ways:
container run IMAGE [COMMAND], or
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.
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.
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
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.
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
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.
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.
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:
Podman pulls the new image in the background (if not already present)
Your container is automatically stopped
The container is recreated with the new image
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
For images using mutable tags like :latest or :edge, use the container upgrade command:
admin@example:/> container upgrade NAME\n
This command:
Stops the running container
Pulls the latest version of the image from the registry
Recreates the container with the new image
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.
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.
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:
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.
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.
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
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.
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
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.
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.
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.
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.
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:
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.
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):
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.
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.
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:
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.
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.
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).
Something which the container bridge network type does behind the scenes with one end of an automatically created VETH pair.\u00a0\u21a9
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
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
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.
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
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.
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):
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:
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.
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.
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.
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.
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.
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:
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.
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.
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
Rebase the new branch onto the corresponding upstream release
git rebase 2025.02.2\n
Push the new branch and tags
git push origin 2025.02.2-kkit --tags\n
In Infix, checkout new branch of Buildroot
cd buildroot\ngit fetch\ngit checkout 2025.02.2-kkit\n
Commit and push the changes. Remember to update the ChangeLog!
Create a pull request.
Note
Remember to set the pull request label to ci:main to ensure full CI coverage.
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.
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.
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:
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.
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:
Fork Infix to your own user or organization1
Fork all the Infix submodules, e.g., kernelkit/buildroot to your own user or organization as well
Clone your fork of Infix to your laptop/workstation
Deactivate the Actions you don't want in your fork
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.
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
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.
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.
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.
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.
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.
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
This requires the system DNS resolver to be configured.\u00a0\u21a9
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:
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:
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.
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.
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.
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:
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.
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
E.g., lldpd which includes the lldpcli too, handy to sniff and display LLDP packets.\u00a0\u21a9
mdns-scan: a tool for scanning for mDNS/DNS-SD services on the local network.\u00a0\u21a9
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.
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'.
"},{"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.
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.
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.
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
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
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.
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.
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.
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.
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.
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
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
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.
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.
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)
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:
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.
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
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.
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.
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
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.
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.
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.
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.
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
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:
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.
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.
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
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.
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
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
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
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.)
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
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.
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.
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):
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
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.
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
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
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
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.
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.
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:
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:
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.
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:
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:
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.
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.
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.
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:
static: Active members selected based on link status (carrier)
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.
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.
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
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
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
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
(source MAC XOR destination MAC XOR EtherType) MODULO num_links \u21a9
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.
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
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.
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":"
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.
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
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
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.
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.
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.
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.
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.
Permit by default - write-default: permit allows operators to configure any module
Minimal operator rules - Only one rule to permit system RPCs (reboot, set time)
Future proof - New YANG modules automatically configurable by operators
Targeted denials - Only sensitive items (passwords, keys) are explicitly denied
Global denials - Password/keystore/truststore denied for everyone via group \"*\"
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:
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.
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
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.
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.
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.
Order matters - Rule-lists are evaluated in order. Place admin's permit-all rule first so it bypasses global denials.
Use path-based denials - For protecting specific data (like password hashes), use path rules. For protecting entire modules (like keystore), use module-name rules.
Test thoroughly - Always test user permissions after changes. NACM errors can be subtle (nodes may be silently omitted from read operations).
Keep it simple - The factory configuration uses only 6 rules for three user levels. Fewer rules are easier to understand and maintain.
Document rules - Use the \"comment\" field to explain why specific permissions are granted or denied.
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.
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
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.
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.
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:
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
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.
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.
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
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.
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 poll2 Polling interval in log2 seconds (2 = 4s) precision0.1 Assumed precision in seconds (0.1 = 100ms) refid\"GPS\" Reference identifier (e.g., GPS, GNSS, GLO) preferfalse Prefer this source over other reference clocks ppsfalse Enable PPS for microsecond-level accuracy offset0.0 Constant offset correction in seconds delay0.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
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.
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.
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.
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.
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
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.
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
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:
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:
~/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.
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).
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 p2pieee802-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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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_REQPDELAY_REQ Yes Initiates P2P neighbour-delay measurement PDELAY_RESP Yes Neighbour reply to PDELAY_REQPDELAY_RESP_FOLLOW_UP No Carries precise PDELAY_RESPt3 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.
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.
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):
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.
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.
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:
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.
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.
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
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.
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.
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
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
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
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
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.
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
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
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
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
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.
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
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":"
"},{"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":"
"},{"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":"
"},{"location":"scripting-netconf/#operational-data-examples","title":"Operational Data Examples","text":""},{"location":"scripting-netconf/#read-interface-state","title":"Read Interface State","text":"
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-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.
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:
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).
"},{"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.
"},{"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
"},{"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.
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.
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)
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/#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":"
~$ ./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":"
"},{"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
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
~$ 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
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.
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:
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:
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.
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.
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.
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.
"},{"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.
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":"
"},{"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
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.
"},{"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:
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:
Change the configuration without saving it to startup-config
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
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:
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\":
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:
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).
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.
"},{"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.
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.
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)
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.
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:
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
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.
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.
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.
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
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.
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.
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.
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.
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.
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:
NACM permissions - what the user can access
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:
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:
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:
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.
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.
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.
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.
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.
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.
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
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.
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.
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
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.
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.
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.
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.
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:
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
To run the tests on a preexisting topology from the host's network namespace, specify the hostTEST_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:
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:
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:
"},{"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:
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:
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:
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
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/#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:
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.
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.
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.
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.
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.
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.
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.
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.
Download and unpack the release to install. Make the image pkg bundle available at some URL2
(Optional) Backup the startup configuration
Assume the unit has booted the primary image. Then running the upgrade command installs a new image on the secondary partition
As part of a successful upgrade, the boot-order is implictly changed to boot the newly installed image
Reboot the unit
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.
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.
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.
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
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.
Find the backup configuration file
Run upgrade URL to install Infix image to downgrade to
Copy backup startup configuration to current startup configuration (from shell)
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.
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.
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.
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
Set up an FTP/TFTP/SFTP or HTTP/HTTPS server on the same LAN.\u00a0\u21a9
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.
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:
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:
Base (chassis) MAC address of the system
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.
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.
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.
"},{"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.
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.
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.
The allowed-ips setting in WireGuard serves two critical purposes:
Ingress Filtering: Only packets with source IPs in the allowed list are accepted from the peer
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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:
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.
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.
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.
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.
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:
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.
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.
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.
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:
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
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 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
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.:
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:
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.
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.
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.
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.
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!
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:
In configure context the following commands are available:
Command Description set foo bar val Set bar leaf node in foo subcontext to valno foo bar Clear/delete configuration made to bar in fooedit 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 fooabort 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 logcommit"},{"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:
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:
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.
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.
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
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.
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.
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.
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.
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
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:
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.
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
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.