Skip to content

Commit de88396

Browse files
committed
dhcp: add configurable backoff parameters for DHCPv4
In cloud and virtual environments the DHCP service is typically ready within hundreds of milliseconds of the interface coming up, and once ready, responds within single-digit milliseconds. The RFC 2131 defaults (4s initial interval, 64s backoff cap) are designed for congested broadcast networks and are unnecessarily conservative in this context. Add three new configuration options to tune DHCPv4 retransmission: initial_interval - initial retransmission interval (default 4s) backoff_cutoff - exponential backoff cap (default 64s) backoff_jitter - random jitter per retry (default ±1000ms) Defaults match RFC 2131 so existing behaviour is unchanged. Option naming aligns with dhclient (initial-interval, backoff-cutoff). Minimum of 1 is enforced at parse time for interval and cutoff; invalid values are logged and the default is used. These options are DHCPv4-only; DHCPv6 retransmission follows RFC 8415 constants and is not user-configurable. A test harness (tests/backoff/test_backoff.c) was used to validate timing assumptions and correctness across multiple scenarios (defaults, min-latency, cloud-recommended) and to confirm rejection of invalid values. It requires root and network namespaces so may not be suitable for all CI environments. It can be removed if it doesn't have long-term value. Example test output: ``` $ sudo ./tests/backoff/test_backoff -b ./src/dhcpcd -n 128 dhcpcd DHCPDISCOVER backoff integration test ============================================= Binary: /home/cpatterson/git/dhcpcd/src/dhcpcd Runs: 128 per test --- defaults (N=128) --- dhcpcd.conf: timeout=75 PASS: 128/128 runs produced data Retry Expected Min Avg Max N Result ------------------------------------------------------ Init 1±1.0s 0.0s 1.0s 2.0s 128 PASS 1 4±1.0s 3.0s 4.0s 5.0s 128 PASS 2 8±1.0s 7.0s 8.0s 9.0s 128 PASS 3 16±1.0s 15.0s 16.0s 17.0s 128 PASS 4 32±1.0s 31.1s 32.1s 33.0s 128 PASS 5 64±1.0s 63.0s 64.0s 65.0s 128 PASS Elapsed: 75.9s --- min-latency (N=128) --- dhcpcd.conf: timeout=12, nodelay, initial_interval=1, backoff_cutoff=1, backoff_jitter=0 PASS: 128/128 runs produced data Retry Expected Min Avg Max N Result ------------------------------------------------------ Init 0 - - - 128 PASS 1 1±0.0s 1.0s 1.0s 1.0s 128 PASS 2 1±0.0s 1.0s 1.0s 1.0s 128 PASS 3 1±0.0s 1.0s 1.0s 1.0s 128 PASS 4 1±0.0s 1.0s 1.0s 1.0s 128 PASS 5 1±0.0s 1.0s 1.0s 1.0s 128 PASS 6 1±0.0s 1.0s 1.0s 1.0s 128 PASS 7 1±0.0s 1.0s 1.0s 1.0s 128 PASS 8 1±0.0s 1.0s 1.0s 1.0s 128 PASS 9 1±0.0s 1.0s 1.0s 1.0s 128 PASS 10 1±0.0s 1.0s 1.0s 1.0s 128 PASS Elapsed: 12.9s --- cloud (N=128) --- dhcpcd.conf: timeout=12, nodelay, initial_interval=1, backoff_cutoff=1, backoff_jitter=100 PASS: 128/128 runs produced data Retry Expected Min Avg Max N Result ------------------------------------------------------ Init 0 - - - 128 PASS 1 1±0.1s 0.9s 1.0s 1.1s 128 PASS 2 1±0.1s 0.9s 1.0s 1.1s 128 PASS 3 1±0.1s 0.9s 1.0s 1.1s 128 PASS 4 1±0.1s 0.9s 1.0s 1.1s 128 PASS 5 1±0.1s 0.9s 1.0s 1.1s 128 PASS 6 1±0.1s 0.9s 1.0s 1.1s 128 PASS 7 1±0.1s 0.9s 1.0s 1.1s 128 PASS 8 1±0.1s 0.9s 1.0s 1.1s 128 PASS 9 1±0.1s 0.9s 1.0s 1.1s 128 PASS 10 1±0.1s 0.9s 1.0s 1.1s 128 PASS Elapsed: 13.0s --- reject initial_interval=0 --- dhcpcd.conf: initial_interval 0 (expect rejection) PASS: dhcpcd rejected initial_interval 0 (exit=0) output: invalid initial interval: 0 --- reject backoff_cutoff=0 --- dhcpcd.conf: backoff_cutoff 0 (expect rejection) PASS: dhcpcd rejected backoff_cutoff 0 (exit=0) output: invalid backoff cutoff: 0 ============================================= All tests passed. ``` Signed-off-by: Chris Patterson <cpatterson@microsoft.com>
1 parent 0edb765 commit de88396

File tree

7 files changed

+1007
-7
lines changed

7 files changed

+1007
-7
lines changed

src/dhcp.c

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type)
11731173
*lp = (uint8_t)(*lp + vivco->len + 1);
11741174
}
11751175
}
1176-
1176+
11771177
if (ifo->vsio_len &&
11781178
!has_option_mask(ifo->nomask, DHO_VIVSO))
11791179
{
@@ -1864,15 +1864,21 @@ send_message(struct interface *ifp, uint8_t type,
18641864
state->xid);
18651865
RT = 0; /* bogus gcc warning */
18661866
} else {
1867+
unsigned int jitter = ifo->backoff_jitter;
1868+
18671869
if (state->interval == 0)
1868-
state->interval = 4;
1870+
state->interval = ifo->initial_interval;
18691871
else {
1872+
unsigned int cutoff = ifo->backoff_cutoff;
1873+
18701874
state->interval *= 2;
1871-
if (state->interval > 64)
1872-
state->interval = 64;
1875+
if (state->interval > cutoff)
1876+
state->interval = cutoff;
18731877
}
1878+
18741879
RT = (state->interval * MSEC_PER_SEC) +
1875-
(arc4random_uniform(MSEC_PER_SEC * 2) - MSEC_PER_SEC);
1880+
(arc4random_uniform(jitter * 2) - jitter);
1881+
18761882
/* No carrier? Don't bother sending the packet.
18771883
* However, we do need to advance the timeout. */
18781884
if (!if_is_link_up(ifp))

src/dhcpcd.conf.5.in

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,42 @@ You can use this option to stop this from happening.
304304
.It Ic fallback Ar profile
305305
Fall back to using this profile if DHCP fails.
306306
This allows you to configure a static profile instead of using ZeroConf.
307+
.It Ic initial_interval Ar seconds
308+
Set the initial DHCPv4 retransmission interval to
309+
.Ar seconds .
310+
The minimum value is 1.
311+
The default is 4 seconds as per RFC 2131.
312+
This option only affects DHCPv4;
313+
DHCPv6 retransmission is governed by RFC 8415.
314+
See also
315+
.Ic backoff_cutoff
316+
and
317+
.Ic backoff_jitter .
318+
.It Ic backoff_cutoff Ar seconds
319+
Cap the DHCPv4 exponential backoff interval at
320+
.Ar seconds .
321+
The minimum value is 1.
322+
The default is 64 seconds as per RFC 2131.
323+
Setting this to 1 effectively disables exponential growth, so
324+
retransmissions use only the initial interval plus jitter.
325+
This option only affects DHCPv4;
326+
DHCPv6 retransmission is governed by RFC 8415.
327+
See also
328+
.Ic initial_interval
329+
and
330+
.Ic backoff_jitter .
331+
.It Ic backoff_jitter Ar milliseconds
332+
Set the random jitter applied to each DHCPv4 retransmission interval.
333+
The jitter is applied as \(+-
334+
.Ar milliseconds .
335+
The default is 1000 (\(+-1 second) as per RFC 2131.
336+
A value of 0 disables jitter, producing deterministic retransmission timing.
337+
This option only affects DHCPv4;
338+
DHCPv6 retransmission is governed by RFC 8415.
339+
See also
340+
.Ic backoff_cutoff
341+
and
342+
.Ic initial_interval .
307343
.It Ic fallback_time Ar seconds
308344
Start fallback after
309345
.Ar seconds .

src/if-options.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ const struct option cf_options[] = {
177177
{"fallback_time", required_argument, NULL, O_FALLBACK_TIME},
178178
{"ipv4ll_time", required_argument, NULL, O_IPV4LL_TIME},
179179
{"nosyslog", no_argument, NULL, O_NOSYSLOG},
180+
{"initial_interval", required_argument, NULL, O_INITIAL_INTERVAL},
181+
{"backoff_cutoff", required_argument, NULL, O_BACKOFF_CUTOFF},
182+
{"backoff_jitter", required_argument, NULL, O_BACKOFF_JITTER},
180183
{NULL, 0, NULL, '\0'}
181184
};
182185

@@ -2580,6 +2583,33 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo,
25802583
logsetopts(logopts);
25812584
}
25822585
break;
2586+
case O_INITIAL_INTERVAL:
2587+
ARG_REQUIRED;
2588+
ifo->initial_interval =
2589+
(uint32_t)strtou(arg, NULL, 0, 1, UINT32_MAX, &e);
2590+
if (e) {
2591+
logerrx("invalid initial interval: %s", arg);
2592+
return -1;
2593+
}
2594+
break;
2595+
case O_BACKOFF_CUTOFF:
2596+
ARG_REQUIRED;
2597+
ifo->backoff_cutoff =
2598+
(uint32_t)strtou(arg, NULL, 0, 1, UINT32_MAX, &e);
2599+
if (e) {
2600+
logerrx("invalid backoff cutoff: %s", arg);
2601+
return -1;
2602+
}
2603+
break;
2604+
case O_BACKOFF_JITTER:
2605+
ARG_REQUIRED;
2606+
ifo->backoff_jitter =
2607+
(uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e);
2608+
if (e) {
2609+
logerrx("invalid backoff jitter: %s", arg);
2610+
return -1;
2611+
}
2612+
break;
25832613
default:
25842614
return 0;
25852615
}
@@ -2667,6 +2697,9 @@ default_config(struct dhcpcd_ctx *ctx)
26672697
#ifdef INET
26682698
ifo->fallback_time = DEFAULT_FALLBACK;
26692699
ifo->ipv4ll_time = DEFAULT_IPV4LL;
2700+
ifo->initial_interval = DEFAULT_INITIAL_INTERVAL;
2701+
ifo->backoff_cutoff = DEFAULT_BACKOFF_CUTOFF;
2702+
ifo->backoff_jitter = DEFAULT_BACKOFF_JITTER;
26702703
#endif
26712704
ifo->metric = -1;
26722705
ifo->auth.options |= DHCPCD_AUTH_REQUIRE;

src/if-options.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@
5252
#define DEFAULT_REQUEST 180 /* secs to request, mirror DHCP6 */
5353
#define DEFAULT_FALLBACK 5 /* secs until fallback */
5454
#define DEFAULT_IPV4LL 5 /* secs until ipv4ll */
55+
#define DEFAULT_INITIAL_INTERVAL 4 /* DHCP_BASE per RFC 2131 */
56+
#define DEFAULT_BACKOFF_CUTOFF 64 /* DHCP_MAX per RFC 2131 */
57+
#define DEFAULT_BACKOFF_JITTER 1000 /* +/- milliseconds */
5558

5659
#ifndef HOSTNAME_MAX_LEN
5760
#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */
@@ -190,6 +193,9 @@
190193
#define O_VSIO O_BASE + 57
191194
#define O_VSIO6 O_BASE + 58
192195
#define O_NOSYSLOG O_BASE + 59
196+
#define O_INITIAL_INTERVAL O_BASE + 60
197+
#define O_BACKOFF_CUTOFF O_BASE + 61
198+
#define O_BACKOFF_JITTER O_BASE + 62
193199

194200
extern const struct option cf_options[];
195201

@@ -257,6 +263,9 @@ struct if_options {
257263
uint32_t request_time;
258264
uint32_t fallback_time;
259265
uint32_t ipv4ll_time;
266+
uint32_t initial_interval;
267+
uint32_t backoff_cutoff;
268+
uint32_t backoff_jitter;
260269
unsigned long long options;
261270
bool randomise_hwaddr;
262271

tests/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
SUBDIRS= crypt eloop-bench
1+
SUBDIRS= crypt eloop-bench backoff
22

3-
all:
3+
all:
44
for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done
55

66
install:

tests/backoff/Makefile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
PROG= test_backoff
2+
SRCS= test_backoff.c
3+
4+
CFLAGS+= -Wall -Wextra -std=c99 -D_DEFAULT_SOURCE
5+
LDFLAGS+= -lm
6+
7+
all: ${PROG}
8+
9+
${PROG}: ${SRCS}
10+
${CC} ${CFLAGS} -o ${PROG} ${SRCS} ${LDFLAGS}
11+
12+
test: ${PROG}
13+
@echo "Run as root: sudo ./${PROG} [-b path-to-dhcpcd]"
14+
15+
clean:
16+
rm -f ${PROG}
17+
18+
distclean: clean

0 commit comments

Comments
 (0)