Compare commits

...

3 Commits

Author SHA1 Message Date
Beniamino Galvani 57c9133b99 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.
2025-04-16 22:57:36 +02:00
Beniamino Galvani 6052d7584a 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 "IPv4DuplicateAddressDetectionTimeout"
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.
2025-04-16 22:57:36 +02:00
Beniamino Galvani 5c14eef88c network/ipv4acd: check for IFF_NOARP before starting ACD
The IFF_NOARP flag indicates that ARP is disabled in kernel. It is
automatically set for those device that can't do ARP (and therefore can't do
ACD).

Technically, it's possible that an interface is ARP-capable but has IFF_NOARP
manually set to avoid using the protocol. In that case, it makes sense to
disable ACD as well because ACD would send ARP packets.
2025-04-16 22:00:56 +02:00
10 changed files with 83 additions and 20 deletions

5
NEWS
View File

@ -489,6 +489,11 @@ CHANGES WITH 257:
existing interfaces, and invoke 'networkctl reload' or restart
systemd-networkd.
* The timeout for IPv4 Duplicate Address Detection can now be
configured via a new IPv4DuplicateAddressDetectionTimeout=
setting. The default timeout value has been changed from 7 seconds to
200 milliseconds.
systemd-boot, systemd-stub, and related tools:
* The EFI stub now supports loading of .ucode sections with microcode

View File

@ -987,6 +987,17 @@ DuplicateAddressDetection=none</programlisting></para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>IPv4DuplicateAddressDetectionTimeout=</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 200 milliseconds.</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_TIMEOUT 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 TIMEOUT_DEFAULT_USEC (200 * USEC_PER_MSEC)
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. */
uint64_t time_unit;
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 = TIMEOUT_DEFAULT_USEC / TOTAL_TIMEOUT,
.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 %" PRIu64 "msec",
IPV4_ADDRESS_FMT_VAL(acd->address),
(acd->time_unit * TOTAL_TIMEOUT + (USEC_PER_MSEC - 1)) / 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);
} else
r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC);
r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT * acd->time_unit);
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,
(PROBE_MAX - PROBE_MIN) * acd->time_unit);
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, 0);
if (r < 0)
goto fail;
}
@ -442,6 +464,18 @@ 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 timeout_usec) {
assert_return(acd, -EINVAL);
if (timeout_usec == 0)
timeout_usec = TIMEOUT_DEFAULT_USEC;
/* Clamp the total duration to a value between 1ms and 1 minute */
acd->time_unit = CLAMP(timeout_usec, 1U * USEC_PER_MSEC, 60U * USEC_PER_SEC) / TOTAL_TIMEOUT;
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 timeout_usec) {
assert_return(ll, -EINVAL);
return sd_ipv4acd_set_timeout(ll->acd, timeout_usec);
}
int sd_ipv4ll_detach_event(sd_ipv4ll *ll) {
assert_return(ll, -EINVAL);

View File

@ -25,7 +25,7 @@ DEFINE_PRIVATE_HASH_OPS_FULL(
bool link_ipv4acd_supported(Link *link) {
assert(link);
if (link->flags & IFF_LOOPBACK)
if (link->flags & (IFF_LOOPBACK | IFF_NOARP))
return false;
/* ARPHRD_INFINIBAND seems to potentially support IPv4ACD.
@ -39,13 +39,6 @@ bool link_ipv4acd_supported(Link *link) {
if (ether_addr_is_null(&link->hw_addr.ether))
return false;
if (streq_ptr(link->kind, "vrf"))
return false;
/* L3 or L3S mode do not support ARP. */
if (IN_SET(link_get_ipvlan_mode(link), NETDEV_IPVLAN_MODE_L3, NETDEV_IPVLAN_MODE_L3S))
return false;
return true;
}
@ -212,6 +205,7 @@ int ipv4acd_configure(Link *link, const Address *address) {
assert(link);
assert(link->manager);
assert(link->network);
assert(address);
if (address->family != AF_INET)
@ -256,6 +250,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);
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);
if (r < 0)
return r;
r = sd_ipv4ll_set_ifindex(link->ipv4ll, link->ifindex);
if (r < 0)
return r;

View File

@ -139,6 +139,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.IPv4DuplicateAddressDetectionTimeout, config_parse_sec, 0, offsetof(Network, ipv4_dad_timeout)
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;
/* 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 timeout_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 timeout_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);