1
0
mirror of https://github.com/systemd/systemd.git synced 2025-02-09 13:57:42 +03:00

Merge pull request #4512 from pfl/ndisc_exponential_backoff

Ndisc exponential backoff
This commit is contained in:
Lennart Poettering 2017-05-30 14:49:07 +02:00 committed by GitHub
commit 4af273d149
3 changed files with 183 additions and 26 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,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;

View File

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