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.
This commit is contained in:
Beniamino Galvani
2025-04-14 22:37:23 +02:00
committed by Yu Watanabe
parent 52990feb2b
commit c2691d8e7c
9 changed files with 78 additions and 12 deletions

View 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>

View File

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

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

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

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

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

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

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

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