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..4bf558b12b 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,19 +265,52 @@ 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; + 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); + + 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; } r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); @@ -285,23 +319,9 @@ static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { 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; - } - - 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; @@ -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; 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; }