From 4be31a9c35015f1564f10821708ca1973c9f8ac3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 12 Sep 2026 01:14:08 +0900 Subject: [PATCH 1/4] network: add a configuration-source filter to link_drop_requests() Preparation for later changes. --- src/network/networkd-link.c | 85 +++++++++++++++++++++++-------------- src/network/networkd-link.h | 1 + 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 50a499723d..fc61c3818e 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1068,13 +1068,17 @@ static void link_drop_from_master(Link *link) { link_unref(set_remove(master->slaves, link)); } -static int link_drop_requests(Link *link) { +int link_drop_requests(Link *link, NetworkConfigSource source) { Request *req; int ret = 0; assert(link); assert(link->manager); + /* If the interface is already removed, then per-source request removal is not necessary. */ + if (source >= 0 && link->state == LINK_STATE_LINGER) + return 0; + ORDERED_SET_FOREACH(req, link->manager->request_queue) { if (req->link != link) continue; @@ -1082,39 +1086,54 @@ static int link_drop_requests(Link *link) { /* If the request is already called, but its reply is not received, then we need to * drop the configuration (e.g. address) here. Note, if the configuration is known, * it will be handled later by link_drop_unmanaged_addresses() or so. */ - if (req->waiting_reply && link->state != LINK_STATE_LINGER) - switch (req->type) { - case REQUEST_TYPE_ADDRESS: { - Address *address = ASSERT_PTR(req->userdata); + bool remove = req->waiting_reply && link->state != LINK_STATE_LINGER; - if (address_get(link, address, NULL) < 0) - RET_GATHER(ret, address_remove(address, link)); - break; - } - case REQUEST_TYPE_NEIGHBOR: { - Neighbor *neighbor = ASSERT_PTR(req->userdata); + switch (req->type) { + case REQUEST_TYPE_ADDRESS: { + Address *address = ASSERT_PTR(req->userdata); - if (neighbor_get(link, neighbor, NULL) < 0) - RET_GATHER(ret, neighbor_remove(neighbor, link)); - break; - } - case REQUEST_TYPE_NEXTHOP: { - NextHop *nexthop = ASSERT_PTR(req->userdata); + if (source >= 0 && address->source != source) + continue; - if (nexthop_get_by_id(link->manager, nexthop->id, NULL) < 0) - RET_GATHER(ret, nexthop_remove(nexthop, link->manager)); - break; - } - case REQUEST_TYPE_ROUTE: { - Route *route = ASSERT_PTR(req->userdata); + if (remove && address_get(link, address, /* ret= */ NULL) < 0) + RET_GATHER(ret, address_remove(address, link)); + break; + } + case REQUEST_TYPE_NEIGHBOR: { + Neighbor *neighbor = ASSERT_PTR(req->userdata); - if (route_get(link->manager, route, NULL) < 0) - RET_GATHER(ret, route_remove(route, link->manager)); - break; - } - default: - ; - } + if (source >= 0 && neighbor->source != source) + continue; + + if (remove && neighbor_get(link, neighbor, /* ret= */ NULL) < 0) + RET_GATHER(ret, neighbor_remove(neighbor, link)); + break; + } + case REQUEST_TYPE_NEXTHOP: { + NextHop *nexthop = ASSERT_PTR(req->userdata); + + if (source >= 0 && nexthop->source != source) + continue; + + if (remove && nexthop_get_by_id(link->manager, nexthop->id, /* ret= */ NULL) < 0) + RET_GATHER(ret, nexthop_remove(nexthop, link->manager)); + break; + } + case REQUEST_TYPE_ROUTE: { + Route *route = ASSERT_PTR(req->userdata); + + if (source >= 0 && route->source != source) + continue; + + if (remove && route_get(link->manager, route, /* ret= */ NULL) < 0) + RET_GATHER(ret, route_remove(route, link->manager)); + break; + } + default: + /* If a source is specified, then ignore all other kinds of requests. */ + if (source >= 0) + continue; + } request_detach(req); } @@ -1135,7 +1154,7 @@ static Link *link_drop(Link *link) { /* Drop all references from other links and manager. Note that async netlink calls may have * references to the link, and they will be dropped when we receive replies. */ - (void) link_drop_requests(link); + (void) link_drop_requests(link, _NETWORK_CONFIG_SOURCE_INVALID); link_free_bound_to_list(link); link_free_bound_by_list(link); @@ -1427,7 +1446,7 @@ static void link_enter_unmanaged(Link *link) { "Unmanaging interface."); (void) link_stop_engines(link, /* may_keep_dynamic= */ false); - (void) link_drop_requests(link); + (void) link_drop_requests(link, _NETWORK_CONFIG_SOURCE_INVALID); (void) link_drop_static_config(link); /* The bound_to map depends on .network file, hence it needs to be freed. But, do not free the @@ -1515,7 +1534,7 @@ int link_reconfigure_impl(Link *link, LinkReconfigurationFlag flags) { isempty(joined) ? "" : ")"); /* Dropping configurations based on the old .network file. */ - r = link_drop_requests(link); + r = link_drop_requests(link, _NETWORK_CONFIG_SOURCE_INVALID); if (r < 0) return r; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 99cdbbeef8..5ee95f6a6c 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -239,6 +239,7 @@ int link_ipv6ll_gained(Link *link); int link_ipv6ll_lost(Link *link, const struct in6_addr *dropped_ipv6ll, bool has_replacement); bool link_has_ipv6_connectivity(Link *link); +int link_drop_requests(Link *link, NetworkConfigSource source); int link_stop_engines(Link *link, bool may_keep_dynamic); DECLARE_STRING_TABLE_LOOKUP(link_state, LinkState); From c5bf6bac23046c061c07164fe1bd016068728129 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 12 Sep 2026 08:53:46 +0900 Subject: [PATCH 2/4] network: disable IPv4ACD when the address request is cancelled If an address with IPv4ACD is requested, then sd_ipv4acd instance for the address is configured. If the request is cancelled, we should also disable the IPv4ACD instance. Note, if the address is already configured, the IPv4ACD instance for the address will be removed when the address is removed. See address_drop(). --- src/network/networkd-address.c | 3 +++ src/network/networkd-link.c | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 582069cf39..888ecdf260 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -1335,6 +1335,9 @@ int address_remove_and_cancel(Address *address, Link *link) { if (waiting || (address->link && address_exists(address))) return address_remove(address, link); + /* Otherwise, the address should be already gone or not configured yet. */ + ipv4acd_detach(link, address); + return 0; } diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index fc61c3818e..64d1f1f6fe 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1095,8 +1095,12 @@ int link_drop_requests(Link *link, NetworkConfigSource source) { if (source >= 0 && address->source != source) continue; - if (remove && address_get(link, address, /* ret= */ NULL) < 0) - RET_GATHER(ret, address_remove(address, link)); + if (address_get(link, address, /* ret= */ NULL) < 0) { + if (remove) + RET_GATHER(ret, address_remove(address, link)); + else + ipv4acd_detach(link, address); + } break; } case REQUEST_TYPE_NEIGHBOR: { From 830b60ae1fcd435b162b1fa7552609dd425fb6b7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 12 Sep 2026 01:34:47 +0900 Subject: [PATCH 3/4] network: drop pending requests when a DHCP lease is lost or a client is stopped When a DHCP lease is lost or a client is stopped, there may exist requests not processed yet. We need to cancel them, otherwise an address or route requested by the lost lease may be configured later. --- src/network/networkd-dhcp-prefix-delegation.c | 4 ++++ src/network/networkd-dhcp4.c | 4 ++++ src/network/networkd-dhcp6.c | 4 ++++ src/network/networkd-ndisc.c | 3 +++ 4 files changed, 15 insertions(+) diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index 85c5233472..c5757ac8f2 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -271,6 +271,10 @@ int dhcp_pd_remove(Link *link, bool only_marked) { } } + if (!only_marked) + /* Also drop pending requests when the lease is lost or the client is stopped. */ + RET_GATHER(ret, link_drop_requests(link, NETWORK_CONFIG_SOURCE_DHCP_PD)); + return ret; } diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 468c3eb920..bd5cf845a6 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -272,6 +272,10 @@ static int dhcp4_remove_address_and_routes(Link *link, bool only_marked) { RET_GATHER(ret, address_remove_and_cancel(address, link)); } + if (!only_marked) + /* Also drop pending requests when the lease is lost or the client is stopped. */ + RET_GATHER(ret, link_drop_requests(link, NETWORK_CONFIG_SOURCE_DHCP4)); + return ret; } diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 799caeb15b..db3d9297c2 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -83,6 +83,10 @@ static int dhcp6_remove(Link *link, bool only_marked) { RET_GATHER(ret, address_remove_and_cancel(address, link)); } + if (!only_marked) + /* Also drop pending requests when the lease is lost or the client is stopped. */ + RET_GATHER(ret, link_drop_requests(link, NETWORK_CONFIG_SOURCE_DHCP6)); + return ret; } diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 4198a6d017..3476effb2f 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -3194,6 +3194,9 @@ int ndisc_stop(Link *link) { void ndisc_flush(Link *link) { assert(link); + /* First, drop pending requests. */ + (void) link_drop_requests(link, NETWORK_CONFIG_SOURCE_NDISC); + /* Remove all addresses, routes, RDNSS, DNSSL, DNR, and Captive Portal entries, without exception. */ (void) ndisc_drop_outdated(link, /* router= */ NULL, /* timestamp_usec= */ USEC_INFINITY); (void) ndisc_drop_redirect(link, /* router= */ NULL); From 2aed6d9ad7226dad88a2386533ba63f6fc408c7f Mon Sep 17 00:00:00 2001 From: Heran Yang Date: Thu, 10 Sep 2026 16:14:09 +0800 Subject: [PATCH 4/4] network: find pending address in IPv4 ACD callback When IPv4 ACD is enabled for a DHCPv4 address, the address remains in the request queue until ACD succeeds. Hence, when ACD detects a conflict, the address may not be present in link->addresses yet. on_acd() currently looks up the address with address_get(), which only searches link->addresses. This makes the callback return early and prevents dhcp4_address_on_conflict() from sending DHCPDECLINE. Use address_get_harder(), which also looks up pending address requests. Fixes #39460 --- src/network/networkd-ipv4acd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/networkd-ipv4acd.c b/src/network/networkd-ipv4acd.c index 5e6090231a..86f2745cd1 100644 --- a/src/network/networkd-ipv4acd.c +++ b/src/network/networkd-ipv4acd.c @@ -152,7 +152,7 @@ static void on_acd(sd_ipv4acd *acd, int event, void *userdata) { void *val, *key; HASHMAP_FOREACH_KEY(val, key, link->ipv4acd_by_address) if (val == acd) { - (void) address_get(link, key, &address); + (void) address_get_harder(link, key, &address); break; }