From 1bd6f8953dab8db9c05ea3e54a890486b1e1a56f Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 19 May 2017 16:22:45 +0300 Subject: [PATCH 1/3] 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 60e183ff8c..82b896dba3 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 0437e0b0b7..5dfe6ea36f 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; From e82a19cb1833c64604c11f307a84cf998f4aeffe Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 19 May 2017 16:22:46 +0300 Subject: [PATCH 2/3] sd-ndisc.c: Move Router Solicitation sending after timer computaion Move ICMPv6 Router Solicitation sending after timer computation so that timers are already set up when the packet is being sent. This makes it possible to create a test that inspects Router Solicitation timer values when the Router Solicitation is sent out on the network. --- src/libsystemd-network/sd-ndisc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 5dfe6ea36f..4bf558b12b 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -281,12 +281,6 @@ static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { assert(nd); assert(nd->event); - r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); - if (r < 0) { - log_ndisc_errno(r, "Error sending Router Solicitation: %m"); - goto fail; - } - assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0); nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); @@ -319,6 +313,12 @@ static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { goto fail; } + 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, next solicitation in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX, nd->retransmit_time, USEC_PER_SEC)); From 5a67ed2403cae2632e71a5bbeb883ff077c44186 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 19 May 2017 16:22:47 +0300 Subject: [PATCH 3/3] test-ndisc-rs.c: Test Router Solicitation timer computation Test ICMPv6 Router Solicitation exponential backoff timer by computing the minimum and maximum values according to RFC 7559, Section 2 and the algorithm itself described in RFC 3315, Section 14. Reset the sd_ndisc timer to trigger after a zero second delay, which causes the ndisc timeout to be triggered immediately once the caller of the "sending" function returns to the main loop. --- src/libsystemd-network/test-ndisc-rs.c | 100 +++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index b4a6539599..c3e1a8e340 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -27,6 +27,7 @@ #include "icmp6-util.h" #include "socket-util.h" #include "strv.h" +#include "ndisc-internal.h" static struct ether_addr mac_addr = { .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} @@ -35,6 +36,7 @@ static struct ether_addr mac_addr = { static bool verbose = false; static sd_event_source *test_hangcheck; static int test_fd[2]; +static sd_ndisc *test_timeout_nd; typedef int (*send_ra_t)(uint8_t flags); static send_ra_t send_ra_function; @@ -237,6 +239,9 @@ static int send_ra(uint8_t flags) { } int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { + if (!send_ra_function) + return 0; + return send_ra_function(0); } @@ -320,6 +325,100 @@ static void test_rs(void) { sd_event_unref(e); } +static int test_timeout_value(uint8_t flags) { + static int count = 0; + static usec_t last = 0; + sd_ndisc *nd = test_timeout_nd; + usec_t min, max; + char time_string_min[FORMAT_TIMESPAN_MAX]; + char time_string_nd[FORMAT_TIMESPAN_MAX]; + char time_string_max[FORMAT_TIMESPAN_MAX]; + + assert_se(nd); + assert_se(nd->event); + + if (++count >= 20) + sd_event_exit(nd->event, 0); + + if (last == 0) { + /* initial RT = IRT + RAND*IRT */ + min = NDISC_ROUTER_SOLICITATION_INTERVAL - + NDISC_ROUTER_SOLICITATION_INTERVAL / 10; + max = NDISC_ROUTER_SOLICITATION_INTERVAL + + NDISC_ROUTER_SOLICITATION_INTERVAL / 10; + } else { + /* next RT = 2*RTprev + RAND*RTprev */ + min = 2 * last - last / 10; + max = 2 * last + last / 10; + } + + /* final RT > MRT */ + if (last * 2 > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL) { + min = NDISC_MAX_ROUTER_SOLICITATION_INTERVAL - + NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 10; + max = NDISC_MAX_ROUTER_SOLICITATION_INTERVAL + + NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 10; + } + + format_timespan(time_string_min, FORMAT_TIMESPAN_MAX, + min, USEC_PER_MSEC); + format_timespan(time_string_nd, FORMAT_TIMESPAN_MAX, + nd->retransmit_time, USEC_PER_MSEC); + format_timespan(time_string_max, FORMAT_TIMESPAN_MAX, + max, USEC_PER_MSEC); + + log_info("backoff timeout interval %2d %s%s <= %s <= %s", + count, + (last * 2 > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL)? "(max) ": "", + time_string_min, time_string_nd, time_string_max); + + assert_se(min <= nd->retransmit_time); + assert_se(max >= nd->retransmit_time); + + last = nd->retransmit_time; + + assert_se(sd_event_source_set_time(nd->timeout_event_source, 0) >= 0); + + return 0; +} + +static void test_timeout(void) { + sd_event *e; + sd_ndisc *nd; + usec_t time_now = now(clock_boottime_or_monotonic()); + + if (verbose) + printf("* %s\n", __FUNCTION__); + + send_ra_function = test_timeout_value; + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_ndisc_new(&nd) >= 0); + assert_se(nd); + + test_timeout_nd = nd; + + assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); + + assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); + assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); + + assert_se(sd_event_add_time(e, &test_hangcheck, clock_boottime_or_monotonic(), + time_now + 2U * USEC_PER_SEC, 0, + test_rs_hangcheck, NULL) >= 0); + + assert_se(sd_ndisc_start(nd) >= 0); + + sd_event_loop(e); + + test_hangcheck = sd_event_source_unref(test_hangcheck); + + nd = sd_ndisc_unref(nd); + + sd_event_unref(e); +} + int main(int argc, char *argv[]) { log_set_max_level(LOG_DEBUG); @@ -327,6 +426,7 @@ int main(int argc, char *argv[]) { log_open(); test_rs(); + test_timeout(); return 0; }