Compare commits

...
Author SHA1 Message Date
Joachim Wiberg eec5546100 board/common: add missing newline at end of /etc/motd
With the changes in 9847a8f we can keep the /etc/motd file with a proper
newline at EOF so that also Classic builds make sense.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-01 20:19:17 +01:00
Joachim Wiberg 4451e70ba8 .github: adjust release tag variables
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-01 18:38:22 +01:00
Joachim Wiberg 1f29bfa5a6 .github: set 'latest' flag for non-pre-release builds
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-01 16:03:26 +01:00
Joachim Wiberg aea6ef3961 doc: update release notes for v24.02
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-01 14:17:20 +01:00
Joachim Wiberg 1e4a88e0b9 doc: new text-editor topic for cli
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-01 14:14:33 +01:00
Joachim Wiberg ba25914cbe confd: add support for changing text-editor backend
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-01 14:13:43 +01:00
Joachim Wiberg 84391c18a4 board/common: introduce Debian-like alternatives system
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-01 14:13:06 +01:00
Joachim Wiberg c73b3b59c2 doc: update CLI introduction and tutorial with new commands
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-01 11:30:51 +01:00
Joachim Wiberg b3e6a38d76 package/klish: bump for motd-like Banner="" extension for klish.conf
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-01 10:04:19 +01:00
Joachim Wiberg 9847a8fdd1 confd: migrate infix-system:motd -> infix-system:motd-banner
Inspired by openconfig-system[1], except the infix-system type is binary
to allow encoding an entire file, with optional control characters.

The openconfig-system model also carries login-banner, which is displayed
before login.  This could be added later to infix-system to let the user
set an OpenSSH login Banner.

[1]:  https://openconfig.net/projects/models/schemadocs/yangdoc/openconfig-system.html

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-01 10:04:19 +01:00
Joachim Wiberg 87bffb3816 confd: import base64 encode/decode functions, 3-clause BSD licensed
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-01 10:04:19 +01:00
jovatnandGitHub 8b44a42424 Update ChangeLog.md
Remote trailing dot (.)

[skip ci]
2024-02-29 14:33:13 +01:00
jovatnandGitHub 2e408d2ee1 Update ChangeLog.md
Include IETF Hardware data in news section.

[skip ci]
2024-02-29 14:31:03 +01:00
Joachim Wiberg 300734872c doc: minor fixups and clarfiy new port counters as augments
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 14:19:41 +01:00
jovatnandGitHub 6b91a6765c Update ChangeLog.md
Proposed changes to description of ietf-hardware and infix-hardware models.

[skip ci]
2024-02-29 14:08:37 +01:00
Joachim Wiberg 9ab8bd5bc9 doc: restore lost diff link
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 12:44:33 +01:00
Joachim Wiberg 955de32ea8 doc: prepare v24.02 release notes
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 12:39:42 +01:00
Joachim Wiberg b449b7ea82 Revert ".github: temporarily disable Infix Classic builds"
This reverts commit 2bcb02d3ef and thus
reeneables classic builds again.  Since we build on /mnt on the runner
there should be enough room also for them.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 12:16:14 +01:00
Mattias Walström f0c23ca048 Update revision for infix-if-type 2024-02-29 10:22:32 +01:00
Mattias WalströmandJoachim Wiberg c6626b6520 test: Increase timeouts for OSPF tests
This since when running the test in the cloud with github
supplied runner, everthing sometimes gets really slow.
2024-02-29 09:46:08 +01:00
Joachim Wiberg 9c203f614b confd: log error when failing to record changes to NTP client
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 09:14:53 +01:00
Joachim Wiberg 1df1df7096 .github: new package requirement for coverity runs
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 08:54:58 +01:00
Joachim Wiberg 92744b7318 statd: fix string length comparison
Calling strlen() returns a size that's one byte less than what's needed
to store the string, so when comparing with sizeof(), which returns the
number of available bytes, we must compare >= and not just >.

Found by GCC using 'make check'.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 08:45:23 +01:00
Joachim Wiberg be7b434d74 Update list of 'make check' packages
- new: execd, keyack
 - statd has been converted to autotools

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 08:44:08 +01:00
Joachim Wiberg a47e8b6d4a statd: fix library ordering for linker
When building for cppcheck/coverity we may get a stupid linker that can
wind up not finding, e.g., fexist() in libite (-lite) if it's not added
last in the list of libraries.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 08:42:07 +01:00
Joachim Wiberg b004f081a1 .github: check disk usage in $HOME
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 07:55:31 +01:00
Joachim Wiberg 06365fa3da .github: sync release job with latest changes
- Cleanup any docker images to free up space
 - Build on /mnt
 - Update artifacts: pattern after API changes
 - Add .sha256 checksums to each release tarball

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 07:47:54 +01:00
Joachim Wiberg 75b47b8a7a .github: adapt to new artifact action behavior
No more artifact/ subdirectory with upload/download-artifact v4,
artifacts are already in the working directory.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-29 07:17:24 +01:00
Joachim Wiberg aa039449da .github: fake it, let qemu target build in $HOME
Apparently, running "make test-qeneth" from an output directory that's
not in $HOME does not work in Azure.  This patch adds an exception for
the x86_64-netconf target that ensures regression tests run just as if
nothing had changed.

Other platform//variants will be built on /mnt for the time being since
we're running out of space in the runner:

df -h =================================================================
Filesystem      Size  Used Avail Use% Mounted on
/dev/root        73G   58G   16G  80% /
tmpfs           7.9G  172K  7.9G   1% /dev/shm
tmpfs           3.2G  1.1M  3.2G   1% /run
tmpfs           5.0M     0  5.0M   0% /run/lock
/dev/sda15      105M  6.1M   99M   6% /boot/efi
/dev/sdb1        74G  4.1G   66G   6% /mnt
tmpfs           1.6G   12K  1.6G   1% /run/user/1001
=======================================================================

This is the output of the new "Manny" investigator, it revealed that we
only have 16 GiB of space, after checkout and cache donwload, to build
and upload *all* platforms/variants.  Hence the cheat with /mnt, which
we honestly don't know will remain mounted on these runners.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 20:57:14 +01:00
Joachim Wiberg cd7412aea4 .github: use correct build directory variable
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 18:45:56 +01:00
Joachim Wiberg 2564d2597b .github: sudo make me a build directory
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 18:42:33 +01:00
Joachim Wiberg cb1054e4a3 .github: use /mnt as output directory for builds
- Clean up any lingering docker images
 - Redirect output to /mnt/$arch-$flavor

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 18:39:48 +01:00
Joachim Wiberg 24549d149c .github: split into multiple selectable jobs
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 18:33:22 +01:00
Mattias WalströmandJoachim Wiberg 2bf6ec7d1a test: ipv4_autoconf: Fix racecondition on slow test-PCs
Refactor test, remove fragile solution with tcpdump started AFTER
the linklocal was activated, this opened for a race condition since
it was listening for a message sent once.

Now wait for the linklocal address instead.
2024-02-28 18:25:43 +01:00
Mattias WalströmandJoachim Wiberg ac97ce01a5 test: iface: Fix bug in function address_exist()
did not allow any other protocol to checked except DHCP
2024-02-28 18:25:43 +01:00
Joachim Wiberg a730cc0439 .github: peek & poke
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 18:24:35 +01:00
Joachim Wiberg 3c8879cbdf .github: take inventory also after checkout & cache restore
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 18:18:40 +01:00
Joachim Wiberg c9dc5b928a .github: check disk
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 17:01:01 +01:00
Joachim Wiberg a9458f79b1 .github: run cleanup *after* taking inventory
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 15:35:49 +01:00
Joachim Wiberg 1b1ca208e1 .github: give Manny a broom
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 15:34:03 +01:00
Joachim Wiberg 5ee342849d .github: add warehouse manager
TODO: add optional cleanup input argument.

[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 15:09:41 +01:00
Joachim WibergandMattias Walström 2bcb02d3ef .github: temporarily disable Infix Classic builds
Debugging build/runner problems with GitHub Actions.  Resolution pending
self-hosted runner becoming operational.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 13:55:57 +01:00
Joachim WibergandMattias Walström c1787622a1 test: fix running on systems without /dev/kvm
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 13:55:57 +01:00
Joachim WibergandMattias Walström e0f97b1840 .github: run regression tests only once, either push or pull-request
Currently the regression tests runs twice when branches and PR:s originate
from the same repo.  This commit adopts a fix suggested at

https://wildwolf.name/github-actions-how-to-avoid-running-the-same-workflow-multiple-times/

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 13:55:57 +01:00
Mattias Walström d1b9f1fb45 Add new test start_from_startup.py to just see that the device can boot from startup config
Useful to run in a loop.
2024-02-28 13:18:51 +01:00
Mattias Walström e77772e8c5 test: usb: Refactor to use a common function to wait boot 2024-02-28 13:18:51 +01:00
Mattias Walström c6d9c68726 Add new function that only waits for boot (reboot) to complete
This since it can take an undefined time to reboot the DUT.
2024-02-28 13:18:51 +01:00
Mattias Walström 103398f070 make run: Increase default memory for qemu
This since the image has gotten larger.
2024-02-28 13:13:32 +01:00
Joachim WibergandMattias Walström 2f844f9ac7 Revert ".github: switch regression test to self-hosted runner"
This reverts commit 4b2acdedc5 since the
self-hosted runner(s) are not yet working properly.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-28 11:21:53 +01:00
Richard AlpeandMattias Walström 27a4278cce statd: add good-octets counters
This patch adds out-good-octets and in-good-octets as augment in
the infix-ethernet-interface yang model. These counters represents
OctetsTransmittedOK and OctetsReceivedOK from ethtool.

Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-28 11:21:53 +01:00
Joachim Wiberg 3a04bec92d test: drop --interactive in non-interactive docker sessions
With a self-hosted runner we get the following classic error when
calling `make test-unit`:

    make[1]: Entering directory '/home/runner/_work/infix/infix/buildroot'
    /home/runner/_work/infix/infix/test/env /home/runner/_work/infix/infix/test/9pm/9pm.py /home/runner/_work/infix/infix/test/case/all-repo.yaml /home/runner/_work/infix/infix/test/case/all-unit.yaml
    the input device is not a TTY
    make[2]: *** [/home/runner/_work/infix/infix/board/x86_64/board.mk:14: test-unit] Error 1

This patch drops interactive from all non-interactive docker sessions.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 12:33:20 +01:00
Joachim Wiberg 9aa9a60aca test: replace hard-coded dependency on podman in Infamy
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 12:05:31 +01:00
Joachim Wiberg 954d26a7e2 statd: fix build without containers
Some customers do not need, or do not want, to be able to run
containers, so all NETCONF related daemons and applications
need to take this into consideration.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 11:47:07 +01:00
Joachim Wiberg 4b2acdedc5 .github: switch regression test to self-hosted runner
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 11:46:44 +01:00
Joachim Wiberg 0741d2a655 .github: simplify PR template slightly add reminder of YANG changes
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 10:39:22 +01:00
Joachim Wiberg e38063c0ac Minor update/clarification now that content has shifted downwards
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 10:27:19 +01:00
Joachim Wiberg a2bc3bef61 .github: reduce number of caches required by CI builds
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 10:08:20 +01:00
Joachim Wiberg eceeb8c59f confd: fix cni_popen() when building without container support
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 08:14:41 +01:00
Joachim Wiberg d12d66b013 package/klish-plugin-sysrepo: display default value in help text
When available, show the default value of a leaf node after the
description when issuing 'help leaf-node'.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 08:14:41 +01:00
Joachim Wiberg e73742504d Add new intro blurb from homepage to README
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 08:14:41 +01:00
Joachim Wiberg 6cde353029 Drop unicode from tagline, does not survive syslog
The tagline is read by Finit at boot, from /etc/os-release, and
logged when changing runlevel.  The unicode charachter does not
survive the trip via syslog to log file.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 08:14:41 +01:00
Joachim Wiberg a9e78aa329 patches/iproute2: backport ip address 'accept symbolic names'
This change completes the backport of symbolic names for ip address
protocol field:

    ip addr add IFADDR proto 5
=>
    ip addr add IFADDR proto dhcp

Ref:
https://lore.kernel.org/netdev/20230602155419.8958-1-stephen@networkplumber.org/

Fix #286

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 08:14:41 +01:00
Joachim Wiberg 7a52f49c7d execd: fix printf format specifier for systemf()
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 08:14:41 +01:00
Joachim Wiberg ba76103c7f execd: refactor to autotools
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 08:14:41 +01:00
Joachim Wiberg b0b1a79aa5 statd: refactor to autotools, same as confd
Fix issue #299

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 08:14:41 +01:00
Joachim Wiberg 5762a73c45 package/confd: reduce sysrepo default log level
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 08:14:41 +01:00
Joachim Wiberg 4909a9e553 patches: backport finit silence udevd by default
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-27 08:14:41 +01:00
Joachim WibergandTobias Waldekranz 83821def3b confd: always create bridge with default_pvid 0
Fix #310
2024-02-26 16:36:29 +01:00
Richard AlpeandJoachim Wiberg f1ab05a86f yanger: remove stray variables and brackets (cosmetic)
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-26 13:01:06 +01:00
Richard AlpeandJoachim Wiberg 412ee33f09 yanger: fix indentation (cosmetic)
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-26 13:01:06 +01:00
Richard AlpeandJoachim Wiberg 0f3433e9a0 yanger: remove stray semicolons (cosmetic)
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-26 13:01:06 +01:00
Richard AlpeandJoachim Wiberg 807b113d4e yanger: refactor test backend
Simplify the code a bit by passing the test file to the runner
function instead of checking it in every caller.

Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-26 13:01:06 +01:00
Richard AlpeandJoachim Wiberg 5f25c063d5 yanger: introduce main function
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-26 13:01:06 +01:00
Joachim Wiberg 5038cd47e5 .github: proposed pull request template/checklist
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 5386ec9a3a test,doc,board: Infamy Made Easy
As of 4768cae6, we can use conserver/telnet to connect to the docker0
bridge IP:90XX, where XX is DUTXX.  This was a great UX improvement
and this patch set aims to further the experience by:

 - allow running Infamy (infix-test container) completely rootless¹
 - reduce the expsed port range 50->10 (can be improved further)
 - use 'podman --publish-all' ports, which allocates ten random
   ports for the exposed Qemu telnet ports
 - add Quick Start Guide to doc/testing.md

This restores the possiblity of running multiple "make test-sh"
instances, e.g., when multiple users share the same server.

The 'console' script included in this commit uses 'podman inspect' to
find the port number for a given DUT, and optional instance.  It takes
either the console/dut number (1-N), or the dut name from the topology.

Also in this commit:
 - set hostname for easy identification (for console script)
 - set conatainer --name to hostname
 - adjust workdir from buildroot to infix/test
 - simplify PS1 and add time to prompt (when did the test finish?)

When running 'make test-sh' the prompt now tells you which instance you
are running, e.g., infmay10.  Calling 'console 1 infamy10' connects to
console 1 (port 9001) on the infamy10 instance.

For more information, see the Quick Start Guide in doc/testing.md.  It
shows how to use the new helper scripts: console and shell.

Fix #227

_______
¹) devs using podman, instead of docker, with slirp4netns installed, can
   run the infix-test container completely rootless.  We like this, and
   as of today podman is our recommendation.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 00c2c0c608 test/case: new test, container connected using physical interface
Verifies that a regular/physical port/interface can be handed over to a
container.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 4856705803 test/case: new test, plain bridge + veth connection for container
Vanilla bridge and veth pair managed entirely by Infix.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg a38f5902c8 test/case: new test, verify connectivity behind docker0 masquerade
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg ec4c1b1d70 test/case: new test, basic container test
- New helper class for container testing
 - New helper class to urllib, Furl

Due to extremely weak Python-fu in the undersigned, this patch changes
the __init__.py file to add new helper classes for container tests.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg bdbbfcb50f test/infamy: improved logging for connect + factory-default
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg f8b4a858bc test/infamy: pep-8 cleanup and simplification
- Fix most of what pylint complains about
 - Simplify constructs and add missing whitespace
 - Simplify output of YANG model downloader

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 3da045dd40 test/infamy: handle empty NETCONF reply from remote
This patch handles a corner case we for some reason have not run into
yet.  With the new container operational data it was triggered almost
immediately, so let's add some basic guards around it and return None

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 72a86301f0 test/infamy: save location data for Device and add address() method
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Tobias WaldekranzandJoachim Wiberg fcc9fe84f4 test/infamy: Support calling arbitrary functions from within a netns
Add a helper that lets you call a Python function (or lambda) in the
context of a network samespace.

Example:

    with infamy.IsolatedMacVlan("eth0") as ns:
        res = ns.call(lambda: subprocess.run(["ip", "link"],
	                                     capture_output=True))
	print(res.stdout, f"\n[exitcode:{res.returncode}]")

    The call to subprocess.run will be executed in the context of the
    network namespace, and thus only list "lo" and the "iface"
    macvlan.

The return value of the function passed to ns.call() is passed back
over a multiprocessing.Pipe, which requires that the object be
"picklable". This is true for most objects (even more complex ones
like the CompletedProcess object seen in the example above). Some
notable exceptions are things like file objects, sockets, etc.

The setns(2) syscall is unfortunately not available in current
versions of Python shipped with neither Ubuntu nor Alpine at the time
of this writing. Therefore, shoot from the hip, assume that we're
running on an x86_64 CPU, and just hotwire the syscall directly with
some constants we found in a dumpster.
2024-02-25 19:49:27 +01:00
Joachim Wiberg 9ce763662f package/curios-httpd: new package, bundle a demo container
This is an example of how to create a package that downloads an OCI
archive and installs it to /lib/oci

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 257636d997 package/klish: bump for __ptype -> xml param.name fix
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 25c31ce218 src/klish-plugin-infix: improve log message on failed copy
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg bcdd311cb8 src/klish-plugin-infix: support for copying remote files with auth
This patch adds the optional 'user name' argument to the 'copy' command,
enabling using curl scp/sftp protocols for fetching and uploading files.

Changes to cfg_adjust() allow saving and referencing files in the user's
home directory.  Useful for both fetching upgrade bundles and container
images.

The 'dir' command now lists files in both $HOME and /cfg.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 3159d6c042 src/klish-plugin-infix: new symbol shell@infix with user tracking
This patch adds a new klish symbol, shell@infix, which performs proper
droprivs before calling the configured shell.

Fix #298: track user id so shell command gets correct user

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 8354997104 src/klish-plugin-infix: break out shell to optional shell.xml
Some customers don't want unprivileged CLI users to be able to exit to
a shell.  The shell is currently hard-coded to bash, but should be one
of the ietf-system shells, configurable --with-shell=foo.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg b0a0b31e68 src/klish-plugin-infix: refactor copy@infix, allow file -> file copy
This refactor is basically only an extra step after

   copy source-ds -> dest-ds

to also

   copy source-file -> dest-file

Which in the case of factory-config and startup-config is relevant.  We
want to update the startup datastore, in case of additional commands,
but also the file /cfg/startup-config.cfg in case of consecutive reboot.

Fix #259 copy factory-config startup-config

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 686f0bec03 confd: refactor, relocate container network code to cni.[ch]
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 360d3b322d confd: use podman stop/start to prevent container corruption
Sending SIGTERM to conmon is not a safe shutdown of a podman container.
To handle gracefully handle shutdown, restarting and provide an orderly
start of dependencies, we use the Finit sysv trick via container script
wrapper to call 'podman stop foo'.

However, since podman does not support syslog as output for containers
we employ an old FIFO trick with another program, k8s-logger, to allow
logs to reach syslog.  Please note that k8s-logger must have properly
started before we call `podman start` -- this makes us fully dependent
on the 'container' wrapper script.  Hence the documentation update.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 6fa1eb9e00 package/k8s-logger: new package
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg a530db9ae5 board/common: confine containers in their own parent cgroup
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 337f39a74d board/common: quiet flag and check if running for start/stop
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 3b5c0248d7 confd: handle factory-default RPC better wrt. container networks
Because sysrepo callbacks are threaded and factory-default RPC is called
from a separate subscription (to prevent blocking), we cannot prevent
ietf-intefaces.c from being called before infix-containers.c, regardless
of the priority we set for our subscriptions.

When assigning a physical network interface this becomes a bit of a pain
during factory-default RPC since the physical interface is hidden from
the host network namespace.

So, when applying factory to running, we check each interface if it was
a container-network previously, if so we call on the container script in
the exit of the current dagger generation to move the interface back to
the host netns.

This affects all other functions that assume interfaces only live in the
host netns.  To that end a set of new helper functions have been added
to wrap iproute2 commands in nsenter when the interface lives elsewhere.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 168fb24957 confd: handle building without containers
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg ec3e3b3322 confd: minor refactor, split hook, improve log messages
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 67bb7e5928 confd: simplify container network naming
The interface name is already a unique qualifier, so we can simplify CNI
network naming to ease the burden when debugging.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 214ca82bd6 confd: allow host container interfaces without IP address
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg a05a3609c5 confd: add sloppy inference of container-network type
"Sloppy" because, for some reason, we don't get sysrepo update callbacks
for presence containers.  I.e., when calling 'set container-network'
compared to 'edit container-network'.

Yes, I've gone over klish-plugins-sysrepo with a fine-toothed comb to
see if it's the culprit, but no, it seems to be sysrepo.  Hence the
defaulting of the type also in the change path.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg e4aa36dfe7 confd: tighten up container data validation in YANG model
Review found quite a few strings that could be given an obvious pattern
or specialized sub-type, e.g. inet:ip-address, to restrict the input
data validation.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 34b06fa448 confd: update container documentation and YANG models
Due to the deprecation of CNI i Podman[1], and its upcoming removal in
Podman v5.0, we decided to cut our losses and modify all documentation
and models to become backend neutral.  We will continue to use CNI as
long as possible -- it gives us host interfaces, for instance, that is
not part of netawark yet, it seems.

Also, sync mount and volume documentation with YANG model changes.

[1]: https://blog.podman.io/2023/11/cni-deprecation-and-removal-from-podman-5-0/

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 81d8643a6f confd: rename container 'file' to 'mount', add more mount types
After discussions in the development team, and an external demo, we
decided to rename file mounts to just mounts and adopt established
nomenclature.

With this change we now allow mounting any type of file or directory
from the host to the container, for regular files/directories a bind
mount is used.  We also introduce 'source' and 'type', to support
mounting of host files and directories, the destination path has been
renamed to 'target'.  The mount cousin, 'volume', which basically is a
specialized mount, has also been updated to use 'target'.

The 'source' and 'content' are now hidden behind a YANG choice to ensure
users can only use them the intended way (either/or not both).

Initial models had 'destination' (path) as mandatory, with this change
we can reintroduce that to ensure this proper configurations.

The documentation in both YANG model and markdown has been updated,
significantly, to provide lots of examples.

Please note, glob style mounts have not been tested, at all.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 8edf238b68 confd: handle active but unsaved containers in unclean shutdowns
When container configurations are not saved to startup-config and the
user reboot the system (or lose power) we will have lingering active
containers cached on persistent storage.

This patch adds a cleanup() function to the post hook, right before
we try to launch any new containers.  This callback must be run for
all cases, even if the running-config has no containers configured.

Also, a minor amendment to volume pruning.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 61cba2615f confd: make container privileged mode configurable
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg a6f6fcac6a confd: add support for oci: and oci-archive: images
1. add support for extracting, and finding the directory holding the
    index.json metadata file in, OCI images.  An archive in Infix is either
    a .tar or .tar.gz archive, which we need to unpack for this version of
    podman to be able to load them.
 2. new RPC '/infix-containers:oci-load' calls 'container load' of an
    OCI archive (tarball), optionally gzipped.  The resulting image uses
    the directory name of the unpacked tarball, so the container script
    offers a way to retag the image after loading it.

First class citizens in container transport are docker:// and the OCI
family of URI:s.  A docker:// URL, or a local docker-archive:path, is
assumed to be well formed, in which case we leave it up to podman to
handle.

Note: 'podman import' does not fully understand OCI formats.  It drops
      ENTRYPOINT and COMMAND, while 'podman load' handles things a lot
      better.  Only letdown is it does not support nameing the image.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 8ea159ce5d confd: add feature flag for containers
Not all customers want Container support in their Ínfix builds.  This
patch adds build-time support for detecting if podman is enabled in
the configuration.

 - Klish container commands have been broken out to their own xml file
 - infix-interfaces have been given a 'containers' feature
 - ietf-interfces.c enables 'containers' feature on an #ifdef
 - infix-container.c is only built if --enable-containers

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 235d3e46e6 confd: major refactor of infix-containers after team review
- Rename container/container -> containers/container
 - Collapse containers-state into containers
 - Complete refactor of network/host-network for improved UX.  A new
   must expression ensure mutual exclusion between host and networks
 - Rename entrypoint -> command, for consistency (overrides ENTRYPOINT)
 - Move nodes around a bit for more logical placement in model
 - Clean out all traces of a container on removal
 - Rename container image_id -> image-id for consistency

The active queue is for tracking currently running containers, ensuring
only relevant changes are applied, and also for container image upgrade,
at the end of which these scripts are re-run to activate the new image

Also, fix misplaced '-f' when pruning unused volumes.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg a9a7b7016d statd: initial support for container-state information
Also, add missing dependency on libite to packaging.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg ed86a29bfc doc: initial container documentation
Brief overview of configuration support.  Need to add admin-exec level
commands, but since operational and actions still are missing, we keep
it out until after the code review.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 580599f549 confd: misc. fixes and cleanup
- drop debug logs
 - no need to restart upgraded containers, they restart automatically
 - fix ordering in volume prune (when containers are removed)
 - skip directories in container activation (inbox -> execd queue)
 - Fix 'container pull IMG creds USER[:PASS]' bug, extra '=' variable
   assignment inside infix.xml
 - Fix 'container run IMG CMD ARGS' to actually put the CMD in
   ENTRYPOINT and append ARGS to image.  The podman run and create
   commands are *not* behaving the same way
 - Rename 'attach' to 'exec', execute command inside an running container
 - Add 'container [shell | connect]' to start shell in -- " -- " -- " --
 - Add 'container [stat | cleanup | usage stats]' commands
 - Add in="tty" to commands thay may end up being interactive
 - Split <ACTION> line into multiple lines for readability
 - Fix container shift logic:

     root@infix-00-00-00:/> show container
     /usr/sbin/container: line 361: shift: shift count out of range

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg b7d119b23f confd: add NETCONF actions to start/stop/restart containers
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg ff32314676 confd: rename volume dir -> path, same as bind-mounted files
Also, update container YANG model descriptions and ensure 'path' is
mandatory for each list element and attach a regexp pattern to fix
the mountpoint to an absolute path inside the container.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg cd4e8b0a9f confd: add support for mounting read-only files in containers
In the container configuration context:

    edit file ntp.conf
    set path /etc/ntp.conf
    set content

The last command opens a text editor where you can paste the contents
of the file.  This is stored base64 encoded in the datastore as well
as in JSON/XML.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 31502c8aab confd: add support for upgrading container images
This patch adds a new CLI command 'container upgrade foo', where 'foo'
is the name of the container.  If more than one container use the same
image, multiple upgrades must be done because a container runs not on
the 'image:tag' but on the hash of the 'image:tag' it was created from.

Rename "done" queue to "active", since we want to recreate an active
container after fetching an updated base image.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg aafa88b648 confd: add support for container volumes
This commit adds support for writable container volumes.  A volume, when
compared to a mount, is a writable directory created when the container
starts and is automatically rsync'ed with the underlying directory it
is mounted on on first use.  A mount otoh does not rsync so it results
only in an empty directory inside the container.

The podman/docker mount feature will be used later to bind mount single
files or sharing directories from the host, e.g., /sys/class/leds/ to
one of more containers.

Note: unused volumes are automatically pruned.  Hence, a volume
      currently cannot be moved to another container.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 414703e851 confd: minor refactor, /run/container/env -> /run/container/args
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 256c462c0c confd: add support for read-only containers
This creates a container without a writable layer.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 679a6671ed confd: do not recreate existing containers that are unchanged
This patch allows containers created with "podman create" to surivce
rebots, and even reconfigurations at runtime, as long as their config
has not changed.  Allowing some level of persistence in the writable
layer given to containers.

For even more persistency, support for volumes (they sync with dir in
container on first use) and mounts (creates empty writable dirs in the
container) must be added.  A third option, a variant of mounts, is to
allow a leaf-list "file", where binary content can be added and then
mounted into the container, e.g.

/run/containers/files/$name/file.ext -> container://$name/etc/file.ext

None of this is available yet, but seems likely we need to add in the
very near term.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg d8dde9cde2 confd: add support for custom container ENTRYPOINT
This was a tough nut to crack.  Turns out the trick to changing the
ENTRYPOINT is to set --entrypoint=command and then call 'podman create
... command args'.  Not entirely obvious since the documented approach
is to use a JSON array as the argument: podman create
--entrypoint='["command", "args" ]'.

Admittedly, encoding this in C to transfer it via a POSIX shell script
to the command line, is not the easiest task I've undertaken.  So I gave
up and found this workaround.

Worth noting, however, is that one *must* set `--entrypoint`, it is not
enough to just append the command to the 'podman create' command line.
Due to differences in docker and podman, we cannot supply the full args
to an alternate entrypoint command.  But for the command to actually run
we need to override the image's ENTRYPOINT and send the command and args
as the last arguments on the command line to podman create.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg f1bf02eda5 confd: refactor how container networks are sent to container helper
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg aa7ea66a0d confd: add support for setting container hostname
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 7a57d4f0fa confd: add support for container environment variables
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 3d85eba3fc confd: add support for publishing container ports
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg b04c81199d confd: add support for host containers, shares host's interfaces
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg ffd2128614 confd: add support for container restart and restart policy
Also, ensure the deleted container is actually deleted before recreating
it with a new configuration.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 2b19b61068 confd: add support for manual start of containers
This commit adds 'manual:yes' to the container's Finit service
configuration and changes from pod: to container: prefix for a
unique namespace to prevent collision with regular services.

The prefix container: is more correct that pod:, which should
be reserved for any future pod support.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>

confd: fix missing variable in container script condition

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 0f0c119553 configs: sync r2s defconfig with latest changes
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 5fdc408ade configs: add libssh2 to NETCONF builds, needed by libcurl
libcurl use libssh2 for scp/sftp access

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 9e801dfa2f confd: initial support for Docker containers using podman
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 0bda0727b7 confd: minor, whitespace only
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 0b6f7a6b93 package/execd: new local package
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg ad0463e96c libsrx: minor, constify since we don't modify these args
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 3bdf360b2e libsrx: minor, coding style
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 58ebae5624 yanger: pep-8 style fixes and cleanup
- reserved words: map -> xlate
 - add missing docstrings
 - convert to recommended constructs: proto in (foo, bar)
 - drop unused 'f' format strings: f""
 - too short exception vars: e -> err, d -> entry
 - drop unnecessary () in if expressions
 - add whitespace for readability and navigation

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg b3e418c6a7 board/common: let qemu.sh take kernel/init args on command line
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg f0f0e737a9 board/common: BusyBox ash != bash, set bash as root default shell
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 84f83d635b board/common: fix #294: drop 'v' from version in filenames
The files inside a release tarball, as well as the tarball name itself,
should not have the leading 'v', that's just for the tag.

Also, add -ver to GNS3 disk.img file as well.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg e5dd01ffcc patches/finit: fix out-of-place [OK] messages at shutdown/reboot
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 4f98b0b761 package/podman: install bash completion
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 074754d42d package/podman: minor, drop helper scripts
To be replaced with native support for generating container network
interfaces and running under finit.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 51dc940059 package/klish-plugin-sysrepo: new commands 'change' + 'text-editor'
This bump adds support for 'set foo' to set foo=true, but also two new
commands from srp_helper@: 'change password', 'text-editor content'.

 - ietf-system: 'change password' now starts an interactive session that
   results in a random-salted sha512 encrypted hash.
 - 'text-editor <node-of-binary-type>', starts 'editor file | base64'
 - 'set <node-of-bool-type>', sets node=true

A prototype askpass script has been added as a proof-of-concept.  It
follows the design of other askpass style programs, like ssh-askpass,
that pass the resulting password on stdout.  With the additional support
for also writing it to an output file, e.g., a pipe.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 4793cf0770 package/skeleton-init-finit: silence mdev/mdevd/udevd log notices
On every system reconf (initctl reload) the .conf files are evaluated
again and log notices about skipping alternative syslogd enties are
logged.

We know that we run the udevd dependend syslogd entry, and everything
else basically in runlevel S depends on it, so not much else would work
if syslogd didn't start.  So we can safely set 'nowarn'.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 0468f35fb6 cli: minor, whitespace and fix 'show' subcommand help
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Joachim Wiberg 1e87a30147 confd: skip ethtool command for etherlike interfaces
When "plopping" back an interface from another network namespace, e.g.,
when removing a container network interface, we cannot just rely on the
'ethernet' node *not* being available for an interface.

Experience shows that sysrepo might send us a curve ball (see comment)
which we interpret to be a true Ethernet interface.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00
Mattias Walström 83effa6fd4 Use common CFLAGS in all src-packages.
And fix bugs that the new CFLAGS find.
2024-02-23 14:01:23 +01:00
Mattias Walström 8f996859dc Add infix CFLAGS, used when building src/ (confd, statd....) 2024-02-23 14:01:23 +01:00
Mattias Walström 4869b73b14 test: usb: test startup with no hardware configuration
In this case the USB ports shall be locked.
2024-02-23 14:01:23 +01:00
Mattias Walström c1309da10b infamy: Add support to copy running to startup
Also add support for rebooting unit.
2024-02-23 14:01:23 +01:00
Mattias Walström acc39b1b15 yanger: usb: Refactor to only check authorized_default if locked/unlocked
authorized is unreliable on boot.
2024-02-23 14:01:23 +01:00
Mattias Walström ddd0fc5a35 yanger: vpd: set class to vpd for all vpd nodes
class is mandatory in the standard.
2024-02-23 14:01:23 +01:00
Mattias Walström afa243cd2f confd: core: keep one instance of /run/system.json open
Add entry in struct confd to keep it accessable for all callbacks.
2024-02-23 14:01:23 +01:00
Mattias Walström b66b56f504 probe: rename usb-ports-names => usb-port-names 2024-02-23 14:01:23 +01:00
Mattias Walström d4a1a7c91c doc: Add USB documentation 2024-02-23 14:01:23 +01:00
Mattias Walström c57a4655b8 ietf-routing: Handle return value from touch() 2024-02-23 14:01:23 +01:00
Mattias Walström 30e2a814eb factory: getchar() returns integer not char 2024-02-23 14:01:23 +01:00
Mattias Walström caea3132c9 probe: Change to return the sysfs path for authorized and authorized_default
Leave it to confd to check if the file exist.
2024-02-23 14:01:23 +01:00
Mattias Walström 746392cac5 x86_64: Update linux_defconfig with USB as module
This to be able to control USB ports in infix using authorized in /sys
2024-02-23 14:01:23 +01:00
Mattias Walström 5afaeae46c Update qeneth to support USB 2024-02-23 14:01:23 +01:00
Mattias Walström 15b32cfcc2 infamy: Add a simple USB test 2024-02-23 14:01:23 +01:00
Mattias Walström 8e0db4c7e4 topology: virt: Add usb-sticks 2024-02-23 14:01:23 +01:00
Mattias Walström 420e0d5b36 qeumu: Add USB ports 2024-02-23 14:01:23 +01:00
Mattias Walström 69eae34203 probe: Add support for virtual USB ports (qemu) 2024-02-23 14:01:23 +01:00
Mattias Walström c0a071e87a Add 'show hardware' only show USB port status for now 2024-02-23 14:01:23 +01:00
Mattias Walström 2011f5cbf2 modprobe.d: Unauthorize all USB ports on boot 2024-02-23 14:01:23 +01:00
Mattias Walström a6ff416401 confd: Add support for ietf-hardware
Add supports to enable disable USB port
2024-02-23 14:01:23 +01:00
Mattias Walström 66c20aa79d bootstrap: Enable states feature to ietf-hardware 2024-02-23 14:01:23 +01:00
Mattias Walström b0f2388153 probe: Add support for multiple /sys nodes for a of-node 2024-02-23 14:01:23 +01:00
Mattias Walström 5e707753e9 ietf-hardware: YANG: Deviate everything not supported yet 2024-02-23 14:01:23 +01:00
Mattias Walström 6099ac79e2 Add script to generate entries for external ports
For the user to be able to enable them in runtime
2024-02-23 14:01:23 +01:00
Mattias Walström 6f41ea5ccb probe: Read information about external ports (usb)
from Device tree and put it into system.json
2024-02-23 14:01:23 +01:00
Mattias Walström c58bd5ead4 aarch64: Configure USB_CORE as a module
This to be able to control the USB ports in infix using authorized in /sys
2024-02-23 14:01:23 +01:00
Mattias Walström bd9acd003d Alder: DTS: Add information about external ports (USB) 2024-02-23 14:01:23 +01:00
Richard AlpeandTobias Waldekranz 0cb1459f43 cli-pretty: only print stp state if oper status is up
Only print bridge port stp state if operational status of the
interface is up.

Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-20 21:43:48 +01:00
Richard AlpeandTobias Waldekranz 1827b65182 test: update cli-output to reflect new bridge output
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-20 21:43:48 +01:00
Richard AlpeandTobias Waldekranz 1807306178 cli-pretty: print vlan filtering bridge
Handle printing a vlan filtering bridge in "show interfaces".

Example output:
br0             bridge     DOWN        vlan:10u,20t
│               ipv4                   192.168.1.1/24 (static)
├ e0            bridge     DISABLED    vlan:10u,20t pvid:10
└ e1            bridge     DISABLED    vlan:10t,20u pvid:20
br1             bridge
└ e2            bridge     FORWARDING  vlan:30u pvid:30
e3              ethernet   UP          02:00:00:00:00:03
                ipv6                   fe80::ff:fe00:3/64 (link-layer)
e4              ethernet   UP          02:00:00:00:00:04
                ipv6                   fe80::ff:fe00:4/64 (link-layer)
lo              ethernet   UP          00:00:00:00:00:00
                ipv4                   127.0.0.1/8 (static)
                ipv6                   ::1/128 (static)

Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-20 21:43:48 +01:00
Richard AlpeandTobias Waldekranz 475a3a2919 yanger: add bridge vlan, pvid and stp-state
This patch adds information about a vlan filtering bridge to the
operational datastore.

The information included is vlans, pvid and stp-state. These changes
reflects what can be configured though confd.

NOTE: pvid is still missing for bridge interfaces. Discussions on how
to best add this is ongoing. It's currently not configurable due to an
existing self check in the infix-if-bridge yang model.

Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-20 21:43:48 +01:00
Richard AlpeandTobias Waldekranz 0937921cbc test: add br1 to sysrepo emulator
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-20 21:43:48 +01:00
Richard AlpeandTobias Waldekranz 6ad80c80ab test: make e2 into br1 bridge port in system output
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-20 21:43:48 +01:00
Richard AlpeandTobias Waldekranz 4150f45cc4 test: add system output for vlan filtering bridge
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-20 21:43:48 +01:00
Richard AlpeandTobias Waldekranz 1da00a9764 yang: add stp-state to infix-if-bridge
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-20 21:43:48 +01:00
Tobias Waldekranzandjovatn 015215073a doc: Initial documention of VPD 2024-02-13 15:45:29 +01:00
Richard AlpeandJoachim Wiberg d73d5440c5 test: remove hardcoded interface type
This can now a days be inferred and isn't needed. Also, the interface
type in virtual testing is now etherlike not ethernet.

Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-05 18:01:30 +01:00
Richard AlpeandJoachim Wiberg 104a70d46c confd: handle etherlike delete as ethernet
This resolves the regression where configured etherlike interface mac
addresses where not deleted from the system.

Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-05 18:01:30 +01:00
Richard AlpeandJoachim Wiberg 795f2615cb test: update cli output test to reflect etherlike changes
* Remove bogus ethernet statistics for br0
* Change parentbus for e0 and e1 to make them ethernet devices (not
  virtio -> etherlike).

Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-05 18:01:30 +01:00
Richard AlpeandJoachim Wiberg debcf81a7a yanger: only fill eth info for Ethernet interfaces
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-05 18:01:30 +01:00
Richard AlpeandJoachim Wiberg ee50a7c061 yanger: identify new interface type "etherlike"
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-05 18:01:30 +01:00
Richard AlpeandJoachim Wiberg f35f176860 confd: identify virtio interfaces as "etherlike"
Instead of "ethernet". Prior to this patch virtio interfaces where
identified as "ethernet", which resulted in /run/net trying to enable
autoneg on them. Which isn't supported and resulted in an ABANDONED
"default config".

Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-05 18:01:30 +01:00
Richard AlpeandJoachim Wiberg 198be2a9c6 confd: add new interface type "etherlike"
Needed for devices that share a lot of properties with an ethernet
device but doesn't support "ethtool" and such. An example is TAP
interfaces.

Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-05 18:01:30 +01:00
Richard AlpeandJoachim Wiberg 79ce152abb confd: fix indentation in gen-interfaces
Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-05 18:01:30 +01:00
Tobias WaldekranzandJoachim Wiberg 870d41644f confd: ethtool: Condense logic around autoneg configuration
Rely on the interface diff to figure out if any autoneg related config
has changed; if so, generate the resulting configuration based on the
current interface config.

- Remove loopback check, the model only allows ethernet config on
  interfaces of type "ethernet".

- Split up the input validation of speed/duplex to give the user an
  error message with as much specificity as possible.

- Validate that the configured speed/duplex is supported.

- Move the generated script to execute earlier to avoid unnecessary
  link flapping.

- Use negative POSIX error codes, these are (or at least should be)
  mapped to sysrepo errors in netdag_gen_iface.
2024-02-05 18:01:30 +01:00
Richard AlpeandJoachim Wiberg 43017cc79f confd: add the ability to set speed, duplex and autoneg
Add the ability to configure interface speed, duplex and autoneg.
Initially this was pared with YANG model deviations that restricted
explicit interface speed and duplex when autoneg was enabled. But due
to conflicting default states in YANG, this code was dropped.

Signed-off-by: Richard Alpe <richard@bit42.se>
2024-02-05 18:01:30 +01:00
200 changed files with 8358 additions and 1630 deletions
+37
View File
@@ -0,0 +1,37 @@
<!--- **Summarize** your changes in the title above -->
## Description
<!--
-- A description of changes, detailing *why* changes are made.
-- Remember: assign a reviewer, or use @mentions if org. member.
-->
## Other information
<!-- Other relevant info, e.g., before/after screenshots -->
## Checklist
Tick relevant boxes, this PR is-a or has-a:
- [ ] Bugfix
- [ ] Regression tests
- [ ] ChangeLog updates (for next release)
- [ ] Feature
- [ ] YANG model change => revision updated?
- [ ] Regression tests added?
- [ ] ChangeLog updates (for next release)
- [ ] Documentation added?
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (please detail in commit messages)
- [ ] Build related changes
- [ ] Documentation content changes
- [ ] Other (please describe):
## References
<!-- Please list references to related issue(s) -->
+21 -9
View File
@@ -16,6 +16,11 @@ jobs:
variant: [netconf, classic]
fail-fast: false
steps:
- name: Maintenance
run: |
docker image prune -af
docker volume prune -f
docker container prune -f
- uses: actions/checkout@v4
- name: Set Build Variables
id: vars
@@ -27,22 +32,25 @@ jobs:
fi
echo "dir=infix-$target" >> $GITHUB_OUTPUT
echo "tgz=infix-$target.tar.gz" >> $GITHUB_OUTPUT
if [ "$target" = x86_64 ]; then
echo "out=$PWD/output" >> $GITHUB_OUTPUT
else
echo "out=/mnt/x-$target" >> $GITHUB_OUTPUT
fi
- name: Restore Cache of dl/
uses: actions/cache@v4
with:
path: dl/
key: dl-${{ matrix.platform }}-${{ matrix.variant }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'configs/*', 'package/*/*.hash') }}
key: dl-${{ matrix.variant }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'configs/*', 'package/*/*.hash') }}
restore-keys: |
dl-${{ matrix.platform }}-${{ matrix.variant }}-
dl-${{ matrix.platform }}-
dl-${{ matrix.variant }}-
dl-
- name: Restore Cache of .ccache/
uses: actions/cache@v4
with:
path: .ccache/
key: ccache-${{ matrix.platform }}-${{ matrix.variant }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
key: ccache-${{ matrix.platform }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
restore-keys: |
ccache-${{ matrix.platform }}-${{ matrix.variant }}-
ccache-${{ matrix.platform }}-
ccache-
- name: Configure & Build
@@ -52,22 +60,26 @@ jobs:
else
target=${{ matrix.platform }}_${{ matrix.variant }}_defconfig
fi
echo "Buildring $target ..."
echo "Building $target ..."
sudo mkdir ${{ steps.vars.outputs.out }}
sudo chown $(id -un):$(id -gn) ${{ steps.vars.outputs.out }}
export O=${{ steps.vars.outputs.out }}
make $target
make
- name: Prepare Artifact
run: |
cd output
cd ${{ steps.vars.outputs.out }}
mv images ${{ steps.vars.outputs.dir }}
ln -s ${{ steps.vars.outputs.dir }} images
tar chfz ${{ steps.vars.outputs.tgz }} ${{ steps.vars.outputs.dir }}
- name: Test
if: matrix.platform == 'x86_64' && matrix.variant == 'netconf'
run: |
export O=${{ steps.vars.outputs.out }}
make test-qeneth
- uses: actions/upload-artifact@v4
with:
path: output/${{ steps.vars.outputs.tgz }}
path: ${{ steps.vars.outputs.out }}/${{ steps.vars.outputs.tgz }}
name: artifact-${{ matrix.variant }}-${{ matrix.platform }}
release:
if: ${{github.repository_owner == 'kernelkit' && github.ref_name == 'main'}}
@@ -90,7 +102,7 @@ jobs:
prerelease: true
tag: "latest"
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "artifact/*"
artifacts: "*.tar.gz"
- name: Summary
run: |
cat <<EOF >> $GITHUB_STEP_SUMMARY
+3 -2
View File
@@ -50,8 +50,9 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get -y update
sudo apt-get -y install pkg-config libjansson-dev libev-dev libite-dev \
libaugeas-dev libglib2.0-dev libpcre2-dev
sudo apt-get -y install pkg-config libjansson-dev libev-dev \
libaugeas-dev libglib2.0-dev libpcre2-dev \
libuev-dev libite-dev
- name: Build dependencies
run: |
git clone https://github.com/CESNET/libyang.git
+100
View File
@@ -0,0 +1,100 @@
name: Manny the Manager
on:
workflow_dispatch:
inputs:
checkout:
required: true
type: boolean
cleanup:
required: true
type: boolean
peek:
required: true
type: boolean
jobs:
inventory:
runs-on: ubuntu-latest
steps:
- name: Disk usage ...
run: |
cd
du -hs .[^.]*
- name: Disk inventory (1/2) ...
run: |
echo "df -h ========================================================================="
df -h
echo "mounts ========================================================================"
mount
- name: File inventory (1/2) ...
run: |
echo "Current directory: $(pwd)"
echo "Files in $HOME ================================================================"
ls $HOME
echo "Find $HOME ===================================================================="
find $HOME
- name: Container inventory ...
run: |
echo "Available container images: ==================================================="
docker images
echo "Available containers: ========================================================="
docker ps -a
checkout:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Restore Cache of dl/
uses: actions/cache@v4
with:
path: dl/
key: dl-netconf-${{ hashFiles('.git/modules/buildroot/HEAD', 'configs/*', 'package/*/*.hash') }}
restore-keys: |
dl-netconf-
dl-
- name: Restore Cache of .ccache/
uses: actions/cache@v4
with:
path: .ccache/
key: ccache-x86_64-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
restore-keys: |
ccache-x86_64-
ccache-
- name: Disk inventory (2/2) ...
run: |
echo "df -h ========================================================================="
df -h
echo "mounts ========================================================================"
mount
- name: File inventory (2/2) ...
run: |
echo "Current directory: $(pwd)"
echo "Files in $HOME ================================================================"
ls $HOME
echo "Find $HOME ===================================================================="
find $HOME
peeky:
if: ${{ inputs.peek }}
runs-on: ubuntu-latest
steps:
- name: Peek & Poke ...
run: |
whoami
ls -l /mnt/
cat /mnt/DATALOSS_WARNING_README.txt
sudo mkdir /mnt/x-aarch64
sudo chown $(id -un):$(id -gn) /mnt/x-aarch64
ls -l /mnt/
cleanup:
if: ${{ inputs.cleanup }}
needs: [inventory, peeky]
runs-on: ubuntu-latest
steps:
- name: Cleaning up cruft ...
run: |
docker image prune -af
docker volume prune -f
docker container prune -f
+4 -5
View File
@@ -17,6 +17,7 @@ jobs:
build:
name: Regression Testing
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- uses: actions/checkout@v4
with:
@@ -31,18 +32,16 @@ jobs:
uses: actions/cache@v4
with:
path: dl/
key: dl-x86_64-netconf-${{ hashFiles('.git/modules/buildroot/refs/heads/master', 'package/*/*.hash') }}
key: dl-netconf-${{ hashFiles('.git/modules/buildroot/refs/heads/master', 'package/*/*.hash') }}
restore-keys: |
dl-x86_64-netconf-
dl-x86_64-
dl-netconf-
dl-
- name: Restore Cache of .ccache/
uses: actions/cache@v4
with:
path: .ccache/
key: ccache-x86_64-netconf-${{ hashFiles('.git/modules/buildroot/refs/head/master', 'package/*/*.hash') }}
key: ccache-x86_64-${{ hashFiles('.git/modules/buildroot/refs/head/master', 'package/*/*.hash') }}
restore-keys: |
ccache-x86_64-netconf-
ccache-x86_64-
ccache-
- name: Configure NETCONF
+51 -20
View File
@@ -28,9 +28,14 @@ jobs:
variant: [netconf, classic]
fail-fast: false
steps:
- name: Maintenance
run: |
docker image prune -af
docker volume prune -f
docker container prune -f
- uses: actions/checkout@v4
- name: Set Release Variables
id: build
id: vars
run: |
if [ -n "${{ inputs.version }}" ]; then
ver=${{ inputs.version }}
@@ -38,11 +43,6 @@ jobs:
ver=${GITHUB_REF#refs/tags/}
fi
echo "ver=${ver}" >> $GITHUB_OUTPUT
if echo $ver | grep -qE 'v[0-9\.]+(-alpha|-beta|-rc)[0-9]*'; then
echo "pre=true" >> $GITHUB_OUTPUT
else
echo "pre=false" >> $GITHUB_OUTPUT
fi
fver=${ver#v}
if [ "${{ matrix.variant }}" = "netconf" ]; then
target=${{ matrix.platform }}-${fver}
@@ -51,56 +51,86 @@ jobs:
fi
echo "dir=infix-$target" >> $GITHUB_OUTPUT
echo "tgz=infix-$target.tar.gz" >> $GITHUB_OUTPUT
echo "out=/mnt/x-$target" >> $GITHUB_OUTPUT
- name: Restore Cache of dl/
uses: actions/cache@v4
with:
path: dl/
key: dl-${{ matrix.platform }}-${{ matrix.variant }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'configs/*', 'package/*/*.hash') }}
key: dl-${{ matrix.variant }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'configs/*', 'package/*/*.hash') }}
restore-keys: |
dl-${{ matrix.platform }}-${{ matrix.variant }}-
dl-${{ matrix.platform }}-
dl-${{ matrix.variant }}-
dl-
- name: Restore Cache of .ccache/
uses: actions/cache@v4
with:
path: .ccache/
key: ccache-${{ matrix.platform }}-${{ matrix.variant }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
key: ccache-${{ matrix.platform }}-${{ hashFiles('.git/modules/buildroot/HEAD', 'package/*/*.hash') }}
restore-keys: |
ccache-${{ matrix.platform }}-${{ matrix.variant }}-
ccache-${{ matrix.platform }}-
ccache-
- name: Configure & Build
env:
INFIX_RELEASE: ${{ steps.build.outputs.ver }}
INFIX_RELEASE: ${{ steps.vars.outputs.ver }}
run: |
if [ "${{ matrix.variant }}" = "netconf" ]; then
target=${{ matrix.platform }}_defconfig
else
target=${{ matrix.platform }}_${{ matrix.variant }}_defconfig
fi
echo "Buildring $target ..."
echo "Building $target ..."
sudo mkdir ${{ steps.vars.outputs.out }}
sudo chown $(id -un):$(id -gn) ${{ steps.vars.outputs.out }}
export O=${{ steps.vars.outputs.out }}
make $target
make
- name: Prepare Artifact
run: |
cd output
mv images ${{ steps.build.outputs.dir }}
ln -s ${{ steps.build.outputs.dir }} images
tar chfz ${{ steps.build.outputs.tgz }} ${{ steps.build.outputs.dir }}
cd ${{ steps.vars.outputs.out }}
mv images ${{ steps.vars.outputs.dir }}
ln -s ${{ steps.vars.outputs.dir }} images
tar chfz ${{ steps.vars.outputs.tgz }} ${{ steps.vars.outputs.dir }}
- uses: actions/upload-artifact@v4
with:
path: output/${{ steps.build.outputs.tgz }}
path: ${{ steps.vars.outputs.out }}/${{ steps.vars.outputs.tgz }}
name: artifact-${{ matrix.variant }}-${{ matrix.platform }}
release:
name: Release Infix ${{ github.ref_name }}
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Set Release Variables
id: rel
run: |
if [ -n "${{ inputs.version }}" ]; then
ver=${{ inputs.version }}
else
ver=${GITHUB_REF#refs/tags/}
fi
echo "ver=${ver}" >> $GITHUB_OUTPUT
if echo $ver | grep -qE 'v[0-9.]+(-alpha|-beta|-rc)[0-9]*'; then
echo "pre=true" >> $GITHUB_OUTPUT
echo "latest=false" >> $GITHUB_OUTPUT
elif echo $ver | grep -qE '^v[0-9.]+\.[0-9.]+(\.[0-9]+)?$'; then
echo "pre=false" >> $GITHUB_OUTPUT
echo "latest=true" >> $GITHUB_OUTPUT
else
echo "pre=false" >> $GITHUB_OUTPUT
echo "latest=false" >> $GITHUB_OUTPUT
fi
echo "pre=${{ steps.rel.outputs.pre }}"
echo "latest=${{ steps.rel.outputs.latest }}"
- uses: actions/download-artifact@v4
with:
pattern: "artifact-*"
merge-multiple: true
- name: Create checksums ...
run: |
for file in *.tar.gz; do
sha256sum $file > $file.sha256
done
- name: Extract ChangeLog entry ...
run: |
awk '/^-----*$/{if (x == 1) exit; x=1;next}x' doc/ChangeLog.md \
@@ -109,9 +139,10 @@ jobs:
- uses: ncipollo/release-action@v1
with:
name: Infix ${{ github.ref_name }}
prerelease: ${{ needs.build.outputs.pre }}
prerelease: ${{ steps.rel.outputs.pre }}
makeLatest: ${{ steps.rel.outputs.latest }}
bodyFile: release.md
artifacts: artifact/*
artifacts: "*.tar.gz*"
- name: Summary
run: |
cat <<EOF >> $GITHUB_STEP_SUMMARY
+1 -1
View File
@@ -54,7 +54,7 @@ config INFIX_IMAGE_ID
config INFIX_TAGLINE
string "Operating system tagline"
default "Infix a Network Operating System"
default "Infix -- a Network Operating System"
help
Mandatory. Used for identifying the OS, e.g. as PRETTY_NAME in
/etc/os-release and description in the GNS3 appliance.
+5 -3
View File
@@ -22,12 +22,14 @@ $(config):
@echo "'make <board>_defconfig' before building an image."
@exit 1
%: | buildroot/Makefile
@+$(call bmake,$@)
# Workaround, see board/x86_64/board.mk
test:
@+$(call bmake,$@)
buildroot/Makefile:
@git submodule update --init
.PHONY: all check
.PHONY: all check test
+40 -5
View File
@@ -18,11 +18,39 @@
</details>
Infix is a Linux Network Operating System (NOS) based on [Buildroot][1],
and [sysrepo][2]. A powerful mix that ease porting to different
platforms, simplify long-term maintenance, and provide made-easy
management using NETCONF[^1] (remote) or the built-in [command
line interface (CLI)][3] (click the foldout for an example).
Infix is a free, Linux based, immutable Network Operating System (NOS)
built on [Buildroot][1], and [sysrepo][2]. A powerful mix that ease
porting to different platforms, simplify long-term maintenance, and
provide made-easy management using NETCONF[^1] or the built-in [command
line interface (CLI)][3] from a console or SSH login. *Click the
foldout (▶ Example CLI Session) below for an example.*
Although primarily focused on switches and routers, the core values
may be appealing for other use-cases as well:
- Runs from a squashfs image on a read-only partition
- Single configuration file on a separate partition
- Built around YANG with standard IETF models
- Linux switchdev provides open switch APIs
- Atomic upgrades to secondary partition
- Highly security focused
An immutable[^2] operating system enhances security and inherently makes
it maintenance-free. Configuration and data, e.g, containers, is stored
on separate partitions to ensure complete separation from system files
and allow for seamless backup, restore, and provisioning.
In itself Infix is perfectly suited for dedicated networking tasks and
native support for Docker containers provides a versatile platform that
can easily be adapted to any customer need. Be it legacy applications,
network protocols, process monitoring, or edge data analysis, it can run
close to end equipment. Either directly connected on dedicated Ethernet
ports or indirectly using virtual network cables to exist on the same
LAN as other connected equipment.
The simple design of Infix provides complete control over both system
and data, minimal cognitive burden, and makes it incredibly easy to get
started.
<details><summary><b>Example CLI Session</b></summary>
@@ -78,6 +106,7 @@ The [following boards](board/aarch64/README.md) are fully supported:
- Marvell CN9130 CRB
- Marvell EspressoBIN
- Microchip SparX-5i PCB135 (eMMC)
- NanoPi R2S
An x86_64 build is also available, primarily intended for development
and testing, but can also be used for evaluation and demo purposes. For
@@ -92,6 +121,12 @@ more information, see: [Infix in Virtual Environments](doc/virtual.md).
[^1]: NETCONF or RESTCONF, <https://datatracker.ietf.org/doc/html/rfc8040>,
for more information, see [Infix Variants](doc/variant.md).
[^2]: An immutable operating system is one with read-only file systems,
atomic updates, rollbacks, declarative configuration, and workload
isolation. All to improve reliability, scalability, and security.
For more information, see <https://ceur-ws.org/Vol-3386/paper9.pdf>
and <https://www.zdnet.com/article/what-is-immutable-linux-heres-why-youd-run-an-immutable-linux-distro/>.
[1]: https://buildroot.org/
[2]: https://www.sysrepo.org/
[3]: doc/cli/introduction.md
+3 -1
View File
@@ -25,6 +25,8 @@
infix {
vpds = <&vpd_cpu &vpd_product &vpd_power>;
usb-ports = <&cp0_usb3_1>;
usb-port-names = "USB";
};
};
@@ -268,7 +270,7 @@
};
&cp0_usb3_1 {
status = "disabled";
status = "okay";
usb-phy = <&cp0_usb3_0_phy1>;
phys = <&cp0_utmi1>, <&cp0_comphy3 1>;
phy-names = "usb", "usb3";
+25 -38
View File
@@ -21,7 +21,6 @@ CONFIG_CFS_BANDWIDTH=y
CONFIG_RT_GROUP_SCHED=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_CPUSETS=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
@@ -32,7 +31,6 @@ CONFIG_SCHED_AUTOGROUP=y
CONFIG_BLK_DEV_INITRD=y
CONFIG_KALLSYMS_ALL=y
CONFIG_PROFILING=y
CONFIG_SMP=y
CONFIG_ARCH_SPARX5=y
CONFIG_ARCH_MVEBU=y
CONFIG_ARCH_VEXPRESS=y
@@ -49,12 +47,6 @@ CONFIG_CPUFREQ_DT=y
CONFIG_ARM_ARMADA_37XX_CPUFREQ=y
CONFIG_ARM_ARMADA_8K_CPUFREQ=y
CONFIG_ACPI=y
CONFIG_ARM64_CRYPTO=y
CONFIG_CRYPTO_SHA1_ARM64_CE=y
CONFIG_CRYPTO_SHA2_ARM64_CE=y
CONFIG_CRYPTO_GHASH_ARM64_CE=y
CONFIG_CRYPTO_AES_ARM64_CE_CCM=y
CONFIG_CRYPTO_AES_ARM64_CE_BLK=y
CONFIG_KPROBES=y
CONFIG_JUMP_LABEL=y
# CONFIG_GCC_PLUGINS is not set
@@ -67,10 +59,8 @@ CONFIG_TRANSPARENT_HUGEPAGE=y
CONFIG_CMA=y
CONFIG_NET=y
CONFIG_PACKET=y
CONFIG_UNIX=y
CONFIG_XDP_SOCKETS=y
CONFIG_XDP_SOCKETS_DIAG=y
CONFIG_INET=y
CONFIG_IP_MULTICAST=y
CONFIG_IP_ADVANCED_ROUTER=y
CONFIG_IP_MULTIPLE_TABLES=y
@@ -112,7 +102,6 @@ CONFIG_NFT_MASQ=m
CONFIG_NFT_REDIR=m
CONFIG_NFT_NAT=m
CONFIG_NFT_TUNNEL=m
CONFIG_NFT_OBJREF=m
CONFIG_NFT_QUEUE=m
CONFIG_NFT_REJECT=m
CONFIG_NFT_COMPAT=m
@@ -186,9 +175,7 @@ CONFIG_VLAN_8021Q=y
CONFIG_VLAN_8021Q_GVRP=y
CONFIG_VLAN_8021Q_MVRP=y
CONFIG_NET_SCHED=y
CONFIG_NET_SCH_CBQ=y
CONFIG_NET_CLS_BASIC=y
CONFIG_NET_CLS_TCINDEX=y
CONFIG_NET_CLS_BPF=y
CONFIG_NET_CLS_FLOWER=y
CONFIG_NETLINK_DIAG=y
@@ -236,7 +223,6 @@ CONFIG_ATA=y
CONFIG_SATA_AHCI=y
CONFIG_SATA_AHCI_PLATFORM=y
CONFIG_AHCI_MVEBU=y
CONFIG_PATA_PLATFORM=y
CONFIG_PATA_OF_PLATFORM=y
CONFIG_MD=y
CONFIG_BLK_DEV_DM=y
@@ -325,6 +311,7 @@ CONFIG_8139CP=y
# CONFIG_NET_VENDOR_WIZNET is not set
# CONFIG_NET_VENDOR_XILINX is not set
CONFIG_SFP=y
CONFIG_AX88796B_PHY=y
CONFIG_MARVELL_PHY=y
CONFIG_MARVELL_10G_PHY=m
CONFIG_MICREL_PHY=y
@@ -332,12 +319,12 @@ CONFIG_MICROCHIP_PHY=y
CONFIG_MICROCHIP_T1_PHY=y
CONFIG_MICROSEMI_PHY=m
CONFIG_MDIO_BITBANG=y
CONFIG_MDIO_MVUSB=y
CONFIG_MDIO_MVUSB=m
CONFIG_MDIO_MSCC_MIIM=y
CONFIG_MDIO_BUS_MUX_MMIOREG=y
CONFIG_USB_RTL8152=y
CONFIG_USB_USBNET=y
CONFIG_USB_NET_CDC_EEM=y
CONFIG_USB_RTL8152=m
CONFIG_USB_USBNET=m
CONFIG_USB_NET_CDC_EEM=m
# CONFIG_USB_NET_CDC_NCM is not set
# CONFIG_USB_NET_NET1080 is not set
# CONFIG_USB_NET_CDC_SUBSET is not set
@@ -371,7 +358,6 @@ CONFIG_I2C_MV64XXX=y
CONFIG_I2C_SLAVE=y
CONFIG_SPI=y
CONFIG_SPI_ARMADA_3700=y
CONFIG_SPI_NXP_FLEXSPI=y
CONFIG_SPI_ORION=y
CONFIG_SPI_PL022=y
CONFIG_SPI_MUX=y
@@ -427,30 +413,31 @@ CONFIG_REGULATOR_S2MPS11=y
# CONFIG_HID_LOGITECH is not set
# CONFIG_HID_MICROSOFT is not set
# CONFIG_HID_MONTEREY is not set
CONFIG_USB=y
CONFIG_USB_ULPI_BUS=y
CONFIG_USB=m
CONFIG_USB_OTG=y
CONFIG_USB_XHCI_HCD=y
CONFIG_USB_XHCI_MVEBU=y
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_EHCI_HCD_PLATFORM=y
CONFIG_USB_OHCI_HCD=y
CONFIG_USB_OHCI_HCD_PLATFORM=y
CONFIG_USB_STORAGE=y
CONFIG_USB_XHCI_HCD=m
CONFIG_USB_XHCI_MVEBU=m
CONFIG_USB_EHCI_HCD=m
CONFIG_USB_EHCI_HCD_PLATFORM=m
CONFIG_USB_OHCI_HCD=m
CONFIG_USB_OHCI_HCD_PLATFORM=m
CONFIG_USB_STORAGE=m
CONFIG_USB_DWC3=y
CONFIG_USB_DWC2=y
CONFIG_USB_CHIPIDEA=y
CONFIG_USB_CHIPIDEA=m
CONFIG_USB_CHIPIDEA_UDC=y
CONFIG_USB_CHIPIDEA_HOST=y
CONFIG_USB_ISP1760=y
CONFIG_USB_HSIC_USB3503=y
CONFIG_USB_HSIC_USB3503=m
CONFIG_NOP_USB_XCEIV=y
CONFIG_USB_ULPI=y
CONFIG_USB_GADGET=y
CONFIG_USB_SNP_UDC_PLAT=y
CONFIG_TYPEC=y
CONFIG_TYPEC_TCPM=y
CONFIG_TYPEC_TCPCI=y
CONFIG_TYPEC_RT1711H=y
CONFIG_TYPEC_TCPM=m
CONFIG_TYPEC_TCPCI=m
CONFIG_TYPEC_RT1711H=m
CONFIG_MMC=y
CONFIG_MMC_BLOCK_MINORS=32
CONFIG_MMC_ARMMMCI=y
@@ -462,7 +449,6 @@ CONFIG_MMC_SPI=y
CONFIG_MMC_DW=y
CONFIG_MMC_DW_EXYNOS=y
CONFIG_MMC_DW_K3=y
CONFIG_MMC_CQHCI=y
CONFIG_MMC_SDHCI_XENON=y
CONFIG_SCSI_UFSHCD=y
CONFIG_SCSI_UFSHCD_PLATFORM=y
@@ -503,7 +489,6 @@ CONFIG_EXTCON_USB_GPIO=y
CONFIG_IIO=y
CONFIG_TI_ADC081C=y
CONFIG_PWM=y
CONFIG_PHY_XGENE=y
CONFIG_PHY_MVEBU_CP110_COMPHY=y
CONFIG_PHY_MVEBU_CP110_UTMI=y
CONFIG_PHY_SAMSUNG_USB2=y
@@ -512,13 +497,11 @@ CONFIG_MUX_MMIO=y
CONFIG_EXT2_FS=y
CONFIG_EXT3_FS=y
CONFIG_EXT4_FS_POSIX_ACL=y
CONFIG_EXT4_FS=y
CONFIG_BTRFS_FS=y
CONFIG_BTRFS_FS_POSIX_ACL=y
CONFIG_FANOTIFY=y
CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y
CONFIG_QUOTA=y
CONFIG_AUTOFS4_FS=y
CONFIG_FUSE_FS=y
CONFIG_VIRTIO_FS=y
CONFIG_OVERLAY_FS=y
@@ -536,17 +519,21 @@ CONFIG_SECURITY=y
CONFIG_CRYPTO_CCM=m
CONFIG_CRYPTO_ECHAINIV=y
CONFIG_CRYPTO_ANSI_CPRNG=y
CONFIG_CRYPTO_GHASH_ARM64_CE=y
CONFIG_CRYPTO_SHA1_ARM64_CE=y
CONFIG_CRYPTO_SHA2_ARM64_CE=y
CONFIG_CRYPTO_AES_ARM64_CE_BLK=y
CONFIG_CRYPTO_AES_ARM64_CE_CCM=y
CONFIG_DMA_CMA=y
CONFIG_CMA_SIZE_MBYTES=0
CONFIG_IRQ_POLL=y
CONFIG_PRINTK_TIME=y
CONFIG_DEBUG_KERNEL=y
CONFIG_MAGIC_SYSRQ=y
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
CONFIG_MAGIC_SYSRQ=y
CONFIG_DEBUG_FS=y
CONFIG_DETECT_HUNG_TASK=y
# CONFIG_SCHED_DEBUG is not set
# CONFIG_DEBUG_PREEMPT is not set
# CONFIG_RCU_TRACE is not set
CONFIG_FUNCTION_TRACER=y
# CONFIG_STRICT_DEVMEM is not set
+3 -3
View File
@@ -1,7 +1,7 @@
#
# Automatically generated make config: don't edit
# Busybox version: 1.36.0
# Tue Oct 3 18:00:40 2023
# Thu Feb 8 08:22:32 2024
#
CONFIG_HAVE_DOT_CONFIG=y
@@ -1132,9 +1132,9 @@ CONFIG_SV_DEFAULT_SERVICE_DIR=""
CONFIG_SH_IS_ASH=y
# CONFIG_SH_IS_HUSH is not set
# CONFIG_SH_IS_NONE is not set
CONFIG_BASH_IS_ASH=y
# CONFIG_BASH_IS_ASH is not set
# CONFIG_BASH_IS_HUSH is not set
# CONFIG_BASH_IS_NONE is not set
CONFIG_BASH_IS_NONE=y
CONFIG_SHELL_ASH=y
CONFIG_ASH=y
CONFIG_ASH_OPTIMIZE_FOR_SIZE=y
+2 -2
View File
@@ -22,7 +22,7 @@ diskimg=disk.img
ver()
{
if [ -n "$INFIX_RELEASE" ]; then
printf -- "-%s" "$INFIX_RELEASE"
printf -- "-%s" "${INFIX_RELEASE#v}"
return
fi
}
@@ -42,7 +42,7 @@ fi
load_cfg DISK_IMAGE
if [ "$DISK_IMAGE" = "y" ]; then
ixmsg "Creating Disk Image"
diskimg="${NAME}-disk.img"
diskimg="${NAME}-disk$(ver).img"
bootcfg=
if [ "$DISK_IMAGE_BOOT_DATA" ]; then
bootcfg="-b $DISK_IMAGE_BOOT_DATA -B $DISK_IMAGE_BOOT_OFFSET"
+2 -2
View File
@@ -75,9 +75,9 @@ config QEMU_MACHINE
config QEMU_MACHINE_RAM
string "RAM size (k/M/G)"
default "256M"
default "384M"
help
The default, 255 MiB, works for most configurations, even less for
The default, 384 MiB, works for most configurations, even less for
the Infix Classic builds. However, if you get kernel panic with:
"System is deadlocked on memory", try increasing this one.
+31 -4
View File
@@ -24,11 +24,22 @@ prognm=$(basename "$0")
usage()
{
echo "usage: $prognm [opts]"
echo "Usage:"
echo " $prognm [opts] [ARGS]"
echo
echo " -c Run menuconfig to change Qemu settings"
echo " -h This help text"
echo "Options:"
echo " -c Run menuconfig to change Qemu settings"
echo " -h This help text"
echo
echo "Arguments:"
echo " ARGS1 Args before the '--' separator are for kernel space"
echo " -- Separator"
echo " ARGS2 Args after the '--' separator are for the init process"
echo " Also, qemu.cfg has QEMU_APPEND which can affect this."
echo
echo "Example:"
echo " qemu.sh -- finit.debug"
echo "___________________________________________________________________"
echo "Note: 'kconfig-frontends' package (Debian/Ubuntu) must be installed"
echo " for -c to work: sudo apt install kconfig-frontents"
@@ -130,6 +141,18 @@ serial_args()
echo -n "-device virtconsole,nr=1,name=gdbserver,chardev=gdbserver "
}
usb_args()
{
USBSTICK="usb.vfat"
if ! [ -f $USBSTICK ]; then
dd if=/dev/zero of=${USBSTICK} bs=8M count=1 >/dev/null 2>&1
mkfs.vfat $USBSTICK >/dev/null 2>&1
fi
echo -n "-drive if=none,id=usbstick,format=raw,file=${USBSTICK} "
echo -n "-usb "
echo -n "-device usb-ehci,id=ehci "
echo -n "-device usb-storage,bus=ehci.0,drive=usbstick "
}
rw_args()
{
[ "$CONFIG_QEMU_RW" ] || return
@@ -235,6 +258,7 @@ run_qemu()
$(rootfs_args) \
$(serial_args) \
$(rw_args) \
$(usb_args) \
$(host_args) \
$(net_args) \
$(wdt_args) \
@@ -346,6 +370,10 @@ else
load_qemucfg qemu.cfg
fi
if [ -z "$QEMU_EXTRA_APPEND" ]; then
QEMU_EXTRA_APPEND="$*"
fi
generate_dot
echo "Starting Qemu :: Ctrl-a x -- exit | Ctrl-a c -- toggle console/monitor"
@@ -353,4 +381,3 @@ line=$(stty -g)
stty raw
run_qemu $(dtb_args)
stty "$line"
+1
View File
@@ -0,0 +1 @@
/usr/bin/mg
@@ -0,0 +1,4 @@
service :%i pid:!/run/k8s-logger-%i.pid <usr/container:%i> \
[2345] k8s-logger -cni %i -f local1 /run/containers/%i.fifo -- Logger for container %i
sysv :%i pid:!/run/container:%i.pid <pid/k8s-logger:%i> kill:10 \
[2345] container -n %i -- container %i
@@ -0,0 +1,3 @@
# Do not authorize usb ports before they have been checked in in the device-tree
options usbcore authorized_default=0
+1 -1
View File
@@ -1,4 +1,4 @@
.-------.
| . . | Infix -- a Network Operating System
|-. v .-| https://kernelkit.github.io
'-'---'-'
'-'---'-'
@@ -0,0 +1 @@
local1.* -/var/log/container
+1 -1
View File
@@ -1 +1 @@
mg
/etc/alternatives/editor
+1 -1
View File
@@ -1 +1 @@
mg
/etc/alternatives/editor
+590
View File
@@ -0,0 +1,590 @@
#!/bin/sh
all=""
env=""
port=""
force=
log()
{
logger -I $PPID -t container -p local1.notice -- "$*"
}
# Unpacks a given oci-archive.tar[.gz] in the current directory. Sanity
# checks, at least one index.json in the top-level dir of the archive.
# If there are more index files, this function does not handle them.
unpack_archive()
{
image=$1
name=$2
# Supported transports for load and create
case "$image" in
oci:*) # Unpacked OCI image
file=${image#oci:}
;;
oci-archive:*) # Packed OCI image, .tar or .tar.gz format
file=${image#oci-archive:}
;;
*) # docker://*, docker-archive:*, or URL
echo "$image"
return 0
;;
esac
if [ ! -e "$file" ]; then
if [ -e "/var/lib/conatainers/oci/$file" ]; then
file="/var/lib/conatainers/oci/$file"
elif [ -e "/lib/oci/$file" ]; then
file="/lib/oci/$file"
else
log "Error: cannot find OCI archive $file in search path."
exit 1
fi
fi
if [ -d "$file" ]; then
index=$(find "$file" -name index.json)
if [ -z "$index" ]; then
log "Error: cannot find index.json in OCI image $file"
exit 1
fi
else
index=$(tar tf "$file" |grep index.json)
if [ -z "$index" ]; then
log "Error: invalid OCI archive, cannot find index.json in $file"
exit 1
fi
[ -n "$quiet" ] || log "Extracting OCI archive $file ..."
tar xf "$file" || (log "Error: failed unpacking $file in $(pwd)"; exit 1)
fi
dir=$(dirname "$index")
[ -n "$quiet" ] || log "Loading OCI image $dir ..."
podman load -qi "$dir" >/dev/null
# Rename image from podman default $dir:latest
if [ -n "$name" ]; then
podman tag "$dir" "$name" >/dev/null
podman rmi "$dir" >/dev/null
else
name=$dir
fi
echo "$name"
}
running()
{
run=$(podman inspect "$1" 2>/dev/null |jq .[].State.Running)
[ "$run" = "true" ] && return 0
return 1
}
# shellcheck disable=SC2086
create()
{
name=$1
image=$2
shift 2
if [ -z "$name" ] || [ -z "$image" ]; then
echo "Usage:"
echo " container create NAME IMAGE"
exit 1
fi
# Unpack and load docker-archive/oci/oci-archive, returning image
# name, or return docker:// URL for download.
image=$(unpack_archive "$image")
if [ -z "$logging" ]; then
logging="--log-driver k8s-file --log-opt path=/run/containers/$name.fifo"
fi
args="$args --cgroup-parent=containers"
args="$args --restart=$restart --systemd=false --tz=local $privileged --replace --quiet"
args="$args $ro $vol $mount $hostname $entrypoint $env $port $logging"
pidfn=/run/container:${name}.pid
[ -n "$quiet" ] || log "---------------------------------------"
[ -n "$quiet" ] || log "Got name: $name image: $image"
[ -n "$quiet" ] || log "Got networks: $network"
if [ -n "$network" ]; then
for net in $network; do
args="$args --net=$net"
done
for srv in $dns; do
args="$args --dns=$srv"
done
for domain in $search; do
args="$args --dns-search=$domain"
done
else
network="--net none"
fi
# shellcheck disable=SC2048
if podman create --name "$name" --conmon-pidfile="$pidfn" $args "$image" $*; then
[ -n "$quiet" ] || log "Successfully created container $name from $image"
rm -f "/run/containers/env/${name}.env"
[ -n "$manual" ] || start "$name"
exit 0
fi
log "Error: failed creating container $name, please check the configuration."
exit 1
}
delete()
{
name=$1
image=$2
if [ -z "$name" ]; then
echo "Usage:"
echo " container delete NAME"
exit 1
fi
podman rm -vif "$name" >/dev/null 2>&1
[ -n "$quiet" ] || log "Container $name has been removed."
}
waitfor()
{
timeout=$2
while [ ! -f "$1" ]; do
_=$((timeout -= 1))
if [ $timeout -le 0 ]; then
log "Timeout waiting for $1, aborting!"
exit 1
fi
sleep 1;
done
}
start()
{
name=$1
if running "$name"; then
[ -n "$quiet" ] || echo "$name: already running."
return
fi
initctl -bq cond set "container:$name"
# Real work is done by wrap() courtesy of finit sysv emulation
}
stop()
{
name=$1
if ! running "$name"; then
[ -n "$quiet" ] || echo "$name: not running."
return
fi
initctl -bq cond clr "container:$name"
# Real work is done by wrap() courtesy of finit sysv emulation
}
wrap()
{
name=$1
cmd=$2
podman "$cmd" "$name"
}
# Removes network $1 from all containers
netwrm()
{
net=$1
for c in $(podman ps $all --format "{{.Names}}"); do
for n in $(podman inspect "$c" |jq -r '.[].NetworkSettings.Networks | keys[]'); do
if [ "$n" = "$net" ]; then
podman network disconnect $force "$n" "$c" >/dev/null
fi
done
done
}
usage()
{
cat <<EOF
usage:
container [opt] cmd [arg]
options:
-a, --all Show all, of something
--dns NAMESERVER Set nameserver(s) when creating a container
--dns-search LIST Set host lookup search list when creating container
-c, --creds USR[:PWD] Credentials to pass to curl -u for remote ops
-d, --detach Detach a container started with 'run IMG [CMD]'
-e, --env FILE Environment variables when creating container
--entrypoint Disable container image's ENTRYPOINT, run cmd + arg
-f, --force Force operation, e.g. remove
-h, --help Show this help text
--hostname NAME Set hostname when creating container
--net NETWORK Network interface(s) when creating or finding container
-l, --log-driver DRV Log driver to use
--log-opt OPT Logging options to log driver
--log-path PATH Path for k8s-file log pipe
-m, --mount HOST:DEST Bind mount a read-only file inside a container
--manual Do not start container automatically after creation
-n, --name NAME Alternative way of supplying name to start/stop/restart
--privileged Give container extended privileges
-p, --publish PORT Publish ports when creating container
Syntax: [[ip:][hostPort]:]containerPort[/protocol]
-q, --quiet Quiet operation, called from confd
-r, --restart POLICY One of "no", "always", or "on-failure:NUM"
--read-only Do not create a writable layer
-s, --simple Show output in simplified format
-v, --volume NAME:PATH Create named volume mounted inside container on PATH
commands:
create NAME IMAGE NET Create container NAME using IMAGE with networks NET
delete [network] NAME Remove container NAME or network NAME from all containers
exec NAME CMD Run a command inside a container
find [ifname PID] Find PID of container where '--net IFNAME' currently lives
or, find the name of our IFNAME inside the container @PID
help Show this help text
list [image | oci] List names (only) of containers, images, or OCI archives
load [NAME | URL] NM Load OCI tarball fileNAME or URL to image NM
remove IMAGE Remove an (unused) container image
restart [NAME] Restart a crashed container
run NAME [CMD] Run a container interactively, with an optional command
save IMAGE FILE Save a container image to an OCI tarball FILE[.tar.gz]
shell Start a shell inside a container
show [image | volume] Show containers, images, or volumes
stat Show continuous stats about containers (Ctrl-C aborts)
start [NAME] Start a container, see -n
stop [NAME] Stop a container, see -n
volume [prune] Prune unused volumes
EOF
}
while [ "$1" != "" ]; do
case $1 in
-a | --all)
all="-a"
;;
-c | --creds)
shift
creds="-u $1"
;;
-d | --detach)
detach="-d"
;;
--dns)
shift
dns="$dns $1"
;;
--dns-search)
shift
search="$search $1"
;;
-e | --env)
shift
env="$env --env-file=$1"
;;
--entrypoint)
entrypoint="--entrypoint=\"\""
;;
-f | --force)
force="-f"
;;
-h | --help)
usage
exit 0
;;
--hostname)
shift
hostname="--hostname $1"
;;
-l | --log-driver)
shift
logging=" --log-driver=$1"
;;
--log-opt)
shift
logging="$logging --log-opt $1"
;;
--log-path)
shift
logging="$logging --log-opt path=$1"
log_path="$1"
;;
-m | --mount)
shift
mount="--mount=$1"
;;
--manual)
manual=true
;;
-n | --name)
shift
name="$1"
;;
--net)
shift
if [ -n "$network" ]; then
network="$network $1"
else
network=$1
fi
;;
--privileged)
privileged="--privileged=true"
;;
-p | --publish)
shift
port="$port -p $1"
;;
-q | --quiet)
quiet="-q"
;;
-r | --restart)
shift
restart=$1
;;
--read-only)
ro="--read-only=true"
;;
-s | --simple)
simple=true
;;
-v | --volume)
shift
vol="$vol -v $1"
;;
*)
break
;;
esac
shift
done
cmd=$1
if [ -n "$cmd" ]; then
shift
fi
case $cmd in
# Does not work atm., cannot attach to TTY because
# we monitor 'podman start -ai foo' with Finit.
# attach)
# podman attach "$1"
# ;;
create)
[ -n "$quiet" ] || log "Got create args: $*"
create "$@"
;;
delete)
cmd=$1
name=$2
if [ "$cmd" = "network" ] && [ -n "$name" ]; then
netwrm "$name"
else
delete "$@"
fi
;;
exec)
podman exec -it "$@"
;;
find)
cmd=$1
pid=$2
if [ "$cmd" = "ifname" ] && [ -n "$pid" ]; then
nsenter -t "$pid" -n ip -d -j link | \
jq --arg ifname "$network" -r '.[] | select(.ifalias==$ifname) | .ifname'
else
containers=$(podman ps $all --format "{{.Names}}")
for c in $containers; do
json=$(podman inspect "$c")
nets=$(echo "$json" |jq -r '.[].NetworkSettings.Networks | keys[]' 2>/dev/null)
for n in $nets; do
if [ "$network" = "$n" ]; then
pid=$(echo "$json" | jq .[].State.Pid)
echo "$pid"
exit 0
fi
done
done
fi
;;
help)
usage
;;
load)
url=$1
name=$2
# shellcheck disable=SC2086
if echo "$url" | grep -q "://"; then
file=$(basename "$url")
curl -k $creds -Lo "$file" "$url"
else
file="$url"
fi
# shellcheck disable=SC2086
name=$(unpack_archive "$file" $name)
# Show resulting image(s) matching $name
if [ -n "$name" ]; then
podman images -n "$name"
else
exit 1
fi
;;
ls | list)
cmd=$1
[ -n "$cmd" ] && shift
case $cmd in
image*)
podman images $all --format "{{.Repository}}:{{.Tag}}"
;;
oci)
find /lib/oci /var/lib/containers/oci -type f 2>/dev/null
;;
*)
podman ps $all --format "{{.Names}}"
;;
esac
;;
pull)
podman pull "$@"
;;
remove)
podman rmi $all $force -i "$1"
;;
run)
img=$1
cmd=$2
[ -n "$port" ] || port="-P"
if [ -n "$cmd" ]; then
shift 2
[ -n "$detach" ] || echo "Starting $img ENTRYPOINT $cmd :: use Ctrl-p Ctrl-q to detach"
podman run -it --rm $detach $port --entrypoint="$cmd" "$img" "$@"
else
[ -n "$detach" ] || echo "Starting $img :: use Ctrl-p Ctrl-q to detach"
podman run -it --rm $detach $port "$img"
fi
;;
save)
name=$1
file=$2
if echo "$file" | grep -q ".gz"; then
file=${file%%.gz}
gzip=true
fi
if ! echo "$file" | grep -q ".tar"; then
file=${file}.tar
gzip=true
fi
podman save -o "$file" "$name"
if [ -s "$file" ] && [ -n "$gzip" ]; then
gzip "$file"
fi
;;
shell)
podman exec -it "$1" sh -l
;;
show)
cmd=$1
[ -n "$cmd" ] && shift
case $cmd in
image*)
if [ -n "$simple" ]; then
podman images $all --format "{{.Names}} {{.Size}}" \
| sed 's/\[\(.*\)\] /\1 /g' \
| awk '{ printf "%-60s %s %s\n", $1, $2, $3}'
else
podman images $all
fi
;;
volume*)
printf "%-20s CONTAINER\n" "VOLUME"
for v in $(podman volume ls --format "{{.Name}}"); do
printf "%-20s" "$v"
podman ps -a --filter volume="$v" --format '{{.Names}}' | sed 's/^/ /'
done
;;
*)
if [ -n "$simple" ]; then
podman ps $all --format "{{.ID}} {{.Names}} {{.Image}}" \
| awk '{ printf "%s %-30s %s\n", $1, $2, $3}'
else
podman ps $all
fi
;;
esac
;;
start)
if [ -n "$name" ]; then
wrap "$name" start
elif [ -n "$1" ]; then
start "$1"
else
usage
exit 1
fi
;;
restart)
if [ -n "$name" ]; then
wrap "$name" restart
elif [ -n "$1" ]; then
stop "$1"
start "$1"
else
usage
exit 1
fi
;;
stop)
if [ -n "$name" ]; then
wrap "$name" stop
elif [ -n "$1" ]; then
stop "$1"
else
usage
exit 1
fi
;;
stat*)
podman stats -i 2
;;
upgrade)
img=$(podman inspect "$1" | jq -r .[].ImageName)
if [ -z "$img" ]; then
echo "No such container ($1), or invalid ImageName. Cannot upgrade."
exit 1;
fi
podman stop "$1"
podman pull "$img" || (echo "Failed fetching $img, check your network (settings)."; exit 1)
"/var/lib/containers/active/${1}.sh" || (echo "Failed recreating container $1"; exit 1)
;;
volume)
cmd=$1
[ -n "$cmd" ] && shift
case $cmd in
prune)
podman volume $force prune
;;
*)
false
;;
esac
;;
*)
usage
exit 1
;;
esac
+23
View File
@@ -0,0 +1,23 @@
#!/bin/sh
# shellcheck disable=SC3045
OUTPUT=$1
read -r -s -p "New password: " password
>&2 echo
read -r -s -p "Retype password: " password_again
>&2 echo
if [ "$password" != "$password_again" ]; then
echo "Passwords do not match, try again."
exit 1
fi
if [ -z "$OUTPUT" ]; then
echo "$password"
exit 0
fi
umask 0177
echo "$password" | mkpasswd > "$OUTPUT"
exit 0
@@ -1,36 +0,0 @@
#!/usr/bin/env python3
import subprocess
import sys
import json
def get_ethtool_output(interface):
try:
output = subprocess.check_output(['ethtool', interface], stderr=subprocess.DEVNULL, text=True)
return output.splitlines()
except subprocess.CalledProcessError:
print("Error: Failed to run ethtool on interface", interface)
sys.exit(1)
def parse_ethtool_output(lines, keys):
result = {}
for line in lines:
line = line.strip()
key = line.split(':', 1)[0].strip()
if key in keys:
key, value = line.split(':', 1)
result[key.strip()] = value.strip()
return result
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} INTERFACE")
sys.exit(1)
interface = sys.argv[1]
keys = ["Duplex", "Auto-negotiation"]
lines = get_ethtool_output(interface)
parsed_data = parse_ethtool_output(lines, keys)
print(json.dumps(parsed_data, indent=4))
+47 -7
View File
@@ -40,10 +40,13 @@ class DTSystem:
continue
ph, = struct.unpack(">L", open(phandle, "rb").read())
sys[ph] = root
if ph not in sys:
sys[ph] = []
sys[ph].append(root)
phs = set(list(dt.keys()) + list(sys.keys()))
self.devs = { ph: Device(ph, dt.get(ph), sys.get(ph)) for ph in phs }
self.devs = { ph: [Device(ph, dt.get(ph), s if s is not None else "") for s in (sys.get(ph) or []) if ph is not None] for ph in phs }
self.base = Device(0, None, DTSystem.BASE)
self.infix = Device(0, None, DTSystem.INFIX)
@@ -56,7 +59,7 @@ class DTSystem:
elems = len(data) // struct.calcsize(">L")
return struct.unpack(">" + elems * "L", data)
def device_from_ph(self, ph):
def devices_from_ph(self, ph):
return self.devs.get(ph)
def into_vpd(self, dev):
@@ -79,12 +82,23 @@ class DTSystem:
"data": parse(),
}
def infix_usb_devices(self, out):
names=self.infix.str_array("usb-port-names")
phs=self.__get_phandle_array("usb-ports")
data=dict(zip(names,phs))
if data != {}:
out["usb-ports"] = []
for name,ph in data.items():
[out["usb-ports"].extend([{"name": name, "path": dev.attrpath("authorized")}, {"name": name, "path": dev.attrpath("authorized_default")}]) for dev in self.devices_from_ph(ph)]
def infix_devices(self, kind):
phs = self.__get_phandle_array(kind)
return [self.device_from_ph(ph) for ph in phs]
return [[d for d in self.devices_from_ph(ph)] for ph in phs]
def infix_vpds(self):
return [self.into_vpd(dev) for dev in self.infix_devices("vpds")]
flat_devices = [device for sublist in self.infix_devices("vpds") for device in sublist]
return [self.into_vpd(device) for device in flat_devices]
class QEMUSystem:
BASE = "/sys/firmware/qemu_fw_cfg"
@@ -109,6 +123,28 @@ class QEMUSystem:
def vpds(self):
return [self.product_vpd()]
def usb_ports(self):
ports = [
{
"name": "USB",
"path": "/sys/bus/usb/devices/usb1/authorized"
},
{
"name": "USB",
"path": "/sys/bus/usb/devices/usb1/authorized_default"
},
{
"name": "USB2",
"path": "/sys/bus/usb/devices/usb2/authorized"
},
{
"name": "USB2",
"path": "/sys/bus/usb/devices/usb2/authorized_default"
}]
return ports
class Device:
def __init__(self, ph, dtpath, syspath):
self.ph, self.dtpath, self.syspath = ph, dtpath, syspath
@@ -143,6 +179,9 @@ class Device:
val = self.attr(attr)
return val.decode("utf-8").strip("\0") if val else default
def str_array(self, attr, default=None):
val = self.attr(attr)
return val.decode("utf-8").strip("\0").split("\0") if val else default
def dtattrpath(self, attr):
return os.path.join(self.dtpath, attr)
@@ -199,8 +238,9 @@ def probe_qemusystem(out):
qsys = QEMUSystem()
vpds = qsys.vpds()
usb_ports = qsys.usb_ports()
vpd_inject(out, vpds)
out["usb-ports"] = usb_ports
for (attr, default) in (
("vendor", "QEMU"),
("product-name", "VM"),
@@ -221,7 +261,7 @@ def probe_qemusystem(out):
def probe_dtsystem(out):
dtsys = DTSystem()
vpds = dtsys.infix_vpds()
dtsys.infix_usb_devices(out)
model = dtsys.base.str("model")
if model:
out["product-name"] = model
+5 -5
View File
@@ -10,17 +10,15 @@ test-env = $(test-dir)/env \
test-env-qeneth = $(call test-env,-q $(test-dir)/virt/quad,$(1))
test-env-run = $(call test-env,-C -t $(BINARIES_DIR)/qemu.dot,$(1))
.PHONY: test-%
test-unit:
$(test-dir)/env $(test-dir)/9pm/9pm.py $(UNIT_TESTS)
test-qeneth:
test test-qeneth:
$(call test-env-qeneth,\
$(BR2_EXTERNAL_INFIX_PATH)/test/9pm/9pm.py \
$(INFIX_TESTS))
test-qeneth-sh:
$(call test-env-qeneth,/bin/sh)
test-sh test-qeneth-sh:
$(call test-env-qeneth,-i /bin/sh)
test-run: | ~/.infix-test-venv
$(call test-env-run,\
@@ -34,3 +32,5 @@ test-run-play: | ~/.infix-test-venv
~/.infix-test-venv:
$(test-dir)/docker/init-venv.sh $(test-dir)/docker/pip-requirements.txt
.PHONY: test-unit test test-sh test-qeneth test-qeneth-sh test-run test-run-sh test-run-play
+6 -12
View File
@@ -38,10 +38,8 @@ CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_NET=y
CONFIG_PACKET=y
CONFIG_UNIX=y
CONFIG_XDP_SOCKETS=y
CONFIG_XDP_SOCKETS_DIAG=y
CONFIG_INET=y
CONFIG_IP_MULTICAST=y
CONFIG_IP_ADVANCED_ROUTER=y
CONFIG_IP_MULTIPLE_TABLES=y
@@ -83,7 +81,6 @@ CONFIG_NFT_MASQ=m
CONFIG_NFT_REDIR=m
CONFIG_NFT_NAT=m
CONFIG_NFT_TUNNEL=m
CONFIG_NFT_OBJREF=m
CONFIG_NFT_QUEUE=m
CONFIG_NFT_REJECT=m
CONFIG_NFT_COMPAT=m
@@ -156,9 +153,7 @@ CONFIG_VLAN_8021Q=y
CONFIG_VLAN_8021Q_GVRP=y
CONFIG_VLAN_8021Q_MVRP=y
CONFIG_NET_SCHED=y
CONFIG_NET_SCH_CBQ=y
CONFIG_NET_CLS_BASIC=y
CONFIG_NET_CLS_TCINDEX=y
CONFIG_NET_CLS_BPF=y
CONFIG_NET_CLS_FLOWER=y
CONFIG_NETLINK_DIAG=y
@@ -224,11 +219,11 @@ CONFIG_WATCHDOG=y
CONFIG_WATCHDOG_SYSFS=y
CONFIG_SOFT_WATCHDOG=y
CONFIG_I6300ESB_WDT=y
CONFIG_USB=y
CONFIG_USB_XHCI_HCD=y
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_UHCI_HCD=y
CONFIG_USB_STORAGE=y
CONFIG_USB=m
CONFIG_USB_XHCI_HCD=m
CONFIG_USB_EHCI_HCD=m
CONFIG_USB_UHCI_HCD=m
CONFIG_USB_STORAGE=m
CONFIG_NEW_LEDS=y
CONFIG_LEDS_CLASS=y
CONFIG_LEDS_TRIGGERS=y
@@ -246,7 +241,6 @@ CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
CONFIG_EXT4_FS=y
CONFIG_BTRFS_FS=y
CONFIG_BTRFS_FS_POSIX_ACL=y
CONFIG_AUTOFS4_FS=y
CONFIG_FUSE_FS=y
CONFIG_VIRTIO_FS=y
CONFIG_OVERLAY_FS=y
@@ -260,8 +254,8 @@ CONFIG_SQUASHFS_ZSTD=y
CONFIG_9P_FS=y
CONFIG_NLS_CODEPAGE_437=y
CONFIG_NLS_ISO8859_1=y
CONFIG_CRYPTO_GCM=y
CONFIG_CRYPTO_AES=y
CONFIG_CRYPTO_GCM=y
CONFIG_DEBUG_KERNEL=y
CONFIG_MAGIC_SYSRQ=y
CONFIG_DEBUG_FS=y
+12 -5
View File
@@ -14,6 +14,7 @@ BR2_INIT_FINIT=y
BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y
BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs ${BR2_EXTERNAL_INFIX_PATH}/board/netconf/xattrs"
# BR2_TARGET_ENABLE_ROOT_LOGIN is not set
BR2_SYSTEM_BIN_SH_BASH=y
BR2_TARGET_GENERIC_GETTY_TERM="xterm"
BR2_SYSTEM_DHCP="eth0"
BR2_ENABLE_LOCALE_WHITELIST="C en_US en_CA"
@@ -33,7 +34,6 @@ BR2_LINUX_KERNEL_CUSTOM_DTS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/dt
BR2_LINUX_KERNEL_DTB_KEEP_DIRNAME=y
BR2_LINUX_KERNEL_INSTALL_TARGET=y
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y
BR2_PACKAGE_STRACE=y
BR2_PACKAGE_STRESS_NG=y
BR2_PACKAGE_JQ=y
@@ -53,7 +53,9 @@ BR2_PACKAGE_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_UBOOT_TOOLS_FIT_CHECK_SIGN=y
BR2_PACKAGE_UBOOT_TOOLS_MKENVIMAGE=y
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_CA_CERTIFICATES=y
BR2_PACKAGE_LIBSSH_OPENSSL=y
BR2_PACKAGE_LIBSSH2=y
BR2_PACKAGE_LIBSSH2_OPENSSL=y
BR2_PACKAGE_LIBOPENSSL_BIN=y
BR2_PACKAGE_LIBCURL_CURL=y
BR2_PACKAGE_NETOPEER2_CLI=y
@@ -70,7 +72,6 @@ BR2_PACKAGE_FPING=y
BR2_PACKAGE_FRR=y
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPTABLES=y
BR2_PACKAGE_IPTABLES_NFTABLES=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_LLDPD=y
@@ -88,10 +89,9 @@ BR2_PACKAGE_SOCAT=y
BR2_PACKAGE_TCPDUMP=y
BR2_PACKAGE_TRACEROUTE=y
BR2_PACKAGE_ULOGD=y
BR2_PACKAGE_BASH=y
BR2_PACKAGE_BASH_COMPLETION=y
BR2_PACKAGE_HTOP=y
BR2_PACKAGE_SUDO=y
BR2_PACKAGE_HTOP=y
BR2_PACKAGE_IRQBALANCE=y
BR2_PACKAGE_KMOD_TOOLS=y
BR2_PACKAGE_PWGEN=y
@@ -121,6 +121,8 @@ INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://github.com/kernelkit/infix/tree/main/doc"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_CONFD=y
BR2_PACKAGE_CURIOS_HTTPD=y
BR2_PACKAGE_EXECD=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
@@ -130,9 +132,14 @@ BR2_PACKAGE_FINIT_PLUGIN_RTC=y
BR2_PACKAGE_FINIT_PLUGIN_TTY=y
BR2_PACKAGE_FINIT_PLUGIN_URANDOM=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_K8S_LOGGER=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_PODMAN=y
BR2_PACKAGE_PODMAN_DRIVER_BTRFS=y
BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_QUERIERD=y
BR2_PACKAGE_LIBINPUT=y
+12
View File
@@ -15,6 +15,7 @@ BR2_INIT_FINIT=y
BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y
BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs ${BR2_EXTERNAL_INFIX_PATH}/board/netconf/xattrs"
# BR2_TARGET_ENABLE_ROOT_LOGIN is not set
BR2_SYSTEM_BIN_SH_BASH=y
BR2_TARGET_GENERIC_GETTY_TERM="xterm"
BR2_SYSTEM_DHCP="eth0"
BR2_ENABLE_LOCALE_WHITELIST="C en_US en_CA"
@@ -63,6 +64,9 @@ BR2_PACKAGE_UBOOT_TOOLS_FIT_CHECK_SIGN=y
BR2_PACKAGE_UBOOT_TOOLS_MKENVIMAGE=y
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_CA_CERTIFICATES=y
BR2_PACKAGE_LIBSSH_OPENSSL=y
BR2_PACKAGE_LIBSSH2=y
BR2_PACKAGE_LIBSSH2_OPENSSL=y
BR2_PACKAGE_LIBOPENSSL_BIN=y
BR2_PACKAGE_LIBCURL_CURL=y
BR2_PACKAGE_NETOPEER2_CLI=y
@@ -147,6 +151,7 @@ INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://github.com/kernelkit/infix/tree/main/doc"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_CONFD=y
BR2_PACKAGE_EXECD=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
@@ -156,9 +161,16 @@ BR2_PACKAGE_FINIT_PLUGIN_RTC=y
BR2_PACKAGE_FINIT_PLUGIN_TTY=y
BR2_PACKAGE_FINIT_PLUGIN_URANDOM=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_K8S_LOGGER=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_PODMAN=y
BR2_PACKAGE_PODMAN_DRIVER_BTRFS=y
BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_QUERIERD=y
BR2_PACKAGE_LIBINPUT=y
# SIGN_ENABLED is not set
# GNS3_APPLIANCE is not set
+12 -5
View File
@@ -13,6 +13,7 @@ BR2_INIT_FINIT=y
BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y
BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs ${BR2_EXTERNAL_INFIX_PATH}/board/netconf/xattrs"
# BR2_TARGET_ENABLE_ROOT_LOGIN is not set
BR2_SYSTEM_BIN_SH_BASH=y
BR2_TARGET_GENERIC_GETTY_TERM="xterm"
BR2_SYSTEM_DHCP="eth0"
BR2_ENABLE_LOCALE_WHITELIST="C en_US en_CA"
@@ -29,7 +30,6 @@ BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/lin
BR2_LINUX_KERNEL_INSTALL_TARGET=y
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y
BR2_PACKAGE_STRACE=y
BR2_PACKAGE_STRESS_NG=y
BR2_PACKAGE_JQ=y
@@ -48,7 +48,9 @@ BR2_PACKAGE_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_UBOOT_TOOLS_FIT_CHECK_SIGN=y
BR2_PACKAGE_UBOOT_TOOLS_MKENVIMAGE=y
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_CA_CERTIFICATES=y
BR2_PACKAGE_LIBSSH_OPENSSL=y
BR2_PACKAGE_LIBSSH2=y
BR2_PACKAGE_LIBSSH2_OPENSSL=y
BR2_PACKAGE_LIBOPENSSL_BIN=y
BR2_PACKAGE_LIBCURL_CURL=y
BR2_PACKAGE_NETOPEER2_CLI=y
@@ -65,7 +67,6 @@ BR2_PACKAGE_FPING=y
BR2_PACKAGE_FRR=y
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPTABLES=y
BR2_PACKAGE_IPTABLES_NFTABLES=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_LLDPD=y
@@ -83,10 +84,9 @@ BR2_PACKAGE_SOCAT=y
BR2_PACKAGE_TCPDUMP=y
BR2_PACKAGE_TRACEROUTE=y
BR2_PACKAGE_ULOGD=y
BR2_PACKAGE_BASH=y
BR2_PACKAGE_BASH_COMPLETION=y
BR2_PACKAGE_HTOP=y
BR2_PACKAGE_SUDO=y
BR2_PACKAGE_HTOP=y
BR2_PACKAGE_IRQBALANCE=y
BR2_PACKAGE_KMOD_TOOLS=y
BR2_PACKAGE_PWGEN=y
@@ -124,6 +124,8 @@ INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://github.com/kernelkit/infix/tree/main/doc"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_CONFD=y
BR2_PACKAGE_CURIOS_HTTPD=y
BR2_PACKAGE_EXECD=y
BR2_PACKAGE_STATD=y
BR2_PACKAGE_FACTORY=y
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
@@ -133,9 +135,14 @@ BR2_PACKAGE_FINIT_PLUGIN_RTC=y
BR2_PACKAGE_FINIT_PLUGIN_TTY=y
BR2_PACKAGE_FINIT_PLUGIN_URANDOM=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_K8S_LOGGER=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_PODMAN=y
BR2_PACKAGE_PODMAN_DRIVER_BTRFS=y
BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_QUERIERD=y
GNS3_APPLIANCE_RAM=512
+221 -1
View File
@@ -4,6 +4,224 @@ Change Log
All notable changes to the project are documented in this file.
[v24.02.0][] - 2024-03-01
-------------------------
> **Note:** the `root` account is disabled in official builds. Only the
> `admin` user can log in to the system. This can be changed, but only
> in developer builds: `make menuconfig` -> System configuration ->
> `[*]Enable root login with password`
### YANG Status
Infix devices support downloading all YANG models over NETCONF, including
models with submodules. As a rule, standard models are used as long as
they map to underlying Linux concepts and services. All exceptions are
listed in Infix specific models, detailing deviations and augmentations.
Currently supported models:
- [ieee802-ethernet-interface][]:
- Toggle port speed & duplex auto-negotiation on/off
- Set port speed and duplex when auto-negotiation is off
- Query port speed/duplex and auto-negotiation status (operational)
- **Frame counters:**
| **YANG** | **Linux / Ethtool** |
|-----------------------------|-----------------------------------|
| `out-frames` | `FramesTransmittedOK` |
| `out-multicast-frames` | `MulticastFramesXmittedOK` |
| `out-broadcast-frames` | `BroadcastFramesXmittedOK` |
| `in-total-octets` | `FramesReceivedOK` |
| | + `FrameCheckSequenceErrors` |
| | + `FramesLostDueToIntMACRcvError` |
| | + `AlignmentErrors` |
| | + `etherStatsOversizePkts` |
| | + `etherStatsJabbers` |
| `in-frames` | `FramesReceivedOK` |
| `in-multicast-frames` | `MulticastFramesReceivedOK` |
| `in-broadcast-frames` | `BroadcastFramesReceivedOK` |
| `in-error-undersize-frames` | `undersize_pkts` |
| `in-error-fcs-frames` | `FrameCheckSequenceErrors` |
| `in-good-octets` | `OctetsReceivedOK` |
| `out-good-octets` | `OctetsTransmittedOK` |
- [ietf-hardware][]:
- Populates standard hardware model from corresponding data in device EEPROMs
- **augments:**
- Initial support for USB ports
- Vital Product Data (VPD) from device EEPROMs ([ONIE][] structure)
- [infix-hardware][]: Deviations and augments
- [ietf-system][]:
- **augments:**
- Message of the Day (MotD) banner, shown after SSH or console login.
Please note: the legacy `motd` has been replaced with `motd-banner` os
of v24.02. Use CLI `text-editor` to modify the latter
- User login shell, default: `/bin/false` (no SSH or console login)
- State information for remotely querying firmware version information
- **deviations:**
- timezone-name, using IANA timezones instead of plain string
- UTC offset, only support per-hour offsets with [tzdata][]
- Usernames, clarifying Linux restrictions
- Unsupported features marked as deviations, e.g. RADIUS
- [infix-system-software][]: firmware upgrade with `install-bundle` RPC
- [ietf-interfaces][]:
- deviation to allow read-write `if:phys-address` for custom MAC address
- [ietf-ip][]: augments
- IPv4LL similar to standardized IPv6LL
- [ietf-ip][]: deviations (`not-supported`) added for IPv4 and IPv6:
- `/if:interfaces/if:interface/ip:ipv4/ip:address/ip:subnet/ip:netmask`
- `/if:interfaces/if:interface/ip:ipv6/ip:address/ip:status`
- `/if:interfaces/if:interface/ip:ipv4/ip:neighbor`
- `/if:interfaces/if:interface/ip:ipv6/ip:neighbor`
- [ietf-routing][]: Base model for routing
- [ietf-ipv4-unicast-routing][]: Static unicast routing, incl. operational
data, i.e., setting static IPv4 routes and reading IPv4 routing table
- [ietf-ipv6-unicast-routing][]: Static unicast routing, incl. operational
data, i.e., setting static IPv6 routes and reading IPv6 routing table
- [ietf-ospf][]: Limited support for OSPFv2, with additional support for
injecting default route, and route redistribution. Underlying routing
engine in use is Frr. Includes operational status + data (routes).
See [infix-routing][] model for detailed list of deviations
- [infix-ethernet-interface][]: deviations for ieee802-ethernet-interface
- [infix-routing][]: Limit ietf-routing to one instance `default` per
routing protocol, also details unsupported features (deviations) to both
ietf-routing and ietf-ospf models, as well as augments made to support
injecting default route in OSPFv2
- [infix-if-bridge][]: Linux bridge interfaces with native VLAN support
- [infix-if-type][]: deviation for interface types, limiting number to
supported types only. New identities are derived from default IANA
interface types, ensuring compatibility with other standard models, e.g.,
`ieee802-ethernet-interface.yang`
- [infix-if-veth][]: Linux VETH pairs
- [infix-if-vlan][]: Linux VLAN interfaces, e.g. `eth0.10`
- [infix-containers][]: Support for Docker containers, incl. operational data
to query status and remotely stop/start containers
- [infix-dhcp-client][]: DHCPv4 client, including supported options
- **Configurable services:**
- [ieee802-dot1ab-lldp][]: stripped down to an `enabled` setting
- [infix-services][]: support for enabling mDNS service/device discovery
[tzdata]: https://www.iana.org/time-zones
[ietf-system]: https://www.rfc-editor.org/rfc/rfc7317.html
[ietf-interfaces]: https://www.rfc-editor.org/rfc/rfc7223.html
[ietf-ip]: https://www.rfc-editor.org/rfc/rfc8344.html
[ietf-if-vlan-encapsulation]: https://www.ietf.org/id/draft-ietf-netmod-sub-intf-vlan-model-08.html
[ietf-routing]: https://www.rfc-editor.org/rfc/rfc8349
[ietf-ipv4-unicast-routing]: https://www.rfc-editor.org/rfc/rfc8349#page-29
[ietf-ipv6-unicast-routing]: https://www.rfc-editor.org/rfc/rfc8349#page-37
[ietf-hardware]: https://www.rfc-editor.org/rfc/rfc8348
[ietf-ospf]: https://www.rfc-editor.org/rfc/rfc9129
[ieee802-dot1ab-lldp]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/ieee802-dot1ab-lldp%402022-03-15.yang
[ieee802-ethernet-interface]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/ieee802-ethernet-interface%402019-06-21.yang
[infix-ethernet-interface]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/infix-ethernet-interface%402024-02-27.yang
[infix-containers]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/infix-containers%402024-02-01.yang
[infix-dhcp-client]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/infix-dhcp-client%402024-01-30.yang
[infix-hardware]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/infix-hardware%402024-01-18.yang
[infix-if-bridge]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/infix-if-bridge%402024-02-19.yang
[infix-if-type]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/infix-if-type%402024-01-29.yang
[infix-if-veth]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/infix-if-veth%402023-06-05.yang
[infix-if-vlan]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/infix-if-vlan%402023-10-25.yang
[infix-ip]: https://github.com/kernelkit/infix/tree/f0c23ca/src/confd/yang/infix-ip%402023-09-14.yang
[infix-routing]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/infix-routing%402024-01-09.yang
[infix-services]: https://github.com/kernelkit/infix/blob/f0c23ca/src/confd/yang/infix-services%402023-10-16.yang
[infix-system-software]: https://github.com/kernelkit/infix/tree/f0c23ca/src/confd/yang/infix-system-software%402023-06-27.yang
[ONIE]: https://opencomputeproject.github.io/onie/design-spec/hw_requirements.html
### Changes
- New hardware support: NanoPi R2S from FriendlyELEC, a simple two-port router
- Static routing support, now also for IPv6
- Dynamic routing support with OSPFv2, limited (see `infix-routing.yang` for
deviations), but still usable in most relevant use-cases. If you are using
this and are interested in more features, please let us know!
- Multiple area support, including different area types
- Route redistribution
- Default route injection
- Full integration with Bidirectional Forward Detection (BFD)
- Operational status, including but not limited to:
* OSPF Router ID
* Neighbor status
* OSPF routing table
* Interface type, incl. passive status
- For more information, see `doc/networking.md`
- Support for disabling USB ports in `startup-config` (no auto-mount yet!)
- Initial support for Docker containers, see documentation for details:
- Custom Infix model, see `infix-containers.yang` for details
- Add image URL/location and volumes/mounts/interfaces to configuration,
the system ensures the image is downloaded and container created in the
background before launching it. If now networking is available the job
is queued and retried every time a new network route is learned
- Status and actions (stop/start/restart) available in operational datastore
- Possible to move physical switch ports inside container, see docs
- Possible to bundle OCI archives in Infix image, as well as storing any
file content in `factory-config` to override container image defaults
- IEEE Ethernet interface:
- Support for setting port speed/duplex or auto-negotiating
- New per-port counters, augments to IEEE model added in infix-ethernet.yang:
`in-good-octets`, `out-good-octets`
- Many updates to DHCPv4 client YANG model:
- new options, see `infix-dhcp-client.yang` for details:
- Default options: subnet, router, dns+domain, hostname, broadcast, ntpsrv
- Set NTP servers, require NTP client in ietf-system to be enabled, will
be treated as non-preferred sources, configured `prefer` servers wins
- Learn DNS servers, statically configured servers always takes precedence
- Install routes, not only from option 3, but also options 121 and 249
- Support for ARP:ing for client lease (default enabled)
- Configurable route metrics, by default metric 100 to allow static routes
to win over DHCP routes, useful for backup DHCP connections
- IETF Hardware data: added YANG model for vital product data representation,
and augments for initial USB support (enable/disable)
- IETF System:
- the `motd` augment in `infix-system.yang` for *Message of the Day* has
been marked as obsolete and replaced with `motd-banner`. The new setting
is of type *binary* and allows control codes and multi-line content to be
stored. The legacy `motd` will remain for the foreseeable future and
takes precedence over the new `motd-banner` setting
- new `text-editor` augment in `infix-system.yang` to select the backend for
the new `text-editor` command: `emacs`, `nano`, or `vi`
- Many updates to the test system, *Infamy*, incl. new Quick Start Guide in
updated `doc/testing.md` to help new developers get started
- Add `htop` to default builds, useful for observing and attaching (strace)
- Change the default shell of the `admin` user from `clish` to `bash`. Change
required for factory production and provisioning reasons. Only affects the
built-in default, customer specific `factory-config`'s are not affected!
- CLI: the `set` command on a boolean can now be used without an argument,
`set boolean` sets the boolean option to true
- CLI: new command `change`, for use with ietf-system user passwords, starts
an interactive password dialog, including confirmation entry. The resulting
password is by default salted and hashed using sha512crypt
- CLI: new command `text-editor`, for use with binary fields, e.g., `content`
for file mounts in containers, or the new `motd-banner`:
admin@infix-c0-ff-ee:/config/system/> text-editor motd-banner
... exit with Ctrl-x Ctrl-c ...
admin@infix-c0-ff-ee:/config/system/> show
motd-banner VGhpcyByZWxlYXNlIHdhcyBzcG9uc29yZWQgYnkgQWRkaXZhIEVsZWt0cm9uaWsK;
- CLI: new admin-exec command `show ntp [sources]`
- CLI: new admin-exec command `show dns` to display DNS client status
- CLI: new admin-exec command `show ospf [subcommand]`
- CLI: new admin-exec command `show container [subcommand]`
- CLI: new admin-exec command `show hardware` only USB port status for now
- CLI: updates to the `show interfaces` command to better list bridge VLANs
### Fixes
- Fix #177: ensure bridge is not bridge port to itself
- Fix #259: failure to `copy factory-config startup-config` in CLI
- Fix #278: allow DHCP client to set system hostname (be careful)
- Fix #283: hostname in DHCP request adds quotation marks
- Fix #294: drop stray `v` from version suffix in release artifacts
- Fix #298: drop privileges properly before launching user `shell` in CLI
- Fix #312: race condition in `ipv4_autoconf.py`, causes test to block forever
- Backport upstream fix to netopeer2-server for fetching YANG models that
refer to submodules over NETCONF
- CLI: drop developer debug in `set` command
- Fix out-of-place `[OK]` messages at shutdown/reboot
- Fix garbled syslog messages due to unicode in Infix tagline, drop unicode
[v23.11.0][] - 2023-11-30
-------------------------
@@ -478,7 +696,9 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
- N/A
[buildroot]: https://buildroot.org/
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v23.10.0...HEAD
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v24.02.0...HEAD
[v24.02.0]: https://github.com/kernelkit/infix/compare/v23.11.0...v24.02.0
[v23.11.0]: https://github.com/kernelkit/infix/compare/v23.10.0...v23.11.0
[v23.10.0]: https://github.com/kernelkit/infix/compare/v23.09.0...v23.10.0
[v23.09.0]: https://github.com/kernelkit/infix/compare/v23.08.0...v23.09.0
[v23.08.0]: https://github.com/kernelkit/infix/compare/v23.06.0...v23.08.0
+2
View File
@@ -44,6 +44,8 @@ In *configure context* the following commands are available:
| `set foo bar val` | Set `bar` leaf node in `foo` subcontext to `val` |
| `no foo bar` | Clear/delete configuration made to `bar` in `foo` |
| `edit foo baz` | Enter `baz` sub-sub-context in `foo` subcontext |
| `change password` | Start password dialogue to change a user's password |
| `text-editor foo` | Open a text editor to edit binary setting `foo` |
| `abort` | Abort changes in configuration, return to admin-exec |
| `exit` | Exit one level sub-context, or abort from top-level |
| `leave` | Save changes to `running-config`, return to admin-exec |
+4
View File
@@ -21,5 +21,9 @@ keybindings are really useful to learn!
| `help introduction` | An introduction to the CLI |
| `help keybindings` | Lists keybindings and other helpful tricks |
| `help tutorial` | The CLI User Guide |
| `help text-editor` | Help with the built-in text-editor command |
> 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`
+70
View File
@@ -0,0 +1,70 @@
# Text Editor
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.
## Escape Meta/Alt Control Shift
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
```
Key combinations with a `-` (dash) mean holding down the modifier key.
Combinations without a `-` (dash) mean without any modifier key.
### Quick help `C-h q`
- 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
C-x C-c exit C-x b switch C-x 0 only other C-space mark C-_ undo
C-x C-f find C-x k close C-x 1 only this C-w kill-rg C-s search
C-x C-s save C-x C-b list C-x 2 split two C-k kill-ln C-r r-search
C-x s save-all C-x h mark C-x ^ enlarge C-y yank M-% replace
C-x i insert C-x g goto-ln C-x o other win C-x C-x swap M-q reformat
```
### Save & Exit `C-x C-c`
- Hold down the Ctrl key
- tap `X`
- tap `c`
- release `Ctrl`
> The status field at the bottom asks if you are really sure, and/or if
> you want to add a final Enter/newline to the file. For binary content
> that final newline may be important.
## Changing the Editor
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
admin@host:/config/> edit system
admin@host:/config/system/> set text-editor <TAB>
emacs nano vi
admin@host:/config/system/> set text-editor nano
admin@host:/config/system/> leave
admin@example:/>
```
+60 -28
View File
@@ -1,12 +1,11 @@
User Guide
==========
The command line interface (CLI, see-ell-aye) is built on the open source
component [klish][1], which implements a CISCO-like, or Juniper Networks
JunOS-like, CLI for UNIX systems.
The command line interface (CLI, see-ell-aye) implements a CISCO-like,
or Juniper Networks JunOS-like, CLI for UNIX systems.
New users always get the CLI as the default "shell" when logging in, but
the default `admin` user logs in to the Bash. To access the CLI, type:
the default `admin` user logs in to `bash`. To access the CLI, type:
cli
@@ -104,6 +103,7 @@ the version we are currently running, intact in case of trouble.
both partitions you must reboot to the new version (to verify it works)
and then repeat the same command.
Configure Context
-----------------
@@ -113,21 +113,24 @@ followed by Enter. Available commands, press `?` at the prompt:
```
admin@host:/> configure
admin@host:/config/>
abort Abandon candidate
check Validate candidate
commit Commit current candidate to running-config
delete Delete configuration setting(s)
diff Summarize uncommitted changes
do Execute operational mode command
edit Descend to the specified configuration node
exit Ascend to the parent configuration node, or abort (from top)
leave Finalize candidate and apply to running-config
no Alias for delete
rollback Restore candidate to running-config
set Set configuration setting
show Show configuration
top Ascend to the configuration root
up Ascend to the parent configuration node
abort Abandon candidate
change Interactively change setting, e.g. password
check Validate candidate
commit Commit current candidate to running-config
delete Delete configuration setting(s)
diff Summarize uncommitted changes
do Execute operational mode command
edit Descend to the specified configuration node
end Alias to up, leave this subsection/node
exit Ascend to the parent configuration node, or abort (from top)
leave Finalize candidate and apply to running-config
no Alias for delete
rollback Restore candidate to running-config
set Set configuration setting
show Show configuration
text-editor Modify binary content in a text editor
top Ascend to the configuration root
up Ascend to the parent configuration node
```
The `edit` command lets you change to a sub-configure context, e.g.:
@@ -235,23 +238,52 @@ is committed.
User management, including passwords, is also a part of `ietf-system`.
```
admin@host:/config/> edit system authentication
admin@host:/config/system/authentication/> do password encrypt
Password: ******
$1$oVHGR0AP$6Pad1Pm8jPwPsan5WHULS1
admin@host:/config/system/authentication/> set user admin password $1$oVHGR0AP$6Pad1Pm8jPwPsan5WHULS1
admin@host:/config/system/> leave
admin@host:/config/> edit system authentication user admin
admin@host:/config/system/authentication/user/admin/> change password
New password:
Retype password:
admin@host:/config/system/authentication/user/admin/> leave
```
The call to `do password encrypt` brings up the helpful admin-exec
command to hash, and optionally salt, your password. This encrypted
string is what goes into the system configuration.
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`.
### SSH Authorized Key
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
admin@host:/config/system/authentication/user/admin/> edit authorized-key example@host
admin@host:/config/system/authentication/user/admin/authorized-key/example@host/> set algorithm ssh-rsa
admin@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=
admin@host:/config/system/authentication/user/admin/authorized-key/example@host/> show
algorithm ssh-rsa;
key-data AAAAB3NzaC1yc2EAAAADAQABAAABgQC8iBL42yeMBioFay7lty1C4ZDTHcHyo739gc91rTTH8SKvAE4g8Rr97KOz/8PFtOObBrE9G21K7d6UBuPqmd0RUF2CkXXN/eN2PBSHJ50YprRFt/z/304bsBYkDdflKlPDjuSmZ/+OMp4pTsq0R0eNFlX9wcwxEzooIb7VPEdvWE7AYoBRUdf41u3KBHuvjGd1M6QYJtbFLQMMTiVe5IUfyVSZ1RCxEyAB9fR9CBhtVheTVsY3iG0fZc9eCEo89ErDgtGUTJK4Hxt5yCNwI88YaVmkE85cNtw8YwubWQL3/tGZHfbbQ0fynfB4kWNloyRHFr7E1kDxuX5+pbv26EqRdcOVGucNn7hnGU6C1+ejLWdBD7vgsoilFrEaBWF41elJEPKDzpszEijQ9gTrrWeYOQ+x++lvmOdssDu4KvGmj2K/MQTL2jJYrMJ7GDzsUu3XikChRL7zNfS2jYYQLzovboUCgqfPUsVba9hqeX3U67GsJo+hy5MG9RSry4+ucHs=;
admin@host:/config/system/authentication/user/admin/authorized-key/example@host/> leave
```
> **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.
### Creating a VETH Pair
The following example creates a `veth0a <--> veth0b` virtual Ethernet
+374 -254
View File
@@ -2,302 +2,422 @@ Containers in Infix
===================
* [Introduction](#introduction)
* [Docker Containers with Podman](#docker-containers-with-podman)
* [Multiple Networks](#multiple-networks)
* [Hybrid Mode](#hybrid-mode)
* [Enabling Container Support](#enabling-container-support)
* [Debugging Containers](#debugging-containers)
* [Caution](#caution)
* [Getting Started](#getting-started)
* [Examples](#examples)
* [Networking and Containers](#networking-and-containers)
* [Container Bridge](#container-bridge)
* [Container Host Interface](#container-host-interface)
* [Host Networking](#host-networking)
* [Mounts and Volumes](#mounts-and-volumes)
* [Content Mounts](#content-mounts)
* [Example Containers](#example-containers)
* [System Container](#system-container)
* [Application Container: nftables](#application-container--nftables)
* [Application Container: ntpd](#application-container--ntpd)
* [Upgrading a Container Image](#upgradeing-a-container-image)
Introduction
------------
Default builds of Infix do not enable any container support. See below
section, [Enabling Container Support](#enabling-container-support), for
details on how to enable it using Podman.
Infix comes with native support for Docker containers using [podman][].
The [YANG model][1] describes the current level of support, complete
enough to run both system and application containers.
Networking in containers is provided by both Infix and the Container
Network Interface ([CNI](https://www.cni.dev/)) that Podman supports.
Key design features, 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.
> A convenience alias `docker=podman` is available in Infix, remember,
> not all features or syntax of docker is available in podman.
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.
> **Note:** 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 containers in the system.
Docker Containers with Podman
-----------------------------
Caution
-------
We assume you've booted into Infix and start with a familiar example.
This downloads the `hello-world` example container image and runs it:
A word of warning, containers can run on your system in privileged mode,
as `root`. This gives them full access to devices on your system. But
even when though unprivileged containers are fenced from the host with
Linux namespaces, and resource limited using Linux cgroups, which scope
container applications from seeing and accessing the complete system,
there is no guarantee that an application cannot ever break out of this
confinement.
podman run -it --rm docker://hello-world
- 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/attack surface
- An expert with knowledge how to build exploits will be able to
jailbreak/elevate to root even if best practices are followed
We can also set up a port forward to a little web server:
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.
podman run -d --rm -p 80:80 docker://nginx:alpine
In detached (`-d`) state you can check the status using `podman ps` and
try to connect to the web server:
Getting Started
---------------
curl http://localhost
In the CLI, containers can be run in one of two ways:
1. `container run IMAGE [COMMAND]`, and
2. enter `configure` context, then `edit container NAME`
The first is useful mostly for testing, or running single commands in an
image. It is a wrapper for `podman run -it --rm ...`, while the latter
is a wrapper and adaptation of `podman create ...`.
The second create a container with a semi-persistent writable layer that
survives container restarts and host system restarts. However, if you
change the container configuration or upgrade the image (see below), the
container will be recreated and the writable layer is lost. This is why
it is recommended to set up a named volume for directories, or use file
[Content Mounts][], in your container you want truly persistent content.
In fact, in many cases the best way is to create a `read-only` container
and use file mounts and volumes only for the critical parts. Podman
ensures (using tmpfs) `read-only` containers still 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 needs depend on the container image and
application to run.
> **Note:** 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.
### Examples
Classic Hello World:
admin@example-c0-ff-ee:/> container run docker://hello-world
Persistent web server using nginx, sharing the host's network:
admin@example-c0-ff-ee:/> configure
admin@example-c0-ff-ee:/config> edit container web
admin@example-c0-ff-ee:/config/container/web> set image docker://nginx:alpine
admin@example-c0-ff-ee:/config/container/web> set publish 80:80
admin@example-c0-ff-ee:/config/container/web> set network host
admin@example-c0-ff-ee:/config/container/web> leave
admin@example-c0-ff-ee:/> show container
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-c0-ff-ee:~$ curl http://localhost
or connect to port 80 of your running Infix system with a browser. See
the following sections for how to add more interfaces and start/stop the
container at boot/reboot.
> To add your own content to web server, place the HTML files in, e.g.,
> `/cfg/www/*.html` and add `-v /cfg/www:/usr/share/nginx/html:ro` to
> the command line (above). <https://hub.docker.com/_/nginx/>
the following sections for how to add more interfaces and manage your
container at runtime.
### Multiple Networks
Networking and Containers
-------------------------
It is also possible to start a container with multiple networks. The
approach shown here uses CNI profiles, which means the interfaces names
inside the container will always be: `eth0`, `eth1`, etc.
By default, unlike other systems, persistent[^1] containers have no
networking enabled. All network access has to be set up explicitly.
Currently two types of of container networks are supported:
A common setup is to use a VETH pair, with one end in the container and
the other end routed, or bridged, to the rest of the world. The Infix
[CLI Guide](cli/introduction.md) provides examples of both. In either
case you need to create a matching CNI profile for one end of the VETH
pair before starting the container, here we use two network profiles,
the default podman bridge and the VETH profile:
- `cni-host`: one end of a VETH pair, or a physical Ethernet port
- `cni-bridge`: an IP masquerading bridge
cni create host net1 veth0a 192.168.0.42/24
podman run -d --rm --net=podman,net1 --entrypoint "/linuxrc" \
--privileged docker://troglobit/buildroot:latest
The first profile (`podman`) is a the default bridged profile. When a
container is started with that (default behavior), podman dynamically
creates a VETH pair which has one end attached as a bridge port in the
`cni-podman0` bridge managed by podman, and the other end is brought up
as `eth0` inside the container.
### CNI Bridge
The second profile is the one we created, it uses the `host-device`
profile and does not create anything, it simply lifts the peer end of
the pair into the container as `eth1`. This CNI profile can also be
used to hand over control of physical ports to a container.
All interface configuration is done in configure context. Let's start
by creating an IP masquerading bridge, a common default for containers:
> **Note:** here we start the container in `--privileged` mode. This
> allows the container guest unfiltered access to the host system and it
> might not be what you want for a production system. For that at least
> SECCOMP is recommended, which is out of scope for this tutorial.
admin@example-c0-ff-ee:/> configure
admin@example-c0-ff-ee:/config> edit interface docker0
admin@example-c0-ff-ee:/config/interface/docker0/> set type bridge
admin@example-c0-ff-ee:/config/interface/docker0/> set container-network type cni-bridge
admin@example-c0-ff-ee:/config/interface/docker0/> leave
### Real Example
We have to declare the interface type, and then also declare it as a
container network, ensuring the interface cannot be used by the system
for any other purpose. E.g., a `cni-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.
To be able to preserve state in containers between reboots we need a
writable layer, this is done with `podman create`, after which we can
use `podman start`. We build on the previous example:
The default subnet for a `cni-bridge` is 172.17.0.0/16, the bridge will
take 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.
cni create host net1 veth0a 192.168.0.42/24
podman create --name system --conmon-pidfile=/run/pod:system.pid \
--restart=no --systemd=false --tz=local --privileged \
--net=podman,net1 --entrypoint "/linuxrc" -p 222:22 \
docker://troglobit/buildroot:latest
Some of the defaults of a `cni-bridge` can be changed, e.g., instead of
`set container-network type cni-bridge`, above, do:
Here we map the host port 222 to the SSH port of the container, but one
can just as easily map the host's port 22 (SSH). Just make sure to
first disable the host's SSH service.
admin@example-c0-ff-ee:/config/interface/docker0/> edit container-network
admin@example-c0-ff-ee:/config/interface/docker0/container-network/> set type cni-bridge
admin@example-c0-ff-ee:/config/interface/docker0/container-network/> edit subnet 192.168.0.0/16
admin@example-c0-ff-ee:/config/interface/docker0/container-network/subnet/192.168.0.0/16/> set gateway 192.168.255.254
admin@example-c0-ff-ee:/config/interface/docker0/container-network/subnet/192.168.0.0/16/> end
admin@example-c0-ff-ee:/config/interface/docker0/container-network/> edit route 10.0.10.0/24
admin@example-c0-ff-ee:/config/interface/docker0/container-network/route/10.0.10.0/24/> set gateway 192.168.10.254
admin@example-c0-ff-ee:/config/interface/docker0/container-network/route/10.0.10.0/24/> end
admin@example-c0-ff-ee:/config/interface/docker0/container-network/> end
admin@example-c0-ff-ee:/config/interface/docker0/> leave
> **Note:** the new options used here are required for enabling
> monitoring and automate start/stop of containers at boot/reboot.
Other network settings, like DNS and domain, use built-in defaults in
CNI, 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 `cni-bridge`. Below an example of a system container calls
`set network interface docker0`, here we show how to set options for
that network:
This creates the named container `system` which we can now start:
podman start system
and stop:
podman stop system
For this particular image[^1] we need to modify its defaults a bit,
because it is set up to run a DHCP client on the first Ethernet
interface, which in our case is the `podman` default CNI bridge. In
fact, we don't want the container to set up any networking since that is
handled by Infix and podman.
root@infix-12-34-56:~$ podman start system
root@infix-12-34-56:~$ podman exec -it system sh
/ # rm -rf /etc/network/interfaces
/ # exit
root@infix-12-34-56:~$ podman stop system
The change is now saved in the writable layer and the next time the
container is started it will look like this:
root@infix-12-34-56:~$ podman start system
root@infix-12-34-56:~$ podman exec -it system sh
/ # ifconfig
eth0 Link encap:Ethernet HWaddr A2:32:4C:2B:5E:51
inet addr:10.88.0.11 Bcast:10.88.255.255 Mask:255.255.0.0
inet6 addr: fe80::a032:4cff:fe2b:5e51/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:204 errors:0 dropped:107 overruns:0 frame:0
TX packets:15 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:48240 (47.1 KiB) TX bytes:1102 (1.0 KiB)
admin@example-c0-ff-ee:/config/container/ntpd/> edit network docker0
admin@example-c0-ff-ee:/config/container/ntpd/network/docker0/>
admin@example-c0-ff-ee:/config/container/ntpd/network/docker0/> set option
<string> Options for CNI bridges.
admin@example-c0-ff-ee:/config/container/ntpd/network/docker0/> help option
NAME
option <string>
eth1 Link encap:Ethernet HWaddr 56:B5:4E:D1:9C:E5
inet addr:192.168.0.42 Bcast:192.168.0.255 Mask:255.255.255.0
inet6 addr: fe80::54b5:4eff:fed1:9ce5/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:274 errors:0 dropped:213 overruns:0 frame:0
TX packets:16 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:60926 (59.4 KiB) TX bytes:1377 (1.3 KiB)
Automating start/stop at boot/reboot is documented in the next section.
[^1]: A common task one has to do for many other standard images, e.g.,
Alpine Linux and BusyBox available on Docker Hub.
### Hybrid Mode
Since container setup and configuration is not modeled in YANG yet, we
use the Infix *Hybrid mode*, described in [Infix Variants](variant.md).
To start containers in *Hybrid Mode*, provided the images have been
downloaded with `podman pull docker://troglobit/buildroot:latest` and
a container created (above):
```
root@infix:/cfg/start.d$ cat <<HERE >20-enable-container.sh
#!/bin/sh
# Remember to create the veth0a <--> vet0b pair in the CLI first!
cni create host net1 veth0a 192.168.0.42/24
cat <<EOF > /etc/finit.d/available/pod:system.conf
service name:pod :system pid:!/run/pod:system.pid podman --syslog start system -- System container
EOF
initctl enable pod:system
exit 0
HERE
root@infix:/cfg/start.d$ chmod +x 20-enable-container.sh
```
Reboot to activate the changes. To activate the changes without
rebooting, run the script and call `initctl reload`.
> **Note:** the `/etc` directory is a `tmpfs` ramdisk and contents will
> be lost on reboot, so to retain custom CNI profiles after reboot you
> need to either save them and restore in the script above, or recreate
> them on every boot.
DESCRIPTION
Options for CNI bridges.
Example: ip=1.2.3.4 to request a specific IP, both IPv4 and IPv6.
interface_name=foo0 name to set interface name inside container.
admin@example-c0-ff-ee:/config/container/ntpd/network/docker0/> set option ip=172.17.0.2
admin@example-c0-ff-ee:/config/container/ntpd/network/docker0/> set option interface_name=wan
admin@example-c0-ff-ee:/config/container/ntpd/network/docker0/> leave
Enabling Container Support
--------------------------
### CNI Host
Container support is not enabled by default because it is not a common
customer feature, it also prolongs build times a lot due to bringing in
a build-time dependency on Go.
Another common use-case is to move a network interface into the network
namespace of a container. Which the CNI bridge network does behind the
scenes with one end of the automatically created VETH pair. This works
with regular Ethernet interfaces as well, but here we will use a VETH
pair as an example along with a regular bridge (where other Ethernet
interfaces may live as well).
However, customer specific builds may have it, and you can also roll
your own based on any of the available `defconfigs`. For example:
admin@example-c0-ff-ee:/config/> edit interface veth0
admin@example-c0-ff-ee:/config/interface/veth0/> set veth peer ntpd
admin@example-c0-ff-ee:/config/interface/veth0/> set ipv4 address 192.168.0.1 prefix-length 24
admin@example-c0-ff-ee:/config/interface/veth0/> end
admin@example-c0-ff-ee:/config/> edit interface ntpd
admin@example-c0-ff-ee:/config/interface/ntpd/> set ipv4 address 192.168.0.2 prefix-length 24
admin@example-c0-ff-ee:/config/interface/ntpd/> set container-network
cd infix/
make x86_64_defconfig
Run menuconfig, search for `podman` using `/`, enable it and build:
make menuconfig
...
make
Enabling [podman][] select `crun`, `conmon`, and all other dependencies.
The build will take a while, but eventually you can:
make run
This is a routed setup, where we reserve 192.168.0.0/24 for the network
between the host and the `ntpd` container. A perhaps more common case
is to put `veth0` as a port in a bridge with other physical ports. 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 CNI Bridge setup.
Debugging Containers
--------------------
### Host Networking
If the host system is not powered down or rebooted properly, containers
may not start up as they should on the following boot. Below is a very
common problem and solution shown.
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.
```
root@infix-12-34-56:~$ podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
root@infix-12-34-56:~$ grep nginx /var/log/syslog
Jun 25 10:15:48 infix-12-34-56 finit[1]: Service pod:nginx[2376] died, restarting in 5000 msec (10/10)
Jun 25 10:15:48 infix-12-34-56 finit[1]: Starting pod:nginx[2408]
Jun 25 10:15:53 infix-12-34-56 finit[1]: Service pod:nginx keeps crashing, not restarting.
```
The host networking setup cannot be combined with any other network.
If this the system is isolated from remote network access, start by
verifying the image is downloaded:
```
root@infix-12-34-56:/cfg/start.d$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/library/nginx alpine 4937520ae206 10 days ago 43.2 MB
docker.io/troglobit/buildroot latest 68faf6b20f1a 6 weeks ago 41.4 MB
```
OK, let's see what the `podman-service` step (above) created:
```
root@infix-12-34-56:/cfg/start.d$ initctl show pod-nginx.conf
service name:pod :nginx podman run --name nginx --rm -p 80:80 nginx:alpine -- Nginx container
```
Try starting the container manually. Remember to add the `-d` flag to
emulate detached/background operation:
```
root@infix-12-34-56:/cfg/start.d$ podman run --name nginx --rm -d -p 8080:80 nginx:alpine
Error: creating container storage: the container name "nginx" is already in use by 9c73bd8d505b1585d241595bfadede361b87f6c1be9a5656253b5a4d73da57e0. You have to remove that container to be able to reuse that name: that name is already in use
```
Aha, a lingering image with the same name! Where is it?
```
root@infix-12-34-56:/cfg/start.d$ podman ps --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f3386ae9517f docker.io/troglobit/buildroot:latest About an hour ago Exited (0) About an hour ago ecstatic_panini
bf0c6178ea26 docker.io/troglobit/buildroot:latest About an hour ago Exited (0) About an hour ago determined_brown
385155f479c0 docker.io/troglobit/buildroot:latest About an hour ago Exited (0) About an hour ago vibrant_engelbart
99a1b3319d9e docker.io/troglobit/buildroot:latest About an hour ago Exited (0) About an hour ago dreamy_tesla
9c73bd8d505b docker.io/library/nginx:alpine nginx -g daemon o... 11 minutes ago Created 0.0.0.0:80->80/tcp nginx
8a5290504ebc docker.io/troglobit/buildroot:latest 10 minutes ago Created mystifying_liskov
```
Oh, we have two lingering containers that were created but did not stop
correctly. Let's remove them:
```
root@infix-12-34-56:/cfg/start.d$ docker rm -f 9c73bd8d505b
9c73bd8d505b
root@infix-12-34-56:/cfg/start.d$ docker rm -f 8a5290504ebc
8a5290504ebc
```
Now we can manually restart the (supervised) container:
```
root@infix-12-34-56:/cfg/start.d$ initctl restart pod:nginx
root@infix-12-34-56:/cfg/start.d$ initctl status pod:nginx
Status : running
Identity : pod:nginx
Description : Nginx container
Origin : /etc/finit.d/enabled/pod-nginx.conf
Command : podman run --name nginx --rm -p 80:80 nginx:alpine
PID file : none
PID : 2669
User : root
Group : root
Uptime : 15 sec
Restarts : 11 (0/10)
Runlevels : [---234-----]
Memory : 63.8M
CGroup : /system/pod-nginx cpu 0 [100, max] mem [0, max]
├─ 2669 podman run --name nginx --rm -p 80:80 nginx:alpine
└─ 2816 conmon --api-version 1 -c 44d24aa7e98b67ff811596984462b902af3b09a04b4f9bef86e11d246b8cc2ff -u 44d24aa7e98b67ff8
Jun 25 10:15:48 infix-12-34-56 finit[1]: Service pod:nginx[2376] died, restarting in 5000 msec (10/10)
Jun 25 10:15:48 infix-12-34-56 finit[1]: Starting pod:nginx[2408]
Jun 25 10:15:53 infix-12-34-56 finit[1]: Service pod:nginx keeps crashing, not restarting.
Jun 25 10:47:55 infix-12-34-56 finit[1]: Starting pod:nginx[2669]
```
For an example, see below.
Mounts and Volumes
------------------
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-c0-ff-ee:/config/container/system/> edit mount leds
admin@example-c0-ff-ee:/config/container/system/mount/leds> set source /sys/class/leds
admin@example-c0-ff-ee:/config/container/system/mount/leds> set target /sys/class/leds
admin@example-c0-ff-ee:/config/container/system/mount/leds> end
admin@example-c0-ff-ee:/config/container/system/>
Sometimes *volumes* are a better fit. A volume is an automatically
created read-writable entity that follows the life of your container.
admin@example-c0-ff-ee:/config/container/ntpd/> set volume varlib target /var/lib
Volumes survive reboots and upgrading of the base image, unlike the
persistent writable layer you get by default, which does not survive
upgrades. The volume is 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 on the first start. I.e.,
"bind-mount, if empty: then rsync".
> 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.
### Content Mounts
Content mount is a special type of where the file contents for the
container is stored alongside 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-c0-ff-ee:/config/container/ntpd/> edit mount ntpd.conf
admin@example-c0-ff-ee:/config/container/ntpd/mount/ntpd.conf> set content
... interactive editor starts up ...
admin@example-c0-ff-ee:/config/container/ntpd/mount/ntpd.conf> set target /etc/ntpd.conf
admin@example-c0-ff-ee:/config/container/ntpd/mount/ntpd.conf> end
admin@example-c0-ff-ee:/config/container/ntpd/>
The editor is a small [Emacs clone called Mg][2], 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 even if the read-only option is off.
Example Containers
------------------
### System Container
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-c0-ff-ee:/> configure
admin@example-c0-ff-ee:/config> edit container system
admin@example-c0-ff-ee:/config/container/system/> set image ghcr.io/kernelkit/curios:edge
admin@example-c0-ff-ee:/config/container/system/> set network interface docker0
admin@example-c0-ff-ee:/config/container/system/> set publish 222:22
admin@example-c0-ff-ee:/config/container/system/> leave
> **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.
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-c0-ff-ee:/> show container
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
439af2917b44 ghcr.io/kernelkit/curios:edge 41 hours ago Up 16 hours 0.0.0.0:222->222/tcp system
This is a system container, so you can "attach" to it by starting a
shell (or logging in with SSH):
admin@example-c0-ff-ee:/> container shell system
root@439af2917b44:/#
Notice how the hostname inside the container changes. By default the
container ID (hash) is used, but this can be easily changed:
root@439af2917b44:/# exit
admin@infix-00-00-00:/> configure
admin@infix-00-00-00:/config/> edit container system
admin@infix-00-00-00:/config/container/system/> set hostname sys101
admin@infix-00-00-00:/config/container/system/> leave
admin@infix-00-00-00:/> container shell system
root@sys101:/#
[^1]: 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.
### Application Container: nftables
Infix currently does not have a native firewall configuration, and even
when it does it will never expose the full capabilities of `nftables`.
For really advanced setups, the following will be the only alternative:
admin@example-c0-ff-ee:/> configure
admin@example-c0-ff-ee:/config> edit container nftables
admin@example-c0-ff-ee:/config/container/system/> set image ghcr.io/kernelkit/curios-nftables:edge
admin@example-c0-ff-ee:/config/container/system/> set network host
admin@example-c0-ff-ee:/config/container/system/> edit mount nftables.conf
admin@example-c0-ff-ee:/config/container/system/mount/nftables.conf/> set target /etc/nftables.conf
admin@example-c0-ff-ee:/config/container/system/mount/nftables.conf/> set content
... interactive editor starts up where you can paste your rules ...
admin@example-c0-ff-ee:/config/container/system/mount/nftables.conf/> leave
### Application Container: ntpd
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-c0-ff-ee:/> configure
admin@example-c0-ff-ee:/config> edit container ntpd
admin@example-c0-ff-ee:/config/container/ntpd/> set image ghcr.io/kernelkit/curios-ntpd:edge
admin@example-c0-ff-ee:/config/container/ntpd/> set network interface ntpd # From veth0 above
admin@example-c0-ff-ee:/config/container/ntpd/> edit mount ntp.conf
admin@example-c0-ff-ee:/config/container/ntpd/mount/ntp.conf/> set target /etc/ntp.conf
admin@example-c0-ff-ee:/config/container/ntpd/mount/ntp.conf/> set content
... interactive editor starts up where you can paste your rules ...
admin@example-c0-ff-ee:/config/container/ntpd/mount/ntp.conf/> end
admin@example-c0-ff-ee:/config/container/ntpd/> edit volume varlib
admin@example-c0-ff-ee:/config/container/ntpd/volume/varlib/> set target /var/lib
admin@example-c0-ff-ee:/config/container/ntpd/volume/varlib/> leave
admin@example-c0-ff-ee:/> copy running-config startup-config
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.
Upgrading a Container Image
---------------------------
All container configurations are locked to the image hash at the time of
first download, not just ones that use an `:edge` or `:latest` tag. An
upgrade of containers using versioned images is more obvious -- update
the configuration -- but the latter is a bit trickier. Either remove
the configuration and recreate it (leave/apply the changes between), or
use the admin-exec level command:
admin@example-c0-ff-ee:/> container upgrade NAME
Where `NAME` is the name of your container. This command stops your
container, does a `container pull IMAGE`, and then recreates the
container with the new image. Upgraded containers are not automatically
restarted.
admin@example-c0-ff-ee:/> container start NAME
> **Note:** the default writable layer is lost when upgrading the image
> Use named volumes for directories with writable content you wish to
> keep over an upgrade.
[1]: https://github.com/kernelkit/infix/blob/main/src/confd/yang/infix-containers%402023-12-14.yang
[2]: https://github.com/troglobit/mg
[podman]: https://podman.io
+51
View File
@@ -0,0 +1,51 @@
# Hardware information and status
The hardware infomation and status is handled by the YANG model [IETF hardware][ietf-hardware].
with deviations and augmentations in _infix-hardware_.
## USB Ports
For infix to be able to control the USB port(s), a device tree modification is needed (see _alder.dtsi_ for full example).
```
chosen {
infix {
usb-ports = <&cp0_usb3_1>;
usb-port-names = "USB";
};
};
```
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 not want Infix to controll the USB port(s), remove the file or manually enable the USB bus, here is an example:
```
# Enable the bus
echo 1 > /sys/bus/usb/devices/usb1/authorized
```
And then enable sub-devices (e.g. USB memory)
```
# Enable a device plugged into usb1
echo 1 > /sys/bus/usb/devices/usb1/1-1/authorized
```
### Current status
```
admin@example:/> show hardware
USB PORTS
NAME STATE
USB unlocked
```
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.
### Configure USB port
> **Note:** You can only configure an USB ports that exist in _show hardware_ in admin exec.
```
root@example:/> configure
root@example:/config/> set hardware component USB state admin-state unlocked
root@example:/config/> leave
root@example:/>
```
[ietf-hardware]: https://www.rfc-editor.org/rfc/rfc8348.html
+148 -12
View File
@@ -11,11 +11,18 @@ traffic at various points.
TL;DR
-----
make x86_64_defconfig
make
make test-qeneth
Build Infix and run the test suite on a set of virtual Infix nodes.
Runs the test suite on a set of virtual Infix nodes.
$ make x86_64_defconfig
$ make
$ make test
To run a subset of tests, e.g., only the DHCP client tests:
$ make test INFIX_TESTS=case/infix_dhcp/all.yaml
> **Note:** see the below section [Quick Start Guide][] for how to
> connect to the test system, debug and develop tests, and more.
Tenets
@@ -121,15 +128,17 @@ against an Infix image started from `make run`. When the instance is
running, you can open a separate terminal and run `make test-run`, to
run the subset of the test suite that can be mapped to it.
Both `test-qeneth` and `test-run` targets have a respective target
with a `-sh` suffix. These can be used to start an interactive
session in the reproducible environment, which is usually much easier
to work with during a debugging session.
Both `make test` and `make test-run` targets have a respective target
with a `-sh` suffix. These can be used to start an interactive session
in the reproducible environment, which is usually much easier to work
with during a debugging session.
Inside of the reproducible environment, a wrapper for Qeneth is
automatically created that will run it from the running network's
directory. E.g., running a plain `qeneth status` inside a `make
test-qeneth-sh` environment will show the expected status information.
Inside the reproducible environment, a wrapper for Qeneth is created for
the running network's directory. E.g., calling `qeneth status` inside a
`make test-sh` environment show the expected status information.
> **Note:** for more information and help writing a test, see the below
> [Quick Start Guide][].
Physical and Logical Topologies
@@ -274,6 +283,133 @@ it offline, enable STP on all DUTs, and then verify that the resulting
spanning tree matches the expected one.
Quick Start Guide
-----------------
When developing a test, instead of blindly coding in Python and running
`make test` over and over, start a test shell:
$ make test-sh
Info: Generating topology
Info: Generating node YAML
Info: Generating executables
Info: Launching dut1
Info: Launching dut2
Info: Launching dut3
Info: Launching dut4
11:42:52 infamy0:test #
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. Let's run a test:
11:42:53 infamy0:test # ./case/infix_dhcp/dhcp_basic.py
### Connecting to Infamy
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
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
### Connecting to a DUT
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
Trying 127.0.0.1...
Connected to 127.0.0.1.
Infix -- a Network Operating System v23.11.0-226-g0c144da (console)
infix-00-00-00 login: admin
Password:
.-------.
| . . | Infix -- a Network Operating System
|-. v .-| https://kernelkit.github.io
'-'---'-'
Run the command 'cli' for interactive OAM
admin@infix-00-00-00:~$
From here we can observe `dut1` freely while running tests.
> 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`
Like the `shell` script, `console` takes an optional "system" argument
in case you run multiple instances of Infamy:
$ ./test/console 1 infamy2
You can also connect to the console of a DUT from within a `shell`:
$ ./test/shell
11:42:54 infamy0:test # qeneth status
11:42:54 infamy0:test # qeneth console dut1
login: admin
password: *****
admin@infix-00-00-00:~$
> **Note:** disconnect from the qeneth console by using 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.
### Debug a Test
First, add a Python `breakpoint()` to your test and run it from your
`make test-sh` Infamy instance:
11:42:58 infamy0:test # ./case/infix_dhcp/dhcp_basic.py
# Starting (2024-02-10 11:42:59)
# Probing dut1 on port d1a for IPv6LL mgmt address ...
# Connecting to mgmt IP fe80::ff:fe00:0%d1a:830 ...
ok 1 - Initialize
> /home/jocke/src/infix/test/case/infix_dhcp/dhcp_basic.py(44)<module>()
(Pdb)
You are now in the Python debugger, Pdb.
### Debug a Test in a MacVLAN
Most tests use some sort of network connection to the DUTs. From a test
shell you cannot see or debug that connection by default. So you need
to employ a little trick.
Open another terminal, and start a test shell in your Infamy instance:
$ ./test/shell
11:43:19 infamy0:test #
MacVLAN interfaces created by Infamy run in another network namespace,
to enter it we can look for a sleeper process:
11:43:20 infamy0:test # nsenter -n U -t $(pidof sleep infinity)
You are now one step further down:
infamy0:/home/jocke/src/infix/test# ip -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
iface@if7 UP 10.0.0.1/24 fe80::38d0:88ff:fe77:b7cd/64
You can now freely debug the network activity of your test and the
responses from the DUT.
[9PM]: https://github.com/rical/9pm
[Qeneth]: https://github.com/wkz/qeneth
[TAP]: https://testanything.org/
+160
View File
@@ -0,0 +1,160 @@
Vital Product Data
==================
Infix can source Vital Product Data (VPD) from EEPROMs containing an
[ONIE TLV][oniespec]. The load bearing information from VPDs, used by
Infix by default, is:
1. Base (chassis) MAC address of the system
2. Factory default admin password hash
Additionally, every VPD is listed as a separate _component_ in the
_ietf-hardware_ model, in which Infix exports all available attributes
to the user. This is useful to read out exact hardware revisions of
each board making up a system, figure out if a system belongs to a
particular production batch, etc.
## JSON Encoding
To make EEPROM binary generation less cumbersome, Infix defines a JSON
encoding of an ONIE TLV, and provides a tool, [onieprom], to translate
from one encoding to another. I.e. a JSON object can be translated to
its binary equivalent, and vice versa.
A mapping between TLV attribute IDs and JSON keys is defined, using
the Linux kernel [driver] as a reference:
| TLV ID | Description | Key | Type |
|--------|------------------|----------------------|---------------------------------|
| `0x21` | Product Name | `"product-name"` | String |
| `0x22` | Part Number | `"part-number"` | String |
| `0x23` | Serial Number | `"serial-number"` | String |
| `0x24` | MAC #1 Base | `"mac-address"` | String ("c0:ff:ee:00:00:00") |
| `0x25` | Manufacture Date | `"manufacture-date"` | String ("MM/DD/YYYY HH:NN:SS") |
| `0x26` | Device Version | `"device-version"` | Integer (u8) |
| `0x27` | Label Revision | `"label-revision"` | String |
| `0x28` | Platform Name | `"platform-name"` | String |
| `0x29` | ONIE Version | `"onie-version"` | String |
| `0x2a` | Num MACs | `"num-macs"` | Integer (u16) |
| `0x2b` | Manufacturer | `"manufacturer"` | String |
| `0x2c` | Country Code | `"country-code"` | String (ISO 3166-1 2-byte code) |
| `0x2d` | Vendor | `"vendor"` | String |
| `0x2e` | Diag Version | `"diag-version"` | String |
| `0x2f` | Service Tag | `"service-tag"` | String |
| `0xfd` | Vendor Extension | `"vendor-extension"` | List of extensions (see below) |
The JSON encoding of a TLV is a single JSON object with an
arbitrary subset of the attributes from the list above.
**Example:**
```json
{
"product-name": "Wacky Widget",
"serial-number": "#1",
"manufacture-date": "02/13/2024 11:29:52"
}
```
### Vendor Extensions
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][pens]) |
| 2 | String (UTF-8 encoded extension data) |
The format of the extension data is defined by the entity indicated by
the IANA enterprise number. Restricting extension data to UTF-8
encoded strings is a decision imposed by Infix; the ONIE specification
allows for arbitrary binary data.
**Example**:
```json
{
...
"vendor-extension": [
[ 12345, "my extension data" ]
]
}
```
### Infix Specific Extensions
[Kernelkit][kkit]'s IANA enterprise number is `61046`, under which any
extensions required by Infix are stored. The **only** valid extension
data that may be stored under this number is documented in _this
section_. If other device specific data needs to be stored in a VPD,
you must associate that with an enterprise number under our control.
Every Kernelkit extension must be a valid JSON object containing an
arbitrary subset of the following attributes:
| Key | Value | Description |
|------------|----------------------------------|--------------------------------------|
| `"pwhash"` | String (output of `mkpasswd(1)`) | Factory default password for `admin` |
Since the extension is itself stored in a JSON document, it has to be
appropriately quoted.
**Example**:
```json
{
...
"vendor-extension": [
[ 61046, "{\"pwhash\":\"$6$9rufAxdqCrxrwfQR$G0l9cTVlu/vOhxgo/uMKfRDOmZRd5XWF3vKr5da6qYoxuTJBS/Pl9K.5lrabWoWFFc.71yFMaSlZz0O8FtAtl.\"}" ]
]
}
```
## Creating and Parsing ONIE EEPROM Binaries
```
usage: onieprom [-h] [-e] [-d] [infile] [outfile]
positional arguments:
infile
outfile
options:
-h, --help show this help message and exit
-e, --encode Encode JSON input to binary output
-d, --decode Decode binary input to JSON output
```
To convert a compatible JSON document (using the first example above)
to its binary equivalent, we ask [onieprom] to _encode_ it for us:
```sh
~$ onieprom -e example.json >example.bin
~$ hexdump -C example.bin
00000000 54 6c 76 49 6e 66 6f 00 01 00 2d 25 13 30 32 2f |TlvInfo...-%.02/|
00000010 31 33 2f 32 30 32 34 20 31 31 3a 32 39 3a 35 32 |13/2024 11:29:52|
00000020 21 0c 57 61 63 6b 79 20 57 69 64 67 65 74 23 02 |!.Wacky Widget#.|
00000030 23 31 fe 04 dd 69 88 97 |#1...i..|
00000038
```
We can also run the process in reverse, to inspect the contents of a
binary in its equivalent JSON representation:
```sh
~$ onieprom -d example.bin >example-again.json
~$ jq . example-again.json
{
"manufacture-date": "02/13/2024 11:29:52",
"product-name": "Wacky Widget",
"serial-number": "#1"
}
```
[oniespec]: https://opencomputeproject.github.io/onie/design-spec/hw_requirements.html
[onieprom]: ../board/common/rootfs/bin/onieprom
[driver]: https://elixir.bootlin.com/linux/latest/source/drivers/nvmem/layouts/onie-tlv.c
[pens]: https://www.iana.org/assignments/enterprise-numbers/
[kkit]: https://github.com/kernelkit
+1
View File
@@ -3,6 +3,7 @@ IXMSG = printf "\e[37;44m>>> $(call qstrip,$(1))\e[0m\n"
include $(sort $(wildcard $(BR2_EXTERNAL_INFIX_PATH)/package/*/*.mk))
include $(BR2_EXTERNAL_INFIX_PATH)/board/common/common.mk
-include $(BR2_EXTERNAL_INFIX_PATH)/board/$(patsubst "%",%,$(BR2_ARCH))/board.mk
include $(BR2_EXTERNAL_INFIX_PATH)/infix.mk
.PHONY: local.mk
local.mk:
+1
View File
@@ -0,0 +1 @@
INFIX_CFLAGS:=-Wall -Werror -Wextra -Wno-unused-parameter -Wformat=2 -Wformat-overflow=2 -Winit-self -Wstrict-overflow=4 -Wno-format-truncation -Wno-format-nonliteral
+3
View File
@@ -1,4 +1,6 @@
source "$BR2_EXTERNAL_INFIX_PATH/package/confd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/curios-httpd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/execd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/statd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/conmon/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/factory/Config.in"
@@ -6,6 +8,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/faux/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/finit/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/ifupdown-ng/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/iito/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/k8s-logger/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/keyack/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/klish-plugin-infix/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/klish/Config.in"
+1 -1
View File
@@ -8,7 +8,7 @@ run name:error :1 log:console norestart if:<run/bootstrap/failure> \
[S] /usr/libexec/confd/error --
service name:confd log <run/bootstrap/success> \
[S12345] sysrepo-plugind -f -p /run/confd.pid -n -v3 -- Configuration daemon
[S12345] sysrepo-plugind -f -p /run/confd.pid -n -- Configuration daemon
# Bootstrap system with startup-config
run name:startup log:prio:user.notice norestart <pid/confd> \
+10
View File
@@ -4,6 +4,7 @@
#
################################################################################
CONFD_VERSION = 1.0
CONFD_SITE_METHOD = local
CONFD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/confd
@@ -12,6 +13,15 @@ CONFD_LICENSE_FILES = LICENSE
CONFD_REDISTRIBUTE = NO
CONFD_DEPENDENCIES = augeas jansson libite sysrepo libsrx
CONFD_AUTORECONF = YES
define CONFD_CONF_ENV
CFLAGS="$(INFIX_CFLAGS)"
endef
ifeq ($(BR2_PACKAGE_PODMAN),y)
CONFD_CONF_OPTS += --enable-containers
else
CONFD_CONF_OPTS += --disable-containers
endif
define CONFD_INSTALL_EXTRA
cp $(CONFD_PKGDIR)/confd.conf $(FINIT_D)/available/
+7
View File
@@ -0,0 +1,7 @@
config BR2_PACKAGE_CURIOS_HTTPD
bool "curiOS-httpd"
help
Bundle an OCI archive of curiOS-httpd in /lib/oci for
the given target architecture.
https://github.com/kernelkit/curiOS
+18
View File
@@ -0,0 +1,18 @@
################################################################################
#
# curios-httpd
#
################################################################################
CURIOS_HTTPD_VERSION = edge
CURIOS_HTTPD_SOURCE = curios-httpd-oci-$(GO_GOARCH).tar.gz
CURIOS_HTTPD_SITE = https://github.com/kernelkit/curiOS/releases/download/$(CURIOS_HTTPD_VERSION)
CURIOS_HTTPD_LICENSE = GPL
CURIOS_HTTPD_INSTALL_TARGET = YES
define CURIOS_HTTPD_INSTALL_TARGET_CMDS
mkdir -p $(TARGET_DIR)/lib/oci
(cd $(@D)/.. && $(TAR) cfz $(TARGET_DIR)/lib/oci/$(notdir $(@D)).tar.gz $(notdir $(@D)))
endef
$(eval $(generic-package))
+7
View File
@@ -0,0 +1,7 @@
config BR2_PACKAGE_EXECD
bool "execd"
select BR2_PACKAGE_LIBUEV
select BR2_PACKAGE_LIBITE
help
Generic job queue executor with retry on route changes.
+2
View File
@@ -0,0 +1,2 @@
service log:prio:local1.err,tag:container \
[2345] execd /run/containers/queue /var/lib/containers/active -- Container job runner
+29
View File
@@ -0,0 +1,29 @@
################################################################################
#
# execd
#
################################################################################
EXECD_VERSION = 1.0
EXECD_SITE_METHOD = local
EXECD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/execd
EXECD_LICENSE = ISC
EXECD_LICENSE_FILES = LICENSE
EXECD_REDISTRIBUTE = NO
EXECD_DEPENDENCIES = libuev libite
EXECD_AUTORECONF = YES
define EXECD_CONF_ENV
CFLAGS="$(INFIX_CFLAGS)"
endef
EXECD_CONF_OPTS = --prefix= --disable-silent-rules
define EXECD_INSTALL_EXTRA
cp $(EXECD_PKGDIR)/execd.conf $(FINIT_D)/available/
ln -sf ../available/execd.conf $(FINIT_D)/enabled/execd.conf
cp $(EXECD_PKGDIR)/tmpfiles.conf $(TARGET_DIR)/etc/tmpfiles.d/execd.conf
endef
EXECD_TARGET_FINALIZE_HOOKS += EXECD_INSTALL_EXTRA
$(eval $(autotools-package))
+8
View File
@@ -0,0 +1,8 @@
d /run/containers/args 0700 - -
d /run/containers/files 0700 - -
d /var/lib/containers/active 0700 - -
d /var/lib/containers/oci 0755 - -
d /run/containers/inbox 0700 - -
d /run/containers/queue 0700 - -
d /run/cni 0755 - -
L+ /var/lib/cni - - - - /run/cni
+4
View File
@@ -11,6 +11,10 @@ FACTORY_SITE_METHOD = local
FACTORY_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/factory
FACTORY_REDISTRIBUTE = NO
define FACTORY_CONF_ENV
CFLAGS="$(INFIX_CFLAGS)"
endef
define FACTORY_BUILD_CMDS
$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \
LDLIBS="$(TARGET_LDFLAGS)"
+13
View File
@@ -0,0 +1,13 @@
config BR2_PACKAGE_K8S_LOGGER
bool "k8s-logger"
depends on BR2_PACKAGE_PODMAN
depends on BR2_PACKAGE_SYSKLOGD
select BR2_PACKAGE_LIBITE
help
Reads k8s-file style logs written to a fifo (mkfifo) by by podman
and forwards them, line by assembled line, to syslog.
Infix requires use of sysklogd to enable the syslogp() API so that
k8s-logger also includes the container's main PID.
https://github.com/kernelkit/k8s-logger
+3
View File
@@ -0,0 +1,3 @@
# LICENSE hash locally calculated, .tar.gz hash from GitHub release
sha256 01b0285cf2ed7aa2f4c4bc92db3bbe9dcfe75441eda17d93c08b3fe9e32d2111 k8s-logger-1.2.tar.gz
sha256 c556e258d3293cb96277912daf43155d9d65194fb5651249d1fdc489265b9a69 LICENSE
+14
View File
@@ -0,0 +1,14 @@
################################################################################
#
# k8s-logger
#
################################################################################
K8S_LOGGER_VERSION = 1.2
K8S_LOGGER_SITE = https://github.com/kernelkit/k8s-logger/releases/download/v$(K8S_LOGGER_VERSION)
K8S_LOGGER_LICENSE = MIT
K8S_LOGGER_LICENSE_FILES = LICENSE
K8S_LOGGER_DEPENDENCIES = sysklogd libite
K8S_LOGGER_CONF_OPTS = --with-syslogp
$(eval $(autotools-package))
+10
View File
@@ -6,3 +6,13 @@ config BR2_PACKAGE_KLISH_PLUGIN_INFIX
help
A plugin to klish with an opinionated config and some
command extensions, suitable for systems running Infix.
if BR2_PACKAGE_KLISH_PLUGIN_INFIX
config BR2_PACKAGE_KLISH_PLUGIN_INFIX_SHELL
bool "shell command"
default y
help
Include 'shell' command exit to Bash.
endif
@@ -13,6 +13,28 @@ KLISH_PLUGIN_INFIX_REDISTRIBUTE = NO
KLISH_PLUGIN_INFIX_DEPENDENCIES = klish-plugin-sysrepo
KLISH_PLUGIN_INFIX_AUTORECONF = YES
define KLISH_PLUGIN_INFIX_CONF_ENV
CFLAGS="$(INFIX_CFLAGS)"
endef
ifeq ($(BR2_PACKAGE_PODMAN),y)
KLISH_PLUGIN_INFIX_CONF_OPTS += --enable-containers
else
KLISH_PLUGIN_INFIX_CONF_OPTS += --disable-containers
endif
ifeq ($(BR2_PACKAGE_KLISH_PLUGIN_INFIX_SHELL),y)
KLISH_PLUGIN_INFIX_CONF_OPTS += --enable-shell
else
KLISH_PLUGIN_INFIX_CONF_OPTS += --disable-shell
endif
ifeq ($(BR2_PACKAGE_BASH),y)
KLISH_PLUGIN_INFIX_CONF_OPTS += --with-shell=/bin/bash
else
KLISH_PLUGIN_INFIX_CONF_OPTS += --with-shell=/bin/sh
endif
define KLISH_PLUGIN_INFIX_INSTALL_DOC
$(INSTALL) -t $(TARGET_DIR)/usr/share/infix/cli -D -m 0644 \
$(wildcard $(BR2_EXTERNAL_INFIX_PATH)/doc/cli/*.md)
@@ -1,3 +1,3 @@
# Locally calculated
sha256 9d9d33b873917ca5d0bdcc47a36d2fd385971ab0c045d1472fcadf95ee5bcf5b LICENCE
sha256 59ce3461a002a8113be030ef476940027f5e0e73dbe8afc56739cda6b55349ab klish-plugin-sysrepo-f828a96efb12c3f7f8807aae089561fd69f4a6af-br1.tar.gz
sha256 c06a369147ae1071c0c309c0bd9d49d8ae0ff5dd1e804e2009b1a18f7fc12b5c klish-plugin-sysrepo-909cbfb1253a3c3c3abfdc72de9db7aaf3a08de2-br1.tar.gz
@@ -4,7 +4,7 @@
#
################################################################################
KLISH_PLUGIN_SYSREPO_VERSION = f828a96efb12c3f7f8807aae089561fd69f4a6af
KLISH_PLUGIN_SYSREPO_VERSION = 909cbfb1253a3c3c3abfdc72de9db7aaf3a08de2
KLISH_PLUGIN_SYSREPO_SITE = https://github.com/kernelkit/klish-plugin-sysrepo.git
#KLISH_PLUGIN_SYSREPO_VERSION = cdd3eb51a7f7ee0ed5bd925fa636061d3b1b85fb
#KLISH_PLUGIN_SYSREPO_SITE = https://src.libcode.org/pkun/klish-plugin-sysrepo.git
+2
View File
@@ -6,6 +6,8 @@ UnixSocketPath=/run/klishd.sock
Pager="/usr/bin/less -I -F -e -X -K -d -r -S"
UsePager=y
Banner="\nSee the 'help' command for an introduction to the system\n\n"
# Set max size of history. By default 100 lines are saved, for every new
# line after that, the oldest line is dropped. Set to 0 for unlimited.
#HistorySize=100
+1 -1
View File
@@ -1,3 +1,3 @@
# Locally calculated
sha256 9d9d33b873917ca5d0bdcc47a36d2fd385971ab0c045d1472fcadf95ee5bcf5b LICENCE
sha256 0ef97fe42377e32be96a69096fc149e547568e6567d641cc50252356eb64e934 klish-ce81f52b50bcc3c37ce1ce2bec5a7965e2d698d2-br1.tar.gz
sha256 edb7b9a53d57d87756a99fa25c4af3d6219bed9a863541e672580a2d1f6e7052 klish-b898fdd9a128adaad3a5c92d8dd7baac2085df1b-br1.tar.gz
+1 -1
View File
@@ -4,7 +4,7 @@
#
################################################################################
KLISH_VERSION = ce81f52b50bcc3c37ce1ce2bec5a7965e2d698d2
KLISH_VERSION = b898fdd9a128adaad3a5c92d8dd7baac2085df1b
KLISH_SITE = https://github.com/kernelkit/klish.git
#KLISH_VERSION = tags/3.0.0
#KLISH_SITE = https://src.libcode.org/pkun/klish.git
+3 -1
View File
@@ -13,5 +13,7 @@ LIBSRX_INSTALL_STAGING = YES
LIBSRX_REDISTRIBUTE = NO
LIBSRX_DEPENDENCIES = sysrepo
LIBSRX_AUTORECONF = YES
define LIBSRX_CONF_ENV
CFLAGS="$(INFIX_CFLAGS)"
endef
$(eval $(autotools-package))
-119
View File
@@ -1,119 +0,0 @@
#!/bin/sh
# Create CNI profiles for podman
dir=/etc/cni/net.d
force=
num=90
usage()
{
cat <<EOF
usage:
cni [opt] cmd
options:
-f Force operation, e.g. overwrite
-h Show this help text
-n NUM Create $dir/NUM-name-type.conflist
commands:
create host NAME IFNAME IPADDR/PREFIX Create CNI host-device profile
help Show this help text
list List available profiles
show PROFILE Show profile from list command
EOF
}
create_host()
{
nm=$1
iface=$2
addr=$3
fn="$dir/$num-$nm-host.conflist"
if [ -f "$fn" ] && [ "$force" = "" ]; then
echo "$fn: already exists, use -f to force overwriting it."
exit 1
fi
cat <<EOF > "$fn"
{
"cniVersion": "0.4.0",
"name": "$nm",
"plugins": [
{
"type": "host-device",
"device": "$iface",
"ipam": {
"type": "static",
"addresses": [
{
"address": "$addr"
}
]
}
}
]
}
EOF
}
while [ "$1" != "" ]; do
case $1 in
-f | --force)
force=true
;;
-h | --help)
usage
exit 0
;;
-n | --num)
num=$2
shift
;;
*)
break
esac
shift
done
cmd=$1
if [ -n "$cmd" ]; then
shift
fi
case $cmd in
c | create)
type=$1
shift
case $type in
host | veth)
#shellcheck disable=SC2068
create_host $@
;;
*)
echo "Unsupported type"
usage
exit 1
;;
esac
;;
l | ls | list)
ls -l $dir
;;
show | sh | cat)
fn=$1
[ -f "$fn" ] || fn="$dir/$1"
jq . "$fn"
;;
help | *)
usage
;;
esac
-106
View File
@@ -1,106 +0,0 @@
#!/bin/sh
# Create a monitored container service
pmargs=""
enable=
dryrun=
name=
usage()
{
cat <<EOF
summary:
Create a supervised Finit service for a podman container.
Use '-e' flag to also enable the service. When called at bootstrap,
this ensures Finit starts the new service when changing runlevel.
Please note, either the entry point or the CMD must run in foreground.
usage:
podman-service [opts] IMAGE [CMD [ARG]]
options:
-d "DESCRIPTION" Optional service description, default: "NAME container"
-e Enable service (does not reload Finit)
-h Show this help text
-n NAME Short name, lower-case, no spaces, e.g., 'nginx'
-p "ARGS" Arguments for podman, example: '-p "-p 80:80"'
-s Simulate (dry run), only echo to stdout
args:
IMAGE Image or URL, e.g., docker://nginx:alpine
CMD Optional command/daemon to run
ARG Optional arguments to command, e.g., --foreground
EOF
}
service_generate()
{
image=$1
cmd=$2
arg=$3
svc="pod-$name"
gen="initctl -fb create $svc"
if [ -z "$description" ]; then
description="$name container"
fi
# shellcheck disable=SC2209
if [ -n "$dryrun" ]; then
gen=cat
enable=
fi
cat <<EOF | $gen
service name:pod :$name log podman run --name $name --rm $pmargs $image $cmd $arg -- $description
EOF
if [ -n "$enable" ]; then
initctl -fb enable "pod-$name"
fi
}
while [ "$1" != "" ]; do
case $1 in
-d)
shift
description="$1"
;;
-e)
enable=true
;;
-h|--help|help)
usage
exit 0
;;
-n)
shift
name="$1"
;;
-p)
shift
pmargs="$pmargs $1"
;;
-s)
dryrun=true
;;
*)
break
esac
shift
done
image=$1
if [ -z "$image" ]; then
usage
exit 1
fi
shift
if [ -z "$name" ]; then
name=$(echo "$image" | sed 's/\(.*\):.*/\1/g')
fi
# shellcheck disable=SC2068
service_generate "$image" $@
+8 -4
View File
@@ -71,6 +71,13 @@ else
PODMAN_TAGS += exclude_graphdriver_vfs
endif
ifeq ($(BR2_PACKAGE_BASH_COMPLETION),y)
define PODMAN_BASH_COMPLETION
$(INSTALL) -D -m 644 $(@D)/completions/bash/podman \
$(TARGET_DIR)/usr/share/bash-completion/completions/
endef
endif
define PODMAN_INSTALL_TARGET_CMDS
$(TARGET_MAKE_ENV) $(MAKE) -C $(@D) DESTDIR=$(TARGET_DIR) PREFIX=/usr \
install.bin
@@ -81,11 +88,8 @@ define PODMAN_INSTALL_TARGET_CMDS
$(INSTALL) -d -m 755 $(TARGET_DIR)/etc/containers
$(INSTALL) -D -m 644 $(PODMAN_PKGDIR)/containers-policy.json \
$(TARGET_DIR)/etc/containers/policy.json
$(INSTALL) -D -m 755 $(PODMAN_PKGDIR)/podman-service \
$(TARGET_DIR)/usr/bin/podman-service
$(INSTALL) -D -m 755 $(PODMAN_PKGDIR)/cni \
$(TARGET_DIR)/usr/bin/cni
$(PODMAN_SECCOMP_PROFILE)
$(PODMAN_BASH_COMPLETION)
endef
define PODMAN_INSTALL_INIT_SYSTEMD
@@ -4,9 +4,9 @@
# Use <pid/syslogd> as barrier for other system tasks and service that
# rely on modules, firmware, and device nodes to be ready.
service if:udevd env:-/etc/default/sysklogd <run/udevadm:5/success> \
service if:udevd nowarn env:-/etc/default/sysklogd <run/udevadm:5/success> \
[S0123456789] syslogd -F $SYSLOGD_ARGS -- System log daemon
service if:mdev env:-/etc/default/sysklogd <run/coldplug/success> \
service if:mdev nowarn env:-/etc/default/sysklogd <run/coldplug/success> \
[S0123456789] syslogd -F $SYSLOGD_ARGS -- System log daemon
service if:mdevd env:-/etc/default/sysklogd <run/coldplug/success> \
service if:mdevd nowarn env:-/etc/default/sysklogd <run/coldplug/success> \
[S0123456789] syslogd -F $SYSLOGD_ARGS -- System log daemon
+1
View File
@@ -4,6 +4,7 @@ config BR2_PACKAGE_STATD
select BR2_PACKAGE_LIBEV
select BR2_PACKAGE_SYSREPO
select BR2_PACKAGE_LIBSRX
select BR2_PACKAGE_LIBITE
help
Operational Status Daemon. Responsible for handling sysrepo
operational run-time info. Such as interface state and address.
+11 -8
View File
@@ -11,16 +11,19 @@ STATD_LICENSE = BSD-3-Clause
STATD_LICENSE_FILES = LICENSE
STATD_REDISTRIBUTE = NO
STATD_DEPENDENCIES = sysrepo libev libsrx jansson
STATD_AUTORECONF = YES
define STATD_BUILD_CMDS
$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \
LDFLAGS="$(TARGET_LDFLAGS)"
define STATD_CONF_ENV
CFLAGS="$(INFIX_CFLAGS)"
endef
define STATD_INSTALL_TARGET_CMDS
$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \
DESTDIR="$(TARGET_DIR)" install
endef
STATD_CONF_OPTS = --prefix= --disable-silent-rules
ifeq ($(BR2_PACKAGE_PODMAN),y)
STATD_CONF_OPTS += --enable-containers
else
STATD_CONF_OPTS += --disable-containers
endif
define STATD_INSTALL_EXTRA
cp $(STATD_PKGDIR)/statd.conf $(FINIT_D)/available/
@@ -28,4 +31,4 @@ define STATD_INSTALL_EXTRA
endef
STATD_TARGET_FINALIZE_HOOKS += STATD_INSTALL_EXTRA
$(eval $(generic-package))
$(eval $(autotools-package))
@@ -1,78 +0,0 @@
From cbf96a1de0feee85f8a4014f29f9f3b7558dbff0 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Sun, 10 Dec 2023 16:42:29 +0100
Subject: [PATCH 1/2] Silence 'not available' log messages for nowarn
run/task/service
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
src/service.c | 4 ++--
src/svc.c | 5 +++--
src/svc.h | 2 +-
3 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/service.c b/src/service.c
index c6ee18d0..1ee54e5a 100644
--- a/src/service.c
+++ b/src/service.c
@@ -1620,7 +1620,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
strlcat(ident, id, sizeof(ident));
}
- if (ifstmt && !svc_ifthen(1, ident, ifstmt))
+ if (ifstmt && !svc_ifthen(1, ident, ifstmt, nowarn))
return 0;
levels = conf_parse_runlevels(runlevels);
@@ -2027,7 +2027,7 @@ void service_mark_unavail(void)
if (!svc->ifstmt[0])
continue;
- if (!svc_ifthen(1, svc_ident(svc, buf, sizeof(buf)), svc->ifstmt))
+ if (!svc_ifthen(1, svc_ident(svc, buf, sizeof(buf)), svc->ifstmt, svc->nowarn))
svc_mark(svc);
}
}
diff --git a/src/svc.c b/src/svc.c
index 001993de..6bb90049 100644
--- a/src/svc.c
+++ b/src/svc.c
@@ -649,7 +649,7 @@ int svc_enabled(svc_t *svc)
return 0;
}
- if (!svc_ifthen(0, NULL, svc->ifstmt)) {
+ if (!svc_ifthen(0, NULL, svc->ifstmt, svc->nowarn)) {
// dbg("ifthen:%s>", svc->ifstmt[0] ? svc->ifstmt : "<");
return 0;
}
@@ -704,11 +704,12 @@ int svc_conflicts(svc_t *svc)
* @is_conf: Set when called by service_mark_unavail()
* @ident: svc_t identififcation (name:id)
* @stmt: if: statement from .conf file
+ * @quiet: If non-zero, do not warn in syslog
*
* Returns:
* %TRUE(1) yes, use this svc_t, %FALSE(0) prune
*/
-int svc_ifthen(int is_conf, const char *ident, char *stmt)
+int svc_ifthen(int is_conf, const char *ident, char *stmt, int quiet)
{
char stmts[MAX_IDENT_LEN];
int not = 0;
diff --git a/src/svc.h b/src/svc.h
index f94a30e5..520aabcc 100644
--- a/src/svc.h
+++ b/src/svc.h
@@ -249,7 +249,7 @@ void svc_prune_bootstrap (void);
void svc_enable (svc_t *svc);
int svc_enabled (svc_t *svc);
int svc_conflicts (svc_t *svc);
-int svc_ifthen (int is_conf, const char *ident, char *stmt);
+int svc_ifthen (int is_conf, const char *ident, char *stmt, int quiet);
int svc_parse_jobstr (char *str, size_t len, void *user_data, int (*found)(svc_t *, void *), int (not_found)(char *, char *, void *));
--
2.34.1
@@ -1,34 +0,0 @@
From ed88469276c569acd9ef16bcc38cd80afd8dea52 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Sun, 10 Dec 2023 17:32:19 +0100
Subject: [PATCH 2/2] Actually silence the log message, missing hunk for
previous change
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
src/svc.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/svc.c b/src/svc.c
index 6bb90049..773f0ab6 100644
--- a/src/svc.c
+++ b/src/svc.c
@@ -759,11 +759,13 @@ int svc_ifthen(int is_conf, const char *ident, char *stmt, int quiet)
svc = svc_find_by_str(stmt);
if (not && svc) {
- logit(LOG_NOTICE, "skipping %s, %s already loaded.", ident, svc_ident(svc, NULL, 0));
+ if (!quiet)
+ logit(LOG_NOTICE, "skipping %s, %s already loaded.", ident, svc_ident(svc, NULL, 0));
return 0;
}
if (!not && !svc) {
- logit(LOG_NOTICE, "skipping %s, %s not available.", ident, stmt);
+ if (!quiet)
+ logit(LOG_NOTICE, "skipping %s, %s not available.", ident, stmt);
return 0;
}
}
--
2.34.1
@@ -0,0 +1,70 @@
From c2218236546908ecab5eab4b32e698d7bb3d984a Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Sat, 3 Feb 2024 17:30:30 +0100
Subject: [PATCH] shutdown: use cond_clear_noupdate() to prevent nested
service_stop()
Organization: Addiva Elektronik
When the system shuts down, or user changes runlevels, we don't have to
call cond_clear_update(), because this can lead to nested service_stop()
calls, which in turn lead to out of sync progress updates:
[ .. ] Stopping Foo
[ OK ] Stopping Bar
[ OK ]
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
src/cond-w.c | 6 +++++-
src/service.c | 4 +++-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/cond-w.c b/src/cond-w.c
index 2502264..0546282 100644
--- a/src/cond-w.c
+++ b/src/cond-w.c
@@ -35,6 +35,7 @@
#include "cond.h"
#include "pid.h"
#include "service.h"
+#include "sm.h"
struct cond_boot {
TAILQ_ENTRY(cond_boot) link;
@@ -214,7 +215,10 @@ static int do_delete(const char *fpath, const struct stat *sb, int tflag, struct
err(1, "Failed removing condition %s", fpath);
cond = ptr + strlen(COND_BASE) + 1;
- cond_update(cond);
+ if (sm_is_in_teardown(&sm))
+ cond_clear_noupdate(cond);
+ else
+ cond_update(cond);
return 0;
}
diff --git a/src/service.c b/src/service.c
index 8175757..eca9ed0 100644
--- a/src/service.c
+++ b/src/service.c
@@ -2261,7 +2261,7 @@ static void svc_set_state(svc_t *svc, svc_state_t new_state)
/* if PID isn't collected within SVC_TERM_TIMEOUT msec, kill it! */
if (new_state == SVC_STOPPING_STATE) {
dbg("%s is stopping, wait %d sec before sending SIGKILL ...",
- svc_ident(svc, NULL, 0), svc->killdelay / 1000);
+ svc_ident(svc, NULL, 0), svc->killdelay / 1000);
service_timeout_cancel(svc);
service_timeout_after(svc, svc->killdelay, service_kill);
}
@@ -2299,6 +2299,8 @@ static void svc_set_state(svc_t *svc, svc_state_t new_state)
if ((old_state == SVC_RUNNING_STATE && new_state == SVC_PAUSED_STATE) ||
(old_state == SVC_PAUSED_STATE && new_state == SVC_RUNNING_STATE))
; /* only paused during reload, don't clear conds. */
+ else if (sm_is_in_teardown(&sm))
+ cond_clear_noupdate(cond);
else
cond_clear(cond);
--
2.34.1
@@ -0,0 +1,27 @@
From 5998342b3e55638b0f7248383e939a64f53fa8ff Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Mon, 26 Feb 2024 08:41:14 +0100
Subject: [PATCH] system: drop debug mode from udevd
Organization: Addiva Elektronik
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
system/10-hotplug.conf.in | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/system/10-hotplug.conf.in b/system/10-hotplug.conf.in
index 49ea081..a4f64b5 100644
--- a/system/10-hotplug.conf.in
+++ b/system/10-hotplug.conf.in
@@ -19,7 +19,7 @@
# Check for systemd-udevd and eudev, if we find both, we opt for the latter.
service nowarn [S12345789] cgroup.system name:udevd notify:none pid:udevd log /lib/systemd/systemd-udevd -- Device event daemon (udev)
-service nowarn [S12345789] cgroup.system name:udevd notify:none pid:udevd log udevd -D -- Device event daemon (udev)
+service nowarn [S12345789] cgroup.system name:udevd notify:none pid:udevd log udevd -- Device event daemon (udev)
# Wait for udevd to start, then trigger coldplug events and module loading.
# The last 'settle' call waits for it to finalize processing all uevents.
--
2.34.1
@@ -0,0 +1,105 @@
From 709063e8368aeed56fd5775c10d33ca4dc8990a5 Mon Sep 17 00:00:00 2001
From: Stephen Hemminger <stephen@networkplumber.org>
Date: Fri, 2 Jun 2023 08:51:53 -0700
Subject: [PATCH] ipaddress: accept symbolic names
Organization: Addiva Elektronik
The function rtnl_addproto_a2n() was defined but never used.
Use it to allow for symbolic names, and fix the function signatures
so protocol value is consistently __u8.
Fixes: bdb8d8549ed9 ("ip: Support IP address protocol")
Cc: petrm@nvidia.com
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
include/rt_names.h | 4 ++--
ip/ipaddress.c | 2 +-
lib/rt_names.c | 18 +++++-------------
3 files changed, 8 insertions(+), 16 deletions(-)
diff --git a/include/rt_names.h b/include/rt_names.h
index e96d80f3..02750307 100644
--- a/include/rt_names.h
+++ b/include/rt_names.h
@@ -5,7 +5,7 @@
#include <asm/types.h>
const char *rtnl_rtprot_n2a(int id, char *buf, int len);
-const char *rtnl_addrprot_n2a(int id, char *buf, int len);
+const char *rtnl_addrprot_n2a(__u8 id, char *buf, int len);
const char *rtnl_rtscope_n2a(int id, char *buf, int len);
const char *rtnl_rttable_n2a(__u32 id, char *buf, int len);
const char *rtnl_rtrealm_n2a(int id, char *buf, int len);
@@ -14,7 +14,7 @@ const char *rtnl_dsfield_get_name(int id);
const char *rtnl_group_n2a(int id, char *buf, int len);
int rtnl_rtprot_a2n(__u32 *id, const char *arg);
-int rtnl_addrprot_a2n(__u32 *id, const char *arg);
+int rtnl_addrprot_a2n(__u8 *id, const char *arg);
int rtnl_rtscope_a2n(__u32 *id, const char *arg);
int rtnl_rttable_a2n(__u32 *id, const char *arg);
int rtnl_rtrealm_a2n(__u32 *id, const char *arg);
diff --git a/ip/ipaddress.c b/ip/ipaddress.c
index c428dd3d..7accbf7d 100644
--- a/ip/ipaddress.c
+++ b/ip/ipaddress.c
@@ -2547,7 +2547,7 @@ static int ipaddr_modify(int cmd, int flags, int argc, char **argv)
__u8 proto;
NEXT_ARG();
- if (get_u8(&proto, *argv, 0))
+ if (rtnl_addrprot_a2n(&proto, *argv))
invarg("\"proto\" value is invalid\n", *argv);
addattr8(&req.n, sizeof(req), IFA_PROTO, proto);
} else {
diff --git a/lib/rt_names.c b/lib/rt_names.c
index 51d11fd0..b441e98f 100644
--- a/lib/rt_names.c
+++ b/lib/rt_names.c
@@ -242,9 +242,9 @@ static void rtnl_addrprot_initialize(void)
rtnl_addrprot_tab_initialized = true;
}
-const char *rtnl_addrprot_n2a(int id, char *buf, int len)
+const char *rtnl_addrprot_n2a(__u8 id, char *buf, int len)
{
- if (id < 0 || id >= 256 || numeric)
+ if (numeric)
goto numeric;
if (!rtnl_addrprot_tab_initialized)
rtnl_addrprot_initialize();
@@ -255,27 +255,19 @@ numeric:
return buf;
}
-int rtnl_addrprot_a2n(__u32 *id, const char *arg)
+int rtnl_addrprot_a2n(__u8 *id, const char *arg)
{
- static char *cache;
- static unsigned long res;
+ unsigned long res;
char *end;
int i;
- if (cache && strcmp(cache, arg) == 0) {
- *id = res;
- return 0;
- }
-
if (!rtnl_addrprot_tab_initialized)
rtnl_addrprot_initialize();
for (i = 0; i < 256; i++) {
if (rtnl_addrprot_tab[i] &&
strcmp(rtnl_addrprot_tab[i], arg) == 0) {
- cache = rtnl_addrprot_tab[i];
- res = i;
- *id = res;
+ *id = i;
return 0;
}
}
--
2.34.1
+1 -1
View File
@@ -1,5 +1,5 @@
# Host build of critical components, for Coverity Scan mostly
APPS = confd statd factory
APPS = confd execd factory keyack statd
.PHONY: alla
all:
+1 -1
View File
@@ -1,2 +1,2 @@
pkglibexec_SCRIPTS = bootstrap error load \
gen-hostkeys gen-admin-auth gen-hostname gen-interfaces gen-motd
gen-hostkeys gen-admin-auth gen-hostname gen-interfaces gen-motd gen-hardware
+6 -4
View File
@@ -98,6 +98,7 @@ factory()
# Create an overlay for /etc/hostname to change the default in an br2-external
gen-hostname >"$FACTORY_D/20-hostname.json"
gen-motd >"$FACTORY_D/20-motd.json"
gen-hardware >"$FACTORY_D/20-hardware.json"
# shellcheck disable=SC2086
gen-interfaces $GEN_IFACE_OPTS >"$FACTORY_D/20-interfaces.json"
@@ -212,21 +213,22 @@ sysrepoctl -s $SEARCH \
-i iana-if-type@2023-01-26.yang -g wheel -p 0660 \
-i iana-hardware@2018-03-13.yang -g wheel -p 0660 \
-i ietf-hardware@2018-03-13.yang -g wheel -p 0660 \
-e hardware-state \
-i infix-hardware@2024-01-18.yang -g wheel -p 0660 \
-i ieee802-dot1q-types@2022-10-29.yang -g wheel -p 0660 \
-i infix-ip@2023-09-14.yang -g wheel -p 0660 \
-i infix-if-type@2023-08-21.yang -g wheel -p 0660 \
-i infix-if-type@2024-01-29.yang -g wheel -p 0660 \
-i infix-routing@2024-01-09.yang -g wheel -p 0660 \
-i infix-interfaces@2023-09-19.yang -g wheel -p 0660 \
-i infix-interfaces@2024-01-15.yang -g wheel -p 0660 \
-e vlan-filtering \
-i ieee802-dot1ab-lldp@2022-03-15.yang -g wheel -p 0660 \
-i infix-lldp@2023-08-23.yang -g wheel -p 0660 \
-i infix-dhcp-client@2024-01-30.yang -g wheel -p 0660 \
-i infix-shell-type@2023-08-21.yang -g wheel -p 0660 \
-i infix-system@2023-10-19.yang -g wheel -p 0660 \
-i infix-system@2024-02-29.yang -g wheel -p 0660 \
-i infix-services@2023-10-16.yang -g wheel -p 0660 \
-i ieee802-ethernet-interface@2019-06-21.yang -g wheel -p 0660 \
-i infix-ethernet-interface@2024-01-22.yang -g wheel -p 0660 \
-i infix-ethernet-interface@2024-02-27.yang -g wheel -p 0660 \
-I "${INIT_DATA}"
rc=$?
+35
View File
@@ -0,0 +1,35 @@
#!/bin/sh
set -e
usb_ports=$(cat /run/system.json | jq -r '.["usb-ports"] | map(.name) | unique | join(" ")')
gen_port()
{
local port="$1"
cat <<EOF
{
"class": "infix-hardware:usb",
"name": "$port",
"state": {
"admin-state": "unlocked"
}
}
EOF
}
first=1
cat <<EOF
{
"ietf-hardware:hardware": {
"component": [
EOF
for port in $usb_ports; do
if [ $first -eq 0 ]; then
echo -n ','
fi
first=0;
gen_port "$port"
done
cat <<EOF
]}
}
EOF
+75 -54
View File
@@ -57,28 +57,29 @@ EOF
}
# shellcheck disable=SC3043
gen_interface()
gen_iface_json()
{
local ifname="$1"
local br="$2"
local iftype="$1"
local ifname="$2"
local br="$3"
cat <<EOF
,{
"name": "$ifname",
"type": "infix-if-type:ethernet",
"type": "$iftype",
EOF
if [ -n "$br" ]; then
cat <<EOF
cat <<EOF
"ietf-ip:ipv6": {
"enabled": false
},
"infix-interfaces:bridge-port": {
"bridge": "$br"
}
"enabled": false
},
"infix-interfaces:bridge-port": {
"bridge": "$br"
}
EOF
else
cat <<EOF
cat <<EOF
"ietf-ip:ipv6": {}
EOF
fi
@@ -88,52 +89,71 @@ EOF
EOF
}
filter_iface_ports()
{
ifaces="$1"
ports="$2"
iface_devs=""
for phy in $ifaces; do
found=""
for port in $ports; do
if [ "$port" = "$phy" ]; then
found=true
break
fi
done
if [ -z "$found" ]; then
iface_devs="$iface_devs $phy"
fi
done
echo -n "$iface_devs"
}
while [ "$1" != "" ]; do
case $1 in
-4)
ipv4=true
;;
-b)
bridge="$2"
shift
;;
-d)
dhcp=true
;;
-h)
usage
;;
*)
break
-4)
ipv4=true
;;
-b)
bridge="$2"
shift
;;
-d)
dhcp=true
;;
-h)
usage
;;
*)
break
esac
shift
done
phys_ifaces=$(ip -d -j link show | jq -r '
.[] |
select(.link_type == "ether") |
select(.group != "internal") |
select(has("parentbus")) |
.ifname' | sort -V)
.[] |
select(.link_type == "ether") |
select(.group != "internal") |
select(has("parentbus")) |
select(.parentbus != "virtio") |
.ifname' | sort -V)
virtio_ifaces=$(ip -d -j link show | jq -r '
.[] |
select(.link_type == "ether") |
select(.group != "internal") |
select(has("parentbus")) |
select(.parentbus == "virtio") |
.ifname' | sort -V)
ports=$(ip -d -j link show group port | jq -r '
.[] |
select(.link_type == "ether") |
select(.group != "internal") |
select(has("parentbus")) |
.ifname' | sort -V)
ifaces=""
for phy in $phys_ifaces; do
found=""
for port in $ports; do
if [ "$port" = "$phy" ]; then
found=true
break
fi
done
if [ -z "$found" ]; then
ifaces="$ifaces $phy"
fi
done
.[] |
select(.link_type == "ether") |
select(.group != "internal") |
select(has("parentbus")) |
.ifname' | sort -V)
eth_ifaces="$(filter_iface_ports "$phys_ifaces" "$ports")"
ethlike_ifaces="$(filter_iface_ports "$virtio_ifaces" "$ports")"
cat <<EOF
{
@@ -156,20 +176,21 @@ if [ -n "$bridge" ]; then
"name": "$bridge",
"type": "infix-if-type:bridge",
"ietf-ip:ipv4": {
"infix-ip:autoconf": {
"infix-ip:autoconf": {
"enabled": $ipv4
}
},
"ietf-ip:ipv6": {
"enabled": true
"ietf-ip:ipv6": {
"enabled": true
}
EOF
fi
cat <<EOF
}
$(for iface in $ifaces; do gen_interface "$iface"; done)
$(for iface in $ports; do gen_interface "$iface" "$bridge"; done)
$(for iface in $eth_ifaces; do gen_iface_json "infix-if-type:ethernet" "$iface"; done)
$(for iface in $ethlike_ifaces; do gen_iface_json "infix-if-type:etherlike" "$iface"; done)
$(for iface in $ports; do gen_iface_json "infix-if-type:ethernet" "$iface" "$bridge"; done)
]
EOF
if [ "$dhcp" = "true" ] && [ -n "$bridge" ]; then
+1 -1
View File
@@ -6,7 +6,7 @@ if [ -f /etc/motd ]; then
cat <<EOF
{
"ietf-system:system": {
"infix-system:motd": "$(sed ':a;N;$!ba;s/\n/\\n/g;s/\t/\\t/g' /etc/motd)"
"infix-system:motd-banner": "$(base64 -w0 /etc/motd)"
}
}
EOF
+40
View File
@@ -21,11 +21,20 @@ AC_CONFIG_LIBOBJ_DIR(lib)
AC_REPLACE_FUNCS(vasprintf)
AC_REPLACE_FUNCS(asprintf)
#
# Check feature flags
#
AC_ARG_ENABLE(containers,
AS_HELP_STRING([--enable-containers], [Enable support for containers]),,[
enable_containers=no])
AC_ARG_WITH(login-shell,
AS_HELP_STRING([--with-login-shell=shell], [Login shell for new users, default: /bin/false]),
[login_shell=$withval], [login_shell=yes])
AS_IF([test "x$enable_containers" = "xyes"], [
AC_DEFINE(CONTAINERS, 1, [Built with container support])])
AS_IF([test "x$with_login_shell" != "xno"], [
AS_IF([test "x$login_shell" = "xyes"], [login_shell=/bin/false])
AC_DEFINE_UNQUOTED(LOGIN_SHELL, "$login_shell", [Default: /bin/false])],[
@@ -41,6 +50,9 @@ PKG_CHECK_MODULES([libite], [libite >= 2.5.0])
PKG_CHECK_MODULES([sysrepo], [sysrepo >= 2.2.36])
PKG_CHECK_MODULES([libsrx], [libsrx >= 1.0.0])
# Control build with automake flags
AM_CONDITIONAL(CONTAINERS, [test "x$enable_containers" != "xno"])
# Plugin installation path for sysrepo-plugind
PKG_CHECK_VAR([srpdplugindir], [sysrepo], [SRPD_PLUGINS_PATH])
AC_SUBST(srpdplugindir)
@@ -72,3 +84,31 @@ SYSCONFDIR=`eval echo $SYSCONFDIR`
AC_SUBST(SYSCONFDIR)
AC_OUTPUT
cat <<EOF
------------------ Summary ------------------
$PACKAGE_NAME version $PACKAGE_VERSION
Prefix................: $prefix
Exec prefix...........: $eprefix
Sysconfdir............: `eval echo $sysconfdir`
Localstatedir.........: `eval echo $localstatedir`
Default fstab.........: `eval echo $fstab`
System environment....: ${sysconfig_path:-${sysconfig}}
C Compiler............: $CC $CFLAGS $CPPFLAGS $LDFLAGS $LIBS
Linker................: $LD $LLDP_LDFLAGS $LLDP_BIN_LDFLAGS $LDFLAGS $LIBS
Optional features:
Container support ....: $enable_containers
Login shell ..........: $login_shell
------------- Compiler version --------------
$($CC --version || true)
-------------- Linker version ---------------
$($LD --version || true)
---------------------------------------------
Check the above options and compile with:
${MAKE-make}
EOF
+18 -13
View File
@@ -1,4 +1,3 @@
CFLAGS += -Wall -Wextra -Werror -Wno-unused-parameter
AM_CPPFLAGS = -D_DEFAULT_SOURCE -D_XOPEN_SOURCE -D_GNU_SOURCE -DYANG_PATH_=\"$(YANGDIR)\"
CLEANFILES = $(rauc_installer_sources)
plugindir = $(srpdplugindir)
@@ -7,12 +6,12 @@ plugin_LTLIBRARIES = confd-plugin.la
confd_plugin_la_LDFLAGS = -module -avoid-version -shared
confd_plugin_la_CFLAGS = \
$(augeas_CFLAGS) \
$(augeas_CFLAGS) \
$(glib_CFLAGS) \
$(jansson_CFLAGS) \
$(libite_CFLAGS) \
$(sysrepo_CFLAGS) \
$(libsrx_CFLAGS) \
$(jansson_CFLAGS) \
$(libite_CFLAGS) \
$(sysrepo_CFLAGS) \
$(libsrx_CFLAGS) \
$(CFLAGS)
confd_plugin_la_LIBADD = \
@@ -24,16 +23,22 @@ confd_plugin_la_LIBADD = \
$(libsrx_LIBS)
confd_plugin_la_SOURCES = \
core.c core.h \
dagger.c dagger.h \
ietf-interfaces.c \
base64.c base64.h \
core.c core.h \
dagger.c dagger.h \
ietf-interfaces.c \
ietf-system.c \
ietf-factory-default.c \
ietf-routing.c \
infix-dhcp.c \
infix-services.c \
infix-services.c \
infix-factory.c \
infix-system-software.c
infix-system-software.c \
ietf-hardware.c
if CONTAINERS
confd_plugin_la_SOURCES += infix-containers.c cni.c cni.h
endif
rauc_installer_sources = \
rauc-installer.c rauc-installer.h
@@ -44,9 +49,9 @@ EXTRA_DIST = de.pengutronix.rauc.Installer.xml
$(rauc_installer_sources): de.pengutronix.rauc.Installer.xml
$(AM_V_GEN) gdbus-codegen \
--generate-c-code rauc-installer \
--c-generate-autocleanup all \
--c-generate-autocleanup all \
--c-namespace Rauc \
--interface-prefix de.pengutronix.rauc. \
--interface-prefix de.pengutronix.rauc. \
$<
nodist_confd_plugin_la_SOURCES = $(rauc_installer_sources)
+210
View File
@@ -0,0 +1,210 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "base64.h"
/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi>
*/
static const unsigned char base64_table[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* base64_encode - Base64 encode
* @src: Data to be encoded
* @len: Length of the data to be encoded
* @out_len: Pointer to output length variable, or %NULL if not used
*
* Returns:
* Allocated buffer of out_len bytes of encoded data, %NULL on failure
*
* Caller is responsible for freeing the returned buffer. Returned
* buffer is nul terminated to make it easier to use as a C string.
* The nul terminator is not included in out_len.
*/
unsigned char *base64_encode(const unsigned char *src, size_t len, size_t *out_len)
{
unsigned char *out, *pos;
const unsigned char *end, *in;
size_t olen;
int line_len;
olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */
olen += olen / 72; /* line feeds */
olen++; /* nul termination */
if (olen < len)
return NULL; /* integer overflow */
out = malloc(olen);
if (out == NULL)
return NULL;
end = src + len;
in = src;
pos = out;
line_len = 0;
while (end - in >= 3) {
*pos++ = base64_table[in[0] >> 2];
*pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
*pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
*pos++ = base64_table[in[2] & 0x3f];
in += 3;
line_len += 4;
if (line_len >= 72) {
*pos++ = '\n';
line_len = 0;
}
}
if (end - in) {
*pos++ = base64_table[in[0] >> 2];
if (end - in == 1) {
*pos++ = base64_table[(in[0] & 0x03) << 4];
*pos++ = '=';
} else {
*pos++ = base64_table[((in[0] & 0x03) << 4) |
(in[1] >> 4)];
*pos++ = base64_table[(in[1] & 0x0f) << 2];
}
*pos++ = '=';
line_len += 4;
}
if (line_len)
*pos++ = '\n';
*pos = '\0';
if (out_len)
*out_len = pos - out;
return out;
}
/**
* base64_decode - Base64 decode
* @src: Data to be decoded
* @len: Length of the data to be decoded
* @out_len: Pointer to output length variable
*
* Returns:
* Allocated buffer of out_len bytes of decoded data, or %NULL on failure
* Caller is responsible for freeing the returned buffer.
*/
unsigned char *base64_decode(const unsigned char *src, size_t len, size_t *out_len)
{
unsigned char dtable[256], *out, *pos, block[4], tmp;
size_t i, count, olen;
int pad = 0;
memset(dtable, 0x80, 256);
for (i = 0; i < sizeof(base64_table) - 1; i++)
dtable[base64_table[i]] = (unsigned char) i;
dtable['='] = 0;
count = 0;
for (i = 0; i < len; i++) {
if (dtable[src[i]] != 0x80)
count++;
}
if (count == 0 || count % 4) {
errno = EINVAL;
return NULL;
}
olen = count / 4 * 3;
pos = out = malloc(olen);
if (out == NULL)
return NULL;
count = 0;
for (i = 0; i < len; i++) {
tmp = dtable[src[i]];
if (tmp == 0x80)
continue;
if (src[i] == '=')
pad++;
block[count] = tmp;
count++;
if (count == 4) {
*pos++ = (block[0] << 2) | (block[1] >> 4);
*pos++ = (block[1] << 4) | (block[2] >> 2);
*pos++ = (block[2] << 6) | block[3];
count = 0;
if (pad) {
if (pad == 1)
pos--;
else if (pad == 2)
pos -= 2;
else {
/* Invalid padding */
free(out);
return NULL;
}
break;
}
}
}
if (out_len)
*out_len = pos - out;
return out;
}
#ifdef UNITTEST
#include <err.h>
#include <getopt.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int c, decode = 0, bytes;
char *buf, *ptr;
struct stat st;
size_t len;
FILE *fp;
while ((c = getopt(argc, argv, "dh")) != EOF) {
switch (c) {
case 'd':
decode = 1;
break;
case 'h':
default:
printf("%s [-d] file", argv[0]);
return 0;
}
}
if (argc <= optind)
errx(1, "Missing file arg.");
fp = fopen(argv[optind], "r");
if (!fp)
err(1, "failed opening %s", argv[optind]);
if (fstat(fileno(fp), &st))
err(1, "failed fstat %s", argv[optind]);
len = st.st_size;
buf = malloc(len);
bytes = fread(buf, 1, len, fp);
if (bytes != (int)len)
err(1, "failed reading entire file");
if (decode)
ptr = base64_decode(buf, bytes, NULL);
else
ptr = base64_encode(buf, bytes, NULL);
puts(ptr);
free(ptr);
fclose(fp);
return 0;
}
#endif
+11
View File
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef BASE64_H_
#define BASE64_H_
#include <stdlib.h>
unsigned char *base64_encode(const unsigned char *src, size_t len, size_t *out_len);
unsigned char *base64_decode(const unsigned char *src, size_t len, size_t *out_len);
#endif /* BASE64_H_ */
+450
View File
@@ -0,0 +1,450 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <net/if.h>
#include <srx/common.h>
#include <srx/lyx.h>
#include <srx/srx_val.h>
#include "core.h"
#include "cni.h"
#define CNI_NAME "/etc/cni/net.d/%s.conflist"
/*
* When an interface has been handed off to a container it is moved to
* another network namespace. This function asks podman for the PID of
* the container that currently hosts the interface.
*/
pid_t cni_find(const char *ifname)
{
char buf[32] = { 0 };
pid_t pid = 0;
FILE *pp;
if (fexistf("/sys/class/net/%s", ifname))
return 0; /* it's right here, not in a container */
pp = popenf("r", "container --net %s find", ifname);
if (!pp)
return 0;
if (fgets(buf, sizeof(buf), pp)) {
chomp(buf);
pid = atoi(buf);
}
pclose(pp);
return pid;
}
/*
* This function takes the PID from cni_find() and figures out
* the name of our ifname inside the container. For CNI/podman they
* save the host's name as the interface's ifalias.
*/
static char *cni_find_ifalias(pid_t pid, const char *ifname)
{
static char buf[IFNAMSIZ + 2];
char *ptr = NULL;
FILE *pp;
pp = popenf("r", "container --net %s find ifname %d", ifname, pid);
if (!pp)
return NULL;
buf[0] = 0;
if (fgets(buf, sizeof(buf), pp)) {
chomp(buf);
ptr = buf;
}
pclose(pp);
return ptr;
}
/*
* Sometimes we need to perform some kind of 'ip link dev IFNAME'
* command to find buried interface data. This function handles
* the fact that interfaces sometimes are on vacation in another
* network namespace (container).
*/
FILE *cni_popen(const char *fmt, const char *ifname)
{
char cmd[strlen(fmt) + 64];
static char *ifalias;
pid_t pid;
pid = cni_find(ifname);
if (!pid)
return popenf("re", fmt, ifname);
ifalias = cni_find_ifalias(pid, ifname);
if (!ifalias)
return NULL;
snprintf(cmd, sizeof(cmd), fmt, ifalias);
return popenf("re", "nsenter -t %d -n %s", pid, cmd);
}
static bool iface_is_cni(const char *ifname, struct lyd_node *node, const char **type)
{
struct lyd_node *net = lydx_get_child(node, "container-network");
if (net) {
if (type)
*type = lydx_get_cattr(net, "type");
return true;
}
return false;
}
static void cni_gen_addrs(struct lyd_node *ip, FILE *fp, int *ipam)
{
struct lyd_node *addr;
if (!lydx_is_enabled(ip, "enabled"))
return;
LYX_LIST_FOR_EACH(lyd_child(ip), addr, "address") {
struct lyd_node *ip = lydx_get_child(addr, "ip");
struct lyd_node *len = lydx_get_child(addr, "prefix-length");
if (*ipam == 0)
fprintf(fp,
",\n \"ipam\": {\n"
" \"type\": \"static\",\n"
" \"addresses\": [\n");
fprintf(fp, "%s { \"address\": \"%s/%s\" }",
*ipam ? ",\n" : "", lyd_get_value(ip), lyd_get_value(len));
*ipam = 1;
}
}
#if 0 /* Unused for now, use container specific global dns and search settings instead. */
static void cni_gen_dns(struct lyd_node *net, FILE *fp, int *first)
{
struct lyd_node *dns;
dns = lydx_get_child(net, "dns");
if (dns) {
struct lyd_node *node;
fprintf(fp, ",\n \"dns\": {");
*first = 1;
LYX_LIST_FOR_EACH(lyd_child(dns), node, "nameservers") {
if (*first)
fprintf(fp, "\n \"nameservers\": [ ");
else
fprintf(fp, ", ");
fprintf(fp, "\"%s\"", lyd_get_value(node));
(*first)++;
}
if (*first > 1)
fprintf(fp, " ]");
node = lydx_get_child(dns, "domain");
if (node) {
fprintf(fp, "%s\n \"domain\": \"%s\"",
*first > 1 ? "," : "", lyd_get_value(node));
(*first)++;
}
LYX_LIST_FOR_EACH(lyd_child(net), node, "search") {
if (*first)
fprintf(fp, "%s\n \"search\": [ ", *first > 1 ? "," : "");
else
fprintf(fp, ", ");
fprintf(fp, "\"%s\"", lyd_get_value(node));
*first = 0;
}
fprintf(fp, "%s\n }", *first ? "" : "]");
}
}
#endif
/*
* Set up IP masquerading bridge which acts as a gateway for nodes behind it.
* Default subnet, if one is missing in configuration, is: 10.88.0.0/16
*/
static int cni_bridge(struct lyd_node *net, const char *ifname)
{
struct lyd_node *node;
int first = 1;
FILE *fp;
fp = fopenf("w", CNI_NAME, ifname);
if (!fp) {
ERRNO("Failed creating container bridge " CNI_NAME, ifname);
return -EIO;
}
fprintf(fp, "{\n"
" \"cniVersion\": \"1.0.0\",\n"
" \"name\": \"%s\",\n"
" \"plugins\": [\n"
" {\n"
" \"type\": \"bridge\",\n"
" \"bridge\": \"%s\",\n"
" \"isGateway\": true,\n"
" \"ipMasq\": true,\n"
" \"hairpinMode\": true,\n"
// " \"dataDir\": \"/run/containers/networks\",\n"
" \"ipam\": {\n"
" \"type\": \"host-local\"", ifname, ifname);
LYX_LIST_FOR_EACH(lyd_child(net), node, "route") {
struct lyd_node *subnet = lydx_get_child(node, "subnet");
struct lyd_node *gateway = lydx_get_child(node, "gateway");
if (first)
fprintf(fp, ",\n \"routes\": [\n");
else
fprintf(fp, ",\n");
fprintf(fp, " {\n"
" \"dst\": \"%s\"%s\n", lyd_get_value(subnet), gateway ? "," : "");
if (gateway)
fprintf(fp, " \"gw\": \"%s\"\n", lyd_get_value(gateway));
fprintf(fp, " }");
first = 0;
}
if (!first)
fprintf(fp, " ]");
else
fprintf(fp, ",\n \"routes\": [ { \"dst\": \"0.0.0.0/0\" } ]");
first = 1;
LYX_LIST_FOR_EACH(lyd_child(net), node, "subnet") {
struct lyd_node *subnet = lydx_get_child(node, "subnet");
struct lyd_node *gateway = lydx_get_child(node, "gateway");
if (first)
fprintf(fp, ",\n \"ranges\": [\n");
else
fprintf(fp, ",\n");
fprintf(fp, " [{\n"
" \"subnet\": \"%s\"%s\n", lyd_get_value(subnet), gateway ? "," : "");
if (gateway)
fprintf(fp, " \"gateway\": \"%s\"\n", lyd_get_value(gateway));
fprintf(fp, " }]");
first = 0;
}
if (!first)
fprintf(fp, "\n ]");
else
/* Default is a customary docker0 local network */
fprintf(fp, ",\n \"ranges\": [ [{ \"subnet\": \"172.17.0.0/16\" }] ]");
fprintf(fp,
"\n }\n" /* /ipam */
" },\n" /* /bridge */
" {\n"
" \"type\": \"portmap\",\n"
" \"capabilities\": {\n"
" \"portMappings\": true\n"
" }\n"
" },\n" /* /portmap */
" {\n"
" \"type\": \"firewall\"\n"
" },\n" /* /firewall */
" {\n"
" \"type\": \"tuning\"\n"
" }\n" /* /tuning */
" ]\n"
"}\n");
if (fclose(fp))
return -errno;
return 0;
}
static int cni_host(struct lyd_node *net, const char *ifname)
{
struct lyd_node *node, *ip;
int addr = 0, route = 0;
FILE *fp;
fp = fopenf("w", CNI_NAME, ifname);
if (!fp) {
ERRNO("Failed creating container interface " CNI_NAME, ifname);
return -EIO;
}
fprintf(fp, "{\n"
" \"cniVersion\": \"1.0.0\",\n"
" \"name\": \"%s\",\n"
" \"plugins\": [\n"
" {\n"
" \"type\": \"host-device\",\n"
" \"device\": \"%s\"", ifname, ifname);
ip = lydx_get_child(lyd_parent(net), "ipv4");
if (ip)
cni_gen_addrs(ip, fp, &addr);
ip = lydx_get_child(lyd_parent(net), "ipv6");
if (ip)
cni_gen_addrs(ip, fp, &addr);
if (addr)
fprintf(fp, "\n ]");
LYX_LIST_FOR_EACH(lyd_child(net), node, "route") {
struct lyd_node *subnet = lydx_get_child(node, "subnet");
struct lyd_node *gateway = lydx_get_child(node, "gateway");
if (route)
fprintf(fp, ",\n \"routes\": [\n");
else
fprintf(fp, ",\n");
fprintf(fp, " {\n"
" \"dst\": \"%s\"%s\n", lyd_get_value(subnet), gateway ? "," : "");
if (gateway)
fprintf(fp, " \"gw\": \"%s\"\n", lyd_get_value(gateway));
fprintf(fp, " }");
route = 0;
}
if (route)
fprintf(fp, " ]");
fprintf(fp,
"%s"
" }\n"
" ]\n"
"}\n", (addr || route) ? "\n }\n" : "");
if (fclose(fp))
return -errno;
return 0;
}
static int iface_gen_cni(const char *ifname, struct lyd_node *cif)
{
struct lyd_node *net = lydx_get_child(cif, "container-network");
const char *type = lydx_get_cattr(net, "type");
/*
* klish/sysrepo does not seem to call update callbacks for
* presence containers, so we have to be prepared for the
* worst here and perform late type inference. What works:
*
* edit container-network # callback called
*
* What doesn't work:
*
* set container-network # callback not called
*
* Funnily enough, "show running-config" shows the empty
* "container-network": {}, so someone does their job.
*/
if (!type) {
const char *iftype = lydx_get_cattr(cif, "type");
if (iftype && !strcmp(iftype, "infix-if-type:bridge"))
type = "bridge";
else
type = "host";
}
if (!strcmp(type, "host"))
return cni_host(net, ifname);
if (!strcmp(type, "bridge"))
return cni_bridge(net, ifname);
ERROR("Unknown container network type %s, skipping.", type);
return 0;
}
int cni_netdag_gen_iface(struct dagger *net, const char *ifname,
struct lyd_node *dif, struct lyd_node *cif)
{
const char *cni_type = NULL;
if (iface_is_cni(ifname, cif, &cni_type)) {
int err;
err = iface_gen_cni(ifname, cif);
if (err)
return err;
if (cni_type && !strcmp(cni_type, "bridge"))
return 1; /* CNI bridges are managed by podman */
} else if (iface_is_cni(ifname, dif, &cni_type)) {
FILE *fp;
/* No longer a container-network, clean up. */
fp = dagger_fopen_current(net, "exit", ifname, 30, "cni.sh");
if (!fp)
return -EIO;
fprintf(fp, "container -a -f delete network %s >/dev/null\n", ifname);
fclose(fp);
if (cni_type && !strcmp(cni_type, "bridge"))
return 1; /* CNI bridges are managed by podman */
}
return 0;
}
int cni_ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path)
{
sr_val_t inferred = { .type = SR_STRING_T };
struct lyd_node *node, *net;
sr_error_t err = SR_ERR_OK;
char *xpath, *iftype;
sr_data_t *cfg;
xpath = xpath_base(path);
if (!xpath)
return SR_ERR_SYS;
err = sr_get_data(session, path, 0, 0, 0, &cfg);
if (err)
goto err;
node = lydx_get_descendant(cfg->tree, "interfaces", "interface", NULL);
if (!node)
goto out;
net = lydx_get_child(node, "container-network");
if (!net)
goto out;
if (lydx_get_cattr(net, "type"))
goto out; /* CNI type is already set */
/* Infer from ietf-interface type, reduces typing */
iftype = srx_get_str(session, "%s/type", xpath);
if (iftype && !strcmp(iftype, "infix-if-type:bridge"))
inferred.data.string_val = "bridge";
else
inferred.data.string_val = "host";
err = srx_set_item(session, &inferred, 0, "%s/type", path);
if (err)
ERROR("failed setting container-network type %s, err %d: %s",
inferred.data.string_val, err, sr_strerror(err));
if (iftype)
free(iftype);
out:
sr_release_data(cfg);
err:
free(xpath);
return err;
}
+23
View File
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef CONFD_CNI_H_
#define CONFD_CNI_H_
#include "dagger.h"
#ifdef CONTAINERS
pid_t cni_find(const char *ifname);
FILE *cni_popen(const char *fmt, const char *ifname);
int cni_netdag_gen_iface(struct dagger *net, const char *ifname, struct lyd_node *dif, struct lyd_node *cif);
int cni_ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path);
#else
static inline pid_t cni_find(const char *ifname) { return 0; }
static inline FILE *cni_popen(const char *fmt, const char *ifname) { return popenf("re", fmt, ifname); }
static inline int cni_netdag_gen_iface(struct dagger *net, const char *ifname, struct lyd_node *dif, struct lyd_node *cif) { return 0; }
static inline int cni_ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path) { return 0; }
#endif
#endif /* CONFD_CNI_H_ */
+43 -12
View File
@@ -15,7 +15,7 @@ uint32_t core_hook_prio(void)
}
int core_startup_save(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
if (systemf("sysrepocfg -X/cfg/startup-config.cfg -d startup -f json"))
return SR_ERR_SYS;
@@ -23,8 +23,24 @@ int core_startup_save(sr_session_ctx_t *session, uint32_t sub_id, const char *mo
return SR_ERR_OK;
}
static const char *ev2str(sr_event_t ev)
{
switch (ev) {
case SR_EV_UPDATE: return "UPDATE";
case SR_EV_CHANGE: return "CHANGE";
case SR_EV_DONE: return "DONE";
case SR_EV_ABORT: return "ABORT";
case SR_EV_ENABLED: return "ENABLED";
case SR_EV_RPC: return "ABORT";
default:
break;
}
return "UNKNOWN";
}
int core_pre_hook(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
return 0;
}
@@ -35,7 +51,7 @@ int core_pre_hook(sr_session_ctx_t *session, uint32_t sub_id, const char *module
* For details, see https://github.com/sysrepo/sysrepo/issues/2188
*/
int core_post_hook(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
static int num_changes = 0;
@@ -51,10 +67,13 @@ int core_post_hook(sr_session_ctx_t *session, uint32_t sub_id, const char *modul
break;
return SR_ERR_OK;
default:
ERROR("core_commit_done() should not be called with event %d", event);
ERROR("core_post_hook() should not be called with event %s", ev2str(event));
return SR_ERR_SYS;
}
/* Everything done, including interfaces, launch all container scripts */
infix_containers_post_hook(session, priv);
/* skip reload in bootstrap, implicit reload in runlevel change */
if (systemf("runlevel >/dev/null 2>&1"))
return SR_ERR_OK;
@@ -83,9 +102,9 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
/* Save context with default running config datastore for all our models */
*priv = (void *)&confd;
confd.session = session;
confd.conn = sr_session_get_connection(session);
confd.sub = NULL;
confd.fsub = NULL;
confd.conn = sr_session_get_connection(session);
confd.sub = NULL;
confd.fsub = NULL;
if (!confd.conn)
goto err;
@@ -99,15 +118,20 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
rc = sr_session_start(confd.conn, SR_DS_CANDIDATE, &confd.cand);
if (rc)
goto err;
confd.aug = aug_init(NULL, "", 0);
if (!confd.aug)
goto err;
confd.root = json_load_file("/run/system.json", 0, NULL);
if (!confd.root)
goto err;
rc = ietf_interfaces_init(&confd);
if (rc)
goto err;
rc = ietf_system_init(&confd);
if (rc)
goto err;
rc = infix_containers_init(&confd);
if (rc)
goto err;
rc = infix_dhcp_init(&confd);
@@ -117,23 +141,29 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
if (rc)
goto err;
rc = ietf_factory_default_init(&confd);
if(rc)
if (rc)
goto err;
rc = ietf_routing_init(&confd);
if(rc)
if (rc)
goto err;
rc = infix_system_sw_init(&confd);
if (rc)
goto err;
rc = infix_services_init(&confd);
if (rc)
goto err;
rc = ietf_hardware_init(&confd);
if (rc)
goto err;
/* YOUR_INIT GOES HERE */
return SR_ERR_OK;
err:
ERROR("init failed: %s", sr_strerror(rc));
if (confd.root)
json_decref(confd.root);
sr_unsubscribe(confd.sub);
sr_unsubscribe(confd.fsub);
@@ -144,7 +174,8 @@ void sr_plugin_cleanup_cb(sr_session_ctx_t *session, void *priv)
{
struct confd *confd = (struct confd *)priv;
sr_unsubscribe(confd->sub);
sr_unsubscribe(confd->fsub);
sr_unsubscribe(confd->sub);
sr_unsubscribe(confd->fsub);
json_decref(confd->root);
closelog();
}
+37 -1
View File
@@ -21,6 +21,8 @@
#include <sysrepo/values.h>
#include <sysrepo/xpath.h>
#include <jansson.h>
#include <srx/common.h>
#include <srx/helpers.h>
#include <srx/systemv.h>
@@ -40,6 +42,26 @@ static inline void print_val(sr_val_t *val)
free(str);
}
static inline char *xpath_base(const char *xpath)
{
char *path, *ptr;
if (!xpath)
return NULL;
path = strdup(xpath);
if (!path)
return NULL;
if (!(ptr = strstr(path, "]/"))) {
free(path);
return NULL;
}
ptr[1] = 0;
return path;
}
#define REGISTER_CHANGE(s,m,x,f,c,a,u) \
if ((rc = register_change(s, m, x, f, c, a, u))) \
goto fail
@@ -59,7 +81,7 @@ struct confd {
sr_conn_ctx_t *conn;
sr_subscription_ctx_t *sub;
sr_subscription_ctx_t *fsub; /* factory-default sub */
json_t *root;
augeas *aug;
struct dagger netdag;
};
@@ -129,6 +151,17 @@ int ietf_interfaces_init(struct confd *confd);
/* ietf-system.c */
int ietf_system_init(struct confd *confd);
/* infix-containers.c */
#ifdef CONTAINERS
int infix_containers_init(struct confd *confd);
void infix_containers_pre_hook(sr_session_ctx_t *session, struct confd *confd);
void infix_containers_post_hook(sr_session_ctx_t *session, struct confd *confd);
#else
static inline int infix_containers_init(struct confd *confd) { return 0; }
static inline void infix_containers_pre_hook(sr_session_ctx_t *session, struct confd *confd) {}
static inline void infix_containers_post_hook(sr_session_ctx_t *session, struct confd *confd) {}
#endif
/* infix-dhcp.c */
int infix_dhcp_init(struct confd *confd);
@@ -147,4 +180,7 @@ int infix_system_sw_init(struct confd *confd);
/* infix-services.c */
int infix_services_init(struct confd *confd);
/* ietf-hardware.c */
int ietf_hardware_init(struct confd *confd);
#endif /* CONFD_CORE_H_ */
+261
View File
@@ -0,0 +1,261 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <jansson.h>
#include <srx/common.h>
#include <srx/lyx.h>
#include <srx/srx_module.h>
#include <srx/srx_val.h>
#include <jansson.h>
#include <ftw.h>
#include <libgen.h>
#include <limits.h>
#include "core.h"
#define XPATH_BASE_ "/ietf-hardware:hardware"
static int dir_cb(const char *fpath, const struct stat *sb,
int typeflag, struct FTW *ftwbuf)
{
char *filename;
if (typeflag == FTW_DP)
return 0;
filename = basename((char *)fpath);
if (!strcmp(filename, "authorized_default") || !strcmp(filename, "authorized")) {
if (writedf(1, "w", "%s", fpath)) {
ERROR("Failed to authorize %s", fpath);
return FTW_STOP;
}
}
return 0;
}
static bool usb_authorize(struct json_t *root, const char *name, int enabled)
{
json_t *usb_port, *usb_ports;
int index;
usb_ports = json_object_get(root, "usb-ports");
if (!usb_ports) /* No Infix controlled USB ports is ok */
return 0;
json_array_foreach(usb_ports, index, usb_port) {
struct json_t *jname, *jpath;
const char *path;
jname = json_object_get(usb_port, "name");
if (!jname || !json_is_string(jname)) {
ERROR("Did not find USB hardware port (name) for %s", name);
continue;
}
if (!strcmp(name, json_string_value(jname))) {
jpath = json_object_get(usb_port, "path");
if (!jpath || !json_is_string(jpath)) {
ERROR("Did not find USB hardware port (path) for %s", name);
continue;
}
path = json_string_value(jpath);
if (!enabled) {
if (fexist(path)) {
if (writedf(0, "w", "%s", path)) {
ERROR("Failed to unauthorize %s", path);
return 1;
}
}
} else {
char *rpath, *path_dup, *dir;
path_dup = strdup(path);
if (!path_dup) {
ERROR("Failed to allocate memory.");
return 1;
}
dir = dirname((char *)path_dup);
rpath = realpath(dir, NULL);
if (rpath) {
nftw(rpath, dir_cb, 0, FTW_DEPTH | FTW_PHYS);
free(rpath);
}
free(path_dup);
}
}
}
return 0;
}
static char *component_xpath(const char *xpath)
{
char *path, *ptr;
if (!xpath)
return NULL;
path = strdup(xpath);
if (!path)
return NULL;
if (!(ptr = strstr(path, "]/"))) {
free(path);
return NULL;
}
ptr[1] = 0;
return path;
}
static int hardware_cand_infer_class(json_t *root, sr_session_ctx_t *session, const char *path)
{
sr_val_t inferred = { .type = SR_STRING_T };
struct json_t *usb_ports, *usb_port;
sr_error_t err = SR_ERR_OK;
char *name, *class;
char *xpath;
int index;
xpath = component_xpath(path);
if (!xpath)
return SR_ERR_SYS;
class = srx_get_str(session, "%s/class", xpath);
if (class) {
free(class);
goto out_free_xpath;
}
name = srx_get_str(session, "%s/name", xpath);
if (!name) {
err = SR_ERR_INTERNAL;
goto out_free_xpath;
}
usb_ports = json_object_get(root, "usb-ports");
if (!usb_ports)
goto out_free_name; /* No USB-ports is OK */
json_array_foreach(usb_ports, index, usb_port) {
struct json_t *n = json_object_get(usb_port, "name");
if (!n || !json_is_string(n)) {
ERROR("Did not find hardware port for %s", name);
continue;
}
if (!strcmp(name, json_string_value(n))) {
inferred.data.string_val = "infix-hardware:usb";
err = srx_set_item(session, &inferred, 0,
"%s/class", xpath);
break;
}
}
out_free_name:
free(name);
out_free_xpath:
free(xpath);
return err;
}
static int hardware_cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
sr_change_iter_t *iter;
sr_change_oper_t op;
sr_val_t *old, *new;
sr_error_t err = SR_ERR_OK;
struct confd *confd = (struct confd *)priv;
switch (event) {
case SR_EV_UPDATE:
case SR_EV_CHANGE:
break;
default:
return SR_ERR_OK;
}
err = sr_dup_changes_iter(session, "/ietf-hardware:hardware/component//*", &iter);
if (err)
return err;
while (sr_get_change_next(session, iter, &op, &old, &new) == SR_ERR_OK) {
switch (op) {
case SR_OP_CREATED:
case SR_OP_MODIFIED:
break;
default:
continue;
}
err = hardware_cand_infer_class(confd->root, session, new->xpath);
if (err)
break;
}
sr_free_change_iter(iter);
return err;
}
static int change_hardware(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
struct lyd_node *diff, *difs = NULL, *dif = NULL, *cifs = NULL, *cif = NULL;
sr_data_t *cfg;
int rc = SR_ERR_OK;
struct confd *confd = (struct confd *)priv;
if (event != SR_EV_DONE)
return SR_ERR_OK;
rc = sr_get_data(session, XPATH_BASE_ "//.", 0, 0, 0, &cfg);
if (rc)
goto err;
rc = srx_get_diff(session, &diff);
if (rc)
goto err_release_data;
cifs = lydx_get_descendant(cfg->tree, "hardware", "component", NULL);
difs = lydx_get_descendant(diff, "hardware", "component", NULL);
LYX_LIST_FOR_EACH(difs, dif, "component") {
enum lydx_op op;
struct lyd_node *state;
const char *admin_state;
const char *class, *name;
op = lydx_get_op(dif);
name = lydx_get_cattr(dif, "name");
if (op == LYDX_OP_DELETE) {
if (usb_authorize(confd->root, name, 0)) {
rc = SR_ERR_INTERNAL;
goto err_release_diff;
}
continue;
}
LYX_LIST_FOR_EACH(cifs, cif, "component") {
if (strcmp(name, lydx_get_cattr(cif, "name")))
continue;
class = lydx_get_cattr(cif, "class");
if (strcmp(class, "infix-hardware:usb")) {
continue;
}
state = lydx_get_child(dif, "state");
admin_state = lydx_get_cattr(state, "admin-state");
if (usb_authorize(confd->root, name, !strcmp(admin_state, "unlocked"))) {
rc = SR_ERR_INTERNAL;
goto err_release_diff;
}
}
}
err_release_diff:
lyd_free_tree(diff);
err_release_data:
sr_release_data(cfg);
err:
return rc;
}
int ietf_hardware_init(struct confd *confd)
{
int rc = 0;
REGISTER_CHANGE(confd->session, "ietf-hardware", XPATH_BASE_, 0, change_hardware, confd, &confd->sub);
REGISTER_CHANGE(confd->cand, "ietf-hardware", XPATH_BASE_,
SR_SUBSCR_UPDATE, hardware_cand, confd, &confd->sub);
return SR_ERR_OK;
fail:
ERROR("Init hardware failed: %s", sr_strerror(rc));
return rc;
}
+141 -44
View File
@@ -12,7 +12,7 @@
#include <srx/srx_val.h>
#include "core.h"
#include "dagger.h"
#include "cni.h"
#define ERR_IFACE(_iface, _err, _fmt, ...) \
({ \
@@ -26,6 +26,7 @@
#define IF_XPATH "/ietf-interfaces:interfaces/interface"
static bool iface_is_phys(const char *ifname)
{
bool is_phys = false;
@@ -34,7 +35,7 @@ static bool iface_is_phys(const char *ifname)
json_t *link;
FILE *proc;
proc = popenf("re", "ip -d -j link show dev %s 2>/dev/null", ifname);
proc = cni_popen("ip -d -j link show dev %s 2>/dev/null", ifname);
if (!proc)
goto out;
@@ -61,37 +62,13 @@ out:
return is_phys;
}
/*
* Needed because we do deep searches for changes in interfaces,
* e.g. changes in bridge port settings, veth peers, etc.
*/
static char *iface_xpath(const char *xpath)
{
char *path, *ptr;
if (!xpath)
return NULL;
path = strdup(xpath);
if (!path)
return NULL;
if (!(ptr = strstr(path, "]/"))) {
free(path);
return NULL;
}
ptr[1] = 0;
return path;
}
static int ifchange_cand_infer_veth(sr_session_ctx_t *session, const char *path)
{
char *ifname, *type, *peer, *xpath, *val;
sr_error_t err = SR_ERR_OK;
size_t cnt = 0;
xpath = iface_xpath(path);
xpath = xpath_base(path);
if (!xpath)
return SR_ERR_SYS;
@@ -141,7 +118,7 @@ static int ifchange_cand_infer_vlan(sr_session_ctx_t *session, const char *path)
size_t cnt = 0;
long vid;
xpath = iface_xpath(path);
xpath = xpath_base(path);
if (!xpath)
return SR_ERR_SYS;;
type = srx_get_str(session, "%s/type", xpath);
@@ -222,7 +199,7 @@ static int ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path)
char *ifname, *type, *xpath;
sr_error_t err = SR_ERR_OK;
xpath = iface_xpath(path);
xpath = xpath_base(path);
if (!xpath)
return SR_ERR_SYS;
@@ -242,6 +219,10 @@ static int ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path)
inferred.data.string_val = "infix-if-type:ethernet";
else if (!fnmatch("br+([0-9])", ifname, FNM_EXTMATCH))
inferred.data.string_val = "infix-if-type:bridge";
else if (!fnmatch("docker+([0-9])", ifname, FNM_EXTMATCH))
inferred.data.string_val = "infix-if-type:bridge";
else if (!fnmatch("podman+([0-9])", ifname, FNM_EXTMATCH))
inferred.data.string_val = "infix-if-type:bridge";
else if (!fnmatch("lag+([0-9])", ifname, FNM_EXTMATCH))
inferred.data.string_val = "infix-if-type:lag";
else if (!fnmatch("veth+([0-9a-z_-])", ifname, FNM_EXTMATCH))
@@ -301,6 +282,10 @@ static int ifchange_cand(sr_session_ctx_t *session, uint32_t sub_id, const char
err = ifchange_cand_infer_vlan(session, new->xpath);
if (err)
break;
err = cni_ifchange_cand_infer_type(session, new->xpath);
if (err)
break;
}
sr_free_change_iter(iter);
@@ -445,7 +430,7 @@ static int netdag_gen_link_addr(FILE *ip, struct lyd_node *cif, struct lyd_node
* Only physical interfaces support this, virtual ones
* we remove, see netdag_must_del() for details.
*/
fp = popenf("r", "ip -d -j link show dev %s |jq -rM .[].permaddr", ifname);
fp = cni_popen("ip -d -j link show dev %s |jq -rM .[].permaddr", ifname);
if (fp) {
if (fgets(buf, sizeof(buf), fp))
mac = chomp(buf);
@@ -467,7 +452,7 @@ static int netdag_gen_link_addr(FILE *ip, struct lyd_node *cif, struct lyd_node
return 0;
}
static int netdag_gen_ip_addrs(FILE *ip, const char *proto,
static int netdag_gen_ip_addrs(struct dagger *net, FILE *ip, const char *proto,
struct lyd_node *cif, struct lyd_node *dif)
{
struct lyd_node *ipconf = lydx_get_child(cif, proto);
@@ -475,8 +460,15 @@ static int netdag_gen_ip_addrs(FILE *ip, const char *proto,
const char *ifname = lydx_get_cattr(dif, "name");
if (!ipconf || !lydx_is_enabled(ipconf, "enabled")) {
if (if_nametoindex(ifname))
systemf("ip -%c addr flush dev %s\n", proto[3], ifname);
if (!cni_find(ifname) && if_nametoindex(ifname)) {
FILE *fp;
fp = dagger_fopen_next(net, "init", ifname, 49, "flush.sh");
if (fp) {
fprintf(fp, "ip -%c addr flush dev %s\n", proto[3], ifname);
fclose(fp);
}
}
return 0;
}
@@ -653,6 +645,94 @@ skip_mtu:
return err;
}
static int netdag_gen_ethtool_autoneg(struct dagger *net, struct lyd_node *cif)
{
struct lyd_node *eth = lydx_get_child(cif, "ethernet");
struct lyd_node *aneg = lydx_get_child(eth, "auto-negotiation");
const char *ifname = lydx_get_cattr(cif, "name");
const char *speed, *duplex;
int mbps, err = 0;
FILE *fp;
fp = dagger_fopen_next(net, "init", ifname, 10, "ethtool-aneg.sh");
if (!fp)
return -EIO;
fprintf(fp, "ethtool --change %s autoneg ", ifname);
if (!aneg || lydx_is_enabled(aneg, "enable")) {
fputs("on\n", fp);
} else {
speed = lydx_get_cattr(eth, "speed");
if (!speed) {
sr_session_set_error_message(net->session, "%s: "
"\"speed\" must be specified "
"when auto-negotiation is disabled", ifname);
err = -EINVAL;
goto out;
}
mbps = (int)(atof(speed) * 1000.);
if (!((mbps == 10) || (mbps == 100))) {
sr_session_set_error_message(net->session, "%s: "
"\"speed\" must be either 0.01 or 0.1 "
"when auto-negotiation is disabled", ifname);
err = -EINVAL;
goto out;
}
duplex = lydx_get_cattr(eth, "duplex");
if (!duplex || (strcmp(duplex, "full") && strcmp(duplex, "half"))) {
sr_session_set_error_message(net->session, "%s: "
"\"duplex\" must be either "
"\"full\" or \"half\" "
"when auto-negotiation is disabled", ifname);
err = -EINVAL;
goto out;
}
fprintf(fp,"off speed %d duplex %s\n", mbps, duplex);
}
out:
fclose(fp);
return err;
}
static int netdag_gen_ethtool(struct dagger *net, struct lyd_node *cif, struct lyd_node *dif)
{
struct lyd_node *eth = lydx_get_child(dif, "ethernet");
const char *type = lydx_get_cattr(cif, "type");
int err;
/*
* Story time: when assigning a physical interface to a container, and then
* removing it, even though our type may be 'etherlike' we will
* get the following from sysrepo:
*
* "ieee802-ethernet-interface:ethernet": {
* "@": {
* "yang:operation": "delete"
* },
* "duplex": "full"
* },
*
* Hence this "redundant" check.
*/
if (strcmp(type, "infix-if-type:ethernet"))
return 0;
if (lydx_get_descendant(lyd_child(eth), "auto-negotiation", "enable", NULL) ||
lydx_get_child(eth, "speed") ||
lydx_get_child(eth, "duplex")) {
err = netdag_gen_ethtool_autoneg(net, cif);
if (err)
return err;
}
return 0;
}
static int bridge_diff_vlan_port(struct dagger *net, FILE *br, const char *brname, int vid,
const char *brport, int tagged, enum lydx_op op)
{
@@ -815,7 +895,14 @@ static int netdag_gen_bridge(struct dagger *net, struct lyd_node *dif,
mcast_snooping = 0; /* Not supported yet */
fwd_mask = bridge_fwd_mask(cif);
fprintf(ip, "link %s dev %s type bridge group_fwd_mask %d vlan_filtering %d mcast_snooping %d",
/*
* Issue #198: we require explicit VLAN assignment for ports
* when VLAN filtering is enabled. We strongly
* believe this is the only sane way of doing it.
* Issue #310: malplaced 'vlan_default_pvid 0'
*/
fprintf(ip, "link %s dev %s type bridge group_fwd_mask %d vlan_filtering %d"
" vlan_default_pvid 0 mcast_snooping %d",
op, brname, fwd_mask, vlan_filtering ? 1 : 0, mcast_snooping ? 1 : 0);
if (!vlan_filtering) {
fputc('\n', ip);
@@ -827,12 +914,7 @@ static int netdag_gen_bridge(struct dagger *net, struct lyd_node *dif,
goto done;
}
/*
* Issue #198: we require explicit VLAN assignment for ports
* when VLAN filtering is enabled. We strongly
* believe this is the only sane way of doing it.
*/
fprintf(ip, " vlan_protocol %s vlan_default_pvid 0\n", proto);
fprintf(ip, " vlan_protocol %s\n", proto);
vlans = lydx_get_descendant(lyd_child(dif), "bridge", "vlans", NULL);
if (!vlans)
@@ -970,6 +1052,7 @@ static int netdag_gen_afspec_add(struct dagger *net, struct lyd_node *dif,
static int netdag_gen_afspec_set(struct dagger *net, struct lyd_node *dif,
struct lyd_node *cif, FILE *ip)
{
const char *ifname = lydx_get_cattr(cif, "name");
const char *iftype = lydx_get_cattr(cif, "type");
DEBUG_IFACE(dif, "");
@@ -981,7 +1064,7 @@ static int netdag_gen_afspec_set(struct dagger *net, struct lyd_node *dif,
if (!strcmp(iftype, "infix-if-type:veth"))
return 0;
ERROR("unsupported interface type \"%s\"", iftype);
ERROR("unsupported interface type \"%s\" for %s", iftype, ifname);
return -ENOSYS;
}
@@ -1000,7 +1083,8 @@ static bool netdag_must_del(struct lyd_node *dif, struct lyd_node *cif)
{
const char *iftype = lydx_get_cattr(cif, "type");
if (strcmp(iftype, "infix-if-type:ethernet")) {
if (strcmp(iftype, "infix-if-type:ethernet") &&
strcmp(iftype, "infix-if-type:etherlike")) {
if (is_phys_addr_deleted(dif))
return true;
}
@@ -1059,6 +1143,13 @@ static sr_error_t netdag_gen_iface(struct dagger *net,
bool fixed;
FILE *ip;
if ((err = cni_netdag_gen_iface(net, ifname, dif, cif))) {
/* error or managed by CNI/podman */
if (err > 0)
err = 0; /* done, nothing more to do here */
goto err;
}
fixed = iface_is_phys(ifname) || !strcmp(ifname, "lo");
DEBUG("%s(%s) %s", ifname, fixed ? "fixed" : "dynamic",
@@ -1128,8 +1219,8 @@ static sr_error_t netdag_gen_iface(struct dagger *net,
/* Set Addresses */
err = err ? : netdag_gen_link_mtu(ip, dif);
err = err ? : netdag_gen_link_addr(ip, cif, dif);
err = err ? : netdag_gen_ip_addrs(ip, "ipv4", cif, dif);
err = err ? : netdag_gen_ip_addrs(ip, "ipv6", cif, dif);
err = err ? : netdag_gen_ip_addrs(net, ip, "ipv4", cif, dif);
err = err ? : netdag_gen_ip_addrs(net, ip, "ipv6", cif, dif);
if (err)
goto err_close_ip;
@@ -1140,6 +1231,8 @@ static sr_error_t netdag_gen_iface(struct dagger *net,
err = err ? : netdag_gen_sysctl(net, cif, dif);
err = err ? : netdag_gen_ethtool(net, cif, dif);
err_close_ip:
fclose(ip);
err:
@@ -1230,6 +1323,10 @@ int ietf_interfaces_init(struct confd *confd)
{
int rc = 0;
#ifdef CONTAINERS
sr_enable_module_feature(confd->conn, "infix-interfaces", "containers");
#endif
REGISTER_CHANGE(confd->session, "ietf-interfaces", "/ietf-interfaces:interfaces//.",
0, ifchange, confd, &confd->sub);
REGISTER_CHANGE(confd->cand, "ietf-interfaces", "/ietf-interfaces:interfaces//.",
+1 -1
View File
@@ -145,7 +145,7 @@ int parse_ospf(sr_session_ctx_t *session, struct lyd_node *ospf)
return 0;
}
if (bfd_enabled)
touch(BFDD_CONF);
(void)touch(BFDD_CONF);
return 0;
}
+95 -3
View File
@@ -13,6 +13,7 @@
#include <srx/srx_module.h>
#include <srx/srx_val.h>
#include "base64.h"
#include "core.h"
#define XPATH_BASE_ "/ietf-system:system"
@@ -479,8 +480,10 @@ static int change_ntp(sr_session_ctx_t *session, uint32_t sub_id, const char *mo
}
sr_free_values(val, cnt);
if (changes)
touch("/run/chrony/.changes");
if (changes) {
if (touch("/run/chrony/.changes"))
ERRNO("Failed recording changes to NTP client");
}
return err;
}
@@ -1026,18 +1029,105 @@ static int change_motd(sr_session_ctx_t *session, uint32_t sub_id, const char *m
if (message) {
rc = writesf(message, "w", "%s", fn);
free(message);
}
if (rc) {
ERRNO("failed saving %s", fn);
return SR_ERR_SYS;
}
return SR_ERR_OK;
}
static int change_motd_banner(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
const char *fn = "/etc/motd";
unsigned char *raw, *txt;
size_t txt_len;
char *legacy;
int rc = 0;
/* Ignore all events except SR_EV_DONE */
if (event != SR_EV_DONE)
return SR_ERR_OK;
legacy = srx_get_str(session, "/ietf-system:system/infix-sys:motd");
if (legacy) {
NOTE("Legacy /system/motd exists, skipping %s", xpath);
free(legacy);
return SR_ERR_OK;
}
raw = (unsigned char *)srx_get_str(session, "%s", xpath);
if (raw) {
txt = base64_decode(raw, strlen((char *)raw), &txt_len);
if (!txt) {
ERRNO("failed base64 decoding of %s", xpath);
rc = -1;
} else {
FILE *fp = fopen(fn, "w");
if (!fp) {
rc = -1;
} else {
if (fwrite(txt, txt_len, sizeof(txt[0]), fp) != txt_len)
rc = -1;
fclose(fp);
}
free(txt);
}
free(raw);
} else {
(void)remove(fn);
}
if (rc) {
ERROR("failed writing /etc/motd: %s", strerror(errno));
ERRNO("failed saving %s", fn);
return SR_ERR_SYS;
}
return SR_ERR_OK;
}
static int change_editor(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
const char *alt = "/etc/alternatives/editor";
struct { const char *editor, *path; } map[] = {
{ "emacs", "/usr/bin/mg" },
{ "nano", "/usr/bin/nano" },
{ "vi", "/bin/vi" },
};
char *editor;
int rc = 0;
/* Ignore all events except SR_EV_DONE */
if (event != SR_EV_DONE)
return SR_ERR_OK;
editor = srx_get_str(session, "%s", xpath);
if (editor) {
for (size_t i = 0; i < NELEMS(map); i++) {
if (strcmp(map[i].editor, editor))
continue;
erase(alt);
rc = systemf("ln -s %s %s", map[i].path, alt);
if (rc)
ERROR("Failed setting system editor '%s'", map[i].editor);
}
free(editor);
}
if (rc)
return SR_ERR_SYS;
return SR_ERR_OK;
}
static int change_hostname(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
@@ -1135,6 +1225,8 @@ int ietf_system_init(struct confd *confd)
REGISTER_CHANGE(confd->session, "ietf-system", XPATH_AUTH_, 0, change_auth, confd, &confd->sub);
REGISTER_CHANGE(confd->session, "ietf-system", XPATH_BASE_"/hostname", 0, change_hostname, confd, &confd->sub);
REGISTER_CHANGE(confd->session, "ietf-system", XPATH_BASE_"/infix-system:motd", 0, change_motd, confd, &confd->sub);
REGISTER_CHANGE(confd->session, "ietf-system", XPATH_BASE_"/infix-system:motd-banner", 0, change_motd_banner, confd, &confd->sub);
REGISTER_CHANGE(confd->session, "ietf-system", XPATH_BASE_"/infix-system:text-editor", 0, change_editor, confd, &confd->sub);
REGISTER_CHANGE(confd->session, "ietf-system", XPATH_BASE_"/clock", 0, change_clock, confd, &confd->sub);
REGISTER_CHANGE(confd->session, "ietf-system", XPATH_BASE_"/ntp", 0, change_ntp, confd, &confd->sub);
REGISTER_CHANGE(confd->session, "ietf-system", XPATH_BASE_"/dns-resolver", 0, change_dns, confd, &confd->sub);
+447
View File
@@ -0,0 +1,447 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <pwd.h>
#include <sys/utsname.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <srx/common.h>
#include <srx/lyx.h>
#include <srx/srx_module.h>
#include <srx/srx_val.h>
#include "core.h"
#define ARPING_MSEC 1000
#define MODULE "infix-containers"
#define CFG_XPATH "/infix-containers:containers"
#define INBOX_QUEUE "/run/containers/inbox"
#define JOB_QUEUE "/run/containers/queue"
#define ACTIVE_QUEUE "/var/lib/containers/active"
#define LOGGER "logger -t container -p local1.notice"
static const struct srx_module_requirement reqs[] = {
{ .dir = YANG_PATH_, .name = MODULE, .rev = "2024-02-01" },
{ NULL }
};
static int add(const char *name, struct lyd_node *cif)
{
const char *image = lydx_get_cattr(cif, "image");
const char *restart_policy, *string;
struct lyd_node *node, *network;
FILE *fp, *ap;
fp = fopenf("w", "%s/%s.sh", INBOX_QUEUE, name);
if (!fp) {
ERRNO("Failed adding job %s.sh to job queue" INBOX_QUEUE, name);
return SR_ERR_SYS;
}
/* Stop any running container gracefully so it releases its IP addresses. */
fprintf(fp, "#!/bin/sh\n"
"container stop %s\n"
"container delete %s\n"
"container", name, name);
LYX_LIST_FOR_EACH(lyd_child(cif), node, "dns")
fprintf(fp, " --dns %s", lyd_get_value(node));
LYX_LIST_FOR_EACH(lyd_child(cif), node, "search")
fprintf(fp, " --dns-search %s", lyd_get_value(node));
if ((string = lydx_get_cattr(cif, "hostname")))
fprintf(fp, " --hostname %s", string);
if (lydx_is_enabled(cif, "read-only"))
fprintf(fp, " --read-only");
if (lydx_is_enabled(cif, "privileged"))
fprintf(fp, " --privileged");
LYX_LIST_FOR_EACH(lyd_child(cif), node, "volume")
fprintf(fp, " -v %s-%s:%s", name, lydx_get_cattr(node, "name"),
lydx_get_cattr(node, "target"));
LYX_LIST_FOR_EACH(lyd_child(cif), node, "mount") {
const char *dst = lydx_get_cattr(node, "target");
const char *data = lydx_get_cattr(node, "content");
const char *src = lydx_get_cattr(node, "source");
const char *type = lydx_get_cattr(node, "type");
const char *id = lydx_get_cattr(node, "name");
int ro = lydx_get_bool(node, "read-only");
char nm[strlen(id) + strlen(name) + 32];
/* Content mount: create a unique file with 'content' and bind mount */
if (data) {
const char *contdir = "/run/containers/files";
char cmd[256];
FILE *pp;
int pos;
/*
* prefix file name with container name, shared namespace,
* and replace any slashes with hyphens.
*/
pos = snprintf(nm, sizeof(nm), "%s/%s-", contdir, name);
strlcat(nm, id, sizeof(nm));
for (int i = pos; nm[i] && i < (int)sizeof(nm); i++) {
if (nm[i] == '/')
nm[i] = '-';
}
snprintf(cmd, sizeof(cmd), "base64 -d > %s", nm);
pp = popen(cmd, "w");
if (!pp || fputs(data, pp) < 0) {
ERRNO("%s: failed decoding %s base64 'content'", name, id);
if (pp)
pclose(pp);
continue;
}
pclose(pp);
type = "bind"; /* discard any configured setting */
src = nm; /* discard any source, not used for content mounts */
}
fprintf(fp, " -m type=%s,src=%s,dst=%s,readonly=%s",
type, src, dst, ro ? "true" : "false");
}
ap = NULL;
LYX_LIST_FOR_EACH(lyd_child(cif), node, "env") {
if (!ap) {
ap = fopenf("w", "/run/containers/args/%s.env", name);
if (!ap) {
ERRNO("Failed creating env list for container %s", name);
break;
}
}
fprintf(ap, "%s=%s\n", lydx_get_cattr(node, "key"), lydx_get_cattr(node, "value"));
}
if (ap) {
fclose(ap);
fprintf(fp, " -e /run/containers/args/%s.env", name);
}
network = lydx_get_descendant(lyd_child(cif), "network", NULL);
if (network) {
if (lydx_is_enabled(network, "host")) {
fprintf(fp, " --net host");
} else {
LYX_LIST_FOR_EACH(lyd_child(network), node, "interface") {
struct lyd_node *opt;
const char *name;
int first = 1;
name = lydx_get_cattr(node, "name");
fprintf(fp, " --net %s", name);
LYX_LIST_FOR_EACH(lyd_child(node), opt, "option") {
const char *option = lyd_get_value(opt);
fprintf(fp, "%s%s", first ? ":" : ",", option);
first = 0;
}
}
LYX_LIST_FOR_EACH(lyd_child(network), node, "publish")
fprintf(fp, " -p %s", lyd_get_value(node));
}
}
restart_policy = lydx_get_cattr(cif, "restart-policy");
if (restart_policy) {
if (!strcmp(restart_policy, "never"))
fprintf(fp, " -r no"); /* for docker compat */
else if (!strcmp(restart_policy, "always"))
fprintf(fp, " -r always");
else
fprintf(fp, " -r on-failure:10");
}
if (lydx_is_enabled(cif, "manual"))
fprintf(fp, " --manual");
if ((string = lydx_get_cattr(cif, "command")))
fprintf(fp, " --entrypoint");
fprintf(fp, " create %s %s", name, image);
if ((string = lydx_get_cattr(cif, "command")))
fprintf(fp, " %s", string);
fprintf(fp, "\n");
fchmod(fileno(fp), 0700);
fclose(fp);
systemf("initctl -bnq enable container@%s.conf", name);
return 0;
}
static int del(const char *name)
{
const char *queue[] = {
JOB_QUEUE,
INBOX_QUEUE,
ACTIVE_QUEUE,
};
/* Remove any pending download/create job first */
for (size_t i = 0; i < NELEMS(queue); i++) {
char fn[strlen(queue[i]) + strlen(name) + 5];
snprintf(fn, sizeof(fn), "%s/%s.sh", queue[i], name);
erase(fn);
}
return systemf("container delete %s", name) ||
systemf("initctl -bnq disable container@%s.conf", name);
}
static int change(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *_confd)
{
struct lyd_node *diff, *cifs, *difs, *cif, *dif;
sr_error_t err = 0;
sr_data_t *cfg;
switch (event) {
case SR_EV_DONE:
break;
case SR_EV_CHANGE:
case SR_EV_ABORT:
default:
return SR_ERR_OK;
}
err = sr_get_data(session, CFG_XPATH "//.", 0, 0, 0, &cfg);
if (err)
goto err_abandon;
err = srx_get_diff(session, &diff);
if (err)
goto err_release_data;
cifs = lydx_get_descendant(cfg->tree, "containers", "container", NULL);
difs = lydx_get_descendant(diff, "containers", "container", NULL);
/* find the modified one, delete or recreate only that */
LYX_LIST_FOR_EACH(difs, dif, "container") {
const char *name = lydx_get_cattr(dif, "name");
if (lydx_get_op(dif) == LYDX_OP_DELETE) {
del(name);
continue;
}
LYX_LIST_FOR_EACH(cifs, cif, "container") {
const char *nm = lydx_get_cattr(cif, "name");
if (strcmp(name, nm))
continue;
if (!lydx_is_enabled(cif, "enabled"))
del(name);
else
add(name, cif);
break;
}
}
lyd_free_tree(diff);
err_release_data:
sr_release_data(cfg);
err_abandon:
return err;
}
static int action(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath,
const sr_val_t *input, const size_t input_cnt,
sr_event_t event, unsigned request_id,
sr_val_t **output, size_t *output_cnt,
void *priv)
{
char buf[strlen(xpath) + 1];
char *cmd, *name, *ptr;
char quote;
/* /infix-containers:containers/container[name='ntpd']/restart */
strlcpy(buf, xpath, sizeof(buf));
name = strstr(buf, "[name=");
if (!name)
return SR_ERR_INTERNAL;
ptr = &name[6];
quote = ptr[0]; /* Quote char to look for: ' or " */
name = &ptr[1];
ptr = strchr(name, quote);
if (!ptr)
return SR_ERR_INTERNAL;
*ptr++ = 0;
cmd = strstr(ptr, "]/");
if (!cmd)
return SR_ERR_INTERNAL;
cmd += 2;
DEBUG("CALLING 'container %s %s' (xpath %s)", cmd, name, xpath);
if (systemf("container %s %s", cmd, name))
return SR_ERR_INTERNAL;
return SR_ERR_OK;
}
static int oci_load(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath,
const sr_val_t *input, const size_t input_cnt, sr_event_t event,
unsigned request_id, sr_val_t **output, size_t *output_cnt, void *priv)
{
char *uri, *name = "";
uri = input[0].data.string_val; /* mandatory */
if (input_cnt > 1)
name = input[1].data.string_val;
if (systemf("container load %s %s 2>&1 | logger -t confd -p local1.err -I $PPID", uri, name))
return SR_ERR_SYS;
return SR_ERR_OK;
}
static int is_active(sr_session_ctx_t *session, const char *name)
{
return srx_enabled(session, CFG_XPATH "/container[name='%s']/enabled", name);
}
static int is_manual(sr_session_ctx_t *session, const char *name)
{
return srx_enabled(session, CFG_XPATH "/container[name='%s']/manual", name);
}
/*
* When container configurations are not saved to startup-config and the
* user reboot the system (or lose power) we will have lingering active
* containers cached on persistent storage.
*
* This function runs every time a configuration is applied to clean up
* any lingering active jobs to prevent false matches in the cmp magic
* in the below post-hook.
*/
static void cleanup(sr_session_ctx_t *session, struct confd *confd)
{
struct dirent *d;
DIR *dir;
dir = opendir(ACTIVE_QUEUE);
if (!dir)
return;
while ((d = readdir(dir))) {
char name[strlen(ACTIVE_QUEUE) + strlen(d->d_name) + 2];
char *ptr;
if (d->d_name[0] == '.')
continue;
strlcpy(name, d->d_name, sizeof(name));
ptr = strstr(name, ".sh");
if (!ptr)
continue; /* odd, non-script file? */
*ptr = 0;
if (is_active(session, name))
continue;
/* Not found in running-config, remove stale cache. */
snprintf(name, sizeof(name), "%s/%s", ACTIVE_QUEUE, d->d_name);
if (erase(name))
ERRNO("Failed removing stale container job %s", name);
}
closedir(dir);
}
/*
* Containers depend on a lot of other system resources being properly
* set up, e.g., networking, which is run by dagger. So we need to wait
* for all that before we can launch new, or modified, containers. The
* latter is the tricky part.
*
* By default, containers get a writable layer which is preserved across
* restarts/reboots of container or host -- provided we don't recreate
* them on a reboot. Hence the cmp magic below: we check if the command
* to create a container is the same as what is already activated, if it
* is already activated we know 'podman create' has done its thing and
* we can safely start the container.
*/
void infix_containers_post_hook(sr_session_ctx_t *session, struct confd *confd)
{
struct dirent *d;
DIR *dir;
cleanup(session, confd);
dir = opendir(INBOX_QUEUE);
if (!dir) {
ERROR("Cannot open %s to launch scripts.", INBOX_QUEUE);
return;
}
while ((d = readdir(dir))) {
char curr[strlen(ACTIVE_QUEUE) + strlen(d->d_name) + 2];
char next[strlen(INBOX_QUEUE) + strlen(d->d_name) + 2];
if (d->d_name[0] == '.')
continue;
snprintf(curr, sizeof(curr), "%s/%s", ACTIVE_QUEUE, d->d_name);
snprintf(next, sizeof(next), "%s/%s", INBOX_QUEUE, d->d_name);
if (!systemf("cmp %s %s >/dev/null 2>&1", curr, next)) {
char name[strlen(d->d_name) + 1];
char *ptr;
strlcpy(name, d->d_name, sizeof(name));
ptr = strstr(name, ".sh");
if (ptr) {
*ptr = 0;
/* New job is already active, no changes, skipping ... */
if (!is_manual(session, name))
systemf("initctl -bnq cond set container:%s", name);
}
remove(next);
continue;
}
if (movefile(next, JOB_QUEUE))
ERRNO("Failed moving %s to job queue %s", next, JOB_QUEUE);
}
closedir(dir);
systemf("container volume prune -f >/dev/null 2>&1");
}
int infix_containers_init(struct confd *confd)
{
int rc;
rc = srx_require_modules(confd->conn, reqs);
if (rc)
goto fail;
REGISTER_CHANGE(confd->session, MODULE, CFG_XPATH, 0, change, confd, &confd->sub);
REGISTER_RPC(confd->session, CFG_XPATH "/container/start", action, NULL, &confd->sub);
REGISTER_RPC(confd->session, CFG_XPATH "/container/stop", action, NULL, &confd->sub);
REGISTER_RPC(confd->session, CFG_XPATH "/container/restart", action, NULL, &confd->sub);
REGISTER_RPC(confd->session, "/infix-containers:oci-load", oci_load, NULL, &confd->sub);
return SR_ERR_OK;
fail:
ERROR("init failed: %s", sr_strerror(rc));
return rc;
}
+8 -7
View File
@@ -13,9 +13,10 @@
#include <srx/srx_val.h>
#include "core.h"
#define ARPING_MSEC 1000
#define MODULE "infix-dhcp-client"
#define XPATH "/infix-dhcp-client:dhcp-client"
#define ARPING_MSEC 1000
#define MODULE "infix-dhcp-client"
#define XPATH "/infix-dhcp-client:dhcp-client"
#define CACHE_TEMPLATE "/var/lib/misc/%s.cache"
static const struct srx_module_requirement reqs[] = {
{ .dir = YANG_PATH_, .name = MODULE, .rev = "2024-01-30" },
@@ -25,15 +26,15 @@ static const struct srx_module_requirement reqs[] = {
static char *ip_cache(const char *ifname, char *str, size_t len)
{
const char *fn = "/var/lib/misc/%s.cache";
struct in_addr ina;
char buf[128];
FILE *fp;
if (!fexistf(fn, ifname))
if (!fexistf(CACHE_TEMPLATE, ifname))
return NULL;
fp = fopenf("r", fn, ifname);
fp = fopenf("r", CACHE_TEMPLATE, ifname);
if (!fp)
return NULL;
@@ -45,7 +46,7 @@ static char *ip_cache(const char *ifname, char *str, size_t len)
chomp(buf);
if (!inet_aton(buf, &ina)) {
erasef(fn, ifname);
erasef(CACHE_TEMPLATE, ifname);
return NULL;
}
@@ -0,0 +1,374 @@
/*
* Infix Containers YANG module
*/
module infix-containers {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:infix-containers";
prefix infix-cont;
import ietf-yang-types {
prefix yang;
}
import ietf-interfaces {
prefix if;
}
import ietf-inet-types {
prefix inet;
}
import infix-interfaces {
prefix infix-if;
}
revision 2024-02-01 {
description "Initial revision";
reference "internal";
}
/*
* Typedefs
*/
typedef mount-type {
type enumeration {
enum bind {
description "Regular bind mount of host path to container.";
value 1;
}
enum glob {
description "Glob match and bind mount matching host paths to container.";
value 2;
}
}
}
typedef restart-policy {
type enumeration {
enum never {
description "Do not restart containers that exit/crash.";
value 1;
}
enum retry {
description "Restart containers up to 10 times before giving up.";
value 2;
}
enum always {
description "Always restart containers when they exit.";
value 3;
}
}
}
/*
* Data Nodes
*/
container containers {
list container {
key "name";
leaf enabled {
description "Enable or disable a container configuration.";
type boolean;
default true;
}
leaf name {
description "Name of the container";
type string;
}
leaf id {
description "Container ID, unique hash.";
config false;
type string;
}
leaf image {
description "Docker image for the container: [transport]name[:tag|@digest]
quay.io/username/myimage -- Pull myimage:latest
docker://busybox -- Pull busybox:latest from Docker Hub
docker://ghcr.io/usr/img -- Pull img:latest from GitHub packages
dir:/media/usb/myimage:1.1 -- Use myimage v1.1 from USB media
docker-archive:/tmp/archive -- Use archive:latest from tarball
oci-archive:/lib/oci/archive -- Use archive:latest from OCI archive
May be in .tar or .tar.gz format
Note: if a remote repository cannot be reached, the creation of the
container will be put on a queue that retries pull every time
there is a route change in the host's system.";
mandatory true;
type string;
}
leaf image-id {
description "Docker image ID, exact hash used.";
config false;
type string;
}
list env {
description "Set environment variables, key=\"value\" pairs.";
key key;
leaf key {
description "Single word.";
type string {
pattern '[a-zA-Z_][a-zA-Z0-9_]*';
length "1..253";
}
}
leaf value {
description "Argument to key can be a single word or quoted multiple words.";
mandatory true;
type string;
}
}
leaf command {
description "Override ENTRYPOINT from image and run command + args.";
type string;
}
leaf hostname {
description "Sets the container host name that is available inside the container.";
type inet:domain-name;
}
leaf privileged {
description "Give container extended privileges, e.g., access to devices.";
type boolean;
}
leaf restart-policy {
description "Restart policy to when containers exit/crash.";
type restart-policy;
default always;
}
leaf manual {
description "Auto-start or manual start after creation/reboot.";
type boolean;
}
container network {
description "Select network mode: none, host, or container network interfaces.";
leaf host {
description "Run in same network namespace as host, share DNS and publish all ports.";
type boolean;
}
list interface {
description "Container network interface(s) to connect to the container.";
key name;
leaf name {
description "Container network (interface name) to connect to the container.";
type if:interface-ref;
must "/if:interfaces/if:interface[if:name = current()]/infix-if:container-network" {
error-message "Container networks must be interfaces classified as container-network.";
}
}
leaf-list option {
when "deref(../name)/../infix-if:container-network/infix-if:type = 'infix-if:bridge'";
description "Options for a masquerading container bridges.
Example: ip=1.2.3.4 -- request a specific IP (IPv4 or IPv6)
mac=00:01:02:c0:ff:ee -- set fixed MAC address in container
interface_name=foo0 -- set interface name inside container";
type string;
}
}
leaf-list publish {
description "Publish container port, or a range of ports, to the host.
Syntax: [[ip:][hostPort]:]containerPort[/protocol]
Sample: 8080:80 -- forward tcp port 8080 to container port 80
69:69/udp -- forward udp port 69 to container port 69
127.0.0.1:8080:80 -- forward only from loopback interface";
type string;
}
leaf-list dns {
description "Set custom DNS servers, or 'none' to use /etc/resolv.conf in image.";
type inet:ip-address;
}
leaf-list search {
description "Set custom DNS search domains, or '.' to not set search domain.";
type inet:domain-name;
}
must "(host and not(interface)) or (not(host) and interface) or (not(host) and not(interface))" {
error-message "Host and interfaces are mutually exclusive";
}
}
leaf read-only {
description "Create a read-only container. Use volumes for writable directories.";
type boolean;
}
list mount {
description "Files, content, and directories to mount inside container.";
key name;
leaf name {
description "Unique name to identify mount, e.g., 'ntpd.conf' or 'leds'.
Set the source path or *content* to mount in the container,
the latter means the file contents are stored in the host's
startup-config, base64 encoded.
Example source paths:
1. /etc/ntpd.conf File name to bind mount to 'path'
2. /sys/class/leds/ Control LEDs from a container
3. /dev/sda* Share all matching files (type glob!)
For persistent writable directories, *volumes* may be a
better fit for your container and easier to set up.";
type string;
}
leaf type {
description "Mount type, strict bind mount or glob match.";
type mount-type;
default bind;
}
choice data {
case source {
leaf source {
description "Host path to mount in container, may be a glob.
When mounting files, directories (and globs) from the host,
the source must be an absolute path.";
type string {
pattern '/.*';
}
}
}
case content {
leaf content {
description "File contents, in base64 native format (XML/JSON).
With this setting the source path is not used, instead
a temporary read-only file is created on the host with
this content and bind-mounted into the container at the
target destination path.
In the CLI, use 'set content' without and argument to
open an editor for easy copy-paste. On exit from the
editor the contents are base64 encoded automatically.";
type binary;
}
}
}
leaf target {
description "Absolute path to target destination inside container.
For example, to mount file on /etc/ntpd.conf set this path and
then use either the 'source' path to a file on the host system,
or import the text file using the 'content' node.";
mandatory true;
type string {
pattern '/.*';
}
}
leaf read-only {
description "All mounts are read-only by default.
Use this option to allow containers to write to files
and directories on the host system.
NOTE: 'content' files can also be set read-write, but
no changes are saved back to hosts's datastore.";
type boolean;
default true;
}
}
list volume {
description "Create a writable volume that survive container upgrades.";
key name;
leaf name {
description "Single word to identify this (named) volume.
Volumes are retained over the lifetime of a container and
survive both upgrading the image and configuration changes,
which otherwise wipe the default writable layer a container
is given.
Volumes combine well with 'read-only' containers, when you
know which files/directories you want to persist.
Compared to a bind mount, a volume is automatically 'synced'
with the contents of the container's file system on first
use. Hence, upgrading the container image will not update
the volume if the image has new/removed files at 'path'.";
type string {
pattern '[a-zA-Z_][a-zA-Z0-9_]*';
length "1..64";
}
}
leaf target {
description "Absolute path to target destination directory inside the container.";
mandatory true;
type string {
pattern '/.*';
}
}
}
leaf running {
description "Status of container, running or not.";
config false;
type boolean;
}
leaf status {
description "Status of container, human friendly.";
config false;
type string;
}
action start {
description "Start a stopped container.";
}
action stop {
description "Stop a running container.";
}
action restart {
description "Restart a running, or start, a stopped container.";
}
}
}
rpc oci-load {
description "Load an OCI archive from file or URL to an image.";
input {
leaf uri {
description "The URL or local file path, e.g., /lib/oci/archive.tar.gz";
type string;
mandatory true;
}
leaf name {
description "Image name[:tag], default: basename of archive dir + :latest";
type string;
}
}
}
}
@@ -9,11 +9,20 @@ module infix-ethernet-interface {
import ietf-interfaces {
prefix if;
}
import ietf-yang-types {
prefix yang;
reference "IETF RFC 6991";
}
organization "KernelKit";
contact "kernelkit@googlegroups.com";
description "Extensions and deviations to ieee802-ethernet-interface.yang";
revision 2024-02-27 {
description "Add augment for in-good-octets and out-good-octets";
reference "internal";
}
revision 2024-01-22 {
description "Support ethernet but not negotiation-status";
reference "internal";
@@ -27,6 +36,18 @@ module infix-ethernet-interface {
/*
* Data Nodes
*/
augment "/if:interfaces/if:interface/eth:ethernet/eth:statistics/eth:frame" {
leaf out-good-octets {
type yang:counter64;
units octets;
description "A count of data and padding octets of frames that are successfully transmitted.";
}
leaf in-good-octets {
type yang:counter64;
units octets;
description "A count of data and padding octets in frames that are successfully received.";
}
}
/* Deviations for config and status */
+51 -1
View File
@@ -8,7 +8,7 @@ module infix-hardware {
import iana-hardware {
prefix iahw;
}
import ietf-yang-types {
prefix yang;
}
@@ -25,6 +25,56 @@ module infix-hardware {
description "A two-letter country code.";
}
identity hardware-class {
description "infix hardware base class";
}
identity usb {
base hardware-class;
description "This identity is used to describe a USB port";
}
identity vpd {
base hardware-class;
description "This identity is used to a VPD memory on the device.";
}
deviation "/iehw:hardware/iehw:component/iehw:class" {
deviate replace {
type identityref {
base hardware-class;
}
}
}
deviation "/iehw:hardware/iehw:component/iehw:state/iehw:admin-state" {
deviate add {
must ". = 'locked' or . = 'unlocked'" {
error-message "Only 'locked' and 'unlocked' states are allowed here.";
}
}
}
deviation "/iehw:hardware/iehw:component/iehw:state/iehw:standby-state" {
deviate not-supported;
}
deviation "/iehw:hardware/iehw:component/iehw:sensor-data" {
deviate not-supported;
}
deviation "/iehw:hardware/iehw:component/iehw:parent" {
deviate not-supported;
}
deviation "/iehw:hardware/iehw:component/iehw:parent-rel-pos" {
deviate not-supported;
}
deviation "/iehw:hardware/iehw:component/iehw:alias" {
deviate not-supported;
}
deviation "/iehw:hardware/iehw:component/iehw:uri" {
deviate not-supported;
}
deviation "/iehw:hardware/iehw:component/iehw:asset-id" {
deviate not-supported;
}
augment "/iehw:hardware/iehw:component" {
container vpd-data {
config false;
@@ -18,6 +18,10 @@ submodule infix-if-bridge {
contact "kernelkit@googlegroups.com";
description "Linux bridge extension for ietf-interfaces.";
revision 2024-02-19 {
description "Add STP state to bridge port.";
reference "internal";
}
revision 2023-12-02 {
description "Extend bridge-port must expression to ensure a
bridge cannot be a bridge-port to itself.";
@@ -77,6 +81,33 @@ submodule infix-if-bridge {
reserved link-local multicast groups, in 01:80:C2:00:00:0X.";
}
typedef stp-state {
type enumeration {
enum disabled {
value 0;
description "Port is in STP DISABLED state";
}
enum listening {
value 1;
description "Port is in STP LISTENING state";
}
enum learning {
value 2;
description "Port is in STP LEARNING state";
}
enum forwarding {
value 3;
description "Port is in STP FORWARDING state. This is the default vlan state.";
}
enum blocking {
value 4;
description "Port is in STP BLOCKING state.";
}
}
description
"User-friendly enumeration of different bridge port operational states.";
}
/*
* Data Nodes
*/
@@ -158,6 +189,12 @@ submodule infix-if-bridge {
description "Bridge interface to which this interface is attached.";
}
leaf stp-state {
type stp-state;
config false;
description "The operation state of the bridge port.";
}
leaf pvid {
if-feature "vlan-filtering";
type dot1q-types:vlanid;
@@ -0,0 +1,110 @@
submodule infix-if-container {
yang-version 1.1;
belongs-to infix-interfaces {
prefix infix-if;
}
import ietf-inet-types {
prefix inet;
}
import ietf-interfaces {
prefix if;
}
import infix-if-type {
prefix infixift;
}
organization "KernelKit";
contact "kernelkit@googlegroups.com";
description "Container network to interface mapping for ietf-interfaces.
Ensures a container interface can never be a bridge port, or
LAG member, at the same time.";
revision 2024-01-15 {
description "Initial revision.";
reference "internal";
}
/*
* Identities
*/
identity container-network {
description "Container network type";
}
identity bridge {
base container-network;
description "Container bridge with IP masquerading, portmappping, and firewalling.";
}
identity host {
base container-network;
description "Host device, e.g., one end of a VETH pair or other host interface.";
}
/*
* Data Nodes
*/
augment "/if:interfaces/if:interface/infix-if:port" {
description "Augments the interface model with container networks.";
if-feature containers;
case container-network {
container container-network {
presence "Container network mapping.";
leaf type {
description "Masquerading container bridge or a host interface";
type identityref {
base container-network;
}
}
list subnet {
description "Static IP ranges to hand out addresses to containers from.
A container bridge forwards DNS, NTP, and SSH by default to
the host interfaces.";
when "../type = 'infix-if:bridge'";
key subnet;
leaf subnet {
type inet:ip-prefix;
description "Subnet to assign addresses from, round-robin assignment.
The default is from a standard Docker setup.";
default "172.17.0.0/16";
}
leaf gateway {
type inet:ip-address;
description "Optional gateway address for the subnet, defaults to .1.
This will be used as the address of the container bridge.";
}
}
list route {
description "IPv4 or IPv6 routes to be added to container.
For bridge type interfaces the gateway can be omitted, the
IP address of the container bridge will then be used as the
next-hop address.";
key subnet;
leaf subnet {
type inet:ip-prefix;
description "Destination (subnet) IP address.";
}
leaf gateway {
type inet:ip-address;
description "Optional gateway (next-hop) IP address for the route.";
}
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More