From 3da5c65d875e18fe17f920bda120aed551293d40 Mon Sep 17 00:00:00 2001 From: Ramses Date: Tue, 1 Sep 2026 11:28:43 +0200 Subject: [PATCH] network: register already enslaved interfaces when their master appears (#43557) When networkd starts or restarts, links are enumerated in ifindex order. A slave enumerated before its master cannot register itself in link_append_to_master(), as the master Link object does not exist yet. The master then wrongly believes it has no slaves until the next RTM_NEWLINK message for the slave arrives. The stale slaves set affects operational state propagation (degraded-carrier detection) and any code that inspects the slaves of a master. For instance, on restart, when networkd thinks that the master has no slaves yet, we try to set the bond fail-over policy, which the kernel then refuses because in reality the bond does have slaves. To fix the issue, this updates the list of slaves after enumeration. Co-authored-by: Yu Watanabe --- src/network/networkd-link.c | 2 +- src/network/networkd-link.h | 1 + src/network/networkd-manager.c | 13 ++++++++++++- .../conf/25-bond-fail-over-mac.netdev | 9 +++++++++ .../conf/25-bond-fail-over-mac.network | 7 +++++++ test/test-network/systemd-networkd-tests.py | 17 +++++++++++++++++ 6 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 test/test-network/conf/25-bond-fail-over-mac.netdev create mode 100644 test/test-network/conf/25-bond-fail-over-mac.network diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 62fcd306db..3de23c8dd1 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1035,7 +1035,7 @@ static void link_free_bound_by_list(Link *link) { link_dirty(link); } -static int link_append_to_master(Link *link) { +int link_append_to_master(Link *link) { Link *master; int r; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 9340ecab53..99cdbbeef8 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -222,6 +222,7 @@ int link_get_master(Link *link, Link **ret); int link_getlink_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg); int link_call_getlink(Link *link, link_netlink_message_handler_t callback); int link_handle_bound_to_list(Link *link); +int link_append_to_master(Link *link); void link_enter_failed(Link *link); void link_set_state(Link *link, LinkState state); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index a1bc5c4691..291589800f 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -939,7 +939,18 @@ static int manager_enumerate_links(Manager *m) { if (r < 0) return r; - return manager_enumerate_internal(m, m->rtnl, req, manager_rtnl_process_link); + r = manager_enumerate_internal(m, m->rtnl, req, manager_rtnl_process_link); + if (r < 0) + return r; + + /* Slave interfaces enumerated before the master could not register themselves, as the master Link + * object did not exist yet at that time. Register them now, so that the slaves set correctly + * reflects the kernel state. */ + Link *link; + HASHMAP_FOREACH(link, m->links_by_index) + RET_GATHER(r, link_append_to_master(link)); + + return r; } static int manager_enumerate_qdisc(Manager *m) { diff --git a/test/test-network/conf/25-bond-fail-over-mac.netdev b/test/test-network/conf/25-bond-fail-over-mac.netdev new file mode 100644 index 0000000000..122c642117 --- /dev/null +++ b/test/test-network/conf/25-bond-fail-over-mac.netdev @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[NetDev] +Name=bond99 +Kind=bond + +[Bond] +Mode=active-backup +FailOverMACPolicy=active +MIIMonitorSec=1s diff --git a/test/test-network/conf/25-bond-fail-over-mac.network b/test/test-network/conf/25-bond-fail-over-mac.network new file mode 100644 index 0000000000..09da6a0c35 --- /dev/null +++ b/test/test-network/conf/25-bond-fail-over-mac.network @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=bond99 + +[Network] +IPv6AcceptRA=no +Address=192.168.123.45/24 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 7ca0567bab..b8c45017ef 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -6734,6 +6734,23 @@ class NetworkdBondTests(unittest.TestCase, Utilities): check_output('ip link set dummy98 master bond199') self.wait_online('dummy98:enslaved') + def test_bond_fail_over_mac_restart(self): + # The slave needs a lower ifindex than the bond, so that it is enumerated + # first and the race is deterministic. So create it before networkd starts. + check_output('ip link add dummy98 type dummy') + copy_network_unit( + '25-bond-fail-over-mac.netdev', + '25-bond-fail-over-mac.network', + '25-bond-slave.network', + ) + start_networkd() + self.wait_online('dummy98:enslaved', 'bond99:routable') + + self.check_link_attr('bond99', 'bonding', 'fail_over_mac', 'active 1') + + restart_networkd() + self.wait_online('dummy98:enslaved', 'bond99:routable') + def test_bond_active_slave(self): copy_network_unit( '23-active-slave.network',