From 1bd6f8953dab8db9c05ea3e54a890486b1e1a56f Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 19 May 2017 16:22:45 +0300 Subject: [PATCH] sd-ndisc: Implement Router Solicitation backoff method Instead of sending a fixed amount of Router Solicitiations, implement the backoff algorithm proposed in RFC 7559. The backoff algorithm is the same as used by DHCPv6. Time out after 12s as specified in RFC 4861 in order not to delay setting up a link for too long while sending Router Solicitations in the background. Notice that after this change the callback will receive a SD_NDISC_EVENT_TIMEOUT timeout event, and at a later point when a router appears, a received Router Advertisment will cause the callback to be called again with the SD_NDISC_EVENT_ROUTER event. --- src/libsystemd-network/ndisc-internal.h | 7 ++- src/libsystemd-network/sd-ndisc.c | 88 ++++++++++++++++++++++++++------- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/src/libsystemd-network/ndisc-internal.h b/src/libsystemd-network/ndisc-internal.h index 60e183f..82b896d 100644 --- a/src/libsystemd-network/ndisc-internal.h +++ b/src/libsystemd-network/ndisc-internal.h @@ -23,6 +23,10 @@ #include "sd-ndisc.h" +#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC) +#define NDISC_MAX_ROUTER_SOLICITATION_INTERVAL (3600U * USEC_PER_SEC) +#define NDISC_MAX_ROUTER_SOLICITATIONS 3U + struct sd_ndisc { unsigned n_ref; @@ -38,8 +42,9 @@ struct sd_ndisc { sd_event_source *recv_event_source; sd_event_source *timeout_event_source; + sd_event_source *timeout_no_ra; - unsigned nd_sent; + usec_t retransmit_time; sd_ndisc_callback_t callback; void *userdata; diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 0437e0b..5dfe6ea 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -28,12 +28,12 @@ #include "in-addr-util.h" #include "ndisc-internal.h" #include "ndisc-router.h" +#include "random-util.h" #include "socket-util.h" #include "string-util.h" #include "util.h" -#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC) -#define NDISC_MAX_ROUTER_SOLICITATIONS 3U +#define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS) static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) { assert(ndisc); @@ -129,9 +129,10 @@ static int ndisc_reset(sd_ndisc *nd) { assert(nd); nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); + nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra); + nd->retransmit_time = 0; nd->recv_event_source = sd_event_source_unref(nd->recv_event_source); nd->fd = safe_close(nd->fd); - nd->nd_sent = 0; return 0; } @@ -264,45 +265,64 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda return ndisc_handle_datagram(nd, rt); } +static usec_t ndisc_timeout_compute_random(usec_t val) { + /* compute a time that is random within ±10% of the given value */ + return val - val / 10 + + (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC; +} + static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { sd_ndisc *nd = userdata; - usec_t time_now, next_timeout; + usec_t time_now; int r; + char time_string[FORMAT_TIMESPAN_MAX]; assert(s); assert(nd); assert(nd->event); - if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) { - nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); - ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL); - return 0; - } - r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); if (r < 0) { log_ndisc_errno(r, "Error sending Router Solicitation: %m"); goto fail; } - log_ndisc("Sent Router Solicitation"); - nd->nd_sent++; - assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0); - next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL; - r = sd_event_source_set_time(nd->timeout_event_source, next_timeout); - if (r < 0) { - log_ndisc_errno(r, "Error updating timer: %m"); - goto fail; + nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); + + if (!nd->retransmit_time) + nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL); + else { + if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2) + nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL); + else + nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time); } + r = sd_event_add_time(nd->event, &nd->timeout_event_source, + clock_boottime_or_monotonic(), + time_now + nd->retransmit_time, + 10 * USEC_PER_MSEC, ndisc_timeout, nd); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout-no-ra"); + r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT); if (r < 0) { log_ndisc_errno(r, "Error reenabling timer: %m"); goto fail; } + log_ndisc("Sent Router Solicitation, next solicitation in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, + nd->retransmit_time, USEC_PER_SEC)); + return 0; fail: @@ -310,6 +330,20 @@ fail: return 0; } +static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) { + sd_ndisc *nd = userdata; + + assert(s); + assert(nd); + + log_ndisc("No RA received before link confirmation timeout"); + + nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra); + ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL); + + return 0; +} + _public_ int sd_ndisc_stop(sd_ndisc *nd) { assert_return(nd, -EINVAL); @@ -324,6 +358,7 @@ _public_ int sd_ndisc_stop(sd_ndisc *nd) { _public_ int sd_ndisc_start(sd_ndisc *nd) { int r; + usec_t time_now; assert_return(nd, -EINVAL); assert_return(nd->event, -EINVAL); @@ -335,6 +370,10 @@ _public_ int sd_ndisc_start(sd_ndisc *nd) { assert(!nd->recv_event_source); assert(!nd->timeout_event_source); + r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + goto fail; + nd->fd = icmp6_bind_router_solicitation(nd->ifindex); if (nd->fd < 0) return nd->fd; @@ -359,6 +398,19 @@ _public_ int sd_ndisc_start(sd_ndisc *nd) { (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout"); + r = sd_event_add_time(nd->event, &nd->timeout_no_ra, + clock_boottime_or_monotonic(), + time_now + NDISC_TIMEOUT_NO_RA_USEC, + 10 * USEC_PER_MSEC, ndisc_timeout_no_ra, nd); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(nd->timeout_no_ra, nd->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(nd->timeout_no_ra, "ndisc-timeout-no-ra"); + log_ndisc("Started IPv6 Router Solicitation client"); return 1; -- 2.7.4