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);