Skip to content

Commit b09f351

Browse files
authored
networkd: reduce the default IPv4 DAD (ACD) timeout and make it configurable (systemd#37138)
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. Also reduce the default value from 7s to 200ms, which is a more suitable value for today's technology.
2 parents 52990fe + 2451cd2 commit b09f351

File tree

10 files changed

+85
-12
lines changed

10 files changed

+85
-12
lines changed

NEWS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ CHANGES WITH 258 in spe:
109109
* The meson option 'integration-tests' has been deprecated, and will be
110110
removed in a future release.
111111

112+
systemd-networkd and networkctl:
113+
114+
* systemd-networkd now supports configuring the timeout for IPv4
115+
Duplicate Address Detection via a new setting
116+
IPv4DuplicateAddressDetectionTimeoutSec=. The default timeout value
117+
has been changed from 7 seconds to 200 milliseconds.
118+
112119
— <place>, <date>
113120

114121
CHANGES WITH 257:

man/systemd.network.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,17 @@ DuplicateAddressDetection=none</programlisting></para>
991991
</listitem>
992992
</varlistentry>
993993

994+
<varlistentry>
995+
<term><varname>IPv4DuplicateAddressDetectionTimeoutSec=</varname></term>
996+
<listitem>
997+
<para>Configures the maximum timeout for IPv4 Duplicate Address Detection (RFC 5227). Must be a
998+
value between 1 millisecond and 60 seconds. If set, Duplicate Address Detection takes a randomized
999+
time between 57% (4/7) and 100% of the given value. If unset, defaults to 200 milliseconds.</para>
1000+
1001+
<xi:include href="version-info.xml" xpointer="v258"/>
1002+
</listitem>
1003+
</varlistentry>
1004+
9941005
<varlistentry>
9951006
<term><varname>IPv4ReversePathFilter=</varname></term>
9961007
<listitem>

src/libsystemd-network/sd-ipv4acd.c

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,26 @@
2525
#include "string-util.h"
2626
#include "time-util.h"
2727

28-
/* Constants from the RFC */
29-
#define PROBE_WAIT_USEC (1U * USEC_PER_SEC)
30-
#define PROBE_NUM 3U
31-
#define PROBE_MIN_USEC (1U * USEC_PER_SEC)
32-
#define PROBE_MAX_USEC (2U * USEC_PER_SEC)
33-
#define ANNOUNCE_WAIT_USEC (2U * USEC_PER_SEC)
34-
#define ANNOUNCE_NUM 2U
28+
/* Intervals from the RFC in seconds, need to be multiplied by the time unit */
29+
#define PROBE_WAIT 1U
30+
#define PROBE_MIN 1U
31+
#define PROBE_MAX 2U
32+
#define ANNOUNCE_WAIT 2U
33+
#define TOTAL_TIME_UNITS 7U
34+
35+
/* Intervals from the RFC not adjusted to the time unit */
3536
#define ANNOUNCE_INTERVAL_USEC (2U * USEC_PER_SEC)
36-
#define MAX_CONFLICTS 10U
3737
#define RATE_LIMIT_INTERVAL_USEC (60U * USEC_PER_SEC)
3838
#define DEFEND_INTERVAL_USEC (10U * USEC_PER_SEC)
3939

40+
/* Other constants from the RFC */
41+
#define PROBE_NUM 3U
42+
#define ANNOUNCE_NUM 2U
43+
#define MAX_CONFLICTS 10U
44+
45+
/* Default timeout from the RFC */
46+
#define DEFAULT_ACD_TIMEOUT_USEC (200 * USEC_PER_MSEC)
47+
4048
typedef enum IPv4ACDState {
4149
IPV4ACD_STATE_INIT,
4250
IPV4ACD_STATE_STARTED,
@@ -60,6 +68,10 @@ struct sd_ipv4acd {
6068
unsigned n_iteration;
6169
unsigned n_conflict;
6270

71+
/* Indicates the duration of a "time unit", i.e. one second in the RFC but scaled to the
72+
* chosen total duration. Represents 1/7 of the total conflict detection timeout. */
73+
usec_t time_unit_usec;
74+
6375
sd_event_source *receive_message_event_source;
6476
sd_event_source *timer_event_source;
6577

@@ -150,6 +162,7 @@ int sd_ipv4acd_new(sd_ipv4acd **ret) {
150162
*acd = (sd_ipv4acd) {
151163
.n_ref = 1,
152164
.state = IPV4ACD_STATE_INIT,
165+
.time_unit_usec = DEFAULT_ACD_TIMEOUT_USEC / TOTAL_TIME_UNITS,
153166
.ifindex = -1,
154167
.fd = -EBADF,
155168
};
@@ -218,14 +231,20 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata)
218231
case IPV4ACD_STATE_STARTED:
219232
acd->defend_window = 0;
220233

234+
log_ipv4acd(acd,
235+
"Started on address " IPV4_ADDRESS_FMT_STR " with a max timeout of %s",
236+
IPV4_ADDRESS_FMT_VAL(acd->address),
237+
FORMAT_TIMESPAN(TOTAL_TIME_UNITS * acd->time_unit_usec, USEC_PER_MSEC));
238+
221239
ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true);
222240

223241
if (acd->n_conflict >= MAX_CONFLICTS) {
224242
log_ipv4acd(acd, "Max conflicts reached, delaying by %s",
225243
FORMAT_TIMESPAN(RATE_LIMIT_INTERVAL_USEC, 0));
226-
r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC);
244+
r = ipv4acd_set_next_wakeup(
245+
acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT * acd->time_unit_usec);
227246
} else
228-
r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC);
247+
r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT * acd->time_unit_usec);
229248
if (r < 0)
230249
goto fail;
231250

@@ -245,13 +264,16 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata)
245264
if (acd->n_iteration < PROBE_NUM - 2) {
246265
ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false);
247266

248-
r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC));
267+
r = ipv4acd_set_next_wakeup(
268+
acd,
269+
PROBE_MIN * acd->time_unit_usec,
270+
(PROBE_MAX - PROBE_MIN) * acd->time_unit_usec);
249271
if (r < 0)
250272
goto fail;
251273
} else {
252274
ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true);
253275

254-
r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0);
276+
r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT * acd->time_unit_usec, 0);
255277
if (r < 0)
256278
goto fail;
257279
}
@@ -442,6 +464,19 @@ int sd_ipv4acd_set_ifname(sd_ipv4acd *acd, const char *ifname) {
442464
return free_and_strdup(&acd->ifname, ifname);
443465
}
444466

467+
int sd_ipv4acd_set_timeout(sd_ipv4acd *acd, uint64_t usec) {
468+
assert_return(acd, -EINVAL);
469+
470+
if (usec == 0)
471+
usec = DEFAULT_ACD_TIMEOUT_USEC;
472+
473+
/* Clamp the total duration to a value between 1ms and 1 minute */
474+
acd->time_unit_usec = DIV_ROUND_UP(
475+
CLAMP(usec, 1U * USEC_PER_MSEC, 1U * USEC_PER_MINUTE), TOTAL_TIME_UNITS);
476+
477+
return 0;
478+
}
479+
445480
int sd_ipv4acd_get_ifname(sd_ipv4acd *acd, const char **ret) {
446481
int r;
447482

src/libsystemd-network/sd-ipv4ll.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
153153
return 0;
154154
}
155155

156+
int sd_ipv4ll_set_timeout(sd_ipv4ll *ll, uint64_t usec) {
157+
assert_return(ll, -EINVAL);
158+
159+
return sd_ipv4acd_set_timeout(ll->acd, usec);
160+
}
161+
156162
int sd_ipv4ll_detach_event(sd_ipv4ll *ll) {
157163
assert_return(ll, -EINVAL);
158164

src/network/networkd-ipv4acd.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ int ipv4acd_configure(Link *link, const Address *address) {
224224

225225
assert(link);
226226
assert(link->manager);
227+
assert(link->network);
227228
assert(address);
228229

229230
if (address->family != AF_INET)
@@ -268,6 +269,10 @@ int ipv4acd_configure(Link *link, const Address *address) {
268269
if (r < 0)
269270
return r;
270271

272+
r = sd_ipv4acd_set_timeout(acd, link->network->ipv4_dad_timeout_usec);
273+
if (r < 0)
274+
return r;
275+
271276
r = sd_ipv4acd_set_callback(acd, on_acd, link);
272277
if (r < 0)
273278
return r;

src/network/networkd-ipv4ll.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ int ipv4ll_configure(Link *link) {
223223
int r;
224224

225225
assert(link);
226+
assert(link->network);
226227

227228
if (!link_ipv4ll_enabled(link))
228229
return 0;
@@ -253,6 +254,10 @@ int ipv4ll_configure(Link *link) {
253254
if (r < 0)
254255
return r;
255256

257+
r = sd_ipv4ll_set_timeout(link->ipv4ll, link->network->ipv4_dad_timeout_usec);
258+
if (r < 0)
259+
return r;
260+
256261
r = sd_ipv4ll_set_ifindex(link->ipv4ll, link->ifindex);
257262
if (r < 0)
258263
return r;

src/network/networkd-network-gperf.gperf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extension
159159
Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ndisc)
160160
Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ndisc)
161161
Network.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits)
162+
Network.IPv4DuplicateAddressDetectionTimeoutSec, config_parse_sec, 0, offsetof(Network, ipv4_dad_timeout_usec)
162163
Network.IPv6HopLimit, config_parse_uint8, 0, offsetof(Network, ipv6_hop_limit)
163164
Network.IPv6RetransmissionTimeSec, config_parse_sec, 0, offsetof(Network, ipv6_retransmission_time)
164165
Network.IPv6ProxyNDP, config_parse_tristate, 0, offsetof(Network, ipv6_proxy_ndp)

src/network/networkd-network.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ struct Network {
112112
char **bind_carrier;
113113
bool default_route_on_device;
114114
AddressFamily ip_masquerade;
115+
usec_t ipv4_dad_timeout_usec;
115116

116117
/* Protocol independent settings */
117118
UseDomains use_domains;

src/systemd/sd-ipv4acd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int interface_index);
4848
int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd);
4949
int sd_ipv4acd_set_ifname(sd_ipv4acd *acd, const char *interface_name);
5050
int sd_ipv4acd_get_ifname(sd_ipv4acd *acd, const char **ret);
51+
int sd_ipv4acd_set_timeout(sd_ipv4acd *acd, uint64_t usec);
5152
int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address);
5253
int sd_ipv4acd_is_running(sd_ipv4acd *acd);
5354
int sd_ipv4acd_is_bound(sd_ipv4acd *acd);

src/systemd/sd-ipv4ll.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address);
4444
int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata);
4545
int sd_ipv4ll_set_check_mac_callback(sd_ipv4ll *ll, sd_ipv4ll_check_mac_callback_t cb, void *userdata);
4646
int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr);
47+
int sd_ipv4ll_set_timeout(sd_ipv4ll *ll, uint64_t usec);
4748
int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int interface_index);
4849
int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll);
4950
int sd_ipv4ll_set_ifname(sd_ipv4ll *ll, const char *interface_name);

0 commit comments

Comments
 (0)