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.
This commit is contained in:
Patrik Flykt 2017-05-19 16:22:45 +03:00
parent 0d6c68eba3
commit 1bd6f8953d
2 changed files with 76 additions and 19 deletions

View File

@ -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;

View File

@ -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;