network: support explicitly managed IPv4 link-local networks#40785
network: support explicitly managed IPv4 link-local networks#40785aenertia wants to merge 1 commit intosystemd:mainfrom
Conversation
There was a problem hiding this comment.
Thank you for working on that. I've not followed your report in detail yet. Only superficial requests.
Please enable IPv4ACD when the acquired address is link-local:
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index f274a0c4d9..2ede7f557a 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -994,7 +994,8 @@ static int dhcp4_request_address(Link *link, bool announce) {
return log_link_warning_errno(link, r, "DHCP: failed to get broadcast address: %m");
SET_FLAG(addr->flags, IFA_F_NOPREFIXROUTE, !prefixroute_by_kernel(link));
addr->route_metric = link->network->dhcp_route_metric;
- addr->duplicate_address_detection = link->network->dhcp_send_decline ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_NO;
+ addr->duplicate_address_detection =
+ link->network->dhcp_send_decline || in4_addr_is_link_local(&address) ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_NO;
r = free_and_strdup_warn(&addr->label, link->network->dhcp_label);
if (r < 0)
src/network/networkd-address.c
Outdated
| effective_scope = RT_SCOPE_UNIVERSE; | ||
|
|
||
| ipv4_scope = MIN(ipv4_scope, effective_scope); | ||
| } |
There was a problem hiding this comment.
No no, please do not touch this like that.
There was a problem hiding this comment.
I'll revert this ; what is the preferred approach to scope override ? Do I need to fall back to exploring proper state synchronization for the degraded trap via alternative methods?
There was a problem hiding this comment.
First of all, why the degraded state is problematic? Why you want to make the interface labelled as routable??
There was a problem hiding this comment.
We want the interface labeled as routable when a 169.254.x.x address is provided via Static or DHCP sources because it signals to the rest of the system (via networkctl and wait-online) that the administrative intent has been satisfied and the link is "ready for use."
If the current implementation of overriding the scope to universe is too invasive, I would welcome suggestions on how to allow managed IPv4LL addresses to satisfy the routable operational state logic without breaking the RFC 3927 "fallback" use case.
Examples of breakage include: systemd-networkd-wait-online.service timeouts on headless gadgets, inability to distinguish explicit admin intent from DHCP failure in monitoring logs, and logical inconsistency with standard IPv6LL routing paradigms.
There was a problem hiding this comment.
The 169.254.0.0/16 block has long been pigeonholed as a mere fallback mechanism, which often creates unnecessary hurdles for modern networking patterns. When I was designing BGP-routed Data Center fabrics, for instance, I often wanted to use this range for the thousands of simple point-to-point links to keep the internal 10.0.0.0/8 space clean. However, because Link-Local is often tied to a "degraded" status, it usually meant choosing between standards-compliant addressing or having a clean operational dashboard without constant "non-ready" alerts. We see similar patterns in cloud environments like OpenStack, where this range is foundational for internal plumbing and metadata services; having these links report as "ready" ensures that bootstrapping tools like Cloud-init can proceed without hitting unnecessary timeouts. Even in the consumer space, many mobile stacks resort to RFC 1918 subnets for USB tethering as a workaround to ensure the connection is seen as active, which unfortunately risks routing conflicts with a user's home network. By enabling managed administrative intent (Static/DHCP) through unified gateway checks and Address Conflict Detection (ACD), we can finally unlock those 32,000+ unique /31 links for high-density underlays and gadgets, allowing for much simpler architectures that reflect the link's true health.
There was a problem hiding this comment.
Do we have any further discussion on this ; without some way of setting managed intent as routable this is only a partial solution
There was a problem hiding this comment.
I think, it is fine to change link-local address handling. But, let's do that in another PR.
Also, I think, it is better to make the check like the following:
- I'd like to use some consistent rule for both IPv4 and IPv6.
- I think it is better to check the corresponding default gateway is installed.
|
Thanks for the review - yes some of these are indeed in need of changes. What is the preferred practice here for commits on the tree. Do you prefer a squashed/force commit or are added additional commits acceptable? |
|
squashed/force commit please. |
bfc3df8 to
dd45f96
Compare
dd45f96 to
375836a
Compare
Previously, systemd-networkd conflated the ZeroConf Auto-IP mechanism with the 169.254.0.0/16 address space itself. This resulted in aggressive hard-coded restrictions that broke standard managed link-net paradigms such as USB NCM/RNDIS Gadgets and BGP-routed switch-to-switch underlays. This commit allows the 169.254.0.0/16 block to be used as a standard managed network when explicitly requested via Static configuration or DHCPv4 assignment. Key changes: - DHCP Server: Unblocks the in4_addr_is_link_local guards to allow the internal DHCPServer to bind to and emit managed IPv4LL ranges. - DHCP Client: Ensures RFC 3927 compliance by forcing IPv4ACD (Address Conflict Detection) when an IPv4LL address is acquired via DHCP. - Routing: Updates gateway_is_ready() to permit IPv4 Link-Local addresses to serve as valid routing gateways, mirroring existing IPv6LL behavior. - Logging: Adds diagnostic logging to sd_dhcp_server_configure_pool() to identify when a link-local pool is initialized, following the 'silent on success' paradigm for config parsers. - Test: Adds a new integration test 'test_dhcp_server_ipv4ll_managed' to systemd-networkd-tests.py to verify server binding, client lease acquisition, and end-to-end ping reachability over IPv4LL. Resolves: systemd#40783
375836a to
8e27956
Compare
yuwata
left a comment
There was a problem hiding this comment.
Looks mostly good. Superficial requests only.
| if (dhcp_request_contains(req, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED) && | ||
| server->ipv6_only_preferred_usec > 0) { | ||
| be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec); | ||
| be32_t hex = usec_to_be32_sec(server->ipv6_only_preferred_usec); |
There was a problem hiding this comment.
Please do not touch unrelated code.
| &packet->dhcp, req->max_optlen, &offset, 0, | ||
| SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, | ||
| sizeof(sec), &sec); | ||
| sizeof(hex), &hex); |
There was a problem hiding this comment.
Also here. Please drop the change.
| "ignoring assignment: %s", rvalue); | ||
| return 0; | ||
| if (in4_addr_is_localhost(&a.in)) { | ||
| log_syntax(unit, LOG_WARNING, filename, line, 0, |
|
|
||
|
|
||
| def test_dhcp_server_ipv4ll_managed(self): | ||
| copy_network_unit('25-veth.netdev', '25-dhcp-server-ipv4ll.network', '25-dhcp-client-ipv4ll-managed.network', copy_dropins=False) |
There was a problem hiding this comment.
Why copy_dropins= is disabled? I do not think it is necessary. Please drop the last kwarg.
| print(output) | ||
| self.assertRegex(output, r'default via 169\.254\.10\.1 proto dhcp src 169\.254\.10\.[0-9]+ metric 1024') | ||
|
|
||
| call_check('ping', '-c', '1', '169.254.10.1') |
There was a problem hiding this comment.
- Maybe better to specify the interface name?
- Please use
check_output()even the output is not examined. - Note, it is not necessary to split arguments. You can specify the whole command line as a single string.
So, please do like the following:
check_output('ping -I veth99 -c 1 169.254.10.1')|
Thanks for the review and feedback. I will have a look through and ponder over best way to implement requested changes over the next couple of days. But from what I can tell it looks like we will need to split this up a little to get a full solution. |
Description
Fixes #40783
Background
Currently,
systemd-networkdconflates the ZeroConf Auto-IP mechanism defined in RFC 3927 with the169.254.0.0/16prefix itself. This results in aggressive, hard-coded restrictions that break standard "Managed Link-Net" paradigms, such as USB NCM/RNDIS Gadgets, isolated virtual APs, and BGP-routed switch-to-switch underlays.Administrators are currently forced to use RFC 1918 space for these localized Point-to-Point links, which introduces severe subnet collision and routing blackhole hazards.
Changes
This PR removes the artificial restrictions on the
169.254.0.0/16block when it is explicitly requested by an administrator (via Static Configuration or DHCPv4 assignment). Explicit administrative intent now safely overrides fallback/ZeroConf assumptions.Specifically, this implements the following:
Unblock DHCP Server (
networkd-dhcp-server.c): Removes thein4_addr_is_link_localchecks so the internalDHCPServercan bind to and emit IPv4LL ranges.Promote Client State (
networkd-address.c): Conditionally evaluates the effective scope of IPv4LL addresses asRT_SCOPE_UNIVERSEonly if their source isNETWORK_CONFIG_SOURCE_STATICorNETWORK_CONFIG_SOURCE_DHCP4. This prevents the interface from getting permanently trapped in adegradedoperational state.Whitelist Gateways (
networkd-route-util.c): Adds an exception to the route readiness checks (gateway_is_ready) to allow IPv4LL addresses to serve as valid routing gateways (mirroring existing IPv6 link-local behavior).Integration Tests (
systemd-networkd-tests.py): Addstest_dhcp_server_ipv4ll_managedto provision avethpair, assign a169.254.10.xsubnet viaDHCPServer, and verify that the client achieves aroutablestate, installs the gateway, and successfully pings the server.