]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
networkd: make the ACD timeout configurable
authorBeniamino Galvani <b.galvani@gmail.com>
Mon, 14 Apr 2025 20:37:23 +0000 (22:37 +0200)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Sat, 26 Apr 2025 05:44:31 +0000 (14:44 +0900)
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
src/libsystemd-network/sd-ipv4acd.c
src/libsystemd-network/sd-ipv4ll.c
src/network/networkd-ipv4acd.c
src/network/networkd-ipv4ll.c
src/network/networkd-network-gperf.gperf
src/network/networkd-network.h
src/systemd/sd-ipv4acd.h
src/systemd/sd-ipv4ll.h

index f6f91fb45881dc2926e940a8fe081e2770812912..433ec4bb0dafb0d18aca86004bbe4a63a7669e69 100644 (file)
@@ -991,6 +991,17 @@ DuplicateAddressDetection=none</programlisting></para>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>IPv4DuplicateAddressDetectionTimeoutSec=</varname></term>
+        <listitem>
+          <para>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.</para>
+
+          <xi:include href="version-info.xml" xpointer="v258"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>IPv4ReversePathFilter=</varname></term>
         <listitem>
index 423e0432531845068de8e72acd688208a5d03c1f..82d5fa75689b0365054c54e94c1de209b28064e6 100644 (file)
 #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;
 
index 5bf98332a9f184e6968ace16bd96b5c08a7a5fdf..557c145509c39b019afbc44037500d02786cd11f 100644 (file)
@@ -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);
 
index 0ecdee0893ae20aa5c14ae2e039469df4bcfd6a7..3c938546bf84e284688ee41d96983825705ba06f 100644 (file)
@@ -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;
index 7522bca0aa87487205e20c13f037bcd695b3b5c7..9dbaba0fa0ed9b5ce1d223127368a5b3a3ada6f3 100644 (file)
@@ -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;
index 7dd793bf5d46699fe9bad58644fc47d4762d3c98..2b61f3b744097dd99e741562314019c4cba73da8 100644 (file)
@@ -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)
index c83281441e8b3d65c8aa8022e9448bb685f666c6..c783da5f47135a662b7112b16c4b5108779f4179 100644 (file)
@@ -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;
index 6be5770f134a867abcd93d0633a0c0404d2dfc09..65a53f9f2b1fb43aa58621916506e21e1fe69861 100644 (file)
@@ -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);
index 35e4679a0db7ea17993889035f805d715ffc0a6e..1e7922cf671ae2b521f1c103b585668e9088d6dc 100644 (file)
@@ -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);