mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
@@ -14,7 +14,7 @@ The Banana Pi BPI-R64 is a networking board based on the MediaTek MT7622
|
||||
- 8 GB eMMC storage
|
||||
- microSD card slot
|
||||
- MT7531 Gigabit Ethernet switch (4x LAN + 1x WAN)
|
||||
- MT7603E built-in 2.4 GHz WiFi
|
||||
- MT7622 WMAC built-in 2.4 GHz 802.11ac WiFi
|
||||
- USB 3.0 port
|
||||
- 2x Mini PCIe slots
|
||||
|
||||
@@ -24,7 +24,7 @@ Infix comes preconfigured with:
|
||||
|
||||
- **LAN ports** (lan0-lan3): Bridged for internal networking
|
||||
- **WAN port**: DHCP client enabled for internet connectivity
|
||||
- **WiFi** (wifi0-ap): Bridged to LAN (MT7615 PCIe card if fitted, otherwise MT7603E)
|
||||
- **WiFi** (wifi0-ap): Bridged to LAN (MT7622 WMAC, 2.4 GHz; or MT7615 PCIe card if fitted)
|
||||
|
||||
## Boot Switch Reference
|
||||
|
||||
@@ -113,6 +113,19 @@ mmc partconf 0 1 1 0
|
||||
|
||||
## Platform Notes
|
||||
|
||||
### WiFi
|
||||
|
||||
The MT7622 SoC includes a built-in WMAC that provides 2.4 GHz 802.11b/g/n/ac
|
||||
(2×2) — there is no 5 GHz capability from the onboard radio.
|
||||
|
||||
For dual-band operation, a PCIe card can be fitted in one of the two Mini PCIe
|
||||
slots. The [Banana Pi BPI-MT7615][bpi-mt7615] is a purpose-built dual-band
|
||||
(2.4 + 5 GHz, 802.11ac 4×4) module designed for BPI router boards and is a
|
||||
natural fit. With it installed, Infix will use it in preference to the onboard
|
||||
WMAC for the `wifi0-ap` interface.
|
||||
|
||||
[bpi-mt7615]: https://docs.banana-pi.org/en/BPI-MT7615/BananaPi_MT7615
|
||||
|
||||
### mmc0 = eMMC, mmc1 = SD
|
||||
|
||||
On MT7622, MSDC0 (mmc0) is the 8-bit eMMC controller and MSDC1 (mmc1) is the
|
||||
|
||||
+1
-1
Submodule buildroot updated: e411d462cc...168601af48
+28
-2
@@ -3,29 +3,55 @@ Change Log
|
||||
|
||||
All notable changes to the project are documented in this file.
|
||||
|
||||
[v26.04.0][UNRELEASED] -
|
||||
[v26.04.0][] - 2026-04-30
|
||||
-------------------------
|
||||
|
||||
### Changes
|
||||
|
||||
- Upgrade Linux kernel to 6.18.25 (LTS)
|
||||
- Upgrade Buildroot to 2025.02.13 (LTS)
|
||||
- Add support for per-bridge multicast router port in operational, issue #395
|
||||
- Add support for static ARP (IPv4) and neighbor cache (IPv6) entries per
|
||||
interface, issue #819. Static entries are installed as permanent kernel
|
||||
neighbor table entries that are never evicted by normal ARP/NDP aging
|
||||
- Add support for PTP/gPTP (IEEE 1588-2019 / 802.1AS) clock synchronization.
|
||||
Supported clock types: Ordinary Clock, Boundary Clock, and Transparent Clock.
|
||||
See the User Guide for configuration details
|
||||
- Add support for [Banana Pi BPI-R4][BPI-R4], quad-core Cortex-A73 router with
|
||||
4x 2.5 GbE switching, dual 10 GbE SFP+. Variants BPI-R4-2g5 and BPI-R4P have
|
||||
one SFP+ replaced by a 2.5 GbE RJ45, with optional PoE on the R4P
|
||||
- Update [Marvell ESPRESSObin][ESPRESSObin] board support. Allow booting with
|
||||
stock U-Boot, which only supports ext4 rootfs partitions; to use, apply the
|
||||
`ext4` developer snippet before building (`make apply-ext4 all`)
|
||||
- Fix onboard WiFi support on the Banana Pi BPi-R64
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix #520: warn in syslog if multicast flooding is disabled
|
||||
- Fix #769: document dummy interfaces in user guide
|
||||
- Fix #790: document static multicast filters in user guide
|
||||
- Fix #1439: changing hostname does not regenerate DHCP client conf until restart
|
||||
- Fix #1458: `show ntp tracking` displaying a truncated Reference ID, e.g.,
|
||||
`92.2` instead of `92.246.137.39`
|
||||
- Fix #1466: `show container` showing no output for containers whose command
|
||||
line includes environment variables
|
||||
- Fix #1439: changing hostname does not regenerate DHCP client conf until restart
|
||||
- Fix issue with IGMP queries sent with all-zeroes source MAC address
|
||||
- Fix missing IGMP query startup burst when assuming IGMP querier role, as
|
||||
defined in RFC3376 §8.6/§8.7
|
||||
- Fix Raspberry Pi 4 and Pi 400 display instability after soft reboot.
|
||||
Previously the touchscreen/DSI display required a full power cycle to
|
||||
reinitialise correctly; it now works reliably after `reboot`. Please note,
|
||||
you need a fairly up-to-date EEPROM version as well
|
||||
- Fix [BPI-R4][] board README showing inverted DIP switch values for eMMC and
|
||||
SPI NAND boot modes, which would prevent the board from booting correctly
|
||||
- Fix [SAMA7G54][] U-Boot build system selection that caused build failures
|
||||
- Fix [BPI-R3][] PCIe devices failing to initialize on boot due to a missing
|
||||
clock definition in the device tree
|
||||
|
||||
[BPI-R3]: https://wiki.banana-pi.org/Banana_Pi_BPI-R3
|
||||
[BPI-R4]: https://docs.banana-pi.org/en/BPI-R4/BananaPi_BPI-R4
|
||||
[ESPRESSObin]: https://espressobin.net/
|
||||
[SAMA7G54]: https://www.microchip.com/en-us/development-tool/ev21h18a
|
||||
|
||||
[v26.03.0][] - 2026-03-31
|
||||
-------------------------
|
||||
|
||||
@@ -183,6 +183,48 @@ In this setup we have a lot more going on. Multiple multicast router
|
||||
ports have been detected, and behind the scenes someone has also added
|
||||
an IGMP/MLD fast-leave port.
|
||||
|
||||
### Static Multicast Filters
|
||||
|
||||
When IGMP/MLD snooping is in use, traffic for an unregistered group is
|
||||
flooded to all ports until a receiver joins. For MAC multicast groups,
|
||||
or for groups where snooping cannot learn membership automatically, you
|
||||
can add static entries to the MDB that immediately restrict forwarding
|
||||
to a given set of ports.
|
||||
|
||||
> [!NOTE]
|
||||
> Snooping must be enabled on the bridge (or per VLAN) before static
|
||||
> multicast filters can be configured.
|
||||
|
||||
On a plain (non-VLAN) bridge, add a static IPv4 or MAC multicast filter
|
||||
like this:
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||
admin@example:/config/> <b>edit interface br0</b>
|
||||
admin@example:/config/interface/br0/> <b>set bridge multicast-filters multicast-filter 224.1.1.1 ports e2</b>
|
||||
admin@example:/config/interface/br0/> <b>set bridge multicast-filters multicast-filter 224.1.1.1 ports e3</b>
|
||||
admin@example:/config/interface/br0/> <b>set bridge multicast-filters multicast-filter 01:00:5e:01:01:01 ports e2</b>
|
||||
admin@example:/config/interface/br0/> <b>leave</b>
|
||||
admin@example:/> <b>copy running-config startup-config</b>
|
||||
</code></pre>
|
||||
|
||||
Each `ports` entry for the same group adds one port to the filter.
|
||||
Receivers on all other ports will not see traffic for that group.
|
||||
|
||||
On a VLAN-filtering bridge the filter is scoped per VLAN:
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/interface/br1/> <b>set bridge vlans vlan 10 multicast-filters multicast-filter 224.2.2.2 ports e5</b>
|
||||
admin@example:/config/interface/br1/> <b>set bridge vlans vlan 10 multicast-filters multicast-filter 224.2.2.2 ports e6</b>
|
||||
</code></pre>
|
||||
|
||||
To verify the MDB — both statically configured and dynamically learned
|
||||
entries — use:
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>show bridge mdb</b>
|
||||
<span class="header">BRIDGE VID GROUP PORTS </span>
|
||||
br0 224.1.1.1 e2, e3
|
||||
br0 01:00:5e:01:01:01 e2
|
||||
</code></pre>
|
||||
|
||||
### Terminology & Abbreviations
|
||||
|
||||
- **IGMP**: Internet Group Membership Protocol, multicast subscription
|
||||
|
||||
@@ -138,6 +138,49 @@ admin@example:/config/interface/veth0a/> <b>set custom-phys-address chassis offs
|
||||
</code></pre>
|
||||
|
||||
|
||||
## Dummy Interface
|
||||
|
||||
A dummy interface is a virtual interface that is always administratively
|
||||
and operationally UP, regardless of any physical link state. It can
|
||||
hold IP addresses just like any other interface.
|
||||
|
||||
The two most common uses are:
|
||||
|
||||
- **Stable OSPF router-ID**: OSPF picks its router-ID from an interface
|
||||
address. If that interface goes down, adjacencies can flap. Binding
|
||||
the router-ID to a /32 address on a dummy avoids this.
|
||||
- **Stable management address**: A /32 on a dummy gives the device a
|
||||
permanent identity on the network, reachable as long as at least one
|
||||
uplink is up and the address is redistributed into the routing domain.
|
||||
|
||||
> [!TIP]
|
||||
> WiFi interfaces also use dummies as placeholders when the radio
|
||||
> hardware is not detected at boot (e.g., a USB dongle that was
|
||||
> unplugged). See [WiFi](wifi.md) for details.
|
||||
|
||||
### Example: Stable OSPF Router-ID
|
||||
|
||||
Create a dummy interface with a /32 address and use it as the OSPF
|
||||
router-ID so that the ID never changes when physical ports bounce:
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||
admin@example:/config/> <b>edit interface lo0</b>
|
||||
admin@example:/config/interface/lo0/> <b>set type dummy</b>
|
||||
admin@example:/config/interface/lo0/> <b>set ipv4 address 192.0.2.1 prefix-length 32</b>
|
||||
admin@example:/config/interface/lo0/> <b>leave</b>
|
||||
admin@example:/config/> <b>edit routing control-plane-protocol ospfv2 name default ospf</b>
|
||||
admin@example:/config/routing/…/ospf/> <b>set explicit-router-id 192.0.2.1</b>
|
||||
admin@example:/config/routing/…/ospf/> <b>leave</b>
|
||||
admin@example:/> <b>copy running-config startup-config</b>
|
||||
</code></pre>
|
||||
|
||||
To also make the address reachable by other routers, redistribute
|
||||
connected routes (or add `lo0` as an OSPF interface):
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/routing/…/ospf/> <b>set redistribute connected</b>
|
||||
</code></pre>
|
||||
|
||||
|
||||
[^1]: A YANG deviation was previously used to make it possible to set
|
||||
`phys-address`, but this has been replaced with the more flexible
|
||||
`custom-phys-address`.
|
||||
|
||||
@@ -435,6 +435,66 @@ admin@example:/config/interface/eth0/> <b>leave</b>
|
||||
admin@example:/>
|
||||
</code></pre>
|
||||
|
||||
## ARP and Neighbor Cache
|
||||
|
||||
Static ARP entries (IPv4) and neighbor cache entries (IPv6) can be
|
||||
configured per interface. The most common reasons to do so are:
|
||||
|
||||
- **Security** — prevent ARP/NDP spoofing by locking critical hosts
|
||||
(e.g., a default gateway) to their known MAC addresses
|
||||
- **Reliability** — ensure reachability of a host even when ARP/NDP
|
||||
traffic is suppressed or filtered (e.g., across a strict firewall)
|
||||
|
||||
Dynamic entries are learned automatically by the kernel using ARP and
|
||||
Neighbor Discovery Protocol (NDP), and are visible as read-only
|
||||
operational state alongside the static ones.
|
||||
|
||||
### Static IPv4 ARP Entry
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||
admin@example:/config/> <b>edit interface eth0 ipv4</b>
|
||||
admin@example:/config/interface/eth0/ipv4/> <b>set neighbor 192.168.1.100 link-layer-address 00:11:22:33:44:55</b>
|
||||
admin@example:/config/interface/eth0/ipv4/> <b>diff</b>
|
||||
+interfaces {
|
||||
+ interface eth0 {
|
||||
+ ipv4 {
|
||||
+ neighbor 192.168.1.100 {
|
||||
+ link-layer-address 00:11:22:33:44:55;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
admin@example:/config/interface/eth0/ipv4/> <b>leave</b>
|
||||
admin@example:/>
|
||||
</code></pre>
|
||||
|
||||
### Static IPv6 Neighbor Entry
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||
admin@example:/config/> <b>edit interface eth0 ipv6</b>
|
||||
admin@example:/config/interface/eth0/ipv6/> <b>set neighbor 2001:db8::100 link-layer-address 00:11:22:33:44:55</b>
|
||||
admin@example:/config/interface/eth0/ipv6/> <b>diff</b>
|
||||
+interfaces {
|
||||
+ interface eth0 {
|
||||
+ ipv6 {
|
||||
+ neighbor 2001:db8::100 {
|
||||
+ link-layer-address 00:11:22:33:44:55;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
admin@example:/config/interface/eth0/ipv6/> <b>leave</b>
|
||||
admin@example:/>
|
||||
</code></pre>
|
||||
|
||||
The full neighbor table — including dynamically learned entries (origin:
|
||||
*dynamic*) — is available as operational state via NETCONF or RESTCONF.
|
||||
|
||||
> [!NOTE]
|
||||
> Static neighbor entries take effect immediately on `leave` (commit).
|
||||
> They are installed as *permanent* entries in the kernel neighbor table,
|
||||
> which means they are never evicted by the normal ARP/NDP aging process.
|
||||
|
||||
[1]: https://www.rfc-editor.org/rfc/rfc3442
|
||||
[2]: https://www.rfc-editor.org/rfc/rfc8344
|
||||
[3]: https://www.rfc-editor.org/rfc/rfc8981
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
From 184c079c08387d1b0f74de25b63c56304d2156d0 Mon Sep 17 00:00:00 2001
|
||||
From: bazub <github@zubko.cc>
|
||||
Date: Tue, 3 Mar 2026 20:31:33 +0000
|
||||
Subject: [PATCH 1/3] Remove redundant global path var and fix memory
|
||||
corruption
|
||||
Organization: Wires
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/conf.c | 13 ++-----------
|
||||
1 file changed, 2 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/src/conf.c b/src/conf.c
|
||||
index d0abb61d..6e8ec834 100644
|
||||
--- a/src/conf.c
|
||||
+++ b/src/conf.c
|
||||
@@ -121,9 +121,6 @@ static uev_t etcw;
|
||||
|
||||
static TAILQ_HEAD(, conf_change) conf_change_list = TAILQ_HEAD_INITIALIZER(conf_change_list);
|
||||
|
||||
-static char *path;
|
||||
-static char *shell;
|
||||
-
|
||||
static int parse_conf(char *file, int is_rcsd);
|
||||
static void drop_changes(void);
|
||||
|
||||
@@ -377,8 +374,6 @@ void conf_parse_cmdline(int argc, char *argv[])
|
||||
fstab = strdup(ptr);
|
||||
finit_conf = strdup(FINIT_CONF);
|
||||
finit_rcsd = strdup(FINIT_RCSD);
|
||||
- path = getenv("PATH");
|
||||
- shell = getenv("SHELL");
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
parse_arg(argv[i]);
|
||||
@@ -404,13 +399,9 @@ void conf_reset_env(void)
|
||||
free(node);
|
||||
}
|
||||
|
||||
- if (path)
|
||||
- setenv("PATH", path, 1);
|
||||
- else
|
||||
+ if (!getenv("PATH"))
|
||||
setenv("PATH", _PATH_STDPATH, 1);
|
||||
- if (shell)
|
||||
- setenv("SHELL", shell, 1);
|
||||
- else
|
||||
+ if (!getenv("SHELL"))
|
||||
setenv("SHELL", _PATH_BSHELL, 1);
|
||||
setenv("LOGNAME", "root", 1);
|
||||
setenv("USER", "root", 1);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,318 +0,0 @@
|
||||
From 662293e194811213b9b387163dfe8499e3300ccf Mon Sep 17 00:00:00 2001
|
||||
From: bazub <github@zubko.cc>
|
||||
Date: Fri, 6 Mar 2026 20:36:23 +0000
|
||||
Subject: [PATCH 2/3] Use explicit plugin names to prevent subtle macro
|
||||
processing bugs
|
||||
Organization: Wires
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
plugins/alsa-utils.c | 6 +++---
|
||||
plugins/bootmisc.c | 2 +-
|
||||
plugins/dbus.c | 4 ++--
|
||||
plugins/hook-scripts.c | 2 +-
|
||||
plugins/modprobe.c | 2 +-
|
||||
plugins/modules-load.c | 2 +-
|
||||
plugins/netlink.c | 2 +-
|
||||
plugins/pidfile.c | 2 +-
|
||||
plugins/procps.c | 4 ++--
|
||||
plugins/resolvconf.c | 2 +-
|
||||
plugins/rtc.c | 6 +++---
|
||||
plugins/sys.c | 2 +-
|
||||
plugins/tty.c | 1 +
|
||||
plugins/urandom.c | 6 +++---
|
||||
plugins/usr.c | 2 +-
|
||||
plugins/x11-common.c | 4 ++--
|
||||
16 files changed, 25 insertions(+), 24 deletions(-)
|
||||
|
||||
diff --git a/plugins/alsa-utils.c b/plugins/alsa-utils.c
|
||||
index 6b2c3603..a8a967b0 100644
|
||||
--- a/plugins/alsa-utils.c
|
||||
+++ b/plugins/alsa-utils.c
|
||||
@@ -38,7 +38,7 @@
|
||||
static void save(void *arg)
|
||||
{
|
||||
if (rescue) {
|
||||
- dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
+ dbg("Skipping %s plugin in rescue mode.", "alsa-utils");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ static void save(void *arg)
|
||||
static void restore(void *arg)
|
||||
{
|
||||
if (rescue) {
|
||||
- dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
+ dbg("Skipping %s plugin in rescue mode.", "alsa-utils");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ static void restore(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "alsa-utils",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = restore },
|
||||
.hook[HOOK_SHUTDOWN] = { .cb = save }
|
||||
};
|
||||
diff --git a/plugins/bootmisc.c b/plugins/bootmisc.c
|
||||
index 701f73f0..a8ba3808 100644
|
||||
--- a/plugins/bootmisc.c
|
||||
+++ b/plugins/bootmisc.c
|
||||
@@ -172,7 +172,7 @@ static void setup(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "bootmisc",
|
||||
.hook[HOOK_MOUNT_POST] = { .cb = clean },
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = setup },
|
||||
.depends = { "pidfile" },
|
||||
diff --git a/plugins/dbus.c b/plugins/dbus.c
|
||||
index bbf55bc2..a8a155a1 100644
|
||||
--- a/plugins/dbus.c
|
||||
+++ b/plugins/dbus.c
|
||||
@@ -106,7 +106,7 @@ static void setup(void *arg)
|
||||
char *cmd;
|
||||
|
||||
if (rescue) {
|
||||
- dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
+ dbg("Skipping %s plugin in rescue mode.", "dbus");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ static void setup(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "dbus",
|
||||
.hook[HOOK_SVC_PLUGIN] = { .cb = setup },
|
||||
};
|
||||
|
||||
diff --git a/plugins/hook-scripts.c b/plugins/hook-scripts.c
|
||||
index 9aa78173..e75808c5 100644
|
||||
--- a/plugins/hook-scripts.c
|
||||
+++ b/plugins/hook-scripts.c
|
||||
@@ -79,7 +79,7 @@ static void hscript_shutdown(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "hook-scripts",
|
||||
.hook[HOOK_BANNER] = { .cb = hscript_banner },
|
||||
.hook[HOOK_ROOTFS_UP] = { .cb = hscript_rootfs_up },
|
||||
.hook[HOOK_MOUNT_ERROR] = { .cb = hscript_mount_error },
|
||||
diff --git a/plugins/modprobe.c b/plugins/modprobe.c
|
||||
index b6b7e7bb..e52bf228 100644
|
||||
--- a/plugins/modprobe.c
|
||||
+++ b/plugins/modprobe.c
|
||||
@@ -228,7 +228,7 @@ static void coldplug(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "modprobe",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = coldplug },
|
||||
.depends = { "bootmisc", }
|
||||
};
|
||||
diff --git a/plugins/modules-load.c b/plugins/modules-load.c
|
||||
index 53aef82f..e2e45c72 100644
|
||||
--- a/plugins/modules-load.c
|
||||
+++ b/plugins/modules-load.c
|
||||
@@ -213,7 +213,7 @@ static void load(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "modules-load",
|
||||
.hook[HOOK_SVC_PLUGIN] = { .cb = load },
|
||||
};
|
||||
|
||||
diff --git a/plugins/netlink.c b/plugins/netlink.c
|
||||
index a70b01e8..625cbd1b 100644
|
||||
--- a/plugins/netlink.c
|
||||
+++ b/plugins/netlink.c
|
||||
@@ -426,7 +426,7 @@ static void nl_enumerate(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "netlink",
|
||||
.hook[HOOK_SVC_RECONF] = { .cb = nl_reconf },
|
||||
.hook[HOOK_SVC_PLUGIN] = { .cb = nl_enumerate },
|
||||
.io = {
|
||||
diff --git a/plugins/pidfile.c b/plugins/pidfile.c
|
||||
index ae0fdeea..f1232ab6 100644
|
||||
--- a/plugins/pidfile.c
|
||||
+++ b/plugins/pidfile.c
|
||||
@@ -335,7 +335,7 @@ static void pidfile_init(void *arg)
|
||||
* SIGSTP:ed (in state PAUSED) waiting for <net/iface/lo>.
|
||||
*/
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "pidfile",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = pidfile_init },
|
||||
.hook[HOOK_SVC_RECONF] = { .cb = pidfile_reconf },
|
||||
.depends = { "netlink" }, /* bootmisc depends on us */
|
||||
diff --git a/plugins/procps.c b/plugins/procps.c
|
||||
index 826e38ad..5a178d75 100644
|
||||
--- a/plugins/procps.c
|
||||
+++ b/plugins/procps.c
|
||||
@@ -43,7 +43,7 @@ static void setup(void *arg)
|
||||
glob_t gl;
|
||||
|
||||
if (rescue) {
|
||||
- dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
+ dbg("Skipping %s plugin in rescue mode.", "procps");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ static void setup(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "procps",
|
||||
.hook[HOOK_BASEFS_UP] = {
|
||||
.cb = setup
|
||||
},
|
||||
diff --git a/plugins/resolvconf.c b/plugins/resolvconf.c
|
||||
index 1ac29dae..b98e9214 100644
|
||||
--- a/plugins/resolvconf.c
|
||||
+++ b/plugins/resolvconf.c
|
||||
@@ -51,7 +51,7 @@ static void setup(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "resolvconf",
|
||||
.hook[HOOK_BASEFS_UP] = {
|
||||
.cb = setup
|
||||
},
|
||||
diff --git a/plugins/rtc.c b/plugins/rtc.c
|
||||
index faf69415..cf734763 100644
|
||||
--- a/plugins/rtc.c
|
||||
+++ b/plugins/rtc.c
|
||||
@@ -236,7 +236,7 @@ static void rtc_save(void *arg)
|
||||
int fd, rc = 0;
|
||||
|
||||
if (rescue) {
|
||||
- dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
+ dbg("Skipping %s plugin in rescue mode.", "rtc");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ static void rtc_restore(void *arg)
|
||||
int fd, rc = 0;
|
||||
|
||||
if (rescue) {
|
||||
- dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
+ dbg("Skipping %s plugin in rescue mode.", "rtc");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ static void update(uev_t *w, void *arg, int events)
|
||||
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "rtc",
|
||||
.hook[HOOK_BASEFS_UP] = {
|
||||
.cb = rtc_restore
|
||||
},
|
||||
diff --git a/plugins/sys.c b/plugins/sys.c
|
||||
index 438fdfd5..484adc05 100644
|
||||
--- a/plugins/sys.c
|
||||
+++ b/plugins/sys.c
|
||||
@@ -180,7 +180,7 @@ static void sys_init(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "sys",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = sys_init },
|
||||
.depends = { "bootmisc", },
|
||||
};
|
||||
diff --git a/plugins/tty.c b/plugins/tty.c
|
||||
index b3255f9f..c6a750b0 100644
|
||||
--- a/plugins/tty.c
|
||||
+++ b/plugins/tty.c
|
||||
@@ -43,6 +43,7 @@
|
||||
static void tty_watcher(void *arg, int fd, int events);
|
||||
|
||||
static plugin_t plugin = {
|
||||
+ .name = "tty",
|
||||
.io = {
|
||||
.cb = tty_watcher,
|
||||
.flags = PLUGIN_IO_READ,
|
||||
diff --git a/plugins/urandom.c b/plugins/urandom.c
|
||||
index 410d0660..9bb6252b 100644
|
||||
--- a/plugins/urandom.c
|
||||
+++ b/plugins/urandom.c
|
||||
@@ -87,7 +87,7 @@ static void setup(void *arg)
|
||||
int fd, err;
|
||||
|
||||
if (rescue) {
|
||||
- dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
+ dbg("Skipping %s plugin in rescue mode.", "urandom");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ static void save(void *arg)
|
||||
mode_t prev;
|
||||
|
||||
if (rescue) {
|
||||
- dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
+ dbg("Skipping %s plugin in rescue mode.", "urandom");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ static void save(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "urandom",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = setup },
|
||||
.hook[HOOK_SHUTDOWN] = { .cb = save },
|
||||
.depends = { "bootmisc", }
|
||||
diff --git a/plugins/usr.c b/plugins/usr.c
|
||||
index d30d9440..496facc2 100644
|
||||
--- a/plugins/usr.c
|
||||
+++ b/plugins/usr.c
|
||||
@@ -104,7 +104,7 @@ static void usr_init(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "usr",
|
||||
.hook[HOOK_BASEFS_UP] = { .cb = usr_init },
|
||||
.depends = { "bootmisc", },
|
||||
};
|
||||
diff --git a/plugins/x11-common.c b/plugins/x11-common.c
|
||||
index f65331a0..75825a36 100644
|
||||
--- a/plugins/x11-common.c
|
||||
+++ b/plugins/x11-common.c
|
||||
@@ -39,7 +39,7 @@
|
||||
static void setup(void *arg)
|
||||
{
|
||||
if (rescue) {
|
||||
- dbg("Skipping %s plugin in rescue mode.", __FILE__);
|
||||
+ dbg("Skipping %s plugin in rescue mode.", "x11-common");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ static void setup(void *arg)
|
||||
}
|
||||
|
||||
static plugin_t plugin = {
|
||||
- .name = __FILE__,
|
||||
+ .name = "x11-common",
|
||||
.hook[HOOK_SVC_PLUGIN] = { .cb = setup },
|
||||
};
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
From c01faef99b7e4ff9c39f29ad5648db61a4742539 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Thu, 19 Mar 2026 06:37:50 +0100
|
||||
Subject: [PATCH 3/3] service: clear condition before stopping rdeps on reload
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
When a service without SIGHUP reload support (noreload) is touched and
|
||||
'initctl reload' is called, service_update_rdeps() correctly identifies
|
||||
its reverse dependencies but only marks them dirty. It does not clear
|
||||
the service's condition, so when service_step_all() runs:
|
||||
|
||||
- rdeps supporting SIGHUP hit the sm_in_reload() guard and break early,
|
||||
left running while their dependency is being killed.
|
||||
- rdeps without SIGHUP support may receive SIGTERM too late, after the
|
||||
dependency has already died and broken their connection, causing them
|
||||
to exit from RUNNING state and have their restart counter incremented.
|
||||
|
||||
Fix by calling cond_clear() on the service's condition immediately in
|
||||
service_update_rdeps(), before service_step_all() runs. cond_clear()
|
||||
calls cond_update() which calls service_step() inline on all affected
|
||||
services, which see COND_OFF and transition to STOPPING_STATE — all
|
||||
before SIGTERM is ever sent to the dependency itself.
|
||||
|
||||
This mirrors the pattern already used in api.c:do_reload() for direct
|
||||
'initctl reload <svc>' calls.
|
||||
|
||||
Fixes: avahi/mdns stop causing mdns-alias restart counter increment
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/service.c | 16 +++++++++++++++-
|
||||
1 file changed, 15 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/service.c b/src/service.c
|
||||
index b2c6c15b..016b5d2f 100644
|
||||
--- a/src/service.c
|
||||
+++ b/src/service.c
|
||||
@@ -2418,7 +2418,21 @@ void service_update_rdeps(void)
|
||||
if (!svc_is_noreload(svc))
|
||||
continue; /* Yup, no need to stop start rdeps */
|
||||
|
||||
- svc_mark_affected(mkcond(svc, cond, sizeof(cond)));
|
||||
+ /*
|
||||
+ * Clear the condition immediately, before service_step_all()
|
||||
+ * runs. cond_clear() calls cond_update() which calls
|
||||
+ * service_step() on all affected services right now. Those
|
||||
+ * services see COND_OFF and get service_stop() called,
|
||||
+ * transitioning to STOPPING_STATE before we ever send SIGTERM
|
||||
+ * to this service. Without this, the condition is only cleared
|
||||
+ * after the service dies, by which time reverse-dependencies
|
||||
+ * may have already crashed due to the lost connection.
|
||||
+ * See also: api.c do_reload() which does the same for direct
|
||||
+ * 'initctl reload <svc>' calls.
|
||||
+ */
|
||||
+ mkcond(svc, cond, sizeof(cond));
|
||||
+ cond_clear(cond);
|
||||
+ svc_mark_affected(cond);
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# From https://github.com/troglobit/finit/releases/
|
||||
sha256 f0894cd8b79ce030fdc656600208ddb0b994364f86ebad066de2e005e5e18a35 finit-4.16.tar.gz
|
||||
sha256 ca8b489a0ed99c17c0288e9e65e325a8f14af3734dfecf58f516c0a6f884e903 finit-4.17.tar.gz
|
||||
|
||||
# Locally calculated
|
||||
sha256 868cb6c5414933a48db11186042cfe65c87480d326734bc6cf0e4b19b4a2e52a LICENSE
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
################################################################################
|
||||
|
||||
FINIT_VERSION = 4.16
|
||||
FINIT_VERSION = 4.17
|
||||
FINIT_SITE = https://github.com/troglobit/finit/releases/download/$(FINIT_VERSION)
|
||||
FINIT_LICENSE = MIT
|
||||
FINIT_LICENSE_FILES = LICENSE
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
sha256 dcd639c77689c432e84e63267202c00958fb1032e066ae355e3cbc68d9fcd063 mcd-2.3.tar.gz
|
||||
sha256 896c455c1a013a153cbf3186b5f87b7a3344b8939603c0f09e534de714a32e65 mcd-2.4.tar.gz
|
||||
sha256 99a0480db163445c5a1a5bf7f85bc37cbb5be8c40e9e441291ad78e8d4252d0b LICENSE
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
#
|
||||
################################################################################
|
||||
|
||||
MCD_VERSION = 2.3
|
||||
MCD_VERSION = 2.4
|
||||
MCD_SITE = https://github.com/kernelkit/mcd/releases/download/v$(MCD_VERSION)
|
||||
MCD_LICENSE = BSD-3-Clause
|
||||
MCD_LICENSE_FILES = LICENSE
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
From 925bc823c4f4ece91c090371a358134e238e7cc4 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Tue, 28 Apr 2026 15:30:01 +0200
|
||||
Subject: [PATCH] wifi: mt76: mt7615: add MODULE_DEVICE_TABLE for mt7622 wmac
|
||||
Organization: Wires
|
||||
|
||||
Without MODULE_DEVICE_TABLE(of, ...) the OF compatible alias is never
|
||||
exported to modules.alias, so udev cannot autoload mt7615e when the
|
||||
mediatek,mt7622-wmac platform device is probed. Add the export so the
|
||||
module loads automatically on BPI-R64 and similar MT7622-based boards.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
drivers/net/wireless/mediatek/mt76/mt7615/soc.c | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/soc.c b/drivers/net/wireless/mediatek/mt76/mt7615/soc.c
|
||||
index 06a0f2a141e8..538ea1dc333b 100644
|
||||
--- a/drivers/net/wireless/mediatek/mt76/mt7615/soc.c
|
||||
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/soc.c
|
||||
@@ -56,6 +56,7 @@ static const struct of_device_id mt7622_wmac_of_match[] = {
|
||||
{ .compatible = "mediatek,mt7622-wmac" },
|
||||
{},
|
||||
};
|
||||
+MODULE_DEVICE_TABLE(of, mt7622_wmac_of_match);
|
||||
|
||||
struct platform_driver mt7622_wmac_driver = {
|
||||
.driver = {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -12,6 +12,56 @@
|
||||
|
||||
#include "interfaces.h"
|
||||
|
||||
static void warn_mcast_flood_port(struct lyd_node *port_cif)
|
||||
{
|
||||
struct lyd_node *bp, *flood;
|
||||
|
||||
bp = lydx_get_child(port_cif, "bridge-port");
|
||||
if (!bp)
|
||||
return;
|
||||
|
||||
flood = lydx_get_child(bp, "flood");
|
||||
if (!lydx_is_enabled(flood, "multicast"))
|
||||
WARN("%s: multicast flood disabled, topology changes will black-hole multicast until IGMP/MLD rejoin",
|
||||
lydx_get_cattr(port_cif, "name"));
|
||||
}
|
||||
|
||||
static void warn_mcast_flood_bridge(struct lyd_node *cif, const char *brname)
|
||||
{
|
||||
struct ly_set *ports;
|
||||
uint32_t i;
|
||||
|
||||
ports = lydx_find_xpathf(cif, "../interface[bridge-port/bridge='%s']", brname);
|
||||
if (!ports)
|
||||
return;
|
||||
|
||||
for (i = 0; i < ports->count; i++)
|
||||
warn_mcast_flood_port(ports->dnodes[i]);
|
||||
|
||||
ly_set_free(ports, NULL);
|
||||
}
|
||||
|
||||
static void warn_mcast_flood_vlan(struct lyd_node *cif, struct lyd_node *vlan)
|
||||
{
|
||||
static const char *modes[] = { "tagged", "untagged", NULL };
|
||||
struct lyd_node *portentry;
|
||||
struct ly_set *set;
|
||||
const char **mode;
|
||||
|
||||
for (mode = modes; *mode; mode++) {
|
||||
LYX_LIST_FOR_EACH(lyd_child(vlan), portentry, *mode) {
|
||||
set = lydx_find_xpathf(cif, "../interface[name='%s']",
|
||||
lyd_get_value(portentry));
|
||||
if (!set || !set->count) {
|
||||
ly_set_free(set, NULL);
|
||||
continue;
|
||||
}
|
||||
warn_mcast_flood_port(set->dnodes[0]);
|
||||
ly_set_free(set, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int gen_vlan(struct lyd_node *cif, struct lyd_node *vlan, FILE *conf)
|
||||
{
|
||||
const char *iface, *querier, *upper;
|
||||
@@ -26,6 +76,8 @@ static int gen_vlan(struct lyd_node *cif, struct lyd_node *vlan, FILE *conf)
|
||||
if (!strcmp(querier, "off"))
|
||||
return 0;
|
||||
|
||||
warn_mcast_flood_vlan(cif, vlan);
|
||||
|
||||
interval = atoi(lydx_get_cattr(mcast, "query-interval"));
|
||||
|
||||
iface = lydx_get_cattr(cif, "name");
|
||||
@@ -66,6 +118,8 @@ static int gen_bridge(struct lyd_node *cif, FILE *conf)
|
||||
|
||||
interval = atoi(lydx_get_cattr(mcast, "query-interval"));
|
||||
|
||||
warn_mcast_flood_bridge(cif, iface);
|
||||
|
||||
fprintf(conf, "iface %s enable %s igmpv3 query-interval %d\n",
|
||||
iface, !strcmp(querier, "proxy") ? "proxy-queries" : "",
|
||||
interval);
|
||||
|
||||
@@ -681,6 +681,8 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net
|
||||
err = err ? : netdag_gen_link_addr(ip, cif, dif);
|
||||
err = err ? : netdag_gen_ip_addrs(net, ip, "ipv4", cif, dif);
|
||||
err = err ? : netdag_gen_ip_addrs(net, ip, "ipv6", cif, dif);
|
||||
err = err ? : netdag_gen_ip_neighs(net, ip, "ipv4", cif, dif);
|
||||
err = err ? : netdag_gen_ip_neighs(net, ip, "ipv6", cif, dif);
|
||||
if (err)
|
||||
goto err_close_ip;
|
||||
|
||||
|
||||
@@ -114,6 +114,8 @@ int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif,
|
||||
struct lyd_node *dif);
|
||||
int netdag_gen_ip_addrs(struct dagger *net, FILE *ip, const char *proto,
|
||||
struct lyd_node *cif, struct lyd_node *dif);
|
||||
int netdag_gen_ip_neighs(struct dagger *net, FILE *ip, const char *proto,
|
||||
struct lyd_node *cif, struct lyd_node *dif);
|
||||
|
||||
/* if-bridge.c */
|
||||
int bridge_mstpd_gen(struct lyd_node *cifs);
|
||||
|
||||
@@ -242,6 +242,79 @@ static int netdag_set_conf_addrs(FILE *ip, const char *ifname,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int netdag_gen_diff_neigh(FILE *ip, const char *ifname,
|
||||
struct lyd_node *neigh)
|
||||
{
|
||||
enum lydx_op op = lydx_get_op(neigh);
|
||||
struct lyd_node *addr, *lladdr;
|
||||
struct lydx_diff addrd, lladrd;
|
||||
|
||||
addr = lydx_get_child(neigh, "ip");
|
||||
if (!addr)
|
||||
return -EINVAL;
|
||||
|
||||
lydx_get_diff(addr, &addrd);
|
||||
|
||||
if (op == LYDX_OP_DELETE) {
|
||||
fprintf(ip, "neigh del %s dev %s\n", addrd.old, ifname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
lladdr = lydx_get_child(neigh, "link-layer-address");
|
||||
if (!lladdr)
|
||||
return -EINVAL;
|
||||
|
||||
lydx_get_diff(lladdr, &lladrd);
|
||||
fprintf(ip, "neigh replace %s lladdr %s dev %s nud permanent\n",
|
||||
addrd.new, lladrd.new, ifname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int netdag_set_conf_neighs(FILE *ip, const char *ifname,
|
||||
struct lyd_node *ipvx)
|
||||
{
|
||||
struct lyd_node *neigh;
|
||||
|
||||
LYX_LIST_FOR_EACH(lyd_child(ipvx), neigh, "neighbor") {
|
||||
fprintf(ip, "neigh replace %s lladdr %s dev %s nud permanent\n",
|
||||
lydx_get_cattr(neigh, "ip"),
|
||||
lydx_get_cattr(neigh, "link-layer-address"),
|
||||
ifname);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int netdag_gen_ip_neighs(struct dagger *net, FILE *ip, const char *proto,
|
||||
struct lyd_node *cif, struct lyd_node *dif)
|
||||
{
|
||||
struct lyd_node *ipconf = lydx_get_child(cif, proto);
|
||||
struct lyd_node *ipdiff = lydx_get_child(dif, proto);
|
||||
const char *ifname = lydx_get_cattr(dif, "name");
|
||||
struct lyd_node *neigh;
|
||||
int err = 0;
|
||||
|
||||
if (!ipconf || !lydx_is_enabled(ipconf, "enabled")) {
|
||||
FILE *fp = dagger_fopen_net_exit(net, ifname, NETDAG_EXIT_PRE, "flush-neigh.sh");
|
||||
if (fp) {
|
||||
fprintf(fp, "ip -%c neigh flush dev %s nud permanent\n", proto[3], ifname);
|
||||
fclose(fp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (lydx_get_op(lydx_get_child(ipdiff, "enabled")) == LYDX_OP_REPLACE)
|
||||
return netdag_set_conf_neighs(ip, ifname, ipconf);
|
||||
|
||||
LYX_LIST_FOR_EACH(lyd_child(ipdiff), neigh, "neighbor") {
|
||||
err = netdag_gen_diff_neigh(ip, ifname, neigh);
|
||||
if (err)
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int netdag_gen_ip_addrs(struct dagger *net, FILE *ip, const char *proto,
|
||||
struct lyd_node *cif, struct lyd_node *dif)
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ MODULES=(
|
||||
"ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor"
|
||||
"infix-hardware@2026-02-08.yang"
|
||||
"ieee802-dot1q-types@2022-10-29.yang"
|
||||
"infix-ip@2025-11-02.yang"
|
||||
"infix-ip@2026-04-28.yang"
|
||||
"infix-if-type@2026-01-07.yang"
|
||||
"infix-routing@2026-03-11.yang"
|
||||
"ieee802-dot1ab-lldp@2022-03-15.yang"
|
||||
@@ -47,7 +47,7 @@ MODULES=(
|
||||
"ieee802-ethernet-interface@2019-06-21.yang"
|
||||
"infix-ethernet-interface@2024-02-27.yang"
|
||||
"infix-factory-default@2023-06-28.yang"
|
||||
"infix-interfaces@2026-04-09.yang -e vlan-filtering"
|
||||
"infix-interfaces@2026-04-29.yang -e vlan-filtering"
|
||||
"ietf-crypto-types -e cleartext-symmetric-keys"
|
||||
"infix-crypto-types@2026-02-14.yang"
|
||||
"ietf-keystore -e symmetric-keys"
|
||||
|
||||
@@ -29,6 +29,11 @@ submodule infix-if-bridge {
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Linux bridge extension for ietf-interfaces.";
|
||||
|
||||
revision 2026-04-29 {
|
||||
description "Add operational state for multicast router ports per bridge.";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
revision 2025-10-28 {
|
||||
description "Prevent IP addresses on bridge ports.";
|
||||
reference "internal";
|
||||
@@ -389,6 +394,12 @@ submodule infix-if-bridge {
|
||||
}
|
||||
default 125;
|
||||
}
|
||||
|
||||
leaf-list router-ports {
|
||||
config false;
|
||||
description "Ports with detected multicast routers, learned by IGMP/MLD snooping.";
|
||||
type if:interface-ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,9 @@ module infix-if-type {
|
||||
identity dummy {
|
||||
base infix-interface-type;
|
||||
base ianaift:other;
|
||||
description "Linux dummy interface. Useful mostly for testing.";
|
||||
description "Linux virtual interface that is always UP. Useful for
|
||||
stable loopback-style addresses, e.g., as a permanent
|
||||
management address or OSPF router-ID source.";
|
||||
}
|
||||
identity ethernet {
|
||||
base infix-interface-type;
|
||||
|
||||
@@ -41,6 +41,11 @@ module infix-interfaces {
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Linux bridge and lag extensions for ietf-interfaces.";
|
||||
|
||||
revision 2026-04-29 {
|
||||
description "Add operational state for multicast router ports per bridge.";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
revision 2026-04-09 {
|
||||
description "Add ptp-capabilities submodule for per-interface PTP timestamping info.";
|
||||
reference "internal";
|
||||
|
||||
@@ -18,6 +18,10 @@ module infix-ip {
|
||||
|
||||
description "This module augments ietf-ip with Infix extensions and deviations.";
|
||||
|
||||
revision 2026-04-28 {
|
||||
description "Add support for ARP and neighbor cache (ietf-ip neighbor lists).";
|
||||
reference "Internal, issue #819.";
|
||||
}
|
||||
revision 2025-11-02 {
|
||||
description "Change autoconf to presence container, removing enabled leaf.";
|
||||
reference "Internal, issue #1109.";
|
||||
@@ -65,15 +69,8 @@ module infix-ip {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/if:interfaces/if:interface/ip:ipv4/ip:neighbor" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/if:interfaces/if:interface/ip:ipv6/ip:address/ip:status" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
deviation "/if:interfaces/if:interface/ip:ipv6/ip:neighbor" {
|
||||
deviate not-supported;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +218,13 @@ def mctl(ifname, vid, mctldata):
|
||||
return {}
|
||||
|
||||
|
||||
def mctl_router_ports(brname, mctldata):
|
||||
for entry in mctldata.get("multicast-router-ports", []):
|
||||
if entry.get("bridge") == brname:
|
||||
return entry.get("ports", [])
|
||||
return []
|
||||
|
||||
|
||||
def multicast_filters(iplink, vid):
|
||||
filt = ["dev", iplink["ifname"]] + (["vid", str(vid)] if vid else [])
|
||||
brmdb = HOST.run_json(["bridge", "-j", "mdb", "show"] + filt)[0]["mdb"]
|
||||
@@ -253,6 +260,9 @@ def multicast(iplink, info, mctldata):
|
||||
if interval := mctlq.get("interval"):
|
||||
mcast["query-interval"] = interval
|
||||
|
||||
if ports := mctl_router_ports(iplink["ifname"], mctldata):
|
||||
mcast["router-ports"] = ports
|
||||
|
||||
return mcast
|
||||
|
||||
|
||||
|
||||
@@ -28,3 +28,17 @@ def ipaddrs(ifname=None, netns=None):
|
||||
return HOST.run_json(pre + ["ip", "-j", "addr", "show"] + filt)
|
||||
|
||||
return { addr["ifname"]: addr for addr in _ipaddrs(ifname, netns) }
|
||||
|
||||
|
||||
@cache
|
||||
def ipneighs(ifname=None, netns=None):
|
||||
def _ipneighs(ifname, netns):
|
||||
pre = ["ip", "netns", "exec", netns] if netns else []
|
||||
filt = ["dev", ifname] if ifname else []
|
||||
return HOST.run_json(pre + ["ip", "-j", "neigh", "show"] + filt, [])
|
||||
|
||||
result = {}
|
||||
for e in _ipneighs(ifname, netns):
|
||||
if dev := e.get("dev"):
|
||||
result.setdefault(dev, []).append(e)
|
||||
return result
|
||||
|
||||
@@ -1,4 +1,55 @@
|
||||
import ipaddress
|
||||
|
||||
from ..host import HOST
|
||||
from . import common
|
||||
|
||||
|
||||
def neigh_state(states):
|
||||
xlate = {
|
||||
"REACHABLE": "reachable",
|
||||
"STALE": "stale",
|
||||
"DELAY": "delay",
|
||||
"PROBE": "probe",
|
||||
"INCOMPLETE": "incomplete",
|
||||
}
|
||||
return next((xlate[s] for s in states if s in xlate), None)
|
||||
|
||||
|
||||
def neighbors(ifname, family):
|
||||
result = []
|
||||
for entry in common.ipneighs().get(ifname, []):
|
||||
dst = entry.get("dst", "")
|
||||
try:
|
||||
version = ipaddress.ip_address(dst).version
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if version != (4 if family == "inet" else 6):
|
||||
continue
|
||||
|
||||
lladdr = entry.get("lladdr")
|
||||
states = entry.get("state", [])
|
||||
|
||||
if not lladdr:
|
||||
continue
|
||||
|
||||
origin = "static" if "PERMANENT" in states else "dynamic"
|
||||
neigh = {
|
||||
"ip": dst,
|
||||
"link-layer-address": lladdr,
|
||||
"origin": origin,
|
||||
}
|
||||
|
||||
if family == "inet6":
|
||||
if state := neigh_state(states):
|
||||
neigh["state"] = state
|
||||
if entry.get("router"):
|
||||
neigh["is-router"] = [None]
|
||||
|
||||
result.append(neigh)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def inet2yang_origin(inet):
|
||||
"""Translate kernel IP address origin to YANG"""
|
||||
@@ -34,23 +85,31 @@ def addresses(ipaddr, proto):
|
||||
|
||||
def ipv4(ipaddr):
|
||||
ipv4 = {}
|
||||
ifname = ipaddr.get("ifname")
|
||||
|
||||
mtu = ipaddr.get("mtu")
|
||||
if mtu and ipaddr.get("ifname") != "lo":
|
||||
if mtu and ifname != "lo":
|
||||
ipv4["mtu"] = mtu
|
||||
|
||||
if addrs := addresses(ipaddr, "inet"):
|
||||
ipv4["address"] = addrs
|
||||
|
||||
if neighs := neighbors(ifname, "inet"):
|
||||
ipv4["neighbor"] = neighs
|
||||
|
||||
return ipv4
|
||||
|
||||
def ipv6(ipaddr):
|
||||
ipv6 = {}
|
||||
ifname = ipaddr.get("ifname")
|
||||
|
||||
if mtu := HOST.read(f"/proc/sys/net/ipv6/conf/{ipaddr['ifname']}/mtu"):
|
||||
if mtu := HOST.read(f"/proc/sys/net/ipv6/conf/{ifname}/mtu"):
|
||||
ipv6["mtu"] = int(mtu.strip())
|
||||
|
||||
if addrs := addresses(ipaddr, "inet6"):
|
||||
ipv6["address"] = addrs
|
||||
|
||||
if neighs := neighbors(ifname, "inet6"):
|
||||
ipv6["neighbor"] = neighs
|
||||
|
||||
return ipv6
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
- name: Interface with IPv4
|
||||
case: ipv4_address/test.py
|
||||
|
||||
- name: ARP and Neighbor Cache
|
||||
case: neighbor_cache/test.py
|
||||
|
||||
- name: Interface IPv6 Autoconf for Bridges
|
||||
case: ipv6_address/test.py
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
@@ -0,0 +1,25 @@
|
||||
=== ARP and Neighbor Cache
|
||||
|
||||
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/neighbor_cache]
|
||||
|
||||
==== Description
|
||||
|
||||
Verify that static ARP entries (IPv4) and neighbor cache entries (IPv6)
|
||||
can be configured on an interface and are immediately visible in the
|
||||
operational datastore with origin "static". Also verify that removing
|
||||
the entries causes them to disappear from the operational datastore.
|
||||
|
||||
==== Topology
|
||||
|
||||
image::topology.svg[ARP and Neighbor Cache topology, align=center, scaledwidth=75%]
|
||||
|
||||
==== Sequence
|
||||
|
||||
. Set up topology and attach to target DUT
|
||||
. Configure static IPv4 ARP and IPv6 neighbor entries on target:data
|
||||
. Verify static IPv4 ARP entry is visible in operational state
|
||||
. Verify static IPv6 neighbor entry is visible in operational state
|
||||
. Remove static neighbor entries by clearing IPv4 and IPv6 config
|
||||
. Verify static neighbor entries are no longer present
|
||||
|
||||
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ARP and Neighbor Cache
|
||||
|
||||
Verify that static ARP entries (IPv4) and neighbor cache entries (IPv6)
|
||||
can be configured on an interface and are immediately visible in the
|
||||
operational datastore with origin "static". Also verify that removing
|
||||
the entries causes them to disappear from the operational datastore.
|
||||
"""
|
||||
import infamy
|
||||
import infamy.iface as iface
|
||||
|
||||
from infamy.util import until
|
||||
|
||||
IPV4_NEIGH = "192.0.2.1"
|
||||
IPV4_LLADR = "de:ad:be:ef:ca:fe"
|
||||
IPV6_NEIGH = "2001:db8::1"
|
||||
IPV6_LLADR = "de:ad:be:ef:ca:ff"
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Set up topology and attach to target DUT"):
|
||||
env = infamy.Env()
|
||||
target = env.attach("target", "mgmt")
|
||||
_, tport = env.ltop.xlate("target", "data")
|
||||
|
||||
with test.step("Configure static IPv4 ARP and IPv6 neighbor entries on target:data"):
|
||||
target.put_config_dict("ietf-interfaces", {
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": tport,
|
||||
"ipv4": {
|
||||
"neighbor": [{
|
||||
"ip": IPV4_NEIGH,
|
||||
"link-layer-address": IPV4_LLADR,
|
||||
}]
|
||||
},
|
||||
"ipv6": {
|
||||
"neighbor": [{
|
||||
"ip": IPV6_NEIGH,
|
||||
"link-layer-address": IPV6_LLADR,
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
with test.step("Verify static IPv4 ARP entry is visible in operational state"):
|
||||
until(lambda: iface.neighbor_exist(target, tport, IPV4_NEIGH, IPV4_LLADR, "static"))
|
||||
|
||||
with test.step("Verify static IPv6 neighbor entry is visible in operational state"):
|
||||
until(lambda: iface.neighbor_exist(target, tport, IPV6_NEIGH, IPV6_LLADR, "static"))
|
||||
|
||||
with test.step("Remove static neighbor entries by clearing IPv4 and IPv6 config"):
|
||||
target.delete_xpath(
|
||||
f"/ietf-interfaces:interfaces/interface[name='{tport}']/ietf-ip:ipv4")
|
||||
target.delete_xpath(
|
||||
f"/ietf-interfaces:interfaces/interface[name='{tport}']/ietf-ip:ipv6")
|
||||
|
||||
with test.step("Verify static neighbor entries are no longer present"):
|
||||
until(lambda: not iface.neighbor_exist(target, tport, IPV4_NEIGH))
|
||||
until(lambda: not iface.neighbor_exist(target, tport, IPV6_NEIGH))
|
||||
|
||||
test.succeed()
|
||||
@@ -0,0 +1,24 @@
|
||||
graph "1x2" {
|
||||
layout="neato";
|
||||
overlap="false";
|
||||
esep="+80";
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <mgmt> mgmt | <data> data }",
|
||||
pos="0,12!",
|
||||
requires="controller",
|
||||
];
|
||||
|
||||
target [
|
||||
label="{ <mgmt> mgmt | <data> data } | target",
|
||||
pos="10,12!",
|
||||
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
host:mgmt -- target:mgmt [requires="mgmt", color=lightgrey]
|
||||
host:data -- target:data [color=black]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- Title: 1x2 Pages: 1 -->
|
||||
<svg width="424pt" height="55pt"
|
||||
viewBox="0.00 0.00 424.03 55.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 51)">
|
||||
<title>1x2</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-51 420.03,-51 420.03,4 -4,4"/>
|
||||
<!-- host -->
|
||||
<g id="node1" class="node">
|
||||
<title>host</title>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-46.5 100,-46.5 100,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="25" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
<polyline fill="none" stroke="black" points="50,-0.5 50,-46.5 "/>
|
||||
<text text-anchor="middle" x="75" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="50,-23.5 100,-23.5 "/>
|
||||
<text text-anchor="middle" x="75" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
</g>
|
||||
<!-- target -->
|
||||
<g id="node2" class="node">
|
||||
<title>target</title>
|
||||
<polygon fill="none" stroke="black" points="300.03,-0.5 300.03,-46.5 416.03,-46.5 416.03,-0.5 300.03,-0.5"/>
|
||||
<text text-anchor="middle" x="325.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="300.03,-23.5 350.03,-23.5 "/>
|
||||
<text text-anchor="middle" x="325.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polyline fill="none" stroke="black" points="350.03,-0.5 350.03,-46.5 "/>
|
||||
<text text-anchor="middle" x="383.03" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt--target:mgmt</title>
|
||||
<path fill="none" stroke="lightgrey" stroke-width="2" d="M100,-35.5C100,-35.5 300.03,-35.5 300.03,-35.5"/>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:data--target:data</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M100,-11.5C100,-11.5 300.03,-11.5 300.03,-11.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -108,6 +108,28 @@ def is_oper_up(target, iface):
|
||||
return get_oper_status(target, iface) == "up"
|
||||
|
||||
|
||||
def _get_neighbors(target, iface, proto):
|
||||
interface = target.get_iface(iface)
|
||||
if interface is None:
|
||||
return None
|
||||
ip = interface.get(proto) or interface.get(f"ietf-ip:{proto}")
|
||||
return ip.get("neighbor") if ip else None
|
||||
|
||||
|
||||
def neighbor_exist(target, iface, address, lladdr=None, origin=None):
|
||||
"""Check if neighbor 'address' exists on iface, optionally matching lladdr and origin"""
|
||||
for proto in ("ipv4", "ipv6"):
|
||||
for n in _get_neighbors(target, iface, proto) or []:
|
||||
if n.get("ip") != address:
|
||||
continue
|
||||
if lladdr and n.get("link-layer-address") != lladdr:
|
||||
continue
|
||||
if origin and n.get("origin") != origin:
|
||||
continue
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def exist_bridge_multicast_filter(target, group, iface, bridge):
|
||||
"""Check if a bridge has a multicast filter for group with iface"""
|
||||
# The interface array is different in restconf/netconf, netconf has
|
||||
|
||||
Reference in New Issue
Block a user