Skip to content

Commit 4af273d

Browse files
authored
Merge pull request systemd#4512 from pfl/ndisc_exponential_backoff
Ndisc exponential backoff
2 parents 0d6c68e + 5a67ed2 commit 4af273d

File tree

3 files changed

+178
-21
lines changed

3 files changed

+178
-21
lines changed

src/libsystemd-network/ndisc-internal.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323

2424
#include "sd-ndisc.h"
2525

26+
#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
27+
#define NDISC_MAX_ROUTER_SOLICITATION_INTERVAL (3600U * USEC_PER_SEC)
28+
#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
29+
2630
struct sd_ndisc {
2731
unsigned n_ref;
2832

@@ -38,8 +42,9 @@ struct sd_ndisc {
3842

3943
sd_event_source *recv_event_source;
4044
sd_event_source *timeout_event_source;
45+
sd_event_source *timeout_no_ra;
4146

42-
unsigned nd_sent;
47+
usec_t retransmit_time;
4348

4449
sd_ndisc_callback_t callback;
4550
void *userdata;

src/libsystemd-network/sd-ndisc.c

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828
#include "in-addr-util.h"
2929
#include "ndisc-internal.h"
3030
#include "ndisc-router.h"
31+
#include "random-util.h"
3132
#include "socket-util.h"
3233
#include "string-util.h"
3334
#include "util.h"
3435

35-
#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
36-
#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
36+
#define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
3737

3838
static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) {
3939
assert(ndisc);
@@ -129,9 +129,10 @@ static int ndisc_reset(sd_ndisc *nd) {
129129
assert(nd);
130130

131131
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
132+
nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra);
133+
nd->retransmit_time = 0;
132134
nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
133135
nd->fd = safe_close(nd->fd);
134-
nd->nd_sent = 0;
135136

136137
return 0;
137138
}
@@ -264,52 +265,85 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda
264265
return ndisc_handle_datagram(nd, rt);
265266
}
266267

268+
static usec_t ndisc_timeout_compute_random(usec_t val) {
269+
/* compute a time that is random within ±10% of the given value */
270+
return val - val / 10 +
271+
(random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
272+
}
273+
267274
static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
268275
sd_ndisc *nd = userdata;
269-
usec_t time_now, next_timeout;
276+
usec_t time_now;
270277
int r;
278+
char time_string[FORMAT_TIMESPAN_MAX];
271279

272280
assert(s);
273281
assert(nd);
274282
assert(nd->event);
275283

276-
if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
277-
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
278-
ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
279-
return 0;
284+
assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
285+
286+
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
287+
288+
if (!nd->retransmit_time)
289+
nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL);
290+
else {
291+
if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2)
292+
nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL);
293+
else
294+
nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time);
280295
}
281296

282-
r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
283-
if (r < 0) {
284-
log_ndisc_errno(r, "Error sending Router Solicitation: %m");
297+
r = sd_event_add_time(nd->event, &nd->timeout_event_source,
298+
clock_boottime_or_monotonic(),
299+
time_now + nd->retransmit_time,
300+
10 * USEC_PER_MSEC, ndisc_timeout, nd);
301+
if (r < 0)
285302
goto fail;
286-
}
287303

288-
log_ndisc("Sent Router Solicitation");
289-
nd->nd_sent++;
304+
r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
305+
if (r < 0)
306+
goto fail;
290307

291-
assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
292-
next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
308+
(void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout-no-ra");
293309

294-
r = sd_event_source_set_time(nd->timeout_event_source, next_timeout);
310+
r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT);
295311
if (r < 0) {
296-
log_ndisc_errno(r, "Error updating timer: %m");
312+
log_ndisc_errno(r, "Error reenabling timer: %m");
297313
goto fail;
298314
}
299315

300-
r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT);
316+
r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
301317
if (r < 0) {
302-
log_ndisc_errno(r, "Error reenabling timer: %m");
318+
log_ndisc_errno(r, "Error sending Router Solicitation: %m");
303319
goto fail;
304320
}
305321

322+
log_ndisc("Sent Router Solicitation, next solicitation in %s",
323+
format_timespan(time_string, FORMAT_TIMESPAN_MAX,
324+
nd->retransmit_time, USEC_PER_SEC));
325+
306326
return 0;
307327

308328
fail:
309329
sd_ndisc_stop(nd);
310330
return 0;
311331
}
312332

333+
static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) {
334+
sd_ndisc *nd = userdata;
335+
336+
assert(s);
337+
assert(nd);
338+
339+
log_ndisc("No RA received before link confirmation timeout");
340+
341+
nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra);
342+
ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
343+
344+
return 0;
345+
}
346+
313347
_public_ int sd_ndisc_stop(sd_ndisc *nd) {
314348
assert_return(nd, -EINVAL);
315349

@@ -324,6 +358,7 @@ _public_ int sd_ndisc_stop(sd_ndisc *nd) {
324358

325359
_public_ int sd_ndisc_start(sd_ndisc *nd) {
326360
int r;
361+
usec_t time_now;
327362

328363
assert_return(nd, -EINVAL);
329364
assert_return(nd->event, -EINVAL);
@@ -335,6 +370,10 @@ _public_ int sd_ndisc_start(sd_ndisc *nd) {
335370
assert(!nd->recv_event_source);
336371
assert(!nd->timeout_event_source);
337372

373+
r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
374+
if (r < 0)
375+
goto fail;
376+
338377
nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
339378
if (nd->fd < 0)
340379
return nd->fd;
@@ -359,6 +398,19 @@ _public_ int sd_ndisc_start(sd_ndisc *nd) {
359398

360399
(void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
361400

401+
r = sd_event_add_time(nd->event, &nd->timeout_no_ra,
402+
clock_boottime_or_monotonic(),
403+
time_now + NDISC_TIMEOUT_NO_RA_USEC,
404+
10 * USEC_PER_MSEC, ndisc_timeout_no_ra, nd);
405+
if (r < 0)
406+
goto fail;
407+
408+
r = sd_event_source_set_priority(nd->timeout_no_ra, nd->event_priority);
409+
if (r < 0)
410+
goto fail;
411+
412+
(void) sd_event_source_set_description(nd->timeout_no_ra, "ndisc-timeout-no-ra");
413+
362414
log_ndisc("Started IPv6 Router Solicitation client");
363415
return 1;
364416

src/libsystemd-network/test-ndisc-rs.c

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "icmp6-util.h"
2828
#include "socket-util.h"
2929
#include "strv.h"
30+
#include "ndisc-internal.h"
3031

3132
static struct ether_addr mac_addr = {
3233
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
@@ -35,6 +36,7 @@ static struct ether_addr mac_addr = {
3536
static bool verbose = false;
3637
static sd_event_source *test_hangcheck;
3738
static int test_fd[2];
39+
static sd_ndisc *test_timeout_nd;
3840

3941
typedef int (*send_ra_t)(uint8_t flags);
4042
static send_ra_t send_ra_function;
@@ -237,6 +239,9 @@ static int send_ra(uint8_t flags) {
237239
}
238240

239241
int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
242+
if (!send_ra_function)
243+
return 0;
244+
240245
return send_ra_function(0);
241246
}
242247

@@ -320,13 +325,108 @@ static void test_rs(void) {
320325
sd_event_unref(e);
321326
}
322327

328+
static int test_timeout_value(uint8_t flags) {
329+
static int count = 0;
330+
static usec_t last = 0;
331+
sd_ndisc *nd = test_timeout_nd;
332+
usec_t min, max;
333+
char time_string_min[FORMAT_TIMESPAN_MAX];
334+
char time_string_nd[FORMAT_TIMESPAN_MAX];
335+
char time_string_max[FORMAT_TIMESPAN_MAX];
336+
337+
assert_se(nd);
338+
assert_se(nd->event);
339+
340+
if (++count >= 20)
341+
sd_event_exit(nd->event, 0);
342+
343+
if (last == 0) {
344+
/* initial RT = IRT + RAND*IRT */
345+
min = NDISC_ROUTER_SOLICITATION_INTERVAL -
346+
NDISC_ROUTER_SOLICITATION_INTERVAL / 10;
347+
max = NDISC_ROUTER_SOLICITATION_INTERVAL +
348+
NDISC_ROUTER_SOLICITATION_INTERVAL / 10;
349+
} else {
350+
/* next RT = 2*RTprev + RAND*RTprev */
351+
min = 2 * last - last / 10;
352+
max = 2 * last + last / 10;
353+
}
354+
355+
/* final RT > MRT */
356+
if (last * 2 > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL) {
357+
min = NDISC_MAX_ROUTER_SOLICITATION_INTERVAL -
358+
NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 10;
359+
max = NDISC_MAX_ROUTER_SOLICITATION_INTERVAL +
360+
NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 10;
361+
}
362+
363+
format_timespan(time_string_min, FORMAT_TIMESPAN_MAX,
364+
min, USEC_PER_MSEC);
365+
format_timespan(time_string_nd, FORMAT_TIMESPAN_MAX,
366+
nd->retransmit_time, USEC_PER_MSEC);
367+
format_timespan(time_string_max, FORMAT_TIMESPAN_MAX,
368+
max, USEC_PER_MSEC);
369+
370+
log_info("backoff timeout interval %2d %s%s <= %s <= %s",
371+
count,
372+
(last * 2 > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL)? "(max) ": "",
373+
time_string_min, time_string_nd, time_string_max);
374+
375+
assert_se(min <= nd->retransmit_time);
376+
assert_se(max >= nd->retransmit_time);
377+
378+
last = nd->retransmit_time;
379+
380+
assert_se(sd_event_source_set_time(nd->timeout_event_source, 0) >= 0);
381+
382+
return 0;
383+
}
384+
385+
static void test_timeout(void) {
386+
sd_event *e;
387+
sd_ndisc *nd;
388+
usec_t time_now = now(clock_boottime_or_monotonic());
389+
390+
if (verbose)
391+
printf("* %s\n", __FUNCTION__);
392+
393+
send_ra_function = test_timeout_value;
394+
395+
assert_se(sd_event_new(&e) >= 0);
396+
397+
assert_se(sd_ndisc_new(&nd) >= 0);
398+
assert_se(nd);
399+
400+
test_timeout_nd = nd;
401+
402+
assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0);
403+
404+
assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0);
405+
assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0);
406+
407+
assert_se(sd_event_add_time(e, &test_hangcheck, clock_boottime_or_monotonic(),
408+
time_now + 2U * USEC_PER_SEC, 0,
409+
test_rs_hangcheck, NULL) >= 0);
410+
411+
assert_se(sd_ndisc_start(nd) >= 0);
412+
413+
sd_event_loop(e);
414+
415+
test_hangcheck = sd_event_source_unref(test_hangcheck);
416+
417+
nd = sd_ndisc_unref(nd);
418+
419+
sd_event_unref(e);
420+
}
421+
323422
int main(int argc, char *argv[]) {
324423

325424
log_set_max_level(LOG_DEBUG);
326425
log_parse_environment();
327426
log_open();
328427

329428
test_rs();
429+
test_timeout();
330430

331431
return 0;
332432
}

0 commit comments

Comments
 (0)