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 <watanabe.yu+github@gmail.com>
This commit is contained in:
Ramses
2026-09-01 18:28:43 +09:00
committed by GitHub
co-authored by Yu Watanabe
parent f396adf4e9
commit 3da5c65d87
6 changed files with 47 additions and 2 deletions
+1 -1
View File
@@ -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;
+1
View File
@@ -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);
+12 -1
View File
@@ -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) {
@@ -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
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=bond99
[Network]
IPv6AcceptRA=no
Address=192.168.123.45/24
@@ -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',