From: Beniamino Galvani Date: Mon, 14 Apr 2025 20:37:23 +0000 (+0200) Subject: networkd: make the ACD timeout configurable X-Git-Tag: v258-rc1~735^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c2691d8e7c82ec453d5f49ec300f4cf8464548a0;p=thirdparty%2Fsystemd.git 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. --- diff --git a/man/systemd.network.xml b/man/systemd.network.xml index f6f91fb4588..433ec4bb0da 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 423e0432531..82d5fa75689 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 5bf98332a9f..557c145509c 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 0ecdee0893a..3c938546bf8 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 7522bca0aa8..9dbaba0fa0e 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 7dd793bf5d4..2b61f3b7440 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 c83281441e8..c783da5f471 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 6be5770f134..65a53f9f2b1 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 35e4679a0db..1e7922cf671 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);