Skip to content

network: support explicitly managed IPv4 link-local networks#40785

Open
aenertia wants to merge 1 commit intosystemd:mainfrom
aenertia:networkd-IPv4LL-fixes
Open

network: support explicitly managed IPv4 link-local networks#40785
aenertia wants to merge 1 commit intosystemd:mainfrom
aenertia:networkd-IPv4LL-fixes

Conversation

@aenertia
Copy link

Description

Fixes #40783

Background

Currently, systemd-networkd conflates the ZeroConf Auto-IP mechanism defined in RFC 3927 with the 169.254.0.0/16 prefix 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/16 block 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:

  1. Unblock DHCP Server (networkd-dhcp-server.c): Removes the in4_addr_is_link_local checks so the internal DHCPServer can bind to and emit IPv4LL ranges.

  2. Promote Client State (networkd-address.c): Conditionally evaluates the effective scope of IPv4LL addresses as RT_SCOPE_UNIVERSE only if their source is NETWORK_CONFIG_SOURCE_STATIC or NETWORK_CONFIG_SOURCE_DHCP4. This prevents the interface from getting permanently trapped in a degraded operational state.

  3. 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).

  4. Integration Tests (systemd-networkd-tests.py): Adds test_dhcp_server_ipv4ll_managed to provision a veth pair, assign a 169.254.10.x subnet via DHCPServer, and verify that the client achieves a routable state, installs the gateway, and successfully pings the server.

Copy link
Member

@yuwata yuwata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

effective_scope = RT_SCOPE_UNIVERSE;

ipv4_scope = MIN(ipv4_scope, effective_scope);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No no, please do not touch this like that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, why the degraded state is problematic? Why you want to make the interface labelled as routable??

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any further discussion on this ; without some way of setting managed intent as routable this is only a partial solution

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@yuwata yuwata added reviewed/needs-rework 🔨 PR has been reviewed and needs another round of reworks dhcp and removed please-review PR is ready for (re-)review by a maintainer labels Feb 22, 2026
@aenertia
Copy link
Author

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?

@yuwata
Copy link
Member

yuwata commented Feb 22, 2026

squashed/force commit please.

@aenertia aenertia force-pushed the networkd-IPv4LL-fixes branch from bfc3df8 to dd45f96 Compare February 22, 2026 22:26
@github-actions github-actions bot added please-review PR is ready for (re-)review by a maintainer and removed reviewed/needs-rework 🔨 PR has been reviewed and needs another round of reworks labels Feb 22, 2026
@aenertia aenertia force-pushed the networkd-IPv4LL-fixes branch from dd45f96 to 375836a Compare February 22, 2026 22:39
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
@aenertia aenertia force-pushed the networkd-IPv4LL-fixes branch from 375836a to 8e27956 Compare February 22, 2026 23:07
Copy link
Member

@yuwata yuwata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use 8ch indentation.



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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 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')

@yuwata yuwata added reviewed/needs-rework 🔨 PR has been reviewed and needs another round of reworks and removed please-review PR is ready for (re-)review by a maintainer labels Feb 24, 2026
@aenertia
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dhcp network reviewed/needs-rework 🔨 PR has been reviewed and needs another round of reworks tests

Development

Successfully merging this pull request may close these issues.

Bug Report: systemd-networkd DHCP Symmetrical Failure on IANA Link-Local Range (169.254.0.0/16)

2 participants