Merge pull request #1484 from kernelkit/releng

Release preparations
This commit is contained in:
Joachim Wiberg
2026-04-30 13:14:04 +02:00
committed by GitHub
36 changed files with 645 additions and 459 deletions
+15 -2
View File
@@ -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
+28 -2
View File
@@ -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
-------------------------
+42
View File
@@ -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
+43
View File
@@ -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`.
+60
View File
@@ -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 -1
View File
@@ -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
+1 -1
View File
@@ -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 -1
View File
@@ -1,2 +1,2 @@
sha256 dcd639c77689c432e84e63267202c00958fb1032e066ae355e3cbc68d9fcd063 mcd-2.3.tar.gz
sha256 896c455c1a013a153cbf3186b5f87b7a3344b8939603c0f09e534de714a32e65 mcd-2.4.tar.gz
sha256 99a0480db163445c5a1a5bf7f85bc37cbb5be8c40e9e441291ad78e8d4252d0b LICENSE
+1 -1
View File
@@ -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
+54
View File
@@ -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);
+2
View File
@@ -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;
+2
View File
@@ -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);
+73
View File
@@ -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)
{
+2 -2
View File
@@ -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"
+11
View File
@@ -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;
}
}
}
+3 -1
View File
@@ -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";
+4 -7
View File
@@ -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
+61 -2
View File
@@ -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
+3
View File
@@ -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
+1
View File
@@ -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
+63
View File
@@ -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&#45;&#45;target -->
<g id="edge1" class="edge">
<title>host:mgmt&#45;&#45;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&#45;&#45;target -->
<g id="edge2" class="edge">
<title>host:data&#45;&#45;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

+22
View File
@@ -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