Compare commits

..
141 Commits
Author SHA1 Message Date
Mattias Walström b04b978d40 infix-hardware: fix operational with virtual radios
* Show a vendor of the radio (hwsim)
* Allow to show alot of supported AP interfaces, hwsim
  supports 2048
2026-07-02 15:25:55 +02:00
Mattias Walström 1d1afecdc3 Keep the hwsim netdev as an internal interface
It must exist, and will only handle frames when in
state running, therefore we keep it, but mark it internal.
2026-07-02 09:20:38 +02:00
Mattias Walström 2cfddb448c make run: Add support for virtual Wi-Fi interfaces 2026-07-02 09:20:38 +02:00
Mattias Walström f25a3189ed Wi-Fi: For station add connected BSSID to operational 2026-07-02 09:20:38 +02:00
Mattias Walström 1422a0ebdd test: Add Wi-Fi tests
Should map on physical as well, but only tested on virtual.
2026-07-02 09:20:38 +02:00
Mattias Walström ce1892a60b test: factory_config: Update generated files 2026-07-02 09:20:38 +02:00
Mattias Walström 4b340a38b3 statd: wifi: Limit bands to the supported ones
Infix only supports 2.4/5/6 Ghz, ignore all else.
2026-07-02 09:20:38 +02:00
Tobias WaldekranzandGitHub 2a766d7886 Merge pull request #1545 from kernelkit/factory-config-test
test: add generic boot-from-factory-config test
2026-07-01 10:14:13 +02:00
Mattias WalströmandGitHub 1bb9f778ff Merge pull request #1559 from kernelkit/releng
Final release touches
2026-07-01 08:53:31 +02:00
Joachim Wiberg d2abdf0f64 Final ChangeLog update for v26.06
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-01 08:27:16 +02:00
Joachim Wiberg 5df26b217a utils: link to GNS3 blog instead of GNS3 marketplace
Friendlier for first-time users, and drives traffic to the blog, hopefully.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-01 08:26:15 +02:00
Joachim WibergandGitHub b3ae276a14 Merge pull request #1558 from kernelkit/releng
Update ChangeLog for v26.06 release
2026-07-01 08:05:23 +02:00
Joachim Wiberg 5bbb83a3e6 Update ChangeLog for v26.06 release
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-30 22:59:50 +02:00
Richard Alpe e233dbc6da test: reboot at end of factory config test to restore
Reboot at end of factory config test to restore test config.

Signed-off-by: Richard Alpe <richard@bit42.se>
2026-06-30 17:19:07 +02:00
Mattias WalströmandGitHub d83c708cd1 Merge pull request #1553 from kernelkit/wifi-fixes2
Some important wifi fixes
2026-06-30 13:31:01 +02:00
Richard Alpe d333c66f9c test: add generic boot-from-factory-config test
Verify that the device's factory-default configuration boots cleanly and
that the device remains usable afterwards -- i.e. it does not fall back to
the fail-secure failure-config.

The test exercises the device's own first-boot bootstrap path: it clears
the startup-config and reboots with the test-mode startup override set, so
confd initialises running from the factory-config (its first-boot path).
That mirrors a factory-fresh / factory-reset device and avoids applying a
full config swap over the live management session -- confd cannot replace
the whole running datastore from within an RPC without timing out, and a
factory config may reconfigure the very transport we are connected over.

The test is image-generic, using whatever factory-config the running image
was built with, so it covers both the stock Infix factory config and any
spin factory config. A single-node (1x1) topology is used on purpose: a
factory config is not written with a lab full of peers in mind, so booting
it across a multi-node topology could trigger broadcast storms or similar.
NETCONF only, as it is the one management transport present in every
factory config.

Signed-off-by: Richard Alpe <richard@bit42.se>
2026-06-30 10:32:43 +02:00
Mattias Walström 1b11bae8eb test: static_multicast_filters: Fix indendation 2026-06-30 09:18:52 +02:00
Mattias Walström 2744e6ee85 test: Whitespace 2026-06-30 09:18:52 +02:00
Mattias Walström 9b41139066 test: netns: refactor must_receive
Parameter was called `timeout` but it was used as a duration,
how long it should fetch packets to match on.
2026-06-30 09:18:52 +02:00
Mattias Walström d0d95f4098 confd: wifi: Fix issue with reconfigure modes on a wifi radio
The previous mode was not teardowned correctly
2026-06-30 09:18:52 +02:00
Mattias Walström 84dfaf7c03 buildroot: Add missing MBO feature in hostapd 2026-06-30 09:18:52 +02:00
Tobias WaldekranzandGitHub afcc40589a Merge pull request #1556 from kernelkit/misc
Minor fixes
2026-06-30 09:06:25 +02:00
Joachim Wiberg 208f7c9f08 confd: simplify gen-hostname, defer MAC expansion to confd
gen-hostname baked the last three MAC octets straight into the generated
hostname, so the actual MAC ended up stored in the configuration.  Copy
that config to another device and its hostname no longer matched the
hardware.

Emit the %h and %m format specifiers and let confd expand them when the
config is applied, so the MAC is resolved per device and never written
to a saved config.  This also drops the /run/system.json lookup.

Closes #1554

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-29 19:35:04 +02:00
Joachim Wiberg f8c68a38af version: derive OS version from git describe, not the release label
On weekly builds INFIX_RELEASE is "latest", and it fed straight into
INFIX_VERSION, so the keyword showed up as VERSION, VERSION_ID and
PRETTY_NAME in /etc/os-release, in /etc/version (show version), in the
ietf-system os-version operational leaf, and in the mDNS advertisement.
None of those pinned down the source revision; only BUILD_ID did.

Tie INFIX_VERSION to INFIX_BUILD_ID (git describe).  INFIX_RELEASE now
only labels the release channel (IMAGE_VERSION) and names the published
artifacts.

Also exclude the moving 'latest*' tags from git describe, so the build
id can never resolve to "latest" should that tag land on, or nearest to,
the built commit.

Fixes #1524

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-29 18:36:17 +02:00
Mattias WalströmandGitHub 8994879e26 Merge pull request #1551 from kernelkit/kernel-upgrade-953677c4-aa53-4a5e-a399-5b0a5e1482a7
Upgrade to kernel 6.18.37
2026-06-29 14:41:30 +02:00
Joachim WibergandGitHub b5a0ea0b1b Merge pull request #1552 from kernelkit/releng
Update ChangeLog for v26.06.0 release

[skip ci]
2026-06-28 18:07:05 +02:00
Joachim Wiberg 435406af68 Update ChangeLog for v26.06.0 release
Add relevant fixes and features that was missed in PRs.

[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-28 06:44:20 +02:00
ael-bot 79107d2548 Upgrade Linux kernel to 6.18.37 2026-06-27 13:24:01 +00:00
Mattias WalströmandGitHub 7a9cece84f Merge pull request #1550 from kernelkit/refactor-misc-operational 2026-06-27 12:58:26 +02:00
Mattias Walström b8ba1a1042 test: spec: Allow to generate test specification without logical topology 2026-06-27 10:57:22 +02:00
Mattias Walström 60676e21be test: operational_all: Harden to catch more
- Test so the value in operational actually is correct
- Move test to last in suite to see that there are no stray
  data in operational (yangerd)
2026-06-27 10:57:22 +02:00
Joachim WibergandGitHub 763750b6ed Merge pull request #1549 from kernelkit/coverity-findings 2026-06-27 09:45:11 +02:00
Joachim Wiberg 23c5f16a4f confd: guard NULL forced PMD in fixed-speed ethtool config
In netdag_gen_ethtool_autoneg() the unmapped and "exactly one entry"
checks already guarantee a forced PMD link mode is selected, but
Coverity cannot see the correlation and flags the later deref. Fold
!forced into the entry-count guard to make the invariant explicit.

Fixes Coverity CID 561388.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-27 07:26:04 +02:00
Joachim Wiberg 5d3fce35c8 confd: guard NULL band/ssid/class in WiFi config generation
lydx_get_cattr() yields NULL for an absent leaf or a failed radio
component lookup; that NULL was passed straight to strcmp() on several
WiFi paths (wifi_chan_to_freq, the wifi_gen_mesh width branches,
wifi_find_higher_band_twin, dep_radio_components), risking a crash.
Guard each value before use.

Fixes Coverity CID 561387, 561389, 561390, 561391, 561392.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-27 07:25:59 +02:00
Mattias WalströmandGitHub 6d6064f946 Merge pull request #1548 from kernelkit/veth-regression 2026-06-26 18:30:50 +02:00
Joachim WibergandGitHub a4e5c27b9d Merge pull request #1547 from kernelkit/only-start-crond-if-schedule
Only start crond if there exist at least one schedule configured
2026-06-26 16:15:39 +02:00
Joachim Wiberg ddc57ed6ae confd: fix veth teardown regression
When a veth pair has one end in a container and the other in the host
namespace, removing it failed: the container end's teardown deleted the
whole pair, so the host end's own delete then failed ("Cannot find
device") and aborted the teardown, leaving the interface behind.

The host-namespace delete added for the both-ends-in-container case was
emitted for every container veth end.  Restrict it to the primary end,
which for a host/container pair is the host end that already deletes the
pair itself.

Extend the container veth tests to remove the pair and verify it is gone,
covering both the single-end (regression) and both-ends teardown paths.

Fixes: #1546

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-26 16:05:53 +02:00
Joachim Wiberg 2ae1a70bac test: simplify interfaces/veth_delete
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-26 16:04:25 +02:00
Joachim Wiberg 20350fa52c test/infamy: new method, delete_xpaths()
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-26 16:04:25 +02:00
Mattias Walström f866cf15b4 confd: schedule: generate in change, apply in done
Write the crontab to admin.next on change and promote it on done, drop it
on abort.

Signed-off-by: Mattias Walström <lazzer@gmail.com>
2026-06-26 13:33:44 +02:00
Mattias Walström 7030f85484 confd: schedule: start crond only when a schedule is active
crond shipped auto-enabled in finit (symlinked into enabled/), so it ran
on every boot.  With no cron jobs /var/spool/cron/crontabs does not exist
and 'crond -f' exits non-zero, which finit then crash-loops until it gives
up ("Service crond keeps crashing, not restarting").

Ship crond available-only and let confd manage it: the schedule plugin
already rebuilds the crontab on every change, so enable + touch crond when
at least one consumer is active and disable it otherwise.  No jobs, no
crond, no crash loop.

Signed-off-by: Mattias Walström <lazzer@gmail.com>
2026-06-26 13:33:44 +02:00
Joachim WibergandGitHub d194b52629 Merge pull request #1544 from kernelkit/misc 2026-06-22 08:39:14 +02:00
Joachim Wiberg 674fd6c396 confd: allow both ends of a veth pair to be assigned to containers
Previously at least one end of a veth pair had to remain in the host
namespace, because that end created and destroyed the pair.  Assigning
both ends to containers left no one to create it.

Select a deterministic primary end so exactly one side creates the pair.
When the primary is itself a container interface, create the pair in the
host namespace before the container starts; CNI host-device then moves
each end into its container.  Teardown is deferred to the container
removal script so the pair does not linger and block re-creation.

Drop the now-obsolete limitation notes from the documentation and YANG,
and add a regression test connecting two containers over a veth pair.

Fixes: #941

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-20 19:26:22 +02:00
Joachim Wiberg 0229057612 statd: fix temperature sensor display on Marvell switches
The per-port PHY temperature sensors on Marvell DSA switches show up in
'show hardware' with the name the kernel derives from the full device
tree path, e.g. cp0busbusf2000000mdio12a200switch2mdio01, which honestly
is completely unreadable and also overruns the value column.  Name each
sensor after the front-panel port it serves (e1, e2, ...) by matching
the PHY's device-tree phandle against each interface's phy-handle.

Also:
- show system: report a representative SoC temperature on CN913x by
  matching the ap-* and cp<N>-* thermal zones, hottest wins.
- cli-pretty: truncate over-long sensor names so they can never spill
  into the value column again.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-20 19:26:21 +02:00
Joachim Wiberg b3450d004d doc: lead README with the product, tidy prose
Reorder the README so newcomers see the product before the philosophy:
CLI demo and web UI up top, a "Try it in 5 minutes" section linking the
GNS3 and build-from-source guides, then hardware and the rest.  Tone
down the remaining marketing prose in the hardware and use-case lists.

Rename CODE-OF-CONDUCT.md to CODE_OF_CONDUCT.md so GitHub recognises it
as the community code of conduct, and link the .github/ health files
from the README.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-20 19:26:21 +02:00
Joachim WibergandGitHub 5c3a476cbc Merge pull request #1543 from kernelkit/kernel-upgrade-a7245ddb-ba71-4070-a3ac-b58471a404ab 2026-06-19 20:25:03 +02:00
ael-bot ba677bf5c1 Upgrade Linux kernel to 6.18.36 2026-06-19 12:42:46 +00:00
Joachim Wiberg a0c0a5e45c .github: remove unused inventory.yml maintenance workflow
Not needed anymore.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-18 19:19:41 +02:00
Joachim Wiberg 0fd8696860 .github: drop .sha256 sidecar files from releases
GitHub now publishes a sha256 checksum for each release asset, so the
separately generated .sha256 files are redundant.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-18 18:19:14 +02:00
Joachim Wiberg ebc4ce1d90 test: firewall/basic, minor refactor
Refactor to use a dummy interface instead of wasting a dut.  This not
only simplifies the test but also increases test coverage on test rigs
with a single DUT.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-18 17:58:57 +02:00
Joachim WibergandGitHub 25ff6f54f5 Merge pull request #1541 from kernelkit/web-vendoring
Use kernelkit/goyang instead of patched builtin
2026-06-18 17:05:02 +02:00
Joachim WibergandGitHub e28532d042 Merge pull request #1523 from saba8814/feat/add-scheduler-support
Add support for ietf-schedule
2026-06-18 16:54:52 +02:00
Ejub Sabic 797c5c09f1 fix: remove infix from commands and state files
Signed-off-by: Ejub Sabic <ejub1946@outlook.com>
2026-06-18 13:12:18 +02:00
Ejub Sabic be6d287b19 web: added update notification on web-ui
Signed-off-by: Ejub Sabic <ejub1946@outlook.com>
2026-06-18 10:59:03 +02:00
Ejub Sabic 67ea7a74c1 feat: infix-schedule based on ietf-schedule implementation
Signed-off-by: Ejub Sabic <ejub1946@outlook.com>
2026-06-18 10:22:35 +02:00
Joachim Wiberg a365238854 .github: scan Go modules for known vulnerabilities
Add govulncheck CI and Dependabot for the two Go modules, src/webui
and src/netbrowse, so vendored dependencies don't quietly accumulate
CVEs between manual updates.

The workflow reports every finding in the run summary but only fails
on vulnerabilities our code actually calls in a dependency.  Called
stdlib vulnerabilities are surfaced too, but they're fixed by bumping
the Buildroot host Go rather than a module's go.mod, so they don't
gate the build.

Dependabot ignores openconfig/goyang: it's pinned to our kernelkit
fork via a replace directive and stepped by hand.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-18 09:02:36 +02:00
Joachim Wiberg d02f45d777 webui: depend on kernelkit/goyang fork instead of an in-tree copy
The three YANG 1.1 fixes lived as a frozen, one-shot copy of goyang
v1.6.3 under internal/goyang (via a local `replace`) — invisible to
dependency/CVE tracking and impossible to update.  Move them to the
maintained kernelkit/goyang fork (v1.6.3-kkit branch) and depend on it
through go.mod, pinned by commit hash; drop the in-tree copy.  getopt
falls out of the dependency set since it was only used by goyang's CLI,
not pkg/yang.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-17 20:50:01 +02:00
Joachim WibergandGitHub 19c820fac2 Merge pull request #1539 from kernelkit/web-test-check
Web test check
2026-06-17 15:33:27 +02:00
Joachim Wiberg a47e141dc4 webui: only flag unsaved changes when running differs from startup
The "unsaved changes" banner was set unconditionally at every site that
touches config, so it could appear when running-config actually equals
startup — contradicting the "Show diff" modal, which correctly showed
nothing.  A shared updateCfgUnsaved helper now compares running against
startup (the same datastore pair the diff uses) and sets or clears the
banner cookie to match:

 - Apply and restore-to-running clear it when an Apply/restore reverts
   an out-of-band (e.g. CLI) change back to match startup, instead of
   always showing it.
 - The advanced-tree presence toggle writes only the candidate
   datastore, so it no longer sets the running-vs-startup banner at all
   (matching the other candidate-only tree handlers).

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-17 12:48:06 +02:00
Joachim Wiberg 93db076d72 webui: re-read console/netbrowse shortcuts on full page load
These two shortcuts are toggleable in the configuration but were only
detected once at login and baked into the session, so disabling them
didn't drop the topbar icon / user-menu entry until re-login.

Refresh just those two from running-config on full page loads (htmx
swaps and pollers, which never re-draw them, are skipped), so toggling
takes effect on the next reload without logging out.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-17 11:11:29 +02:00
Joachim Wiberg 322811d712 webui: select password hash, mask inputs, hover help on user forms
Add a Hash dropdown to the add-user and change-password forms — yescrypt
(default), SHA-512, SHA-256, MD5 — passed through to mkpasswd. These are
exactly the four hashes the infix-system:crypt-hash YANG type accepts;
unknown values fall back to the default. Password inputs switch to the
CSS-masked type=text used by keystore secrets, which keeps the browser
password manager from offering to save device credentials. Each add-user
field gets a YANG description as a hover tooltip.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-17 10:32:46 +02:00
Joachim Wiberg bbc01a5e54 webui: add "Show diff" to the unsaved-config banner
Adds a button next to "Save to startup" that opens a modal showing a
unified diff between startup-config and running-config, so the pending
changes can be reviewed before persisting them.  The handler fetches
both datastores over RESTCONF (same serializer → low-noise diff),
writes them to temp files, and runs busybox `diff -u`.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-17 10:32:45 +02:00
Joachim Wiberg 584d6c57df webui: make Users summary static, edit shell on expand
Move the per-user shell editor out of the always-visible row into the
expanded detail; the summary now reports the shell as plain text.  This
also fixes the editor showing "bash" for non-bash users: the dropdown
matched the module-prefixed option value against the bare stored shell,
so it never matched and fell back to the first option.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-17 10:32:44 +02:00
Joachim Wiberg 4d398bbf57 test: skip webui test when not available in build
Add conditional YANG feature 'web-ui' to infix-services.yang, set when
the webui is built.  This feature is what the test now looks for to
determine if it should be skipped or not.

Also, clean up remnant of old test, which was entirely replaced by
Mattias' login/csrf test.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-17 10:30:43 +02:00
Joachim WibergandGitHub a7d2e1b806 Merge pull request #1538 from kernelkit/move-mkdocs-action
.github: move setup-mkdocs to kernelkit/actions
2026-06-17 08:57:21 +02:00
Richard AlpeandJoachim Wiberg 390e60cfcb .github: move setup-mkdocs to kernelkit/actions
Finishes the migration started in 63f46d4d: setup-mkdocs was still
referenced via a relative ./.github/actions path, which GitHub resolves
against the calling repo, not infix, breaking spins (e.g. Styx) that
reuse build.yml/build-release.yml. Use kernelkit/actions/setup-mkdocs@v1.2.

Signed-off-by: Richard Alpe <richard@bit42.se>
2026-06-17 06:56:09 +00:00
Joachim WibergandGitHub f62d049aa9 Merge pull request #1540 from kernelkit/bump-buildroot
Bump buildroot to 2025.02.15
2026-06-17 08:39:27 +02:00
Mattias Walström 4554b77105 Bump buildroot to 2025.02.15
See buildroot changelog for full list of fixes:
https://gitlab.com/buildroot.org/buildroot/-/blob/2025.02.x/CHANGES
2026-06-17 06:49:03 +02:00
Ejub ŠabićandGitHub 0c6d96f8a9 Merge pull request #1537 from saba8814/fix/flaky-dhcp-option-121-test
fix: flaky dhcp option 121 test
2026-06-16 11:50:03 +02:00
Ejub Sabic 473cda35bb fix: flaky dhcp option 121 test
This commit increases attempts on checks in this particular test from default 10 to 60.
This has been introduced to match worst case scenario where after 3 retries(5s) each dhcp waits 30 seconds before next 3 retries.
Commits yields to much more stable test behaviour.

Resolves: #1515

Signed-off-by: Ejub Sabic <ejub1946@outlook.com>
2026-06-16 08:39:40 +02:00
Joachim WibergandGitHub 227f1290fd Merge pull request #1457 from kernelkit/web 2026-06-15 22:58:44 +02:00
Joachim Wiberg 12f1edb435 doc: update ChangeLog
Use absolute links so they survive being exported to Release Notes.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 21:02:17 +02:00
Joachim Wiberg f5c549f669 test: Add test for login flow in WebUI
Verify:

 - Unauthenticated GET /  -> 303 to /login
 - Bad-password rejection -> no session is created
 - Session cookie is flagged HttpOnly
 - Authenticated GET /interfaces -> 200
   - with htmx-config meta tag check
 - State-changing POST without CSRF token -> 403
 - POST with X-CSRF-Token header -> succeeds
 - After logout the session no longer grants access

The cookie-preserving WebUI helper class also folds in the IPv6
link-local zone-id workaround (re-encode %25 back to %) that
infamy.restconf already uses.

Signed-off-by: Mattias Walström <lazzer@gmail.com>
2026-06-15 21:02:16 +02:00
Joachim Wiberg 7e19ee8777 webui: add Connectivity and Addresses cards to Overview
Surface network diagnostics the Overview lacked: default gateway(s) with
a live reachability dot, an Internet reachability check, the effective DNS
resolvers (origin static vs DHCP, plus the interface), search domains, NTP
sync, and the IP addresses of every L3 interface.

All from the operational datastore. Reachability is an async per-address
ping (pulsing green dot on reply, red cross on none).

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 21:02:16 +02:00
Joachim Wiberg 7e0e6f244d webui: user-menu Auto-logout and theme fixes
Auto-logout was client-side only while the server kept a fixed 1h idle
timeout, so "Off" still logged you out. Sessions now carry a per-session
timeout the client syncs from the menu (0 = never), capped at 24h.

Theme: the login toggle icon was never initialised on load (stuck on the
'auto' glyph); add color-scheme so native controls follow the OS on
'auto', and re-apply on OS theme change.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 21:02:15 +02:00
Joachim Wiberg d326939e10 webui: final touches
- Add missing Configure -> shortcuts
 - For shortcuts into YANG tree, expand and mark the tree node
 - Revamp the /mdns status page to mimic /ntp => more vertical space

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 21:02:15 +02:00
Joachim Wiberg a975f55284 webui: show device hostname in topbar
Surface the configured hostname right of the logo, pinned to the
sidebar's right edge so it reads as the start of the content area. When
location and/or contact are set they appear in a hover/focus popover, so
the bar stays uncluttered; the name links to Configure > System.

Loaded asynchronously (hx-trigger="load") to keep it out of every page's
data path — the topbar persists across htmx swaps, so it fetches once per
full page load and renders nothing on error or when no hostname is set.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:29 +02:00
Joachim Wiberg 89392c56be webui: add automatic .cfg migration when restoring a backup
Also adjust some card layout in both Backup and Restore cards.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:29 +02:00
Joachim Wiberg 67ec3faa54 webui: relocate and reorder some menu items
External services like the mdns-browser and the web console belong in
the User Menu next to the bundled user guide.  They all open another
browser tab and provide another service.

Place common tasks close to each other and high up: Interface overview,
routing table, and firewall.  Feels slightly better imo.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:29 +02:00
Joachim Wiberg 9272ae382d webui: type-aware Config column in interface overview
The collapsed Interfaces list left DHCP/SLAAC/zeroconf invisible until a
row was unfolded, so L3 interfaces showed only empty cells. Replace the
Addresses column with a Config column whose pills are chosen by role,
mirroring the Status > Interfaces data column but slanted to configured
intent: vid for VLANs, mode for WiFi, 802.1Q for filtering bridges, and
DHCPv4/DHCPv6/SLAAC/Zeroconf for IP interfaces, alongside any static
addresses.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:28 +02:00
Joachim Wiberg a78eeee651 webui: rebuild NTP & DNS client pages around per-item editing
Replace the single bulk-PUT form + client-side row cloning (which broke
when these pages were split out of System settings) with the
server-driven list pattern used by Keystore/Users: each server is a row
keyed by name that expands to edit, with per-row delete and a dedicated
add form.  DNS search domains become add/delete rows.  Server creates
its container on first use, and field-info ⓘ help is sourced from the
YANG descriptions.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:28 +02:00
Joachim Wiberg 1c3086be94 webui: re-render interface IP block in place after save
Surface confd-inferred values (DHCP option lists, etc.) on the curated
Interfaces page: SaveIPv4/IPv6Settings re-render just the interface's IP
block from the fresh candidate, swapped in place via outerHTML, so the
row stays expanded.  The DHCP foldout auto-expands after a save to reveal
the inferred options.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:27 +02:00
Joachim Wiberg cd19b626a7 webui: surface confd inference in the advanced tree editor
confd infers Infix-suggested values (DHCP option lists, related leaves,
normalised values) into the candidate when a change is written, but the
tree editor's write handlers only wrote — SaveLeaf echoed the typed value
back, SaveGroup returned 204 — so the inferred values never appeared
until a manual page reload.

After a save (cfgSaved) re-fetch the current tree page from the now
up-to-date candidate and re-render it, so inference surfaces wherever it
landed on the page.  SaveLeaf now emits cfgSaved + 204 like SaveGroup
(its form uses hx-swap="none") instead of echoing the raw input.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:26 +02:00
Joachim Wiberg b5b310f3c8 webui: fix enabling DHCP/L3 on an interface with no prior IPv4/IPv6
SaveIPv4Settings/SaveIPv6Settings PATCHed the ietf-ip:ipv4 container to
set forwarding, but a plain PATCH 400s ("Target resource does not exist")
when the interface has no L3 config yet — exactly the first-time
DHCP-enable case.  PATCH the interface instead, with the family container
nested in a single-element list entry (rousette rejects the bare-object
form with LY_EVALID once an augmented container is nested), so the
container is created on first use.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:26 +02:00
Joachim Wiberg f875056706 webui: reload page on reconnect -> /login
When we reconnect to a device we should reload the page instead of
silently resume.  Because the device may have been rebooted from the
CLI or experienced power loss.  Either way, our session in RAM only
is likely gone, and if so we'll be redirected to /login again.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:25 +02:00
Joachim Wiberg e412c28c37 webui: bundle on-device User's Guide
Build the mkdocs User's Guide into WebUI images and surface it in the UI.
post-build.sh runs `mkdocs build` from the top-level mkdocs.yml into
/var/www/guide when the webui package is selected and mkdocs is on the
build host; nginx serves it read-only at /guide/.  The build is
best-effort — a missing mkdocs warns and ships without the guide rather
than failing — and minimal images (no webui) skip it.

The WebUI gates a book icon in the topbar and a User Guide item in the
user menu on the docs being present on disk (os.Stat, fail-closed), both
opening /guide/ in a new tab.

CI: a local setup-mkdocs composite action installs mkdocs and the
plugins from mkdocs.yml; the build and release workflows run it before
the build so images produced in CI include the guide.  developers-guide
documents the new build dependency and restores the missing
mkdocs-glightbox plugin.

Fixes #633

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:25 +02:00
Joachim Wiberg 3ccbdf2f0b webui: add network browser shortcut to topbar and nav
Surface the mDNS network browser (netbrowse at network.local) from the
WebUI: a radar icon in the topbar and a Network Browser entry under
Status > Network, both opening it in a new tab.

Like the console, this is a config-gated capability; fold the console and
netbrowse enable flags into a single infix-services web read at login
(DetectWebShortcuts) and render each entry only when enabled, fail-closed
on a read error.  The link is a static https://network.local/ since
netbrowse is a fixed name-based vhost.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:24 +02:00
Joachim Wiberg 8b3ba1f5c5 webui: add web console shortcut to topbar and nav
Surface the ttyd web console (port 7681) from the WebUI: a terminal icon
in the topbar and a Console entry under Maintenance, both opening the
console in a new tab.  The link target is built client-side from the
current hostname since the console runs on its own HTTPS port.

The console is a config-gated capability (infix-services web/console
enabled), so both entries are detected at login and rendered only when
it is enabled — a disabled console shows no entry rather than a dead
link.  Detection fails closed: a config read error hides the entry.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:24 +02:00
Joachim Wiberg 629f0aa74a webui: add support bundle to Backup & Support
Rename the Maintenance > Backup & Restore page to Backup & Support and
add a Support Bundle card.  Download support runs the on-device
`support collect` tool, which gathers configuration, logs, routing,
interfaces, hardware inventory, and container and service status into a
single archive; the WebUI runs as root so the collection is complete.

The handler buffers the archive and only commits response headers once
collection succeeds, so a mid-collection failure is a clean error rather
than a truncated download.  An optional password encrypts the bundle via
the tool's GPG support, fed on stdin so it never reaches the process
list.  Collection blocks for up to a minute with no output, so the write
deadline is extended past the server's 15s WriteTimeout and nginx gets a
matching read timeout; the browser shows a "collecting" state and saves
the blob client-side using the server's filename.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:23 +02:00
Joachim Wiberg dc2d429093 webui: add Maintenance > Diagnostics
New Diagnostics page under Maintenance runs ping, traceroute, mtr and
nmap with live output, plus a one-shot DNS lookup.  A tab per tool
shares a target field, source-interface and address-family selectors,
and per-tool options; the field is focused on load and Enter runs the
active tool from any field.

Ping and traceroute stream their output line by line over SSE.  mtr
runs --raw and its records are translated into a live hop table —
Loss%, Snt, Last, Avg, Best, Worst updating in place — running
continuously until Stop, or for a set number of cycles.  nmap offers a
fixed set of scan profiles (quick, standard, service/version, host
discovery); a CIDR target sweeps a subnet.

Address family is forced with an explicit -4/-6 flag on the base
binary, so a dual-stack hostname no longer follows the system's
IPv6-first resolver preference on hosts that only have IPv4 routes.

The streamer hands the child a real OS pipe and reaps it on exit, so a
client disconnect (Stop closing the EventSource) kills the tool without
leaking processes.  User input is guarded: targets reject a leading
dash and whitespace, the source interface is allow-listed against
configured interfaces, and arguments are always passed as a slice.  The
target field keeps a history in localStorage.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:23 +02:00
Joachim Wiberg b1146cbb31 webui: add Maintenance > Logs viewer
New Logs page under Maintenance presents the on-disk syslog files
(messages, syslog, kern, auth, routing, firewall, upgrade, debug,
container, mail) with a tab per source and an (empty) chip for files
with no content.

The buffer loads the last 250 lines and pins to the bottom on open and
on tab change, with keyboard focus on the pane so PageUp/PageDown work
immediately.  Load earlier pages back 250 lines at a time, preserving
the reader's scroll position as older lines fill in above; once the
current file is exhausted the button re-labels to "Load previous from
<rotation>" and walks back through rotated and gzipped archives.

Live tail streams new entries over SSE.  The server runs tail -F -n 0
and debounces output; the client follows the bottom unless the reader
has scrolled up.  A status pill shows connecting/live/disconnected.
nginx gets a no-buffering location for the tail stream, mirroring the
software progress endpoint.

Download returns the full archive — current file plus every rotated and
gzipped predecessor — concatenated chronologically.

Severity is coloured by a keyword heuristic since sysklogd writes no
<PRI> to disk; BSD-format lines are shown unconditionally.  Filter is
client-side substring or /regex/, focusable with Ctrl/Cmd-F.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:22 +02:00
Joachim Wiberg 10ab88d93f webui: drop Status > Keystore page
GET /keystore was just an http.Redirect to /configure/keystore — no
actual status view ever existed; the link landed users in Configure
and folded the Status group on the way, which was confusing.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:22 +02:00
Joachim Wiberg d07c8a9b5e webui: styling and cleanup
Vendor a Lucide icon set under static/img/icons/ and partial:ize the
inline SVGs across base, login, firmware, sidebar, and yang-tree.

Reorganize the sidebar with mirrored Status/Configure section labels.
Rename NACM to Users & Groups and Firmware to Software; swap Routes and
Routing between Status and Configure.

Split Configure > System into General, NTP Client, and DNS Client pages,
which opens up for relocating the Set Time RPC from the Maintenance
section to a new Date & Time card in the System General config page.

Update browser tab title on htmx navigation via an HX-Trigger
setPageTitle event; encode the payload as 7-bit ASCII so the middle
dot in "Page · Context" survives header transit.

Add missing hover-help ⓘ icons to bridge port configuration, and adjust
their alignment slightly.  While in interface configuration:
 - fix the annoying Save => collapse so one can edit multiple
   interface sections without having to re-expand the iface
 - add missing dhcpv4 settings
 - add missing custom mac (use advanced view for other settings)
 - clean up Save button mess (consolidate)

Rename Firmware -> Software everywhere in the Maintenance section and
fix "Install progress stuck at 10%" issue.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:21 +02:00
Joachim Wiberg c40c1d0c10 webui: ethernet status and configuration
Status overview gains a Speed column reading ietf-interfaces:speed +
ethernet/duplex.  Interface detail page surfaces PHY type, PMD type,
supported and advertised PMDs.

Configure > Interfaces gains a per-row Ethernet panel: auto-negotiation
toggle, advertised PMDs picker, duplex tri-state, mdi-x tri-state.  Each
has a reset-to-YANG-default button.  Backend writes leaf-by-leaf via
PATCH+DELETE since a single PUT silently drops the duplex leaf.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:21 +02:00
Joachim Wiberg beabc1fe5b webui: rate-limit POST /login via nginx limit_req
Adds an nginx-side rate limit on POST /login so a brute-force or DoS
attempt can't pin the box on the bcrypt-shaped credential check inside
rousette/PAM.  Configuration:

- Keyed on $binary_remote_addr (per source IP), 5 requests/minute
  sustained with a burst of 3 nodelay, a real user fumbling their
  password three times in a row sails through; the 4th attempt in
  the same window returns 429.
- GET /login is unmetered (the keyed map yields the empty string,
  which limit_req treats as "no key, no limit"), so a 401-driven
  HX-Redirect bouncing the user back to the login page doesn't
  eat into the budget.
- 32k zone (~500 IPs, the minimum nginx accepts).  At cache
  saturation an attacker rotating addresses still tops out around
  ~2500 attempts/min, all serialised through bcrypt one at a time.
- 429 instead of the default 503 keeps the response out of the
  /50x.html error_page rewrite (which auto-refreshes and would
  loop a throttled client straight back to /login).

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:20 +02:00
Joachim Wiberg d9611c48a9 webui: rename Go module to infix/webui
The webui Go code now lives in the infix source tree rather than being
imported as an external module.  Switch the module path to match that
reality so the imports read coherently with the rest of the codebase.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:20 +02:00
Joachim Wiberg d7120df65f webui: configure, restore HX-Trigger on save error
renderSaveError lost the HX-Trigger header during the Configure-mode
fold, leaving forms with hx-swap="none" silent on validation errors.
Re-emit `{"cfgError":...}` so the toast layer still gets the event.

DeleteKey's response goes back to a bare 200 to match the other
configure_users.go endpoints; the activity-log redirect form belongs
on the Add/Edit flows.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:19 +02:00
Joachim Wiberg 423e78aba9 webui: clear stale cfg-unsaved banner across device reboots
The "Running config has unsaved changes" banner kept showing after a
power cycle even though the device reloaded running from startup at
boot. The cookie was just "1" with a 24-hour MaxAge, so the browser
held on to it and the banner stayed up — but the state it referred to
was long gone.

Carry a unix timestamp in the cookie value (when set) and compare
against a webuiStartTime captured at process start. Cookies whose
timestamp predates this run are from a previous boot or webui restart;
running was reloaded from startup since then, so they no longer
describe reality and the banner must stay hidden.

  - Apply / Restore-to-running: cookie = now → banner shown.
  - Power cycle, page load: webuiStartTime > cookie ts → banner hidden.
  - Save / Apply-and-Save: cookie cleared (unchanged).
  - Legacy "1" / unparseable values: treated as stale.

Also centre the "Device unreachable" connectivity banner past the
sidebar on desktop — left:0 leaves its text drifting behind the
sidebar's z-index. On ≤1024px the sidebar slides off-screen so the
banner reclaims full width.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:19 +02:00
Joachim Wiberg 9f03e0a34b webui: configure, unify delete affordance on the bin icon
Five pages drew their per-row delete control as a bespoke <svg> "X"
glyph instead of the icon-trash partial used by Hardware, Routes,
Firewall, Interfaces and the yang tree fragments.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:18 +02:00
Joachim Wiberg 5570b13791 webui: sidebar, don't auto-navigate when expanding a section
Clicking a section header (Status / Configure / Maintenance) used to
fold the previously-open section AND fire a click on the first page
link inside the just-opened section. That meant going from
"Status > Overview" to "Configure > Hardware" was three clicks worth
of page loads: Configure header expands and lands you on Configure >
Interfaces, then you click Hardware and wait again.

Toggle now just expands or collapses its own section. Sibling
sections stay open until a real page-link click, at which point
updateActiveNav() already closes the others to leave only the
section containing the active page open.

Configure's enter-POST (running → candidate snapshot) still fires
the first time the Configure accordion opens per page lifecycle, so
the candidate datastore is ready by the time the user clicks a
Configure page.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:18 +02:00
Joachim Wiberg 2c80c65962 webui: add Status > Hardware page
Mirrors the CLI's "show hardware" command — a single page that summarises
everything ietf-hardware exposes about the device:

  - Board (chassis info: model, manufacturer, serial, base MAC)
  - USB Ports (state + description)
  - WiFi Radios (bands, standards, max-AP)
  - GPS Receivers
  - Sensors (grouped by parent component, all status values)
  - Other Components (fallback so the page is exhaustive)

This is Phase 1 of webui-roadmap-v2. Phase 2 will trim the dashboard's
Sensors card to a few key vitals with an "All sensors →" link here,
fixing the existing height-clipping regression in the process.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:17 +02:00
Joachim Wiberg bfb5b14ea0 webui: render NTP empty state gracefully when not configured
The NTP page showed a red "Failed to fetch NTP data" banner whenever
/data/ietf-ntp:ntp returned 404 — which is the normal RESTCONF response
when NTP isn't configured at all. Treat 404 as the not-configured case
and render the empty-state card with a Configure → link, mirroring the
DHCP status page.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:17 +02:00
Joachim Wiberg f00511d235 webui: dedupe restconf 404 checks via restconf.IsNotFound
The errors.As(err, &rcErr) + StatusCode == http.StatusNotFound boilerplate
was repeated across six handlers. Replace with the IsNotFound helper
introduced in restconf/errors.go, dropping the now-unused errors import
from each affected file along the way.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:16 +02:00
Joachim Wiberg f8432f59fb webui: auto-logout with configurable inactivity timeout
Adds a per-user inactivity countdown that POSTs /logout after a
configurable timeout (default 15 min, persisted in localStorage)
and navigates to /login. Background pollers (watchdog, *counters
refresh) are excluded from the activity reset so they don't keep
the session alive indefinitely. htmx:responseError on 401 short-
circuits straight to /login so a server-side expiration doesn't
leave the UI failing silently.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:16 +02:00
Joachim Wiberg 9720c7000f webui: configure interfaces, initial wizard support
Idea of wizard is to simplify:

 - Re-adding Ethernet interfaces you've accidentally removed
 - Setting up WiFi access point/station, incl. radio & PSK
 - VLAN filtering bridges

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:15 +02:00
Joachim Wiberg 974ea7260f webui: configure firewall — zone and policy management
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:14 +02:00
Joachim Wiberg b0bd1dad11 webui: configure routes — static route management page
Adds Configure > Routes page for viewing, adding, editing, and deleting
IPv4 and IPv6 static routes.  Each route row expands in-place to an edit
form; YANG field descriptions are shown as ⓘ tooltips.

Also fixes a goyang bug where inline augments inside a uses block were
never applied to the expanded tree, making it so next-hop-address and
outgoing-interface were missing from the schema — and therefore had no
tooltip text.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-15 19:45:14 +02:00
Mattias WalströmandGitHub ff96401ce2 Merge pull request #1530 from saba8814/test/xpath-coverage-heatmap
Added support for xpath coverage report generation
2026-06-15 16:50:38 +02:00
Ejub Sabic 9853e08145 test: Added xpath coverage report generation (make test)
This commit introduces xpath coverage test report. It plugs into transport layer of test system and generates .md report.
Commit also introduces "make xpath-coverage-report" used to generate pdf out of md.

Resolves: #581
Signed-off-by: Ejub Sabic <ejub1946@outlook.com>
2026-06-15 12:26:51 +02:00
Joachim WibergandGitHub 04fbbc6fc8 Merge pull request #1535 from kernelkit/test-report-via-9pm
Generate test report via 9pm
2026-06-15 09:19:23 +02:00
Joachim Wiberg 6e30d4f590 webui: drop wifi status, add 'configure ->' shortcuts
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:42 +02:00
Joachim Wiberg f9733dc52d webui: refactor maintenance section
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:41 +02:00
Joachim Wiberg 1a33604ed6 webui: add activity log support to curated pages
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:40 +02:00
Joachim Wiberg a80a731324 webui: relocate keystore status -> configure
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:39 +02:00
Joachim Wiberg 6e84a75550 webui: advanced configure using a yang tree
Navigating interfaces now work, and the tree follows the accordion style
like the rest of the UI.

Also, some updates to the curated pages: online help, lists of leafrefs
instead of hard-coded list options.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:38 +02:00
Joachim Wiberg 2ef7490f69 webui: add Configure mode with System, Users, and NACM pages
Implements the configure accordion section with a candidate datastore
workflow: opening Configure copies running → candidate (Enter), each
card saves directly to candidate, Apply copies candidate → running
atomically, and Abort discards by resetting candidate from running.

- RESTCONF client: add Put, Patch, Delete, GetDatastore, PutDatastore,
  CopyDatastore, and a shared doRequest/writeJSON helper
- Handlers: ConfigureHandler (enter/apply/abort), ConfigureSystemHandler
  (hostname, clock, NTP, DNS, motd, editor), ConfigureUsersHandler
  (add/delete user, shell, password, SSH keys, NACM group membership)
- Status > NACM page: read-only group permission matrix derived from
  ietf-netconf-acm groups + rule-list mappings
- Sticky Apply/Abort toolbar with custom confirm dialog; server returns
  HX-Redirect:/ so HTMX does a full-page reload, keeping sidebar in sync
- Accordion persistence fixed: Configure excluded from localStorage so it
  never auto-reopens after Abort/Apply; restore no longer closes an
  accordion that contains the active page
- Password hashing by shelling out to mkpasswd(1)

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:36 +02:00
Joachim Wiberg 8b10678dee webui: reorganise sidebar and rename Dashboard to Overview
Group nav items by purpose:
- Security: Firewall, WireGuard, NACM
- Services: Services, DHCP, NTP, LLDP, mDNS (moved from Network)
- Network trimmed to: Interfaces, Routing, WiFi

Rename Dashboard → Overview for clearer Status > System > Overview
navigation hierarchy.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:35 +02:00
Joachim Wiberg 7ac15796e3 webui: new page, services table
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:34 +02:00
Joachim Wiberg e36aac736e webui: new page, mdns neighbor table
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:33 +02:00
Joachim Wiberg fc328eb79b webui: firewall — zone matrix, drill-down, conditional detection
Add a zone-to-zone traffic matrix matching the CLI's traffic_flow logic:
- HOST row/column always present; zone→HOST uses zone action + services
  (not policies), producing allow/conditional/deny verdicts correctly
- Clicking a matrix cell shows a detail panel with the policy or zone
  reason for the verdict
- Conditional (⚠) detection for zones with services or port-forwarding

Enrich the policy and zone tables:
- Immutable (built-in) policies flagged with ⚷ in column header and row
- Policy rows show description as tooltip; sorted by priority
- Zones table renamed "Host Services" column; Policies drops Priority column
- Wide tables (Zones, Policies) span two grid columns like Dashboard WiFi

Fix the info-grid layout: #content lacked width:100% so margin:0 auto
was overriding flex stretch, collapsing the grid to a single column on
both the firewall and dashboard pages.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:32 +02:00
Joachim Wiberg 0ac5630be0 webui: interfaces — tree view, collapsible bridges, forwarding flag
Display interfaces in a hierarchical tree: bridge and LAG parent rows
show a ▼ toggle in the tree column that collapses/expands their member
ports. The vertical connector line disappears when collapsed. Member
ports within each group are sorted alphabetically; loopback interfaces
always appear first, the rest alphabetically.

A ⚑ forwarding-flag column indicates which interfaces have IP
forwarding enabled, fetched concurrently from ietf-routing:routing.

The "Address" and "Detail" columns are replaced by a single "Data"
column matching the CLI layout: IP addresses, VLAN id, WiFi SSID/mode,
WireGuard peer count, etc. Table rows are top-aligned so interfaces
with multiple addresses don't cause odd vertical centering on other
cells; the two narrow icon columns (⚑ and tree) stay middle-aligned.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:30 +02:00
Joachim Wiberg 1dda8075cb webui: fix formatting in dhcp and lldp pages
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:29 +02:00
Joachim Wiberg 613ec53fdf webui: keystore — expandable row detail view for key data
Expandable detail row per key.  Clicking the ▶ arrow left of the name
unfolds a full-width row showing labeled key fields (Value / Public Key
/ Private Key) as word-wrapped monospace blocks (max-width: 72ch),
making base64 data easy to read and copy.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:28 +02:00
Joachim Wiberg b2bf034a91 webui: overhaul dashboard cards
Split the old monolithic Hardware card into dedicated Board, WiFi, and
Sensors cards, matching the structure of `show system` / `show hardware`
in the CLI:

- System Information: add Current Time row, rename Firmware → Boot Partition
- Board: model, manufacturer, Base MAC only (no sensors)
- WiFi (new, conditional): all radios with supported bands, standards
  (11n/11ac/11ax), and max AP count; card spans 2 grid columns so the
  table never needs horizontal scroll
- Sensors (new, conditional, collapsible): all hardware sensors grouped
  by parent component (SFP sub-sensors indent under sfp1/sfp2 headings);
  JS "Show more" button injected only when content overflows 300 px
- Disk Usage: replaced wide multi-column table with a per-mount widget
  (path + % on one line, health bar, "X free of Y" below); health colour
  follows the same warn/crit thresholds as the memory bar
- Fix humanKiB to output IEC units (MiB / GiB) with a space, matching
  the memory display style
- Fix info-table th wrapping (width:1%; white-space:nowrap)
- Fix JS collapsible init to run on DOMContentLoaded, not immediately in
  <head>, so scrollHeight check has a real DOM to inspect
- CSS scroll-shadow on data-table-wrap: pure-CSS right-edge shadow
  indicates overflow without needing JS or a scroll listener
- Equal-height grid rows via align-items:stretch; info-grid-span-2 for
  cards with wide tables

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:27 +02:00
Joachim Wiberg 12482a5ab5 webui: redesign firmware page, use slot bootnames
Replace the RAUC-centric firmware page with a cleaner layout:

- Show software slots (primary/secondary) using their bootnames rather
  than internal RAUC slot names; filter to rootfs-class slots only
- Add boot order display (mirrors `show software` in the CLI)
- Show installed timestamp per slot (truncated to second precision)
- Fix Booted flag comparison: compare against bootname not slot name
- Lay out all three cards (Software, Install from URL, Upload & Install)
  in a responsive grid instead of full-width stacked sections

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:26 +02:00
Joachim Wiberg f2f52f8518 webui: sidebar fixes, content sectioning, and theme fixes
Drop the useless 768–1024px icon-only sidebar mode; everything up to
1024px now uses the hamburger overlay and full-width sidebar.

Fix the user dropdown on touch devices: click now toggles aria-expanded
so CSS-driven visibility works without hover.  A document click listener
closes the menu when tapping outside.

Give the sidebar proper light/dark tokens instead of hardcoded dark
values. Introduce --sidebar-hover-fg (primary in light, white in dark)
so nav-link hover text is legible in both themes.

Fix dropdown hover background using --border instead of --border-subtle,
which had zero contrast against the surface in dark mode.

Keep active nav link in sync with the current URL via JS so it updates
correctly after htmx navigation instead of staying on the initial
server-rendered page.  Also, add YouTube-style progress when laoding
pages that take time, e.g., Dashboard.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:25 +02:00
Joachim Wiberg 917eec3f25 webui: anonymous login, user menu, theme toggle, in-memory sessions
Anonymise the login page: generic "Login" title, lock icon instead
of product logo — makes the device harder to fingerprint.

Add a floating theme toggle button to the login page so users can
switch theme before logging in (cycles auto→light→dark).

Replace the four cluttered sidebar footer buttons (theme, download
config, reboot, logout) with an always-visible topbar user menu.
The dropdown shows the username, three theme options with a checkmark
on the active one, download config, reboot, and logout.

Refactor all handler page-data structs to embed PageData instead of
repeating the four base fields, and add Username (sourced from the
request context) so the topbar can display the logged-in user.

Polish pass on the login card: spacing, focus styles, button weights.

Replace the AES-encrypted-cookie session model with an opaque random
token backed by an in-memory map. The vendored design embedded the
user's username, password, CSRF, and feature flags into the cookie
and persisted the AES key under /var/lib/misc, making the keyfile a
passive credential-recovery oracle — anyone who could read it could
decrypt any captured cookie and recover the plaintext admin password.

Now: cookie is 32 bytes of crypto/rand, base64-url-encoded; sessions
live only in process memory; reboot/respawn/upgrade drops every
active session and the UI's 401 handler bounces the user to /login.
Sliding-window refresh on user activity, janitor goroutine sweeps
expired entries. --session-key flag and /var/lib/misc/webui-session.key
are gone.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:24 +02:00
Joachim Wiberg 6271e40ce0 webui: accordion sidebar and general-purpose status badges
Replace flat nav-section-header labels with collapsible <details>/<summary>
accordion groups (Network, Wireless, VPN, Services, System). Groups default
open; user state persists via localStorage. Tablet icon-only mode always
shows all items regardless of open/closed state.

Add badge-up/down/warning/info/neutral utility classes with full dark mode
support, complementing the existing firewall-specific badge variants.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:23 +02:00
Joachim Wiberg bc535099ec webui: initial import
Initial import of https://github.com/kernelkit/webui2 @ e344d3f

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 22:07:22 +02:00
Joachim Wiberg a9c722fcd5 netbrowse: link neighbor cards to the resolved host, not adminurl's
avahi resolves mDNS hostname conflicts at runtime by appending -2, -3, …
but the advertiser writes its adminurl TXT record with the configured
hostname and never updates it. A device renamed infix-2.local thus keeps
advertising adminurl=http://infix.local, so its card linked to the other
device. Rebase the adminurl onto the actually-resolved host (keeping
scheme, port, and path); a no-op in the common single-device case.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 18:28:22 +02:00
Joachim Wiberg f8f73b89cb statd: extend mount-points + advertised pmds per interface
- Include /run and /tmp in mount-points, likely more interesting than /.
- Include advertised link modes in operational to compare with supported

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 18:28:22 +02:00
Joachim Wiberg ad28fd9db5 statd: add contact + location to ietf-system:system operational
The contact and location leaves were absent from the operational
datastore.  Fetch each leaf from the running ds tool and inject
into ietf-system:system.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 18:28:21 +02:00
Joachim Wiberg 9d5fd5db2f bin: fix file permission regression in /cfg/startup-config.cfg
admin@infix-00-00-00:~$ less /cfg/startup-config.cfg
cat: can't open '/cfg/startup-config.cfg': Permission denied
/cfg/startup-config.cfg: Permission denied
admin@infix-00-00-00:~$ ls -l /cfg/
total 16
-rw-------    1 root     wheel        13655 Jun 14 12:59 startup-config.cfg

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-06-14 18:28:18 +02:00
Richard Alpe 76c314a23a test: render PDF report via 9pm report.py
The test-report target fed a pre-baked report.adoc to asciidoctor-pdf.
Let 9pm's report.py, the author of result.json, both generate and
render the report instead.  It is invoked with Infix's own theme, fonts
and logo so the output is unchanged; downstream projects need only
override LOGO to rebrand it.

Signed-off-by: Richard Alpe <richard@bit42.se>
2026-06-12 15:38:24 +02:00
Richard Alpe 187c19639f Bump 9pm submodule
Signed-off-by: Richard Alpe <richard@bit42.se>
2026-06-12 15:38:22 +02:00
Joachim WibergandGitHub 68dfec210c Merge pull request #1533 from kernelkit/fix-yang-errors
confd: fix libyang must-expression warnings in NTP and WireGuard YANG
2026-06-12 02:22:52 +02:00
Mattias Walström 50e678232d confd: fix libyang must-expression warnings in NTP and WireGuard YANG
Two unrelated must expressions referenced schema nodes that don't exist,
emitting warnings on every schema load (and in netopeer2-cli):

 - NTP: the iburst/burst deviations put the must on the leaf, so
   "ntp:iburst"/"ntp:burst" resolved as a child of the leaf. Use ".".
 - WireGuard: deref() of a key *-ref lands on the key's "name" leaf, so
   "deref(.)/ks:public-key-format" looked under "name". Use
   "deref(.)/../...", and descend into the "public-key" list for the
   truststore public-key-bag.
2026-06-11 12:22:00 +02:00
Joachim WibergandGitHub 3b56b249b5 Merge pull request #1532 from kernelkit/kernel-upgrade-d242c3ba-4cc0-475b-aec6-1feaa224b3f4 2026-06-10 11:20:05 +02:00
ael-bot be3c76b7bf Upgrade Linux kernel to 6.18.35 2026-06-10 07:21:05 +00:00
Mattias WalströmandGitHub 4fa933f4f5 Merge pull request #1531 from saba8814/doc/add-missing-test-spec 2026-06-09 17:49:28 +02:00
Ejub Sabic a2f57bdd4d doc: add missing test doc for container basic test
Signed-off-by: Ejub Sabic <ejub1946@outlook.com>
2026-06-09 08:58:18 +02:00
492 changed files with 53506 additions and 9057 deletions
+1 -1
View File
@@ -272,7 +272,7 @@ other contributions that are not aligned to this Code of Conduct."*
[PEP-8]: https://peps.python.org/pep-0008/
[RDD]: https://tom.preston-werner.com/2010/08/23/readme-driven-development
[cbeams]: https://cbea.ms/git-commit/#seven-rules
[conduct]: CODE-OF-CONDUCT.md
[conduct]: CODE_OF_CONDUCT.md
[DCO]: https://developercertificate.org/
[closing]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests
[gpg-verify]: https://docs.github.com/en/authentication/managing-commit-signature-verification
+15
View File
@@ -0,0 +1,15 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /src/webui
schedule:
interval: weekly
# goyang is pinned to our kernelkit fork via a replace directive and
# stepped by hand when we add patches; leave it for Dependabot to ignore.
ignore:
- dependency-name: github.com/openconfig/goyang
- package-ecosystem: gomod
directory: /src/netbrowse
schedule:
interval: weekly
+1 -7
View File
@@ -113,12 +113,6 @@ jobs:
pattern: "artifact-*"
merge-multiple: true
- name: Create checksums ...
run: |
for file in *.tar.gz; do
sha256sum $file > $file.sha256
done
- uses: ncipollo/release-action@v1
with:
allowUpdates: true
@@ -128,7 +122,7 @@ jobs:
prerelease: true
tag: "latest-boot"
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "*.tar.gz*"
artifacts: "*.tar.gz"
- name: Summary
run: |
+1 -10
View File
@@ -223,15 +223,6 @@ jobs:
output/images/*-emmc.img*
retention-days: 30
- name: Create checksums
run: |
cd output/images/
for file in *-sdcard.img *-emmc.img; do
if [ -f "$file" ]; then
sha256sum "$file" > "$file.sha256"
fi
done
- name: Upload to release
uses: ncipollo/release-action@v1
with:
@@ -242,7 +233,7 @@ jobs:
prerelease: true
tag: "latest-boot"
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "output/images/*-sdcard.img*,output/images/*-emmc.img*"
artifacts: "output/images/*-sdcard.img,output/images/*-emmc.img"
- name: Generate summary
run: |
+3
View File
@@ -45,6 +45,9 @@ jobs:
target: ${{ matrix.target }}
enabled: ${{ inputs.use_cache }}
# WebUI images bundle the mkdocs User's Guide via post-build.sh.
- uses: kernelkit/actions/setup-mkdocs@v1.2
- name: Configure & Build
env:
INFIX_RELEASE: ${{ steps.vars.outputs.ver }}
+3
View File
@@ -124,6 +124,9 @@ jobs:
with:
target: ${{ env.TARGET }}
# WebUI images bundle the mkdocs User's Guide via post-build.sh.
- uses: kernelkit/actions/setup-mkdocs@v1.2
- name: Configure ${{ env.TARGET }}
run: |
make ${{ env.TARGET }}_defconfig
+68
View File
@@ -0,0 +1,68 @@
name: Go Vulnerability Scan
on:
push:
branches:
- main
paths:
- 'src/webui/**'
- 'src/netbrowse/**'
- '.github/workflows/govulncheck.yml'
pull_request:
paths:
- 'src/webui/**'
- 'src/netbrowse/**'
- '.github/workflows/govulncheck.yml'
schedule:
- cron: '5 0 * * 6' # Saturday at 00:05 UTC, same as Coverity
workflow_dispatch:
jobs:
govulncheck:
if: ${{ github.repository_owner == 'kernelkit' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
module:
- src/webui
- src/netbrowse
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: stable
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Scan ${{ matrix.module }}
working-directory: ${{ matrix.module }}
run: |
# Full report, for the run summary. govulncheck exits non-zero
# whenever it finds anything, so don't let it fail the step here.
{
echo "## govulncheck: ${{ matrix.module }}"
echo '```'
govulncheck ./... || true
echo '```'
} | tee -a "$GITHUB_STEP_SUMMARY"
# Gate on vulnerabilities reachable from our code through a
# dependency. govulncheck's call-graph analysis is transitive,
# so indirect use counts too (we call a dep that calls the bad
# symbol). trace[0] is the vulnerable symbol; we key on the
# module it lives in. A chain that bottoms out in stdlib is
# fixed by bumping the Buildroot host Go, not this module's
# go.mod, so it's reported above but doesn't fail the build.
# Keep the json scan and jq unguarded so a tool failure fails the
# gate closed; only grep's no-match exit (all-clear) is tolerated.
govulncheck -format json ./... > scan.json || true
called=$(jq -r 'select(.finding.trace[0].function != null) |
.finding.trace[0].module' scan.json | sort -u)
vulns=$(printf '%s' "$called" | grep -vx stdlib || true)
if [ -n "$vulns" ]; then
echo "::error::Called vulnerabilities in dependencies: $(echo "$vulns" | paste -sd, -)"
exit 1
fi
-88
View File
@@ -1,88 +0,0 @@
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@v6
- uses: kernelkit/actions/cache-restore@v1
with:
target: x86_64
dl-prefix: dl-netconf
- 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
+1 -7
View File
@@ -16,12 +16,6 @@ jobs:
pattern: "artifact-*"
merge-multiple: true
- name: Create checksums ...
run: |
for file in *.tar.gz; do
sha256sum $file > $file.sha256
done
- uses: ncipollo/release-action@v1
with:
allowUpdates: true
@@ -32,7 +26,7 @@ jobs:
prerelease: true
tag: "latest"
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "*.tar.gz*"
artifacts: "*.tar.gz"
- name: Summary
run: |
+1 -12
View File
@@ -83,17 +83,6 @@ jobs:
pattern: "artifact-*"
merge-multiple: true
- name: Create checksums ...
run: |
for file in *.tar.gz; do
sha256sum $file > $file.sha256
done
if ls *.qcow2 &>/dev/null; then
for file in *.qcow2; do
sha256sum "$file" > "$file.sha256"
done
fi
- name: Extract ChangeLog entry ...
run: |
cat doc/ChangeLog.md | ./utils/extract-changelog.sh > release.md
@@ -106,7 +95,7 @@ jobs:
makeLatest: ${{ steps.rel.outputs.latest }}
discussionCategory: ${{ steps.rel.outputs.cat }}
bodyFile: release.md
artifacts: "*.tar.gz*,*.qcow2*"
artifacts: "*.tar.gz,*.qcow2"
- name: Summary
run: |
+12
View File
@@ -143,3 +143,15 @@ jobs:
with:
name: test-report
path: output/images/test-report.pdf
- name: Generate XPath Coverage Report for ${{ env.TARGET }}
if: always()
run: |
make test-dir="$(pwd)/$TEST_PATH" xpath-coverage-report
- name: Upload XPath Coverage Report as Artifact
if: always()
uses: actions/upload-artifact@v7
with:
name: xpath-coverage-report
path: output/images/xpath-coverage-report.pdf
+1 -12
View File
@@ -27,17 +27,6 @@ jobs:
pattern: "artifact-*"
merge-multiple: true
- name: Create checksums
run: |
for file in *.tar.gz; do
sha256sum $file > $file.sha256
done
if ls *.qcow2 &>/dev/null; then
for file in *.qcow2; do
sha256sum "$file" > "$file.sha256"
done
fi
- uses: ncipollo/release-action@v1
with:
tag: latest
@@ -54,7 +43,7 @@ jobs:
**Commit:** ${{ github.sha }}
**Built:** ${{ github.run_id }}
artifacts: "*.tar.gz*,*.qcow2*"
artifacts: "*.tar.gz,*.qcow2"
- name: Summary
run: |
+4 -3
View File
@@ -110,9 +110,10 @@ config INFIX_OEM_PATH
directory (absolute path) and the Infix post-build.sh will call `git
describe -C $INFIX_OEM_PATH`.
Note: for release builds the global variable INFIX_RELEASE overrides
the version information derived from `git describe`. However, the
GIT version is always saved as the BUILD_ID in /etc/os-releases.
Note: the OS version (VERSION, VERSION_ID, BUILD_ID in
/etc/os-release) is always derived from `git describe`. The global
variable INFIX_RELEASE does not change it; it only labels the release
channel (IMAGE_VERSION) and names the published artifacts.
endmenu
+116 -93
View File
@@ -1,63 +1,21 @@
[![License Badge][]][License] [![Release Badge][]][Release] [![GitHub Status][]][GitHub] [![Coverity Status][]][Coverity Scan] [![Discord][discord-badge]][discord-url]
[![License Badge][]][License] [![Release Badge][]][Release] [![GitHub Status][]][GitHub] [![Discord][discord-badge]][discord-url]
<img align="right" src="doc/logo.png" alt="Infix — Immutable.Friendly.Secure" width=480 padding=10>
<img align="right" src="doc/logo.png" alt="Infix — Immutable.Friendly.Secure" width=380 padding=10>
Turn any ARM or x86 device into a powerful, manageable network appliance
in minutes. From $35 Raspberry Pi boards to enterprise switches — deploy
routers, IoT gateways, edge devices, or custom network solutions that
just work.
Infix turns an ARM or x86 device into a managed network appliance. The
same OS runs on a $35 Raspberry Pi and on enterprise switching hardware,
so you can build a router, an IoT gateway, or an edge device on whatever
you have on hand.
## Our Values
More in-depth material is available in our blog and User Guide:
**🔒 Immutable**
Your system never breaks. Read-only filesystem with atomic upgrades
means no configuration drift, no corrupted updates, and instant rollback
if something goes wrong. Deploy once, trust forever.
- <https://www.kernelkit.org/>
- <https://www.kernelkit.org/infix/>
**🤝 Friendly**
Actually easy to use. Auto-generated CLI from standard YANG models comes
with built-in help for every command — just hit <kbd>?</kbd> or
<kbd>TAB</kbd> for context-aware assistance.
## See it in action
Familiar NETCONF & RESTCONF APIs and [comprehensive documentation][4]
mean you're never stuck. Whether you're learning networking or managing
enterprise infrastructure.
**🛡️ Secure**
Built with security as a foundation, not an afterthought. Minimal
attack surface, separation between system and data, and container
isolation. Sleep better knowing your infrastructure is protected.
## Why Choose Infix
**Hardware Flexibility**: Start with a $35 Raspberry Pi, scale to
enterprise switching hardware. Same OS, same tools, same reliability.
**Standards-Based**: Built around YANG models and IETF standards. Learn
once, use everywhere - no vendor lock-in.
**Container Ready**: Run your applications alongside networking
functions. GPIO access, dedicated Ethernet ports, custom protocols —
your device, your rules.
## Use Cases
1. **Home Labs & Hobbyists**:
Transform a Raspberry Pi into a full-featured router with WiFi
1. **IoT & Edge Computing**:
Bridge devices to the cloud with reliable, updatable gateways
1. **Small Business Networks**:
Enterprise-grade features without the complexity or cost
1. **Developers & Makers**:
Test networking concepts, prototype IoT solutions, or build custom
appliances
1. **Network Professionals**:
Consistent tooling from development to production deployment.
How about a digital twin using raw Qemu or [GNS3](https://gns3.com/infix)!
## Quick Example
Configure an interface in seconds - the CLI guides you with built-in help:
The CLI is generated from the [YANG models][inside], so it guides you with
built-in help. Here's setting an IP address on an interface:
<pre><code>admin@infix-12-34-56:/> <b>configure</b>
admin@infix-12-34-56:/config/> <b>edit interface eth0</b>
@@ -94,38 +52,94 @@ eth0 ethernet UP 52:54:00:12:34:56
admin@infix-12-34-56:/> <b>copy running startup</b>
</code></pre>
Notice how <kbd>TAB</kbd> completion shows available options, `show`
displays current config, and `diff` shows exactly what changed before
you commit your changes with the `leave` command.
<kbd>TAB</kbd> completes available options and <kbd>?</kbd> shows online help
for each option and argument. `show` displays the current config, and `diff`
shows exactly what changed before you commit it with `leave`. See the [CLI
documentation][3] for more.
For more information, see [CLI documentation][3].
## Web interface
## Get Started
If the CLI isn't your style, the same configuration is available through the
web interface. Log in from a browser, keep an eye on your device from the
Status dashboard and use the Configure > Interface setup wizard to create more
advanced setups, or just fold out an interface to add an IP address.
Get [pre-built images][5] for your hardware. Use the CLI, web
interface, or standard NETCONF/RESTCONF tools, e.g., `curl`. Add
containers for any custom functionality you need.
<p>
<a href="doc/img/webui-login.png"><img src="doc/img/webui-login.png" alt="Login" align="top" width=220></a>
<a href="doc/img/webui-dashboard.png"><img src="doc/img/webui-dashboard.png" alt="Dashboard" width=290></a>
<a href="doc/img/webui-wizard.png"><img src="doc/img/webui-wizard.png" alt="Setup wizard" width=260></a>
</p>
### Supported Platforms
The web interface is built on the same concepts as the CLI, so operational
status and state are kept separate from configuration and commands.
- **Raspberry Pi 2B/3B/4B/CM4** - Perfect for home labs, learning, and prototyping
- **Banana Pi-R3** - Your next home router and gateway
- **NanoPi R2S** - Compact dual-port router in a tiny package
- **x86_64** - Run in VMs or on mini PCs for development and testing
- **Marvell CN9130 CRB, EspressoBIN** - High-performance ARM64 platforms
- **Microchip SparX-5i** - Enterprise switching capabilities
- **Microchip SAMA7G54-EK** - ARM Cortex-A7
- **NXP i.MX8MP EVK** - Highly capable ARM64 SoC
- **StarFive VisionFive2** - RISC-V architecture support
## Try it in 5 minutes
*Why start with Raspberry Pi?* It's affordable, widely available, has
built-in WiFi + Ethernet, and runs the exact same Infix OS you'd deploy
in production. Perfect for learning, prototyping, or even small-scale
deployments.
You don't need hardware to get started:
- **In a virtual lab** — run a full topology in [GNS3][gns3-post] and test
networks entirely in software.
- **From source** — [build it and `make run`][build-post] to boot Infix in
QEMU, from `git clone` to pinging the internet.
- **On real hardware** — grab a [pre-built image][5] for your board, or run
the `x86_64` image in any VM.
Log in with `admin` / `admin` on the virtual and pre-built images. On
shipped products the factory-reset credentials are customizable — we
typically provision a unique per-device password stored in EEPROM/VPD.
## Supported hardware
- **Raspberry Pi 2B/3B/4B/CM4** - a good starting point; built-in WiFi and Ethernet
- **Banana Pi-R64/R3/R3 Mini/R4** - multi-port routers and gateways
- **NanoPi R2S** - compact dual-port router
- **x86_64** - VMs and mini PCs, for development or production
- **Marvell CN9130 CRB, EspressoBIN** - ARM64 development boards
- **Microchip SparX-5i** - enterprise switching
- **Microchip SAMA7G54-EK** - ARM Cortex-A7 evaluation kit
- **NXP i.MX8MP EVK** - ARM64 SoC evaluation kit
- **StarFive VisionFive2** - RISC-V board
*Why start with Raspberry Pi?* It's cheap, easy to get hold of, has
built-in WiFi and Ethernet, and runs the same Infix you'd deploy in
production — so what you learn on it carries straight over.
> [!TIP]
> 📖 **[Complete documentation][4]** • 💬 **[Join our Discord][discord-url]**
## Technical Details
## Why Infix
**🔒 Immutable**
Read-only filesystem with atomic upgrades. An update either applies
cleanly or rolls back, so a failed upgrade or a power cut midway through
won't leave you with a half-broken system.
**🤝 Friendly**
The CLI is generated from the YANG models, so every command carries its
own help — hit <kbd>?</kbd> or <kbd>TAB</kbd> to see what's available.
The same models are reachable over NETCONF and RESTCONF, with
[documentation][4] for when you get stuck.
**🛡️ Secure**
A small attack surface, separation between system and data, and
container isolation. Since the system partition is read-only, a
compromised service or container can't rewrite the OS underneath it.
## Use cases
1. **Home labs & hobbyists**:
Turn a Raspberry Pi into a router with WiFi
1. **IoT & edge**:
Build gateways you can update in the field
1. **Small business networks**:
Routing, firewalling, and VLANs on affordable hardware
1. **Developers & makers**:
Prototype networking ideas, or build a custom appliance with containers
1. **Network professionals**:
The same tooling from lab to production — spin up a digital twin in raw
Qemu or [GNS3](https://gns3.com/infix)
## Under the hood
<a href="https://bitsign.se">
<picture>
@@ -135,27 +149,29 @@ deployments.
</picture>
</a>
Built on proven open-source foundations: [Linux][0], [Buildroot][1], and
[sysrepo][2] — for reliability you can trust:
Built on [Linux][0], [Buildroot][1], and [sysrepo][2]:
- **Immutable OS**: Read-only filesystem, atomic updates, instant rollback
- **YANG Configuration**: Industry-standard models with auto-generated tooling
- **Hardware Acceleration**: Linux switchdev support for wire-speed packet processing
- **Container Integration**: Docker support with flexible network and hardware access
- **Memory Efficient**: Runs comfortably on devices with as little as 256 MB RAM
- **Code Signing**: Releases are cryptographically signed for integrity verification
- **Immutable OS**: read-only filesystem, atomic updates, rollback on failure
- **YANG configuration**: standard models with an auto-generated CLI and APIs
- **Hardware acceleration**: switchdev offload for wire-speed forwarding
- **Container integration**: Docker, with access to host network and hardware
- **Memory efficient**: runs on devices with as little as 256 MB RAM
- **Code signing**: releases are cryptographically signed
Perfect for everything from resource-constrained edge devices to
high-throughput network appliances.
With the entire system modeled in YANG, scalability is no longer an
issue, be it in development, testing, or end users deploying and
monitoring their devices. All knobs and dials are accessible from the
CLI (console/SSH), or remotely using the native NETCONF or RESTCONF
APIs.
Because the whole system is modeled in YANG, every setting is reachable
the same way: from the CLI over console or SSH, or remotely over the
native NETCONF and RESTCONF APIs. The same models drive development,
testing, and day-to-day monitoring.
> Check the *[Latest Build][]* for bleeding-edge features.
## Contributing
Bug reports, ideas, and pull requests are welcome. Start with
[CONTRIBUTING][contributing] and the [code of conduct][coc]. Found a
security issue? Follow the [security policy][security]. Need a hand?
See [support options][support] or [join us on Discord][discord-url].
---
<div align="center">
@@ -171,6 +187,13 @@ APIs.
[3]: https://www.kernelkit.org/infix/latest/cli/introduction/
[4]: https://www.kernelkit.org/infix/
[5]: https://github.com/kernelkit/infix/releases/latest
[inside]: https://www.kernelkit.org/posts/inside-infix/
[gns3-post]: https://www.kernelkit.org/posts/infix-in-gns3/
[build-post]: https://www.kernelkit.org/posts/building-infix-from-source/
[contributing]: .github/CONTRIBUTING.md
[coc]: .github/CODE_OF_CONDUCT.md
[security]: .github/SECURITY.md
[support]: .github/SUPPORT.md
[Latest Build]: https://github.com/kernelkit/infix/releases/tag/latest "Latest build"
[License]: https://en.wikipedia.org/wiki/GPL_license
[License Badge]: https://img.shields.io/badge/License-GPL%20v2-blue.svg
-42
View File
@@ -87,45 +87,6 @@ Infix ships with the following factory defaults.
> are bridged into the LAN as access points. WiFi is not included with the board
> and is not part of the factory default configuration.
## SIM Card Installation
The BPI-R4 has three SIM holders, one per PCIe slot:
| Slot | PCIe socket | Typical use |
|----------|-------------|--------------------------|
| **SIM1** | M.2 Key-B | 4G/5G WWAN modem |
| **SIM2** | mPCIe #1 | Cellular or other modem |
| **SIM3** | mPCIe #2 | Cellular or other modem |
All three take **nano-SIM (4FF)** cards.
> [!NOTE]
> The official Banana Pi pages show an older board revision with a sliding
> "sled" holder. Newer boards ship with a fixed friction-fit holder instead:
> there is no lid to lift and no spring eject. The [closest matching photo on
> bananapi.org][15] shows the current mechanism.
### Inserting a SIM
1. Orient the card with the gold contacts facing the PCB (printed
side up) and the cut corner pointing toward the M.2/mPCIe socket,
which on this board is also toward the outer edge. The silkscreen
next to each holder shows the same outline.
2. Push the SIM straight in until it is fully seated. There is no
click and the holder is not spring-loaded, so press firmly until
the contacts engage and the rear edge of the card sits flush with
the holder.
3. A SIM that still protrudes is not fully inserted; push again.
### Removing a SIM
The friction grip on these holders is tight. Gently pry the card out
with a fingernail or a thin plastic spudger. Avoid metal tweezers
near the contacts. Removing the front panel of the metal case is
absolutely necessary to access the SIM holder.
For software configuration once the modem is recognised, see the
[Cellular Modem User's Guide][16].
## Getting Started
@@ -397,6 +358,3 @@ make aarch64
[12]: https://github.com/frank-w/u-boot/releases
[13]: https://github.com/frank-w/u-boot/releases/download/CI-BUILD-2026-01-bpi-2026.01-2026-01-15_2013/bpi-r4_spim-nand_bl2.img
[14]: https://github.com/frank-w/u-boot/releases/download/CI-BUILD-2026-01-bpi-2026.01-2026-01-15_2013/bpi-r4_spim-nand_fip.bin
[15]: https://docs.banana-pi.org/bpi-r4/bpi-r4_4g5g_2.jpg
[16]: https://www.kernelkit.org/infix/latest/modem/
@@ -10,13 +10,6 @@
"state": {
"admin-state": "unlocked"
}
},
{
"name": "USB2",
"class": "infix-hardware:usb",
"state": {
"admin-state": "unlocked"
}
}
]
},
@@ -373,7 +366,7 @@
]
},
"infix-meta:meta": {
"version": "1.8"
"version": "1.7"
},
"infix-services:mdns": {
"enabled": true
@@ -10,13 +10,6 @@
"state": {
"admin-state": "unlocked"
}
},
{
"name": "USB2",
"class": "infix-hardware:usb",
"state": {
"admin-state": "unlocked"
}
}
]
},
@@ -365,7 +358,7 @@
]
},
"infix-meta:meta": {
"version": "1.8"
"version": "1.7"
},
"infix-services:mdns": {
"enabled": true
+26
View File
@@ -120,3 +120,29 @@ grep -qsE '^/bin/clish$$' "$TARGET_DIR/etc/shells" \
if [ "$BR2_PACKAGE_HOST_PYTHON_YANGDOC" = "y" ]; then
mkyangdoc "$BINARIES_DIR/yangdoc.html"
fi
# Bundle the mkdocs User's Guide into the rootfs, served by the WebUI's
# nginx at /guide/. Only when the WebUI is present (nothing serves it
# otherwise, and it keeps minimal images small) and mkdocs is on the build
# host. Best-effort: a failed build warns but does not abort the image
# build, and the WebUI hides its User Guide entry when the docs are absent.
mkuserguide()
{
local cfg dst
cfg="$(readlink -f "$common/../..")/mkdocs.yml"
dst="$TARGET_DIR/var/www/guide"
if ! command -v mkdocs >/dev/null 2>&1; then
ixmsg "mkdocs not found, skipping User's Guide bundling"
return
fi
ixmsg "Building User's Guide into $dst"
if ! mkdocs build -f "$cfg" -d "$dst" --clean --quiet; then
ixmsg "WARNING: mkdocs build failed, shipping without on-device User's Guide"
rm -rf "$dst"
fi
}
if [ "$BR2_PACKAGE_WEBUI" = "y" ]; then
mkuserguide
fi
+16
View File
@@ -253,3 +253,19 @@ config QEMU_NET_PORTS
int "Number of Rocker switch ports"
depends on QEMU_NET_ROCKER
default 10
comment "Wireless"
config QEMU_WIFI_RADIOS
int "Number of virtual WiFi radios (mac80211_hwsim)"
default 0
help
Number of simulated 802.11 radios to create with mac80211_hwsim.
The count is passed to the guest via the opt/wifi fw_cfg and loaded
by the 00-hwsim boot script.
0 (default) means no radios, so nothing wireless clutters the guest.
Set a small number (e.g. 2) for local AP/station experiments.
Note: 'make run' is a single guest with no inter-guest wireless
medium, so these radios only reach each other within this one guest.
+11
View File
@@ -310,6 +310,16 @@ EOF
echo -n "-fw_cfg name=opt/vpd,file=$vpd_file"
}
wifi_args()
{
# Number of mac80211_hwsim radios for the guest, read at boot by 00-hwsim
# (fw_cfg opt/wifi). Default 0 -> no radios, no wifi clutter.
radios=${CONFIG_QEMU_WIFI_RADIOS:-0}
wifi="${qdir}/wifi"
echo "$radios" > "$wifi"
echo -n "-fw_cfg name=opt/wifi,file=$wifi "
}
wdt_args()
{
echo -n "-device i6300esb "
@@ -387,6 +397,7 @@ run_qemu()
$(usb_args) \
$(host_args) \
$(net_args) \
$(wifi_args) \
$(wdt_args) \
$(rtc_args) \
$(vpd_args) \
-4
View File
@@ -1,5 +1 @@
CONFD_TIMEOUT=60
#DEBUG=1
# -v debug
CONFD_ARGS=""
+5
View File
@@ -0,0 +1,5 @@
RESTCONF_URL=https://127.0.0.1/restconf
INSECURE_TLS=1
# Spool firmware uploads (and any other temp files) to persistent storage
# instead of the RAM-backed /tmp to enable push-upgrades on low-memory systems.
TMPDIR=/var/tmp
@@ -5,4 +5,3 @@
4 static
5 dhcp
6 random
7 wwan
+1
View File
@@ -0,0 +1 @@
../restconf.app
@@ -0,0 +1,3 @@
allow 127.0.0.1;
allow ::1;
deny all;
+1
View File
@@ -0,0 +1 @@
restconf-access-local.conf
@@ -1,5 +1,6 @@
# /telemetry/optics is for streaming (not used atm)
location ~ ^/(restconf|yang|.well-known)/ {
include /etc/nginx/restconf-access.conf;
grpc_pass grpc://[::1]:10080;
grpc_set_header Host $host;
grpc_set_header X-Real-IP $remote_addr;
@@ -0,0 +1,3 @@
if [ -s /run/os-update ]; then
printf '\n\033[1;33m *** %s ***\033[0m\n\n' "$(cat /run/os-update)"
fi
@@ -0,0 +1 @@
f /run/os-update 0666 admin admin
@@ -620,73 +620,6 @@ def probe_ptp_capabilities(out):
out.setdefault("interfaces", {}).update(ifaces)
def _usb_root_from_sysfs(dev_path):
p = dev_path
while p not in ("/", "/sys"):
parent = os.path.realpath(os.path.join(p, ".."))
basename = os.path.basename(parent)
if basename.startswith("usb") and basename[3:].isdigit():
return parent
p = parent
return None
def _read_usb_vid_pid(dev_path):
p = dev_path
while p not in ("/", "/sys"):
try:
vid = open(os.path.join(p, "idVendor")).read().strip()
pid = open(os.path.join(p, "idProduct")).read().strip()
return vid, pid
except OSError:
pass
p = os.path.dirname(p)
return "", ""
def probe_modems(out):
"""devpath is the USB bus root; ModemManager matches by path prefix.
seen_roots deduplicates cdc-wdm and ttyUSB ports of the same physical modem."""
seen_roots = set()
modems = []
idx = 0
def add_modem(dev_path):
nonlocal idx
usb_root = _usb_root_from_sysfs(dev_path)
if usb_root is None or usb_root in seen_roots:
return
seen_roots.add(usb_root)
vid, pid = _read_usb_vid_pid(dev_path)
modems.append({
"index": idx,
"name": "modem%d" % idx,
"devpath": usb_root,
"vid": vid,
"pid": pid,
})
idx += 1
for cls_dir, prefix in (("/sys/class/usbmisc", "cdc-wdm"),
("/sys/class/tty", "ttyUSB")):
try:
entries = os.listdir(cls_dir)
except OSError:
continue
for entry in entries:
if not entry.startswith(prefix):
continue
try:
dev_path = os.path.realpath(
os.path.join(cls_dir, entry, "device"))
add_modem(dev_path)
except OSError:
pass
if modems:
out["modem"] = modems
def main():
out = {
"vendor": None,
@@ -714,7 +647,6 @@ def main():
probe_wifi_radios(out)
probe_ptp_capabilities(out)
probe_modems(out)
if not out["factory-password-hash"]:
sys.stdout.write("\n\n\033[31mCRITICAL BOOTSTRAP ERROR\n" +
@@ -722,10 +654,17 @@ def main():
err = 1
os.umask(0o337)
# Write atomically: confd reads system.json as soon as ixinit signals
# done, so a truncated in-place write could be read half-written. Write
# to a temp file in the same dir, then rename (atomic on the same fs).
# pylint: disable=invalid-name
with open(SYSTEM_JSON, "w", encoding="ascii") as f:
tmp = SYSTEM_JSON + ".tmp"
with open(tmp, "w", encoding="ascii") as f:
json.dump(out, f)
shutil.chown(SYSTEM_JSON, user="root", group="wheel")
f.flush()
os.fsync(f.fileno())
shutil.chown(tmp, user="root", group="wheel")
os.replace(tmp, SYSTEM_JSON)
return err
@@ -43,5 +43,12 @@ for iface in $ports; do
ip link set "$iface" group port
done
# The mac80211_hwsim monitor tap (hwsim test images only) is a debug
# observer, not a user-facing interface. It cannot be deleted (no rtnl
# link ops), so classify it as internal like the DSA CPU interfaces.
if [ -d /sys/class/net/hwsim0 ]; then
ip link set dev hwsim0 group internal
fi
# At least loopback in iface group
ip link set lo group iface
+21 -13
View File
@@ -176,19 +176,25 @@ def parse_phy_info(phy_name):
if current_band and current_band.get('frequencies'):
result['bands'].append(current_band)
# Determine band names and assign band numbers
# Keep only the bands Infix supports (2.4/5/6 GHz), naming them by their
# first frequency. Hardware may expose others (e.g. S1G on hwsim) that we
# neither configure nor report.
bands = []
for band in result['bands']:
if band['frequencies']:
freq = band['frequencies'][0]
if 2400 <= freq <= 2500:
band['name'] = '2.4GHz'
band['band'] = 1
elif 5150 <= freq <= 5900:
band['name'] = '5GHz'
band['band'] = 2
elif 5955 <= freq <= 7115:
band['name'] = '6GHz'
band['band'] = 3
freq = band['frequencies'][0]
if 2400 <= freq <= 2500:
band['name'] = '2.4GHz'
band['band'] = 1
elif 5150 <= freq <= 5900:
band['name'] = '5GHz'
band['band'] = 2
elif 5955 <= freq <= 7115:
band['name'] = '6GHz'
band['band'] = 3
else:
continue
bands.append(band)
result['bands'] = bands
# Set max TX power
if max_power is not None:
@@ -207,7 +213,9 @@ def parse_phy_info(phy_name):
# Map driver to manufacturer
driver_lower = driver_name.lower()
if 'mt' in driver_lower or 'mediatek' in driver_lower:
if 'hwsim' in driver_lower:
result['manufacturer'] = 'Virtual (hwsim)'
elif 'mt' in driver_lower or 'mediatek' in driver_lower:
result['manufacturer'] = 'MediaTek Inc.'
elif 'rtw' in driver_lower or 'realtek' in driver_lower:
result['manufacturer'] = 'Realtek Semiconductor Corp.'
+55
View File
@@ -0,0 +1,55 @@
#!/bin/sh
# Check for available software updates and notify on login if one exists.
# Called by the scheduler.
NOTIFY_FILE=/run/os-update
TAG=os-update
# Source os-release for VERSION and IMAGE_ID
if [ ! -f /etc/os-release ]; then
logger -t "$TAG" "ERROR: /etc/os-release not found"
exit 1
fi
. /etc/os-release
# Dev/dirty builds have no comparable semver — always show the latest release
IS_RELEASE=true
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then
IS_RELEASE=false
fi
# Read configured update-url from running config, fall back to upstream
UPDATE_URL=$(copy running-config \
-x '/ietf-system:system/infix-system:software/check-update/update-url' \
2>/dev/null \
| jq -r '.. | objects | ."update-url"? // empty')
UPDATE_URL=${UPDATE_URL:-"https://github.com/kernelkit/infix"}
# Derive API URL from the configured update URL.
# Default (github.com): https://github.com/org/repo → https://api.github.com/repos/org/repo
REPO=$(echo "$UPDATE_URL" | sed 's|https://github.com/||; s|/*$||')
API_URL="https://api.github.com/repos/${REPO}/releases/latest"
LATEST_TAG=$(curl -sSL --max-time 10 "$API_URL" 2>/dev/null \
| jq -r '.tag_name // empty')
if [ -z "$LATEST_TAG" ]; then
logger -p daemon.info -t "$TAG" "Update check skipped: could not reach ${API_URL}"
exit 0
fi
LATEST=${LATEST_TAG#v}
# Compare: is $1 strictly newer than $2?
newer() {
[ "$1" = "$2" ] && return 1
[ "$(printf '%s\n%s' "$1" "$2" | sort -V | tail -1)" = "$1" ]
}
if [ "$IS_RELEASE" = false ] || newer "$LATEST" "$VERSION"; then
RELEASE_URL="${UPDATE_URL}/releases/${LATEST_TAG}"
MSG="Software update available: ${LATEST_TAG}, running ${VERSION} (see ${RELEASE_URL})"
logger -t "$TAG" "$MSG"
printf '%s\n' "$MSG" > "$NOTIFY_FILE"
else
logger -p daemon.debug -t "$TAG" "No update available (current: $VERSION, latest: $LATEST)"
printf '' > "$NOTIFY_FILE"
fi
+3 -3
View File
@@ -27,7 +27,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.34"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
BR2_LINUX_KERNEL_INSTALL_TARGET=y
@@ -149,7 +149,6 @@ INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_FEATURE_GPS=y
BR2_PACKAGE_FEATURE_MODEM=y
BR2_PACKAGE_FEATURE_WIFI=y
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
BR2_PACKAGE_FEATURE_WIFI_QUALCOMM=y
@@ -176,7 +175,6 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
@@ -188,6 +186,8 @@ BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_ROUSETTE=y
# BR2_PACKAGE_LANDING is not set
BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
BR2_PACKAGE_PCIUTILS=y
+2 -1
View File
@@ -27,7 +27,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.34"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
BR2_LINUX_KERNEL_INSTALL_TARGET=y
@@ -144,6 +144,7 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
+3 -2
View File
@@ -28,7 +28,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.34"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/arm/linux_defconfig"
BR2_LINUX_KERNEL_INSTALL_TARGET=y
@@ -162,7 +162,6 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
@@ -170,6 +169,8 @@ BR2_PACKAGE_NETBROWSE=y
BR2_PACKAGE_ONIEPROM=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_ROUSETTE=y
# BR2_PACKAGE_LANDING is not set
BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
IMAGE_ITB_AUX=y
+2 -1
View File
@@ -28,7 +28,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.34"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/arm/linux_defconfig"
BR2_LINUX_KERNEL_INSTALL_TARGET=y
@@ -142,6 +142,7 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
+2 -1
View File
@@ -194,7 +194,6 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
@@ -206,6 +205,8 @@ BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_ROUSETTE=y
# BR2_PACKAGE_LANDING is not set
BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
BR2_PACKAGE_PCIUTILS=y
+4 -2
View File
@@ -26,7 +26,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.34"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/linux_defconfig"
BR2_LINUX_KERNEL_INSTALL_TARGET=y
@@ -146,6 +146,7 @@ INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_FEATURE_GPS=y
BR2_PACKAGE_FEATURE_WIFI=y
BR2_PACKAGE_FEATURE_WIFI_HWSIM=y
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
BR2_PACKAGE_FEATURE_WIFI_QUALCOMM=y
BR2_PACKAGE_FEATURE_WIFI_REALTEK=y
@@ -169,7 +170,6 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
@@ -181,6 +181,8 @@ BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_ROUSETTE=y
# BR2_PACKAGE_LANDING is not set
BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
BR2_PACKAGE_PCIUTILS=y
+2 -1
View File
@@ -26,7 +26,7 @@ BR2_ROOTFS_OVERLAY="${BR2_EXTERNAL_INFIX_PATH}/board/common/rootfs ${BR2_EXTERNA
BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build.sh"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.34"
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.37"
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/linux_defconfig"
BR2_LINUX_KERNEL_INSTALL_TARGET=y
@@ -141,6 +141,7 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
+57 -14
View File
@@ -3,33 +3,75 @@ Change Log
All notable changes to the project are documented in this file.
[v26.06.0][UNRELEASED]
--------------
[v26.06.0][] - 2026-07-01
-------------------------
> [!NOTE]
> Noteworthy changes and additions in this release:
>
> **🌐 Web Interface:** Infix gets its first-ever web interface! Browse live
> status and a full operational tree, handle common tasks from curated
> configuration pages, and drop into a YANG tree editor for everything else.
> A maintenance section covers firmware upgrade, backup & restore, and more.
>
> **📶 Wi-Fi Roaming & Mesh:** Access points sharing an SSID can hand clients
> off seamlessly with 802.11k/v/r, form a cable-free 802.11s mesh backhaul,
> and steer dual-band clients onto the faster 5/6 GHz band.
>
> **🗓️ System Scheduling:** Reusable time schedules based on ietf-schedule
> (RFC 9922) let features like scheduled reboot and software update checks
> run on a recurring, cron-style calendar.
### Changes
- Upgrade Linux kernel to 6.18.37 (LTS)
- Upgrade Buildroot to 2025.02.15 (LTS)
- Add basic web interface: static status pages and a tree view of operational
status. Curated configuration pages for some common tasks and a YANG tree
editor for the rest. Also includes a maintenance section for firmware
upgrade, backup & restore, and more
- Add Wi-Fi roaming for fast, seamless handoff between access points that
share an SSID: 802.11k, 802.11v and 802.11r (over-the-air FT). See the
[Wi-Fi User's Guide][wifi] for details
[Wi-Fi][wifi] guide for details
- Add Wi-Fi 802.11s mesh support, letting access points form a wireless
backhaul between each other without cabling
- Add band steering for dual-band access points, nudging dual-band
clients onto the faster 5/6 GHz band
- Add `legacy-rates` option to re-enable 802.11b rates on 2.4 GHz for
old IoT devices (disabled by default)
- Add cellular modem (WWAN) support for USB-attached MBIM/QMI modems,
including USB dongles, mPCIe cards, and M.2 Key-B modules. Multiple
APNs per modem, SIM PIN configuration, and NMEA routing from modem
GPS to gpsd. See the [Modem User's Guide][modem] for details
- Add system scheduling based on ietf-schedule (RFC 9922), using the
iCalendar recurrence grouping pruned to cron-expressible rules. Schedules
are reusable time-specs; features (`scheduled-reboot`,
`software/check-update`) trigger off them via a schedule reference
- Configuring multiple BSS (more than one SSID) on a single Wi-Fi radio now
requires an explicitly configured MAC address per BSS
- New operational `advertised-pmd-types` leaf-list on each Ethernet interface,
exposing the link modes currently advertised, to compare against the
`supported-pmd-types` introduced in v26.05.0
- Release assets no longer ship separate `.sha256` checksum files; the
download page now publishes a SHA-256 checksum for each asset directly
### Fixes
- Firewall masquerade no longer enables the global IPv4/IPv6 forwarding
sysctls. You must now enable IP forwarding explicitly on the interfaces
that should route traffic; enabling NAT alone is no longer enough
- Fix #941: a VETH pair can now connect two containers directly, with both
ends assigned to containers.
- Enabling IP masquerading in the firewall no longer enables IP forwarding on
all interfaces. This has been an issue ever since the firewall support was
introduced in v25.10.0
- Fix file permission regression in `/cfg/startup-config.cfg`, causing the
default `admin` user no permission to read or write the file from shell
- Fix admin url shown for HTTP/HTTPS links in <https://network.local> browser,
used pre-conflict resolution hostname.local, instead of hostname-2.local
- Fix unreadable per-port temperature sensor names in `show hardware` on
Marvell based switches: each sensor is now named after the front-panel port
it serves (e.g. `e1`, `e2`) instead of a raw device-tree path. `show
system` also reports a representative SoC temperature on CN913x platforms
- Fix missing `contact` and `location` settings in operational status; the
values were configurable but never returned on RESTCONF/NETCONF reads
- Fix spurious YANG validation warnings, for NTP and WireGuard configuration,
emitted on every NETCONF session and schema load
[wifi]: https://www.kernelkit.org/infix/latest/wifi/
[modem]: https://www.kernelkit.org/infix/latest/modem/
[wifi]: https://www.kernelkit.org/infix/latest/wifi/
[v26.05.0][] - 2026-05-29
-------------------------
@@ -90,7 +132,7 @@ All notable changes to the project are documented in this file.
[ethernet]: https://www.kernelkit.org/infix/latest/ethernet/#restricting-advertised-link-modes
[BPI-R3]: https://docs.banana-pi.org/en/BPI-R3/BananaPi_BPI-R3
[AcerConnectVero]: https://github.com/kernelkit/infix/tree/main/board/aarch64/acer-connect-vero-w6m/
[AcerConnectVero]: https://github.com/kernelkit/infix/tree/main/board/aarch64/acer-connect-vero-w6m
[v26.04.0][] - 2026-04-30
-------------------------
@@ -2163,7 +2205,8 @@ 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/v26.05.0...HEAD
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v26.06.0...HEAD
[v26.06.0]: https://github.com/kernelkit/infix/compare/v26.05.0...v26.06.0
[v26.05.0]: https://github.com/kernelkit/infix/compare/v26.04.0...v26.05.0
[v26.04.0]: https://github.com/kernelkit/infix/compare/v26.03.0...v26.04.0
[v26.03.0]: https://github.com/kernelkit/infix/compare/v26.02.0...v26.03.0
-1
View File
@@ -23,7 +23,6 @@ regression test system solely relies on NETCONF and RESTCONF.
- [Network Configuration](networking.md)
- [Wi-Fi](wifi.md)
- [DHCP Server](dhcp.md)
- [Cellular Modem (WWAN)](modem.md)
- [Syslog Support](syslog.md)
- **Infix In-Depth**
- [Boot Procedure](boot.md)
+3 -5
View File
@@ -668,11 +668,9 @@ set:
For an example of both, see the next section.
> [!IMPORTANT]
> **VETH Pair Limitation:** When using VETH pairs with containers, at least
> one side of the pair must remain in the host namespace. It is currently
> not possible to create VETH pairs where both ends are assigned to different
> containers. One end must always be accessible from the host.
> [!TIP]
> Both ends of a VETH pair may be assigned to containers, connecting two
> containers directly without involving the host namespace.
[^3]: Something which the container bridge network type does behind the
scenes with one end of an automatically created VETH pair.
+13 -5
View File
@@ -122,13 +122,21 @@ recommend using `pipx` to install the necessary tooling:
```bash
$ sudo apt install pipx
$ pipx install mkdocs
$ pipx inject mkdocs mkdocs-material pymdown-extensions mkdocs-callouts mike mkdocs-to-pdf
$ pipx inject mkdocs mkdocs-material pymdown-extensions mkdocs-callouts mike mkdocs-to-pdf mkdocs-glightbox
```
The last two packages, `mike` and `mkdocs-to-pdf`, are used for online
versioning and PDF generation by GitHub Actions, but since they are in
the `mkdocs.yml` file, everyone who wants to preview the documentation
have to install all the tooling.
The `mike` and `mkdocs-to-pdf` packages are used for online versioning
and PDF generation by GitHub Actions, but since every plugin is listed
in `mkdocs.yml`, anyone who wants to preview the documentation has to
install all the tooling.
> [!IMPORTANT]
> MkDocs is also required to **build a WebUI image**. The build bundles
> the User's Guide into the image (served on-device at `/guide/`), so
> `make` runs `mkdocs build` from `post-build.sh` when the `webui`
> package is selected. If MkDocs is missing the build still succeeds,
> but the image ships without the on-device guide. Minimal images and
> any build without the `webui` package skip this step entirely.
Preview with:
Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

-438
View File
@@ -1,438 +0,0 @@
# Cellular Modem (WWAN)
Infix supports cellular modem connectivity via modems that expose a
QMI or MBIM control interface over USB. Form factor does not matter:
USB dongles, mPCIe cards, and M.2 Key-B modules all work as long as
the modem chipset uses USB on the connector (the typical case for
4G/LTE modems). See *Supported Modems* below for the exceptions.
Setup involves three configuration items:
- A `modem0` hardware component for the physical modem
- A `sim0` hardware component for the SIM card slot
- A `wwan0` network interface that references both and carries the
bearer (APN) configuration
## Architecture
Infix uses a three-layer architecture for cellular modem support:
1. **Modem hardware component (modem0)**: the physical modem
- Configured via `ietf-hardware` with class `infix-hardware:modem`
- `admin-state` controls whether ModemManager and modemd are active
- Holds physical-layer config: allowed bands, preferred mode, location
- Auto-discovered into factory-default config when the modem is
present at first boot
2. **SIM hardware component (sim0)**: the SIM card slot
- Configured via `ietf-hardware` with class `infix-hardware:sim`
- Holds PIN/PUK credentials and carrier profile
- Auto-discovered into factory-default config alongside the modem
3. **Network interface (wwan0)**: data bearer to the cellular network
- Configured via `ietf-interfaces` with type `infix-if-type:modem`
- References a modem component and a SIM component
- Holds bearer config: APN, IP type, roaming, route preference,
authentication
- Always added by the user, never auto-created
## Naming Conventions
| **Name Pattern** | **Type** | **Description** |
|------------------|----------------------|-------------------------------------------------|
| `modemN` | Modem hardware | Hardware component for the physical modem |
| `simN` | SIM hardware | Hardware component for the SIM card slot |
| `wwanN` | Modem interface | Network interface for cellular data |
Where `N` is a number (0, 1, 2, ...).
> [!TIP]
> Using these naming conventions simplifies configuration since type and
> class are automatically inferred. Creating a hardware component named
> `modem0` automatically sets its class to `infix-hardware:modem`, and
> creating an interface named `wwan0` automatically sets its type to
> `infix-if-type:modem`.
>
> **Note:** This inference only works via the CLI. When configuring
> over NETCONF or RESTCONF the class and type must be set explicitly.
## Multi-Bearer (Multiple APNs)
Multiple wwan interfaces can reference the same modem component, each
with a different APN. Same idea as multi-SSID on a WiFi radio: one
hardware modem, several independent data connections.
Configure `wwan0` and `wwan1` both pointing to `modem0`:
```
edit interface wwan0 wwan
set modem modem0
set bearer apn internet
edit interface wwan1 wwan
set modem modem0
set bearer apn corporate.vpn.apn
```
## Current Limitations
- The modem must be present at boot. Hot-plug is not supported.
- If the modem is absent at boot, and no `probe-timeout` is set, a
dummy `wwan0` placeholder is created immediately so IP configuration
can proceed. A reboot is required once the hardware is inserted.
## Supported Modems
Modems exposing a CDC-WDM control interface over USB are supported,
regardless of physical form factor. Two protocols are handled by
ModemManager:
- **MBIM**: Mobile Broadband Interface Model (e.g. Sierra Wireless,
Quectel EM06/EM12)
- **QMI**: Qualcomm MSM Interface (e.g. Sierra Wireless EM7xxx,
Quectel EM/RMxxx)
Most 4G/LTE modules (USB dongles, mPCIe cards, M.2 Key-B) use USB on
the connector even when the slot also carries PCIe lanes. From Infix's
view they are all USB modems. PCIe-only modems (some 5G NR modules)
are not supported, since the modemd / ModemManager pipeline assumes a
USB-attached control interface.
## Step-by-step Setup
### 1. Hardware Detection
At first boot, USB modems detected by the kernel are written to
`/run/system.json` and added as hardware components in the
factory-default configuration. Verify the modem appears:
<pre class="cli"><code>admin@example:/> <b>show modem</b>
──────────────────────────────────────────────────────────────
<span class="title">Cellular Modems</span>
NAME MANUFACTURER MODEL STATE NETWORK SIGNAL
modem0 Quectel EM06-E registered lte good
──────────────────────────────────────────────────────────────
<span class="title">SIM Cards</span>
NAME SLOT STATE OPERATOR
sim0 1 unlocked Tele2
</code></pre>
The `STATE`, `SIM STATE`, and `SIGNAL` columns are color-coded so the
healthy / attention / problem cases are visible at a glance: green for
`registered`, `connected`, `unlocked`, `excellent`, `good`; yellow for
transient or attention states like `enabling`, `pin-required`, `poor`;
red for `failed`, `not-inserted`, `bad`.
If `modem0` does not appear:
- Verify the kernel sees the modem (`dmesg | grep -i mbim` or
`dmesg | grep -i qmi`). Without a kernel driver no further setup
is possible.
- On boards with a custom factory configuration, or after replacing
hardware on a running system, the components may need to be added
manually. See Step 2.
### 2. Hardware Components
If `show modem` already lists `modem0` and `sim0`, the components were
auto-discovered into the factory-default configuration and you can skip
ahead to Step 3.
Otherwise, add them manually. The class is inferred from the component
name (`modemN``infix-hardware:modem`, `simN``infix-hardware:sim`).
The `admin-state` must be set explicitly. There is no implicit default,
and without `unlocked` confd will not start ModemManager or modemd:
<pre class="cli"><code>admin@example:/> <b>configure</b>
admin@example:/config/> <b>edit hardware component modem0</b>
admin@example:/config/hardware/component/modem0/> <b>set state admin-state unlocked</b>
admin@example:/config/hardware/component/modem0/> <b>end</b>
admin@example:/config/hardware/> <b>edit component sim0</b>
admin@example:/config/hardware/component/sim0/> <b>set state admin-state unlocked</b>
admin@example:/config/hardware/component/sim0/> <b>leave</b>
admin@example:/>
</code></pre>
To take the modem offline cleanly without removing the configuration,
set `admin-state locked`. All related services are stopped and the
bearer is torn down before the modem goes offline:
<pre class="cli"><code>admin@example:/config/> <b>edit hardware component modem0</b>
admin@example:/config/hardware/component/modem0/> <b>set state admin-state locked</b>
admin@example:/config/hardware/component/modem0/> <b>leave</b>
</code></pre>
#### Slow USB Modems
USB modems can be slow to enumerate at boot. The kernel wwan interface
may not appear until several seconds after confd starts applying
configuration. The `probe-timeout` leaf inside the `modem` hardware
configuration container controls how long confd waits. It defaults to
30 seconds when the container is present.
To enable the timeout, create the modem configuration container (this
also lets you configure bands, preferred mode, etc.):
<pre class="cli"><code>admin@example:/config/> <b>edit hardware component modem0 modem</b>
admin@example:/config/hardware/component/modem0/modem/> <b>leave</b>
</code></pre>
With `probe-timeout` at its default of 30, confd waits up to 30 seconds
for the wwan interface to appear before proceeding. For most modems it
is ready in 2-5 seconds. If the modem has not appeared within the
timeout, a dummy placeholder interface is created and a reboot is
required for the real interface to take over. Set `probe-timeout 0`
to disable waiting entirely.
> [!NOTE]
> Some Quectel modules (e.g. EM05) re-enumerate the USB device several
> times during a cold-boot firmware-init sequence that takes around 50
> to 60 seconds. Bump `probe-timeout` to 90 for those, otherwise the
> dummy placeholder fires before the modem settles and the real
> interface ends up with a different name (`wwan1` instead of `wwan0`).
### 3. Configure the Bearer (APN)
Bearer configuration lives on the `wwan0` interface. Reference the
modem hardware component and set the APN:
<pre class="cli"><code>admin@example:/> <b>configure</b>
admin@example:/config/> <b>edit interface wwan0</b>
admin@example:/config/interface/wwan0/> <b>set wwan modem modem0</b>
admin@example:/config/interface/wwan0/> <b>set wwan sim sim0</b>
admin@example:/config/interface/wwan0/> <b>set wwan bearer apn internet</b>
admin@example:/config/interface/wwan0/> <b>leave</b>
</code></pre>
The modem will connect automatically once the bearer is configured and
the hardware is unlocked.
**Key bearer parameters:**
- `apn`: Access Point Name, required, provided by your operator
(e.g. `internet`, `data.vodafone.com`, `web.tele2.se`)
- `route-preference`: Administrative distance for the default route
(default: `200`). Higher value = lower priority. The default of 200
places cellular behind wired Ethernet (distance 5) and WiFi, so
cellular acts as a failover when the wired link is up
- `roaming`: Allow data when roaming on a foreign network
(default: `false`)
- `ip-type`: `ipv4`, `ipv6`, or `ipv4v6` dual-stack
(default: `ipv4v6`)
### 4. Configure Authentication
Most consumer APNs connect without credentials. If your operator
requires authentication, first store the password in the keystore, then
reference it from the bearer.
Create the keystore entry for the APN password:
<pre class="cli"><code>admin@example:/> <b>configure</b>
admin@example:/config/> <b>edit keystore symmetric-key apn-pass</b>
admin@example:/config/keystore/…/apn-pass/> <b>set key-format passphrase-key-format</b>
admin@example:/config/keystore/…/apn-pass/> <b>change cleartext-symmetric-key</b>
Passphrase: ************
Retype passphrase: ************
admin@example:/config/keystore/…/apn-pass/> <b>leave</b>
</code></pre>
Then point the bearer's authentication at it:
<pre class="cli"><code>admin@example:/> <b>configure</b>
admin@example:/config/> <b>edit interface wwan0 wwan bearer</b>
admin@example:/config/interface/wwan0/wwan/bearer/> <b>set authentication username myuser</b>
admin@example:/config/interface/wwan0/wwan/bearer/> <b>set authentication password apn-pass</b>
admin@example:/config/interface/wwan0/wwan/bearer/> <b>leave</b>
</code></pre>
Setting any leaf inside `authentication` creates the container, which
enables authentication. The `password` leaf is a reference to a
symmetric key in the keystore, not the plaintext password itself.
The authentication protocol defaults to `chap`. To use PAP instead:
<pre class="cli"><code>admin@example:/config/interface/wwan0/wwan/bearer/> <b>set authentication type pap</b>
</code></pre>
### 5. Configure SIM PIN
If the SIM requires a PIN to unlock, configure it on the SIM hardware
component:
<pre class="cli"><code>admin@example:/config/> <b>edit hardware component sim0</b>
admin@example:/config/hardware/component/sim0/> <b>set sim pin 1234</b>
admin@example:/config/hardware/component/sim0/> <b>leave</b>
</code></pre>
### 6. Verify Connectivity
Once connected, the `wwan0` interface receives an IP address from the
carrier and modemd installs the default route:
<pre class="cli"><code>admin@example:/> <b>show interface wwan0</b>
name : wwan0
type : modem
index : 5
mtu : 1500
operational status : up
ip forwarding : enabled
physical address : 12:34:56:78:9a:bc
ipv4 addresses : 10.142.87.33/30 (wwan)
ipv6 addresses : 2001:db8:1:2::1/64 (wwan)
in-octets : 84213
out-octets : 31456
</code></pre>
Check the full modem state including signal quality and registration:
<pre class="cli"><code>admin@example:/> <b>show modem modem0</b>
<span class="header">MODEM: modem0 </span>
──────────────────────────────────────────────────────────────
<span class="title">Hardware Information</span>
Manufacturer : Quectel
Model : EM06-E
Firmware Version : EM06ELAR04A07M4G
Serial Number : 352753090141905
IMSI : 240021234567890
ICCID : 8946020000001234567
──────────────────────────────────────────────────────────────
<span class="title">Status</span>
State : connected
Power State : on
Signal : Quality: 72% good RSSI: -59 dBm RSRP: -95 dBm RSRQ: -11.0 dB
──────────────────────────────────────────────────────────────
<span class="title">Cellular</span>
Registration : home
Service State : attached
Operator : Tele2
Operator ID : 23002
Network Type : lte
──────────────────────────────────────────────────────────────
<span class="title">SIM Card</span>
Name : sim0
Slot : 1
Lock State : unlocked
Operator : Tele2
</code></pre>
When the modem is in trouble, the same view shows why. A
`State: failed` line is followed by `Failed Reason` (e.g.
`sim-missing`), and the `SIM Card` section's `Lock State` reads
`not-inserted` or `pin-required` to match.
## Cellular Failover
The default `route-preference` (200) is deliberately higher than the
distances used by wired Ethernet (udhcpc default: 5) and WiFi. When a
wired link is up it takes precedence automatically; cellular becomes
the active path only if higher-priority routes are withdrawn.
A default route via the bearer is always installed when the bearer
connects. To adjust the failover priority between two cellular modems,
or to prefer cellular over WiFi, set `route-preference` explicitly:
<pre class="cli"><code>admin@example:/config/> <b>edit interface wwan0 wwan bearer</b>
admin@example:/config/interface/wwan0/wwan/bearer/> <b>set route-preference 100</b>
admin@example:/config/interface/wwan0/wwan/bearer/> <b>leave</b>
</code></pre>
Lower `route-preference` = higher priority.
## Roaming
Data roaming is disabled by default. To allow the modem to connect
when on a foreign (roaming) network:
<pre class="cli"><code>admin@example:/config/> <b>edit interface wwan0 wwan bearer</b>
admin@example:/config/interface/wwan0/wwan/bearer/> <b>set roaming true</b>
admin@example:/config/interface/wwan0/wwan/bearer/> <b>leave</b>
</code></pre>
> [!IMPORTANT]
> Enabling roaming may incur significant charges depending on your
> mobile subscription. Check with your operator before enabling.
## Management Commands
### Restart Bearer
Disconnect and reconnect all bearers without resetting the modem
hardware. Use this after changing APN or authentication settings:
<pre class="cli"><code>admin@example:/> <b>modem restart modem0</b>
</code></pre>
### Reset Modem
Factory-reset the modem firmware. This clears all modem-internal
settings and takes longer than a restart. Only use it if the modem is
in a bad state that a bearer restart cannot fix:
<pre class="cli"><code>admin@example:/> <b>modem reset modem0</b>
</code></pre>
> [!NOTE]
> Not all modules accept `--reset` from ModemManager. Quectel EM05,
> for example, rejects both `--reset` and `--factory-reset`. The only
> way to recover from a hung firmware on these is a physical power
> cycle. When the modem reports the rejection, modemd logs it once and
> stops retrying.
### Send SMS
Send an SMS message via the signalling plane. No active data bearer
is required; the modem only needs to be registered on the network:
<pre class="cli"><code>admin@example:/> <b>modem sms modem0 +46701234567 "Hello from Infix"</b>
</code></pre>
> [!NOTE]
> Some SIM cards have Fixed Dialing Number (FDN) enabled, which
> restricts outgoing SMS and calls to a pre-configured whitelist. If
> `modem sms` fails, check whether FDN is active with `mmcli -m 0` and
> look for `enabled locks: fixed-dialing` in the output.
## Troubleshooting
**Modem not detected (`show modem` shows no modem entry)**
- Verify the modem is connected and recognized by the kernel: check
`dmesg` for `cdc_mbim` or `qmi_wwan` driver messages
- Confirm `/sys/class/usbmisc/` contains a `cdc-wdm*` entry
- The modem must be present at boot; hotplug after boot is not
supported
**`wwan0` shows as `down` or has no IP address**
- Check `show modem modem0`. The State should show `registered` or
`connected`, not `failed`
- If the State is `failed`, look at the `Failed Reason` line that
immediately follows:
- `sim-missing`: the SIM Card section will also show
`Lock State: not-inserted`. Power off the device, insert the
SIM, power back on. A warm reboot is not enough; on most M.2
slots the SIM tray stays powered through reboot, so the modem
keeps the original not-inserted reading
- `unlock-required`: the SIM Card section will show
`Lock State: pin-required`. Configure the PIN with
`set hardware component sim0 sim pin <code>`
- `sim-wrong` / `sim-error`: the SIM is not compatible or is
damaged. Try a different SIM
- Verify the APN is correct for your operator
- Check system logs with `show log` for modemd or ModemManager
messages
**`wwan0` interface is a dummy (no data flows, no carrier address)**
- The modem was not enumerated by the kernel before confd applied
config
- Create the modem hardware configuration container (see Step 2),
which enables the default 30-second probe-timeout so confd waits for
the wwan interface before falling back to a dummy placeholder
**High latency or poor signal**
- Use `show modem modem0` to check signal quality and RSRP
- Signal below -110 dBm RSRP typically indicates poor coverage
- Consider repositioning the antenna or the device
+30
View File
@@ -297,6 +297,36 @@ image. The device will boot into test mode on first power-on.
> unless `startup_override()` is called first (or the marker is removed
> and a normal startup config is saved).
Simulated Wi-Fi
---------------
The virtual topology has no real radios, so Wi-Fi tests run against
`mac80211_hwsim` devices and the "air" between guests is faked. A small
relay, `wifimedium` (`package/feature-wifi/wifimedium`), takes every frame
a radio transmits, forwards it to the other guests, and injects what
arrives back into the local radios.
Each radio (`radio0`, `radio1`, ...) is paired with a carrier NIC of the
same name, and that carrier joins the multicast group (a *cell*) the
topology wires it to. Two radios hear each other only when their carriers
share a cell. Radios on the same cell that tune to different channels are
still kept apart, as on real hardware.
By convention the two general cells carry `radio0` and `radio1` across all
DUTs, so most tests put communicating radios on the same index: a station
joins an AP on the same `radioN`. The mesh-roaming test, for instance,
uses `radio1` for both the gateway APs and the client. That is only a
convention, though. A cell can carry any set of radios, including two of
the *same* DUT. The band-steering test relies on that: it wires one DUT's
`radio2` (2.4GHz) and `radio3` (5GHz) onto a dedicated cell so a single
client radio hears the same SSID on both bands.
How many radios each DUT gets depends on the topology. The number of
`radioN` ports wired to a node is passed in via the `opt/wifi` fw_cfg and
loaded at boot by the `00-hwsim` script, so a node with no wifi links has
no radios at all. The wiring for the standard suite lives in
`test/virt/quad`.
[9PM]: https://github.com/rical/9pm
[Qeneth]: https://github.com/wkz/qeneth
[TAP]: https://testanything.org/
+7
View File
@@ -304,6 +304,7 @@ in-octets : 148388
out-octets : 24555
mode : station
ssid : MyNetwork
bssid : b4:fb:e4:17:b6:a7
signal : -45 dBm (good)
rx bitrate : 72.2 Mbps
tx bitrate : 86.6 Mbps
@@ -319,6 +320,11 @@ In the CLI, signal strength is reported as: excellent, good, fair or bad.
For precise signal strength values in dBm, use NETCONF or RESTCONF to access
the `signal-strength` leaf in the operational datastore.
When connected, `bssid` shows the MAC address of the access point the
station is associated to. It appears only while connected. When several
access points share one SSID (a roaming network), the `bssid` is what
tells them apart, and it changes as the station roams between them.
## Passphrase Requirements
To ensure your connection is secure and compatible with all network
@@ -378,6 +384,7 @@ operational status : up
physical address : f0:09:0d:36:5f:86
mode : station
ssid : MyHomeNetwork
bssid : b4:fb:e4:17:b6:a7
signal : -52 dBm (good)
</code></pre>
+6 -4
View File
@@ -4,10 +4,12 @@ oem-dir := $(call qstrip,$(INFIX_OEM_PATH))
INFIX_TOPDIR = $(if $(oem-dir),$(oem-dir),$(BR2_EXTERNAL_INFIX_PATH))
# Unless the user specifies an explicit build id, source it from git.
# The build id also becomes the image version, unless an official
# release is being built.
export INFIX_BUILD_ID ?= $(shell git -C $(INFIX_TOPDIR) describe --dirty --always --tags)
export INFIX_VERSION = $(if $(INFIX_RELEASE),$(INFIX_RELEASE),$(INFIX_BUILD_ID))
# Exclude the moving 'latest*' tags so the version always resolves to a
# real release tag, see issue #1524. The build id is also the version
# shown to users; INFIX_RELEASE only labels the release channel and names
# artifacts (see INFIX_ARTIFACT below).
export INFIX_BUILD_ID ?= $(shell git -C $(INFIX_TOPDIR) describe --dirty --always --tags --exclude 'latest*')
export INFIX_VERSION = $(INFIX_BUILD_ID)
export INFIX_ARTIFACT = $(call qstrip,$(INFIX_IMAGE_ID)$(if $(INFIX_RELEASE),-$(INFIX_RELEASE)))
INFIX_CFLAGS:=-Wall -Werror -Wextra -Wno-unused-parameter -Wformat=2 -Wformat-overflow=2 -Winit-self -Wstrict-overflow=4 -Wno-format-truncation -Wno-format-nonliteral
-1
View File
@@ -44,7 +44,6 @@ nav:
- Overview: vpn.md
- WireGuard: vpn-wireguard.md
- Wireless LAN (WiFi): wifi.md
- Cellular Modem (wwan): modem.md
- Services:
- Device Discovery: discovery.md
- DHCP Server: dhcp.md
+1 -2
View File
@@ -2,7 +2,6 @@ menu "Packages"
comment "Hardware Support"
source "$BR2_EXTERNAL_INFIX_PATH/package/feature-gps/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/feature-modem/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/feature-wifi/Config.in"
comment "Software Packages"
@@ -31,7 +30,6 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/landing/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/libsrx/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/lowdown/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/mcd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/modemd/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/mdns-alias/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/netbrowse/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/onieprom/Config.in"
@@ -44,6 +42,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/tetris/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/libyang-cpp/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/sysrepo-cpp/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/rousette/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/webui/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/nghttp2-asio/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/date-cpp/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/rauc-installation-status/Config.in"
+8 -6
View File
@@ -1,9 +1,11 @@
#set DEBUG=1
# Single daemon handles gen-config, datastore init, config load, and plugins
service log:console env:/etc/default/confd \
# log:prio:daemon.err
service log:console env:/etc/default/confd \
[S12345] <usr/ixinit> confd -f -v warning \
-F /etc/factory-config.cfg \
-S /cfg/startup-config.cfg \
-E /etc/failure-config.cfg \
-t $CONFD_TIMEOUT \
$CONFD_ARGS \
-F /etc/factory-config.cfg \
-S /cfg/startup-config.cfg \
-E /etc/failure-config.cfg \
-t $CONFD_TIMEOUT \
-- Configuration daemon
+7 -5
View File
@@ -36,8 +36,10 @@ else
CONFD_CONF_OPTS += --disable-gps
endif
define CONFD_INSTALL_EXTRA
for fn in confd.conf resolvconf.conf; do \
for fn in confd.conf crond.conf resolvconf.conf; do \
cp $(CONFD_PKGDIR)/$$fn $(FINIT_D)/available/; \
done
for fn in confd.conf resolvconf.conf; do \
ln -sf ../available/$$fn $(FINIT_D)/enabled/$$fn; \
done
cp $(CONFD_PKGDIR)/tmpfiles.conf $(TARGET_DIR)/etc/tmpfiles.d/confd.conf
@@ -91,10 +93,10 @@ define CONFD_INSTALL_YANG_MODULES_GPS
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/gps.inc
endef
endif
ifeq ($(BR2_PACKAGE_FEATURE_MODEM),y)
define CONFD_INSTALL_YANG_MODULES_MODEM
ifeq ($(BR2_PACKAGE_WEBUI),y)
define CONFD_INSTALL_YANG_MODULES_WEBUI
$(COMMON_SYSREPO_ENV) \
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/modem.inc
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/web.inc
endef
endif
@@ -127,7 +129,7 @@ CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_CONTAINERS
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_WIFI
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_GPS
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_MODEM
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_WEBUI
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_IN_ROMFS
CONFD_TARGET_FINALIZE_HOOKS += CONFD_CLEANUP
+2
View File
@@ -0,0 +1,2 @@
# Cron daemon for infix-schedule
service [2345] crond -f -- Cron daemon
-25
View File
@@ -1,25 +0,0 @@
config BR2_PACKAGE_FEATURE_MODEM
bool "Feature Modem"
select BR2_PACKAGE_MODEMD
select BR2_PACKAGE_MODEM_MANAGER
select BR2_PACKAGE_MODEM_MANAGER_ATVIADBUS
select BR2_PACKAGE_MODEM_MANAGER_LIBMBIM
select BR2_PACKAGE_MODEM_MANAGER_LIBQMI
select BR2_PACKAGE_MODEM_MANAGER_LIBQRTR
help
Enables cellular modem support in Infix via ModemManager and
the modemd management daemon. Includes drivers for common
USB option modems and QMI/MBIM-based devices.
ATVIADBUS allows raw AT commands over D-Bus (used by modemd to
enable GPS NMEA output via vendor-specific AT commands such as
AT+QGPS=1) without needing to start ModemManager in --debug
mode.
config BR2_PACKAGE_FEATURE_MODEM_QUALCOMM
bool "Qualcomm-based modems (QMI/QRTR/MHI)"
depends on BR2_PACKAGE_FEATURE_MODEM
help
Adds kernel support for Qualcomm-based cellular modems that use
the MHI bus and QRTR IPC router (e.g. Sierra Wireless EM7xxx,
Quectel EM/RMxxx, Telit LN9xx).
-26
View File
@@ -1,26 +0,0 @@
################################################################################
#
# Cellular modem support
#
################################################################################
FEATURE_MODEM_PACKAGE_VERSION = 1.0
FEATURE_MODEM_PACKAGE_LICENSE = MIT
define FEATURE_MODEM_LINUX_CONFIG_FIXUPS
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_SERIAL)
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_SERIAL_WWAN)
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_SERIAL_OPTION)
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_WDM)
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_NET_QMI_WWAN)
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_CDC_MBIM)
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_NET_CDC_MBIM)
$(if $(filter y,$(BR2_PACKAGE_FEATURE_MODEM_QUALCOMM)),
$(call KCONFIG_SET_OPT,CONFIG_QRTR,m)
$(call KCONFIG_SET_OPT,CONFIG_MHI_BUS,m)
$(call KCONFIG_ENABLE_OPT,CONFIG_QRTR_MHI)
)
endef
$(eval $(generic-package))
+47
View File
@@ -0,0 +1,47 @@
#!/bin/sh
# Load mac80211_hwsim virtual radios sized to this DUT's topology, then wait
# for udev to rename the phys (phyN -> radioN) before returning.
#
# The radio count comes from a dedicated fw_cfg file (opt/wifi) that qeneth
# writes per node from the number of radioN carrier ports the topology wired
# to it. A node with no wifi links gets 0 (or no file), so hwsim is not loaded
# at all -- no phys, nothing clutters its interface view.
#
# Runs early in ixinit, before 00-probe, so the system probe records the radios
# by their final radioN names. ixinit is past the <pid/syslogd> / udev-settle
# barrier, so 60-rename-wifi-phy.rules (phyN -> radioN) and
# 70-remove-virtual-wifi-interfaces.rules (drop the stray wlanN) are loaded and
# fire on the phy add events. On success it sets the <usr/hwsim> condition so
# wifimedium starts only where radios exist. Installed only with
# BR2_PACKAGE_FEATURE_WIFI_HWSIM.
wifi=/sys/firmware/qemu_fw_cfg/by_name/opt/wifi/raw
ieee=/sys/class/ieee80211
# Number of radios to create, from the topology (via fw_cfg).
radios=0
[ -r "$wifi" ] && radios=$(cat "$wifi" 2>/dev/null)
# No radios wanted -> don't load hwsim, keep the view wifi-free.
[ "$radios" -gt 0 ] 2>/dev/null || exit 0
modprobe mac80211_hwsim radios="$radios" || exit 0
# Wait (bounded ~10s) for the phyN -> radioN rename to finish: the class has
# entries and none are still named phyN. The rename is a udev RUN, so it lags
# modprobe's return. On timeout the radios never settled -- fail (and log) so
# it is visible why 00-probe may record stale phyN names.
TIMEOUT=100
i=0
while [ "$i" -lt "$TIMEOUT" ]; do
names=$(ls "$ieee" 2>/dev/null)
if [ -n "$names" ] && ! echo "$names" | grep -q '^phy'; then
initctl -bq cond set hwsim
exit 0
fi
i=$((i + 1))
sleep 0.1
done
logger -p user.err -t 00-hwsim "timed out waiting for hwsim phys to be renamed radioN"
exit 1
+12
View File
@@ -18,6 +18,18 @@ config BR2_PACKAGE_FEATURE_WIFI
help
Enables WiFi in Infix. Enables all requried applications.
config BR2_PACKAGE_FEATURE_WIFI_HWSIM
bool "Virtual Wi-Fi radios (mac80211_hwsim)"
depends on BR2_PACKAGE_FEATURE_WIFI
help
Build the mac80211_hwsim virtual radio driver (as a module) for the
QEMU test target, to exercise AP, station, and mesh modes without
physical WiFi adapters. The module is autoloaded at boot (see the
x86_64 /etc/modules-load.d) and frames are bridged between guests by
the wifimedium relay.
Leave disabled on real hardware; it only provides simulated radios.
config BR2_PACKAGE_FEATURE_WIFI_MEDIATEK
bool "Mediatek WiFi Devices"
depends on BR2_PACKAGE_FEATURE_WIFI
+24
View File
@@ -14,6 +14,15 @@ define FEATURE_WIFI_LINUX_CONFIG_FIXUPS
$(call KCONFIG_SET_OPT,CONFIG_CFG80211,m)
$(call KCONFIG_ENABLE_OPT,CONFIG_MAC80211_MESH)
# Virtual radio for automated testing (mac80211_hwsim), built as a
# module and gated behind BR2_PACKAGE_FEATURE_WIFI_HWSIM so it is not
# built on real boards. When selected, the module is loaded early in
# ixinit by 00-hwsim -- sized to the DUT's topology, so radio-less nodes
# load nothing -- and bridged between QEMU guests by the wifimedium relay
# (both installed by FEATURE_WIFI_INSTALL_HWSIM below).
$(if $(BR2_PACKAGE_FEATURE_WIFI_HWSIM),
$(call KCONFIG_SET_OPT,CONFIG_MAC80211_HWSIM,m))
$(if $(filter y,$(BR2_PACKAGE_FEATURE_WIFI_MEDIATEK)),
$(call KCONFIG_ENABLE_OPT,CONFIG_MT7601U)
$(call KCONFIG_ENABLE_OPT,CONFIG_MT76x0U)
@@ -76,5 +85,20 @@ define FEATURE_WIFI_INSTALL_IN_ROMFS
endef
FEATURE_WIFI_POST_INSTALL_TARGET_HOOKS += FEATURE_WIFI_INSTALL_IN_ROMFS
# Virtual-radio (mac80211_hwsim) runtime, installed only when the option is
# selected: the 00-hwsim ixinit script loads the module sized to the opt/wifi
# fw_cfg qeneth writes per node (no radios -> not loaded), and the wifimedium
# relay + its Finit service bridge hwsim frames between QEMU guests so they can
# associate "wirelessly".
ifeq ($(BR2_PACKAGE_FEATURE_WIFI_HWSIM),y)
define FEATURE_WIFI_INSTALL_HWSIM
$(INSTALL) -D -m 0755 $(FEATURE_WIFI_PKGDIR)/00-hwsim $(TARGET_DIR)/usr/libexec/infix/init.d/00-hwsim
$(INSTALL) -D -m 0755 $(FEATURE_WIFI_PKGDIR)/wifimedium $(TARGET_DIR)/usr/libexec/infix/wifimedium
$(INSTALL) -D -m 0644 $(FEATURE_WIFI_PKGDIR)/wifimedium.conf $(TARGET_DIR)/etc/finit.d/available/wifimedium.conf
ln -sf ../available/wifimedium.conf $(TARGET_DIR)/etc/finit.d/enabled/wifimedium.conf
endef
FEATURE_WIFI_POST_INSTALL_TARGET_HOOKS += FEATURE_WIFI_INSTALL_HWSIM
endif
$(eval $(generic-package))
+401
View File
@@ -0,0 +1,401 @@
#!/usr/bin/env python3
"""wifimedium - a virtual WiFi medium for mac80211_hwsim, extended over Ethernet.
mac80211_hwsim simulates 802.11 radios in software. By default all radios in
one kernel share an in-kernel medium; radios in *different* kernels (i.e.
different QEMU guests) cannot hear each other. The moment a userspace process
registers on the hwsim generic-netlink family, the kernel stops delivering
frames itself and hands every transmitted frame to that process, which becomes
responsible for delivery.
This daemon is that process. On each DUT it:
* registers on the "mac80211_hwsim" genl family and receives every frame the
local radios transmit (HWSIM_CMD_FRAME),
* acknowledges each transmit back to the kernel (HWSIM_CMD_TX_INFO_FRAME) so
mac80211's TX path completes, and
* relays the frame per radio: each radio (phy radioN) is paired by name with a
carrier NIC (netdev radioN) that joins one multicast "cell" -- a QEMU socket
multicast group shared by every DUT's radioN (see test/virt/quad and
test/qeneth). A radio's frame is sent only onto its own carrier, and a
carrier's frames are injected only into its own radio
(HWSIM_CMD_FRAME with HWSIM_ATTR_ADDR_RECEIVER).
Each cell is a shared medium: every DUT's radioN is "in range" of every other
radioN. Radios on different indices are on different cells, so two radios
communicate only if they share an index -- the convention the test topologies
follow (a station uses the same radio index as the AP it joins). Channel
filtering in mac80211 still separates radios that share a cell but tune to
different frequencies.
This is intended for the QEMU/qeneth test topology only. On real hardware the
radios use the air, there are no radioN carrier netdevs, and this daemon idles.
See doc/wifi.md.
"""
import os
import socket
import struct
import time
import subprocess
import sys
import select
# ---- generic netlink / hwsim ABI (drivers/net/wireless/virtual/mac80211_hwsim.h)
NETLINK_GENERIC = 16
GENL_ID_CTRL = 16
CTRL_CMD_GETFAMILY = 3
CTRL_ATTR_FAMILY_ID = 1
CTRL_ATTR_FAMILY_NAME = 2
CTRL_ATTR_MCAST_GROUPS = 7
CTRL_ATTR_MCAST_GRP_NAME = 1
CTRL_ATTR_MCAST_GRP_ID = 2
SOL_NETLINK = 270
NETLINK_ADD_MEMBERSHIP = 1
HWSIM_CMD_REGISTER = 1
HWSIM_CMD_FRAME = 2
HWSIM_CMD_TX_INFO_FRAME = 3
HWSIM_ATTR_ADDR_RECEIVER = 1
HWSIM_ATTR_ADDR_TRANSMITTER = 2
HWSIM_ATTR_FRAME = 3
HWSIM_ATTR_FLAGS = 4
HWSIM_ATTR_RX_RATE = 5
HWSIM_ATTR_SIGNAL = 6
HWSIM_ATTR_TX_INFO = 7
HWSIM_ATTR_COOKIE = 8
HWSIM_ATTR_FREQ = 19
HWSIM_ATTR_TX_INFO_FLAGS = 21
HWSIM_TX_STAT_ACK = 1 << 2
# Signal/rate reported for injected (received) frames. A strong, fixed signal
# keeps mac80211 happy; the exact value is not significant for a virtual cell.
RX_SIGNAL = -30
RX_RATE = 0
# The medium is carried as raw Ethernet frames with a custom EtherType on the
# configured interface -- no IP addressing needed (the carrier link has none).
# A broadcast destination means every DUT on the segment is "in range".
ETH_P_WIFIMEDIUM = 0x88B5 # local-experimental EtherType
ETH_BROADCAST = b"\xff\xff\xff\xff\xff\xff"
ETYPE = struct.pack("!H", ETH_P_WIFIMEDIUM)
SOL_PACKET = 263
PACKET_IGNORE_OUTGOING = 23 # don't deliver our own TX back to the socket
NLMSG_HDR = struct.Struct("=IHHII") # len, type, flags, seq, pid
GENL_HDR = struct.Struct("=BBH") # cmd, version, reserved
DEBUG = bool(os.environ.get("WIFIMEDIUM_DEBUG"))
def log(msg):
sys.stderr.write(f"wifimedium: {msg}\n")
sys.stderr.flush()
def dbg(msg):
if DEBUG:
log(msg)
def nla_align(n):
return (n + 3) & ~3
def put_attr(atype, payload):
hdr = struct.pack("=HH", len(payload) + 4, atype)
return hdr + payload + b"\0" * (nla_align(len(payload) + 4) - (len(payload) + 4))
def parse_attrs(buf):
attrs = {}
off = 0
while off + 4 <= len(buf):
alen, atype = struct.unpack_from("=HH", buf, off)
if alen < 4:
break
attrs[atype & 0x7fff] = buf[off + 4:off + alen]
off += nla_align(alen)
return attrs
class Netlink:
"""Minimal generic-netlink client for the hwsim family."""
def __init__(self):
self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_GENERIC)
self.sock.bind((0, 0))
self.seq = 0
# NB: the genl family is "MAC80211_HWSIM" (uppercase); the lowercase
# "mac80211_hwsim" is the platform driver name, not the family.
self.family_id, self.mcast_id = self._resolve("MAC80211_HWSIM", "config")
def fileno(self):
return self.sock.fileno()
def _send(self, family_id, cmd, attrs=b"", flags=0):
self.seq += 1
payload = GENL_HDR.pack(cmd, 1, 0) + attrs
msg = NLMSG_HDR.pack(NLMSG_HDR.size + len(payload), family_id,
flags | 1, self.seq, 0) + payload # NLM_F_REQUEST
self.sock.send(msg)
def _resolve(self, name, grpname):
self._send(GENL_ID_CTRL, CTRL_CMD_GETFAMILY,
put_attr(CTRL_ATTR_FAMILY_NAME, name.encode() + b"\0"))
data = self.sock.recv(65536)
_, mtype, _, _, _ = NLMSG_HDR.unpack_from(data, 0)
if mtype == 2: # NLMSG_ERROR (also used for ACKs; err<0 is a failure)
err = struct.unpack_from("=i", data, NLMSG_HDR.size)[0]
raise RuntimeError(f"genl GETFAMILY({name}) failed: "
f"{os.strerror(-err) if err else 'empty reply'}")
attrs = parse_attrs(data[NLMSG_HDR.size + GENL_HDR.size:])
family_id = struct.unpack("=H", attrs[CTRL_ATTR_FAMILY_ID][:2])[0]
mcast_id = None
for _, grp in _parse_nested(attrs.get(CTRL_ATTR_MCAST_GROUPS, b"")):
gattrs = parse_attrs(grp)
gname = gattrs.get(CTRL_ATTR_MCAST_GRP_NAME, b"").rstrip(b"\0").decode()
if gname == grpname:
mcast_id = struct.unpack("=I", gattrs[CTRL_ATTR_MCAST_GRP_ID])[0]
if mcast_id is None:
raise RuntimeError(f"hwsim multicast group '{grpname}' not found")
return family_id, mcast_id
def register(self):
self.sock.setsockopt(SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, self.mcast_id)
self._send(self.family_id, HWSIM_CMD_REGISTER)
def recv(self):
return self.sock.recv(65536)
def inject(self, receiver, frame, freq):
attrs = put_attr(HWSIM_ATTR_ADDR_RECEIVER, receiver)
attrs += put_attr(HWSIM_ATTR_FRAME, frame)
attrs += put_attr(HWSIM_ATTR_RX_RATE, struct.pack("=I", RX_RATE))
attrs += put_attr(HWSIM_ATTR_SIGNAL, struct.pack("=i", RX_SIGNAL))
if freq:
attrs += put_attr(HWSIM_ATTR_FREQ, struct.pack("=I", freq))
self._send(self.family_id, HWSIM_CMD_FRAME, attrs)
def tx_ack(self, transmitter, flags, tx_info, tx_info_flags, cookie):
attrs = put_attr(HWSIM_ATTR_ADDR_TRANSMITTER, transmitter)
attrs += put_attr(HWSIM_ATTR_FLAGS, struct.pack("=I", flags | HWSIM_TX_STAT_ACK))
attrs += put_attr(HWSIM_ATTR_SIGNAL, struct.pack("=i", RX_SIGNAL))
if tx_info:
attrs += put_attr(HWSIM_ATTR_TX_INFO, tx_info)
if tx_info_flags:
attrs += put_attr(HWSIM_ATTR_TX_INFO_FLAGS, tx_info_flags)
if cookie:
attrs += put_attr(HWSIM_ATTR_COOKIE, cookie)
self._send(self.family_id, HWSIM_CMD_TX_INFO_FRAME, attrs)
def _parse_nested(buf):
off = 0
while off + 4 <= len(buf):
alen, atype = struct.unpack_from("=HH", buf, off)
if alen < 4:
break
yield atype & 0x7fff, buf[off + 4:off + alen]
off += nla_align(alen)
def radio_addr1(phy):
"""The address mac80211_hwsim identifies a radio by on the medium.
A hwsim radio has three addresses (kernel new_radio()). sysfs 'macaddress'
is addresses[0]; the address the kernel stamps as HWSIM_ATTR_ADDR_TRANSMITTER
and matches HWSIM_ATTR_ADDR_RECEIVER against (its rhashtable key) is
addresses[1] -- addresses[0] with bit 0x40 set in the first octet, for the
default no-perm_addr radios we use. So both the transmitter we observe and
the receiver we must inject to are this 0x40 variant, not the sysfs value.
(Note custom-phys-address changes the vif/netdev MAC, not these.)
"""
with open(f"/sys/class/ieee80211/{phy}/macaddress") as fh:
addr = bytearray.fromhex(fh.read().strip().replace(":", ""))
addr[0] |= 0x40
return bytes(addr)
def open_medium(ifname):
"""Raw-Ethernet socket on the medium interface, or (None, None).
Returns (sock, mac); mac is the interface's own (unique) address, used as
the Ethernet source and to drop frames we transmitted ourselves -- the
multicast cell loops our own frames back to us."""
if not ifname:
return None, None
# The carrier NIC is unconfigured (it is not a real DUT port), so Infix
# leaves it administratively down -- a raw packet socket on a down link
# carries no frames. Bring it up; wifimedium owns the medium, like hwsim0.
ifup(ifname)
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
socket.htons(ETH_P_WIFIMEDIUM))
sock.bind((ifname, ETH_P_WIFIMEDIUM))
# Don't receive our own transmitted frames (avoids a self-feedback path
# and any reliance on the source MAC, which can collide between guests).
try:
sock.setsockopt(SOL_PACKET, PACKET_IGNORE_OUTGOING, 1)
except OSError:
pass # pre-4.20 kernels; the length check below still applies
with open(f"/sys/class/net/{ifname}/address") as fh:
mac = bytes.fromhex(fh.read().strip().replace(":", ""))
return sock, mac
# Wire format on the medium: 6-byte transmitter MAC, 4-byte LE freq, then the
# raw 802.11 frame.
WIRE = struct.Struct("=6sI")
def wait_radios_renamed(timeout=20):
"""Wait until the hwsim phys have been renamed phyN -> radioN.
60-rename-wifi-phy.rules renames each phy *asynchronously* (a udev RUN),
and the carrier NICs are already named radioN (mactab/nameif, early boot).
We pair phy and carrier by name, so discovering while a phy is still 'phyN'
would miss it -- and we discover only once. This raced our start (gated
merely on the module load) and left some boots relaying nothing until a
restart. Block until every phy is renamed (no 'phyN' left) and the set has
settled, so discovery sees radioN phys that match the radioN carriers.
"""
base = "/sys/class/ieee80211"
prev, stable = None, 0
for _ in range(timeout * 4):
phys = sorted(os.listdir(base)) if os.path.isdir(base) else []
if phys and not any(p.startswith("phy") for p in phys):
if phys == prev:
stable += 1
if stable >= 2: # unchanged for ~0.5s
return
else:
stable = 0
prev = phys
time.sleep(0.25)
def discover_radios():
"""Pair each local hwsim radio with its multicast carrier interface.
A radio's phy is named radioN (60-rename-wifi-phy.rules) under
/sys/class/ieee80211/. In the qeneth test topology each radio also has a
carrier NIC of the SAME name under /sys/class/net/ -- the topology names the
port radioN, so the mactab/nameif rename the carrier to match -- joined to
one multicast cell shared by every DUT's radioN. Pairing them by name gives
a per-radio relay: a radio's frames go onto its own carrier (its cell) and a
carrier's frames are injected only into its own radio.
Returns a list of {name, addr1, sock, mac}. A phy with no matching carrier
netdev (e.g. real hardware) is skipped, so the daemon simply idles there.
"""
wait_radios_renamed()
radios = []
base = "/sys/class/ieee80211"
if not os.path.isdir(base):
return radios
for phy in sorted(os.listdir(base)):
if not os.path.isdir(f"/sys/class/net/{phy}"):
continue # no carrier of this name -> not a relayed radio
sock, mac = open_medium(phy)
if sock:
radios.append({"name": phy, "addr1": radio_addr1(phy),
"sock": sock, "mac": mac})
return radios
def ifup(ifname):
"""Bring an interface up (best effort)."""
subprocess.run(["ip", "link", "set", ifname, "up"], check=False)
def main():
# The hwsim monitor interface is down after boot; bring it up so the
# simulated medium is observable (tcpdump -i hwsim0) and active.
ifup("hwsim0")
# Discover carriers first. With none -- e.g. 'make run', which creates
# radios but no inter-guest medium -- registering on the hwsim family would
# make the kernel hand us every frame with nowhere to relay it, breaking
# delivery between this guest's own radios. So leave the medium to the
# kernel and idle instead of registering.
radios = discover_radios()
if not radios:
log("no radio carriers found; leaving local hwsim medium to the kernel")
while True:
time.sleep(3600)
nl = Netlink()
nl.register()
log(f"registered on hwsim family {nl.family_id}; "
f"radios={' '.join(r['name'] for r in radios)}")
# TX routing: a frame's transmitter (addresses[1]) -> that radio's carrier.
by_addr1 = {r["addr1"]: r for r in radios}
dbg(f"netlink portid={nl.sock.getsockname()[0]}; "
+ "; ".join(f"{r['name']} addr1={r['addr1'].hex()} "
f"carrier={r['mac'].hex()}" for r in radios))
socks = [nl] + [r["sock"] for r in radios]
while True:
ready, _, _ = select.select(socks, [], [])
if nl in ready:
data = nl.recv()
off = 0
while off + NLMSG_HDR.size <= len(data):
mlen, mtype, _, _, _ = NLMSG_HDR.unpack_from(data, off)
if mlen < NLMSG_HDR.size:
break
if mtype == nl.family_id and \
data[off + NLMSG_HDR.size] == HWSIM_CMD_FRAME:
a = parse_attrs(data[off + NLMSG_HDR.size + GENL_HDR.size:
off + mlen])
tx = a.get(HWSIM_ATTR_ADDR_TRANSMITTER)
frame = a.get(HWSIM_ATTR_FRAME)
if tx and frame:
freq = struct.unpack("=I", a[HWSIM_ATTR_FREQ])[0] \
if HWSIM_ATTR_FREQ in a else 0
flags = struct.unpack("=I", a[HWSIM_ATTR_FLAGS])[0] \
if HWSIM_ATTR_FLAGS in a else 0
# Complete the kernel TX path regardless of delivery.
nl.tx_ack(tx, flags, a.get(HWSIM_ATTR_TX_INFO),
a.get(HWSIM_ATTR_TX_INFO_FLAGS),
a.get(HWSIM_ATTR_COOKIE))
# Send only onto the transmitting radio's own carrier.
r = by_addr1.get(tx)
if r:
r["sock"].send(ETH_BROADCAST + r["mac"] + ETYPE +
WIRE.pack(tx, freq) + frame)
dbg(f"tx {r['name']} freq={freq} len={len(frame)}")
else:
dbg(f"tx from unknown radio {tx.hex()} -- dropped")
off += nla_align(mlen)
for r in radios:
if r["sock"] not in ready:
continue
pkt = r["sock"].recv(65536)
if len(pkt) < 14 + WIRE.size:
continue
# The cell multicasts our own frames back to us; PACKET_IGNORE_-
# OUTGOING does not catch that looped copy, so drop anything whose
# Ethernet source is our own carrier (carrier MACs are unique per
# DUT; transmitter addrs are not, so we must key on the carrier).
if pkt[6:12] == r["mac"]:
continue
tx, freq = WIRE.unpack_from(pkt, 14)
frame = pkt[14 + WIRE.size:]
# Inject into THIS radio only -- its carrier is its cell.
nl.inject(r["addr1"], frame, freq)
dbg(f"rx {r['name']} tx={tx.hex()} freq={freq} len={len(frame)}")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass
+14
View File
@@ -0,0 +1,14 @@
# Virtual WiFi medium for mac80211_hwsim (QEMU test target).
#
# Installed only when BR2_PACKAGE_FEATURE_WIFI_HWSIM is set. Bridges hwsim
# frames between the local radios and -- when a medium interface is configured
# (WIFIMEDIUM_IFACE env or 'wifimedium.iface=' on the kernel cmdline) -- to
# peer DUTs over that Ethernet segment, so two QEMU guests can associate
# "wirelessly". hwsim is loaded by the 00-hwsim ixinit script, which sets the
# <usr/hwsim> condition once radios exist and are renamed -- so this service
# starts only on nodes that actually have radios (registering on the hwsim genl
# family would fail otherwise). On real hardware neither is shipped and the
# radios use the air.
service env:-/etc/default/wifimedium name:wifimedium \
[2345] <usr/hwsim> \
/usr/libexec/infix/wifimedium -- Virtual WiFi medium (hwsim)
+2
View File
@@ -14,6 +14,8 @@ define LANDING_INSTALL_TARGET_CMDS
mkdir -p $(TARGET_DIR)/usr/html/
cp $(@D)/*.html $(TARGET_DIR)/usr/html/
cp $(@D)/*.png $(TARGET_DIR)/usr/html/
$(INSTALL) -D -m 0644 $(LANDING_PKGDIR)/default.conf \
$(TARGET_DIR)/etc/nginx/available/default.conf
endef
$(eval $(generic-package))
-6
View File
@@ -1,6 +0,0 @@
config BR2_PACKAGE_MODEMD
bool "modemd"
select BR2_PACKAGE_MODEM_MANAGER
select BR2_PACKAGE_PYTHON3
help
Daemon which manages modems.
-2
View File
@@ -1,2 +0,0 @@
# Locally calculated
sha256 25b33026a661c4c550374cfcba6890a4363bf19db0c1c31a6e65b5edd113ecf0 LICENSE
-56
View File
@@ -1,56 +0,0 @@
################################################################################
#
# modemd
#
################################################################################
MODEMD_VERSION = 1.0
MODEMD_SITE_METHOD = local
MODEMD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/modemd
MODEMD_LICENSE = BSD-3-Clause
MODEMD_LICENSE_FILES = LICENSE
MODEMD_REDISTRIBUTE = NO
MODEMD_DEPENDENCIES = modem-manager jansson python3 \
host-python3 host-python-pypa-build host-python-installer \
host-python-poetry-core
define MODEMD_BUILD_CMDS
$(TARGET_CC) $(TARGET_CFLAGS) $(TARGET_LDFLAGS) \
$(MODEMD_DIR)/modem-command.c -o $(MODEMD_DIR)/modem-command -ljansson
endef
define MODEMD_BUILD_PYTHON
cd $(MODEMD_SITE) && \
$(PKG_PYTHON_PEP517_ENV) $(HOST_DIR)/bin/python3 $(PKG_PYTHON_PEP517_BUILD_CMD) -o $(@D)/dist
mkdir -p $(TARGET_DIR)/usr/libexec/modemd
rm -f $(TARGET_DIR)/usr/libexec/modemd/modemd \
$(TARGET_DIR)/usr/libexec/modemd/modem-* \
$(TARGET_DIR)/usr/libexec/modemd/sim-setup
cd $(@D) && \
$(HOST_DIR)/bin/python3 $(TOPDIR)/support/scripts/pyinstaller.py \
dist/*.whl \
--interpreter=/usr/bin/python3 \
--script-kind=posix \
--purelib=$(TARGET_DIR)/usr/lib/python$(PYTHON3_VERSION_MAJOR)/site-packages \
--headers=$(TARGET_DIR)/usr/include/python$(PYTHON3_VERSION_MAJOR) \
--scripts=$(TARGET_DIR)/usr/libexec/modemd \
--data=$(TARGET_DIR)
endef
MODEMD_POST_INSTALL_TARGET_HOOKS += MODEMD_BUILD_PYTHON
define MODEMD_INSTALL_TARGET_CMDS
mkdir -p $(TARGET_DIR)/usr/libexec/modemd
mkdir -p $(TARGET_DIR)/lib/udev/rules.d
mkdir -p $(FINIT_D)/available/
mkdir -p $(TARGET_DIR)/sbin
$(INSTALL) -D -m 0644 $(MODEMD_DIR)/finit.conf $(FINIT_D)/available/modemd.conf
install -m 644 $(MODEMD_DIR)/modemd.rules $(TARGET_DIR)/lib/udev/rules.d/90-modemd.rules
install -m 644 $(MODEMD_DIR)/qmi-wwan-ids.rules $(TARGET_DIR)/lib/udev/rules.d/91-qmi-wwan-ids.rules
install -m 644 $(MODEMD_DIR)/77-mm-dell-port-types.rules $(TARGET_DIR)/etc/udev/rules.d/77-mm-dell-port-types.rules
install -m 644 $(MODEMD_DIR)/77-mm-modem-gps.rules $(TARGET_DIR)/etc/udev/rules.d/77-mm-modem-gps.rules
install -D -m 644 $(MODEMD_DIR)/modemd.modules-load $(TARGET_DIR)/etc/modules-load.d/modemd.conf
install -m 755 $(MODEMD_DIR)/modem-command $(TARGET_DIR)/sbin/modem-command
ln -sf /usr/libexec/modemd/modemd $(TARGET_DIR)/sbin/modemd
endef
$(eval $(generic-package))
@@ -1 +0,0 @@
service [2345789] ModemManager -- ModemManager daemon
+30
View File
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>Loading…</title>
<meta charset="utf-8">
<meta http-equiv="refresh" content="3">
<style>
html { color-scheme: light dark; font-family: Tahoma, Verdana, Arial, sans-serif; }
body { max-width: 32em; margin: 4em auto; text-align: center; }
h1 { font-weight: 500; margin-bottom: 0.5em; }
p { color: #888; margin: 0.5em 0; }
.spinner {
display: inline-block;
width: 1.5em;
height: 1.5em;
margin-top: 1em;
border: 3px solid currentColor;
border-top-color: transparent;
border-radius: 50%;
animation: rot 1s linear infinite;
}
@keyframes rot { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<h1>Loading…</h1>
<p>The device is finishing its startup. This page refreshes automatically.</p>
<p><span class="spinner" aria-hidden="true"></span></p>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
config BR2_PACKAGE_WEBUI
bool "webui"
depends on BR2_PACKAGE_HOST_GO_TARGET_ARCH_SUPPORTS
depends on BR2_PACKAGE_ROUSETTE
depends on !BR2_PACKAGE_LANDING
help
Web management interface for Infix, a Go+HTMX application
that provides browser-based configuration and monitoring
via RESTCONF.
+44
View File
@@ -0,0 +1,44 @@
# Throttle POST /login so a brute-force attempt can't pin the box on
# the bcrypt-shaped credential check inside rousette/PAM. GET is left
# unmetered: page loads after a 401-driven HX-Redirect shouldn't eat
# into the budget.
#
# Zone is 32k (~500 IPs) — the minimum nginx accepts via shared-memory
# allocation. With the cache full an attacker rotating source addresses
# still tops out at ~2500 attempts/min total, all serialised through
# bcrypt one at a time.
#
# 429 instead of the default 503 keeps the rate-limit response out of
# the /50x.html error_page rewrite below (which auto-refreshes — would
# loop a throttled client straight back to /login).
map $request_method $webui_login_key {
POST $binary_remote_addr;
default "";
}
limit_req_zone $webui_login_key zone=webui_login:32k rate=5r/m;
limit_req_status 429;
server {
listen 80;
listen [::]:80;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name _;
include ssl.conf;
# 404 also points at /50x.html: the page is a "Loading…" screen
# with a meta-refresh, so the early-boot window where the Go
# backend isn't up yet (and any other transient 404 / 5xx) self-
# recovers as soon as upstream comes back.
error_page 404 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
include /etc/nginx/app/*.conf;
}
+11
View File
@@ -0,0 +1,11 @@
# Shared proxy-pass shape for the webui upstream. Nested locations that
# declare their own proxy_* directives don't inherit from the outer block,
# so each location includes this file rather than relying on inheritance.
proxy_pass http://127.0.0.1:10000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
+72
View File
@@ -0,0 +1,72 @@
# Must be at server scope, not on an inner location: client_max_body_size
# does not inherit into a nested location that declares its own proxy_pass,
# and the http-level 1m default would silently apply and reject bundle
# uploads with 413.
client_max_body_size 256m;
location / {
include /etc/nginx/webui-proxy.conf;
}
# burst=3 nodelay: three POSTs land back-to-back; the next needs a
# fresh token (~12 s at 5r/m) or 429s.
location = /login {
limit_req zone=webui_login burst=3 nodelay;
include /etc/nginx/webui-proxy.conf;
}
# Keep proxy_request_buffering on (the default). Streaming the body races
# the Go handler's response close, producing RST instead of FIN at
# Content-Length and a client-visible 502. nginx spools the body to
# /var/cache/nginx/client-body during the upload instead.
location = /software/upload {
proxy_read_timeout 600s;
include /etc/nginx/webui-proxy.conf;
}
# SSE progress stream: RAUC's Progress D-Bus property doesn't change while
# it's writing a slot, so the upstream goes minutes without a frame. The
# default 60 s proxy_read_timeout closes the stream and the browser sees
# a transient error. Raise to cover a slow image write end-to-end.
location = /software/progress {
proxy_read_timeout 1800s;
proxy_buffering off;
include /etc/nginx/webui-proxy.conf;
}
# SSE live-tail stream for Maintenance > Logs. Same buffering / timeout
# concerns as the software progress stream: nginx must not buffer the
# event frames, and the connection has to stay open through quiet logs.
# The Go side sends a 15 s heartbeat, so 600 s of read timeout is plenty
# of safety margin.
location ~ ^/maintenance/logs/[^/]+/tail$ {
proxy_read_timeout 600s;
proxy_buffering off;
include /etc/nginx/webui-proxy.conf;
}
# Support bundle collection runs `support collect`, which emits nothing
# until it finishes (~50 s) and then sends the whole archive at once.
# The upstream is silent for that window, so the default 60 s
# proxy_read_timeout would 504 just as the bundle is ready. Buffering
# stays on — it's a plain file download, not a stream.
location = /maintenance/support-bundle {
proxy_read_timeout 300s;
include /etc/nginx/webui-proxy.conf;
}
# On-device User's Guide: the mkdocs site bundled into the rootfs at
# /var/www/guide by post-build.sh. Served as static files (no upstream
# call), public like the published guide. Present only when the build
# host had mkdocs; the WebUI hides its User Guide entry when it's absent.
location /guide/ {
alias /var/www/guide/;
index index.html;
}
# Liveness probe — nginx-only, no upstream call. Used by the watchdog
# div in base.html and the reboot-overlay poller.
location = /device-status {
access_log off;
return 204;
}
+29
View File
@@ -0,0 +1,29 @@
################################################################################
#
# webui
#
################################################################################
WEBUI_VERSION = 1.0
WEBUI_SITE_METHOD = local
WEBUI_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/webui
WEBUI_GOMOD = infix/webui
WEBUI_LICENSE = MIT
WEBUI_LICENSE_FILES = LICENSE
WEBUI_REDISTRIBUTE = NO
define WEBUI_INSTALL_EXTRA
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/webui.svc \
$(FINIT_D)/available/webui.conf
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/webui.conf \
$(TARGET_DIR)/etc/nginx/app/webui.conf
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/webui-proxy.conf \
$(TARGET_DIR)/etc/nginx/webui-proxy.conf
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/default.conf \
$(TARGET_DIR)/etc/nginx/available/default.conf
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/50x.html \
$(TARGET_DIR)/usr/html/50x.html
endef
WEBUI_POST_INSTALL_TARGET_HOOKS += WEBUI_INSTALL_EXTRA
$(eval $(golang-package))
+3
View File
@@ -0,0 +1,3 @@
service <!> name:webui log:prio:daemon.info,tag:webui \
[2345] env:-/etc/default/webui webui -listen 127.0.0.1:10000 \
-- Web management interface
@@ -1,21 +0,0 @@
diff --git a/src/qmi-firmware-update/qfu-helpers-udev.c b/src/qmi-firmware-update/qfu-helpers-udev.c
index bda9106..40d648f 100644
--- a/src/qmi-firmware-update/qfu-helpers-udev.c
+++ b/src/qmi-firmware-update/qfu-helpers-udev.c
@@ -364,8 +364,14 @@ device_matches (GUdevDevice *device,
if (!device_sysfs_path)
goto out;
- if (g_strcmp0 (device_sysfs_path, sysfs_path) != 0)
- goto out;
+ /*
+ * don't compare sysfs path for download mode as it
+ * changes from USB4 to USB3 and thus may use a different host controller
+ */
+ if (mode != QFU_HELPERS_DEVICE_MODE_DOWNLOAD) {
+ if (g_strcmp0 (device_sysfs_path, sysfs_path) != 0)
+ goto out;
+ }
if (device_mode != mode)
return NULL;
@@ -1,4 +1,4 @@
From 2b86219131c6bed9658c75334e0b2748d4b68c04 Mon Sep 17 00:00:00 2001
From f614327b6eca20319f5eee20ac954ea8d92276e4 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 19 Sep 2023 18:38:10 +0200
Subject: [PATCH 01/50] net: phy: marvell10g: Support firmware loading on
@@ -1,4 +1,4 @@
From 9eb5c192c3c76f9141b344d3bbbc954a0e4be50e Mon Sep 17 00:00:00 2001
From 7d453cb838611f44d7566b6174ae95a5d2265fa1 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 21 Nov 2023 20:15:24 +0100
Subject: [PATCH 02/50] net: phy: marvell10g: Fix power-up when strapped to
@@ -1,4 +1,4 @@
From c08724121dc4d08ac783333a2ac38178649c918c Mon Sep 17 00:00:00 2001
From 4b08a36e3107c4018ddf3a19760cc87d02281bd7 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Wed, 15 Nov 2023 20:58:42 +0100
Subject: [PATCH 03/50] net: phy: marvell10g: Add LED support for 88X3310
@@ -1,4 +1,4 @@
From cd914eb2ac2b86240294108f4d4ab18b0bfbb44b Mon Sep 17 00:00:00 2001
From e5e81eb1172db22be7c83efc8071108d0d42565e Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 12 Dec 2023 09:51:05 +0100
Subject: [PATCH 04/50] net: phy: marvell10g: Support LEDs tied to a single
@@ -1,4 +1,4 @@
From 25d84725be05967330bbeaac957fade38e338e40 Mon Sep 17 00:00:00 2001
From 1f735096f1b8920ee6f21bf60a4c2d3f6376adeb Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Wed, 27 Mar 2024 10:10:19 +0100
Subject: [PATCH 05/50] net: phy: Do not resume PHY when attaching
@@ -19,10 +19,10 @@ administratively down.
1 file changed, 1 deletion(-)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 78cf05a17f8f..ddf47a586a5a 100644
index 26b08e3dbd1d..7597308534a0 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1750,7 +1750,6 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
@@ -1753,7 +1753,6 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
if (err)
goto error;
@@ -1,4 +1,4 @@
From cb1dbb66184b3a07f8da93d8a52a56e77d97b98f Mon Sep 17 00:00:00 2001
From e38d2d2f07f55819d2191d4282dc2d4688075d7d Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Mon, 4 Mar 2024 16:47:28 +0100
Subject: [PATCH 06/50] net: bridge: avoid classifying unknown multicast as
@@ -1,4 +1,4 @@
From 66e1ca5038625bc0feb132edf07953b2e6c350da Mon Sep 17 00:00:00 2001
From a9b80b26c96e5492ab32a72ee36e3321a2b90ee9 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Tue, 5 Mar 2024 06:44:41 +0100
Subject: [PATCH 07/50] net: bridge: Ignore router ports when forwarding L2
@@ -1,4 +1,4 @@
From 31bfb62384ef4b09238f93db137547d0d6d63663 Mon Sep 17 00:00:00 2001
From 01e41d87efa1b63d292f0acfe4d1fb0e39baab20 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Thu, 4 Apr 2024 16:36:30 +0200
Subject: [PATCH 08/50] net: bridge: drop delay for applying strict multicast
@@ -1,4 +1,4 @@
From c16b809eb7ef941870dcfb6ecceab83570fcd223 Mon Sep 17 00:00:00 2001
From 944c77d075c8e0655bd3a28e96892261465a5cf4 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Thu, 16 May 2024 14:51:54 +0200
Subject: [PATCH 09/50] net: bridge: Differentiate MDB additions from
@@ -1,4 +1,4 @@
From 4ba33d783110e4559b5abfcfd8c095f0e71befc5 Mon Sep 17 00:00:00 2001
From 3f58061ab8f869a6432a1256ee7422bb157b624e Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Fri, 24 Nov 2023 23:29:55 +0100
Subject: [PATCH 10/50] nvmem: layouts: onie-tlv: Let device probe even when
@@ -17,10 +17,10 @@ simply refrain from registering any cells in those cases.
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/nvmem/layouts/onie-tlv.c b/drivers/nvmem/layouts/onie-tlv.c
index 0967a32319a2..48547d5bb502 100644
index 8b0f3c1b8a0e..de85690aaa30 100644
--- a/drivers/nvmem/layouts/onie-tlv.c
+++ b/drivers/nvmem/layouts/onie-tlv.c
@@ -197,7 +197,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
@@ -198,7 +198,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
if (!onie_tlv_hdr_is_valid(dev, &hdr)) {
dev_err(dev, "Invalid ONIE TLV header\n");
@@ -29,7 +29,7 @@ index 0967a32319a2..48547d5bb502 100644
}
hdr_len = sizeof(hdr.id) + sizeof(hdr.version) + sizeof(hdr.data_len);
@@ -205,7 +205,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
@@ -206,7 +206,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
table_len = hdr_len + data_len;
if (table_len > ONIE_TLV_MAX_LEN) {
dev_err(dev, "Invalid ONIE TLV data length\n");
@@ -38,7 +38,7 @@ index 0967a32319a2..48547d5bb502 100644
}
table = devm_kmalloc(dev, table_len, GFP_KERNEL);
@@ -217,7 +217,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
@@ -218,7 +218,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout)
return ret;
if (!onie_tlv_crc_is_valid(dev, table_len, table))
@@ -1,4 +1,4 @@
From f19c6a56de0743c906cbef917bc201d0c5a74c99 Mon Sep 17 00:00:00 2001
From f492d4efac3a65bca4d53e383336c36113e28a8b Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Sun, 11 Aug 2024 11:27:35 +0200
Subject: [PATCH 11/50] net: usb: r8152: add r8153b support for link/activity
@@ -18,7 +18,7 @@ Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1 file changed, 8 insertions(+)
diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c
index d61074178279..701cc975cf11 100644
index 8cf4e81f8f88..1bb93efb96c6 100644
--- a/drivers/net/usb/r8152.c
+++ b/drivers/net/usb/r8152.c
@@ -41,6 +41,11 @@
@@ -1,4 +1,4 @@
From daed6dc750e19a316a1a3db62df42ca31f830bd9 Mon Sep 17 00:00:00 2001
From a2448b84e5483b5b91b46ac31b27609352fd9ed6 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Sun, 10 Aug 2025 18:52:54 +0200
Subject: [PATCH 12/50] arm64: dts: mediatek: mt7986a: rename BPi R3 ports to
@@ -1,4 +1,4 @@
From 42a6936aeb6ed136f7ce7524551307d6eecb3d95 Mon Sep 17 00:00:00 2001
From c4ac90dcc5aefbb704c70ebbfe041dee683b882b Mon Sep 17 00:00:00 2001
From: Mattias Walström <lazzer@gmail.com>
Date: Wed, 20 Aug 2025 21:38:24 +0200
Subject: [PATCH 13/50] drm/panel-simple: Add a timing for the Raspberry Pi 7"
@@ -1,4 +1,4 @@
From c83feafa1899f66855f13b8a5c63a3f04b037686 Mon Sep 17 00:00:00 2001
From 8da101ec2429ba0bf1410346260f9a7ddfb0bdbe Mon Sep 17 00:00:00 2001
From: Mattias Walström <lazzer@gmail.com>
Date: Thu, 21 Aug 2025 11:20:23 +0200
Subject: [PATCH 14/50] input:touchscreen:edt-ft5x06: Add polled mode
@@ -1,4 +1,4 @@
From cb3652859a4c5f0ffad4b4cae5d112370829f9ad Mon Sep 17 00:00:00 2001
From 93646f890903157bb78ef369ddf9d99a9161de0d Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 12 Mar 2024 10:27:24 +0100
Subject: [PATCH 15/50] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for
@@ -1,4 +1,4 @@
From abe555c35cf1f2c35f0b9b05917abb5f5470564e Mon Sep 17 00:00:00 2001
From 29cfa1b5ffba41728dc5e0991d1ee27b903d569c Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Wed, 27 Mar 2024 15:52:43 +0100
Subject: [PATCH 16/50] net: dsa: mv88e6xxx: Improve indirect register access
@@ -1,4 +1,4 @@
From 1e0374357606ce86a7fad14d3eedbf650cab2f31 Mon Sep 17 00:00:00 2001
From a7fa9a45329781519702d46ba05815433fff4acc Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Mon, 22 Apr 2024 23:18:01 +0200
Subject: [PATCH 17/50] net: dsa: mv88e6xxx: Honor ports being managed via
@@ -1,4 +1,4 @@
From f3a986c8529106e426b0fb5ecafdb0a852a0f344 Mon Sep 17 00:00:00 2001
From cda7100b0a29e6a9bd29a67ff9db3bcccb72065c Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Wed, 24 Apr 2024 22:41:04 +0200
Subject: [PATCH 18/50] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user
@@ -1,4 +1,4 @@
From 8eaf45f3a3c527db200164eb44a234a33bea0024 Mon Sep 17 00:00:00 2001
From ccf9f9262e8570cfd522b841465f3edf76b3447a Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 28 May 2024 10:38:42 +0200
Subject: [PATCH 19/50] net: dsa: tag_dsa: Use tag priority as initial
@@ -1,4 +1,4 @@
From 01af01f03f4bbaeead33d32f5f8b340a02aca16c Mon Sep 17 00:00:00 2001
From 3200169701ef4d9afdea35355b3fe41d6f363134 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Tue, 16 Jan 2024 16:00:55 +0100
Subject: [PATCH 20/50] net: dsa: Support MDB memberships whose L2 addresses
@@ -1,4 +1,4 @@
From ee554d75ddc33549f25c132f394ce346dc661cf9 Mon Sep 17 00:00:00 2001
From dea9ff6c0bdf7c3e4abc2fb507d2ab4b94dabb63 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Thu, 21 Mar 2024 19:12:15 +0100
Subject: [PATCH 21/50] net: dsa: Support EtherType based priority overrides

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