From c2691d8e7c82ec453d5f49ec300f4cf8464548a0 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 14 Apr 2025 22:37:23 +0200 Subject: [PATCH 1/2] networkd: make the ACD timeout configurable RFC 5227 specifies randomized intervals to avoid that a large number of hosts powered up at the same time send their message simultaneously. Performing the conflict detection takes a variable time between 4 and 7 seconds from the beginning to the first announcement, as shown by the following diagram where P indicates a probe and A an announcement: time(s) 0 1 2 3 4 5 6 7 8 9 +---+---+---+---+---+---+---+---+---+ SHORTEST P P P A A LONGEST P P P A A The host can't use the address until the first announcement is sent. 7 seconds is a very long time on modern computers especially considering the fact that the round-trip time on current LAN technologies is at most few milliseconds. Section 2.2 of the RFC addresses this matter and hints that a future standard will adjust those timeouts; however that standard doesn't exist yet. Make the timeout configurable via a new IPv4DuplicateAddressDetectionTimeoutSec= option. The intervals defined in the RFC are then scaled proportionally so that the duration of the conflict detection takes at most the given value. Interval happening after the first announcement are not scaled, as recommended by the RFC. --- man/systemd.network.xml | 11 +++++ src/libsystemd-network/sd-ipv4acd.c | 59 +++++++++++++++++++----- src/libsystemd-network/sd-ipv4ll.c | 6 +++ src/network/networkd-ipv4acd.c | 5 ++ src/network/networkd-ipv4ll.c | 5 ++ src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.h | 1 + src/systemd/sd-ipv4acd.h | 1 + src/systemd/sd-ipv4ll.h | 1 + 9 files changed, 78 insertions(+), 12 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index f6f91fb458..433ec4bb0d 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -991,6 +991,17 @@ DuplicateAddressDetection=none + + IPv4DuplicateAddressDetectionTimeoutSec= + + Configures the maximum timeout for IPv4 Duplicate Address Detection (RFC 5227). Must be a + value between 1 millisecond and 60 seconds. If set, Duplicate Address Detection takes a randomized + time between 57% (4/7) and 100% of the given value. If unset, defaults to 7 seconds. + + + + + IPv4ReversePathFilter= diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c index 423e043253..82d5fa7568 100644 --- a/src/libsystemd-network/sd-ipv4acd.c +++ b/src/libsystemd-network/sd-ipv4acd.c @@ -25,18 +25,26 @@ #include "string-util.h" #include "time-util.h" -/* Constants from the RFC */ -#define PROBE_WAIT_USEC (1U * USEC_PER_SEC) -#define PROBE_NUM 3U -#define PROBE_MIN_USEC (1U * USEC_PER_SEC) -#define PROBE_MAX_USEC (2U * USEC_PER_SEC) -#define ANNOUNCE_WAIT_USEC (2U * USEC_PER_SEC) -#define ANNOUNCE_NUM 2U +/* Intervals from the RFC in seconds, need to be multiplied by the time unit */ +#define PROBE_WAIT 1U +#define PROBE_MIN 1U +#define PROBE_MAX 2U +#define ANNOUNCE_WAIT 2U +#define TOTAL_TIME_UNITS 7U + +/* Intervals from the RFC not adjusted to the time unit */ #define ANNOUNCE_INTERVAL_USEC (2U * USEC_PER_SEC) -#define MAX_CONFLICTS 10U #define RATE_LIMIT_INTERVAL_USEC (60U * USEC_PER_SEC) #define DEFEND_INTERVAL_USEC (10U * USEC_PER_SEC) +/* Other constants from the RFC */ +#define PROBE_NUM 3U +#define ANNOUNCE_NUM 2U +#define MAX_CONFLICTS 10U + +/* Default timeout from the RFC */ +#define DEFAULT_ACD_TIMEOUT_USEC (7 * USEC_PER_SEC) + typedef enum IPv4ACDState { IPV4ACD_STATE_INIT, IPV4ACD_STATE_STARTED, @@ -60,6 +68,10 @@ struct sd_ipv4acd { unsigned n_iteration; unsigned n_conflict; + /* Indicates the duration of a "time unit", i.e. one second in the RFC but scaled to the + * chosen total duration. Represents 1/7 of the total conflict detection timeout. */ + usec_t time_unit_usec; + sd_event_source *receive_message_event_source; sd_event_source *timer_event_source; @@ -150,6 +162,7 @@ int sd_ipv4acd_new(sd_ipv4acd **ret) { *acd = (sd_ipv4acd) { .n_ref = 1, .state = IPV4ACD_STATE_INIT, + .time_unit_usec = DEFAULT_ACD_TIMEOUT_USEC / TOTAL_TIME_UNITS, .ifindex = -1, .fd = -EBADF, }; @@ -218,14 +231,20 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) case IPV4ACD_STATE_STARTED: acd->defend_window = 0; + log_ipv4acd(acd, + "Started on address " IPV4_ADDRESS_FMT_STR " with a max timeout of %s", + IPV4_ADDRESS_FMT_VAL(acd->address), + FORMAT_TIMESPAN(TOTAL_TIME_UNITS * acd->time_unit_usec, USEC_PER_MSEC)); + ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true); if (acd->n_conflict >= MAX_CONFLICTS) { log_ipv4acd(acd, "Max conflicts reached, delaying by %s", FORMAT_TIMESPAN(RATE_LIMIT_INTERVAL_USEC, 0)); - r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC); + r = ipv4acd_set_next_wakeup( + acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT * acd->time_unit_usec); } else - r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC); + r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT * acd->time_unit_usec); if (r < 0) goto fail; @@ -245,13 +264,16 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) if (acd->n_iteration < PROBE_NUM - 2) { ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false); - r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC)); + r = ipv4acd_set_next_wakeup( + acd, + PROBE_MIN * acd->time_unit_usec, + (PROBE_MAX - PROBE_MIN) * acd->time_unit_usec); if (r < 0) goto fail; } else { ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true); - r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0); + r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT * acd->time_unit_usec, 0); if (r < 0) goto fail; } @@ -442,6 +464,19 @@ int sd_ipv4acd_set_ifname(sd_ipv4acd *acd, const char *ifname) { return free_and_strdup(&acd->ifname, ifname); } +int sd_ipv4acd_set_timeout(sd_ipv4acd *acd, uint64_t usec) { + assert_return(acd, -EINVAL); + + if (usec == 0) + usec = DEFAULT_ACD_TIMEOUT_USEC; + + /* Clamp the total duration to a value between 1ms and 1 minute */ + acd->time_unit_usec = DIV_ROUND_UP( + CLAMP(usec, 1U * USEC_PER_MSEC, 1U * USEC_PER_MINUTE), TOTAL_TIME_UNITS); + + return 0; +} + int sd_ipv4acd_get_ifname(sd_ipv4acd *acd, const char **ret) { int r; diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 5bf98332a9..557c145509 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -153,6 +153,12 @@ int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { return 0; } +int sd_ipv4ll_set_timeout(sd_ipv4ll *ll, uint64_t usec) { + assert_return(ll, -EINVAL); + + return sd_ipv4acd_set_timeout(ll->acd, usec); +} + int sd_ipv4ll_detach_event(sd_ipv4ll *ll) { assert_return(ll, -EINVAL); diff --git a/src/network/networkd-ipv4acd.c b/src/network/networkd-ipv4acd.c index 0ecdee0893..3c938546bf 100644 --- a/src/network/networkd-ipv4acd.c +++ b/src/network/networkd-ipv4acd.c @@ -224,6 +224,7 @@ int ipv4acd_configure(Link *link, const Address *address) { assert(link); assert(link->manager); + assert(link->network); assert(address); if (address->family != AF_INET) @@ -268,6 +269,10 @@ int ipv4acd_configure(Link *link, const Address *address) { if (r < 0) return r; + r = sd_ipv4acd_set_timeout(acd, link->network->ipv4_dad_timeout_usec); + if (r < 0) + return r; + r = sd_ipv4acd_set_callback(acd, on_acd, link); if (r < 0) return r; diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c index 7522bca0aa..9dbaba0fa0 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -223,6 +223,7 @@ int ipv4ll_configure(Link *link) { int r; assert(link); + assert(link->network); if (!link_ipv4ll_enabled(link)) return 0; @@ -253,6 +254,10 @@ int ipv4ll_configure(Link *link) { if (r < 0) return r; + r = sd_ipv4ll_set_timeout(link->ipv4ll, link->network->ipv4_dad_timeout_usec); + if (r < 0) + return r; + r = sd_ipv4ll_set_ifindex(link->ipv4ll, link->ifindex); if (r < 0) return r; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 7dd793bf5d..2b61f3b744 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -159,6 +159,7 @@ Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extension Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ndisc) Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ndisc) Network.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits) +Network.IPv4DuplicateAddressDetectionTimeoutSec, config_parse_sec, 0, offsetof(Network, ipv4_dad_timeout_usec) Network.IPv6HopLimit, config_parse_uint8, 0, offsetof(Network, ipv6_hop_limit) Network.IPv6RetransmissionTimeSec, config_parse_sec, 0, offsetof(Network, ipv6_retransmission_time) Network.IPv6ProxyNDP, config_parse_tristate, 0, offsetof(Network, ipv6_proxy_ndp) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index c83281441e..c783da5f47 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -112,6 +112,7 @@ struct Network { char **bind_carrier; bool default_route_on_device; AddressFamily ip_masquerade; + usec_t ipv4_dad_timeout_usec; /* Protocol independent settings */ UseDomains use_domains; diff --git a/src/systemd/sd-ipv4acd.h b/src/systemd/sd-ipv4acd.h index 6be5770f13..65a53f9f2b 100644 --- a/src/systemd/sd-ipv4acd.h +++ b/src/systemd/sd-ipv4acd.h @@ -48,6 +48,7 @@ int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int interface_index); int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd); int sd_ipv4acd_set_ifname(sd_ipv4acd *acd, const char *interface_name); int sd_ipv4acd_get_ifname(sd_ipv4acd *acd, const char **ret); +int sd_ipv4acd_set_timeout(sd_ipv4acd *acd, uint64_t usec); int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address); int sd_ipv4acd_is_running(sd_ipv4acd *acd); int sd_ipv4acd_is_bound(sd_ipv4acd *acd); diff --git a/src/systemd/sd-ipv4ll.h b/src/systemd/sd-ipv4ll.h index 35e4679a0d..1e7922cf67 100644 --- a/src/systemd/sd-ipv4ll.h +++ b/src/systemd/sd-ipv4ll.h @@ -44,6 +44,7 @@ int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address); int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata); int sd_ipv4ll_set_check_mac_callback(sd_ipv4ll *ll, sd_ipv4ll_check_mac_callback_t cb, void *userdata); int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr); +int sd_ipv4ll_set_timeout(sd_ipv4ll *ll, uint64_t usec); int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int interface_index); int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll); int sd_ipv4ll_set_ifname(sd_ipv4ll *ll, const char *interface_name); From 2451cd2512fd3d267d4b906d71cd675d6e903a2e Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 14 Apr 2025 22:37:26 +0200 Subject: [PATCH 2/2] networkd: reduce the IPv4 DAD timeout to 200ms The original timeout of 7 seconds is very long for today's networks. Reduce it to 200ms. Note that this change also affects IPv4 link-local addressing. --- NEWS | 7 +++++++ man/systemd.network.xml | 2 +- src/libsystemd-network/sd-ipv4acd.c | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index af63649a16..147e5511a2 100644 --- a/NEWS +++ b/NEWS @@ -109,6 +109,13 @@ CHANGES WITH 258 in spe: * The meson option 'integration-tests' has been deprecated, and will be removed in a future release. + systemd-networkd and networkctl: + + * systemd-networkd now supports configuring the timeout for IPv4 + Duplicate Address Detection via a new setting + IPv4DuplicateAddressDetectionTimeoutSec=. The default timeout value + has been changed from 7 seconds to 200 milliseconds. + — , CHANGES WITH 257: diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 433ec4bb0d..98df365f1b 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -996,7 +996,7 @@ DuplicateAddressDetection=none Configures the maximum timeout for IPv4 Duplicate Address Detection (RFC 5227). Must be a value between 1 millisecond and 60 seconds. If set, Duplicate Address Detection takes a randomized - time between 57% (4/7) and 100% of the given value. If unset, defaults to 7 seconds. + time between 57% (4/7) and 100% of the given value. If unset, defaults to 200 milliseconds. diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c index 82d5fa7568..17db4a53f7 100644 --- a/src/libsystemd-network/sd-ipv4acd.c +++ b/src/libsystemd-network/sd-ipv4acd.c @@ -43,7 +43,7 @@ #define MAX_CONFLICTS 10U /* Default timeout from the RFC */ -#define DEFAULT_ACD_TIMEOUT_USEC (7 * USEC_PER_SEC) +#define DEFAULT_ACD_TIMEOUT_USEC (200 * USEC_PER_MSEC) typedef enum IPv4ACDState { IPV4ACD_STATE_INIT,