sd-ndisc: Implement Router Solicitation backoff method
authorPatrik Flykt <patrik.flykt@linux.intel.com>
Fri, 19 May 2017 13:22:45 +0000 (16:22 +0300)
committerPatrik Flykt <patrik.flykt@linux.intel.com>
Tue, 30 May 2017 07:34:15 +0000 (10:34 +0300)
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
src/libsystemd-network/sd-ndisc.c

index 60e183f..82b896d 100644 (file)
 
 #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;
index 0437e0b..5dfe6ea 100644 (file)
 #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;