From d1b0afe3653b4316a6361d204169620726d468a0 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 19 Jun 2014 15:39:15 +0300 Subject: [PATCH] sd-dhcp6-client: Add DHCPv6 client Solicitation timeout handling Add the core of DHCPv6 client message retransmission and upper bound timer and message count handling according to RFC 3315 Secions 7.1.2 and 14. Omit the DHCPv6 initial delay; for now it is assumed that systemd-networkd will provide decent startup randomization that will desynchronize the clients. When reinitializing the client, clear all timers. --- src/libsystemd-network/dhcp6-protocol.h | 5 + src/libsystemd-network/sd-dhcp6-client.c | 135 +++++++++++++++++++++++ src/systemd/sd-dhcp6-client.h | 2 + 3 files changed, 142 insertions(+) diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h index 0f408bce7..6ca72ec15 100644 --- a/src/libsystemd-network/dhcp6-protocol.h +++ b/src/libsystemd-network/dhcp6-protocol.h @@ -41,6 +41,10 @@ enum { DHCP6_PORT_CLIENT = 546, }; +#define DHCP6_SOL_MAX_DELAY 1 * USEC_PER_SEC +#define DHCP6_SOL_TIMEOUT 1 * USEC_PER_SEC +#define DHCP6_SOL_MAX_RT 120 * USEC_PER_SEC + enum { DHCP6_DUID_LLT = 1, DHCP6_DUID_EN = 2, @@ -51,6 +55,7 @@ enum { enum DHCP6State { DHCP6_STATE_STOPPED = 0, DHCP6_STATE_RS = 1, + DHCP6_STATE_SOLICITATION = 2, }; enum { diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 15d174bee..d98ae0294 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -46,6 +46,10 @@ struct sd_dhcp6_client { int index; struct ether_addr mac_addr; DHCP6IA ia_na; + usec_t retransmit_time; + uint8_t retransmit_count; + sd_event_source *timeout_resend; + sd_event_source *timeout_resend_expire; sd_dhcp6_client_cb_t cb; void *userdata; @@ -109,6 +113,12 @@ static int client_initialize(sd_dhcp6_client *client) client->ia_na.timeout_t2 = sd_event_source_unref(client->ia_na.timeout_t2); + client->retransmit_time = 0; + client->retransmit_count = 0; + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + client->timeout_resend_expire = + sd_event_source_unref(client->timeout_resend_expire); + client->state = DHCP6_STATE_STOPPED; return 0; @@ -124,6 +134,118 @@ static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) { return client; } +static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, + void *userdata) { + sd_dhcp6_client *client = userdata; + + assert(s); + assert(client); + assert(client->event); + + client_stop(client, DHCP6_EVENT_RESEND_EXPIRE); + + return 0; +} + +static usec_t client_timeout_compute_random(usec_t val) { + return val - val / 10 + + (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC; +} + +static int client_timeout_resend(sd_event_source *s, uint64_t usec, + void *userdata) { + int r = 0; + sd_dhcp6_client *client = userdata; + usec_t time_now, init_retransmit_time, max_retransmit_time; + usec_t max_retransmit_duration; + uint8_t max_retransmit_count; + char time_string[FORMAT_TIMESPAN_MAX]; + + assert(s); + assert(client); + assert(client->event); + + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + + switch (client->state) { + case DHCP6_STATE_SOLICITATION: + init_retransmit_time = DHCP6_SOL_TIMEOUT; + max_retransmit_time = DHCP6_SOL_MAX_RT; + max_retransmit_count = 0; + max_retransmit_duration = 0; + + break; + + case DHCP6_STATE_STOPPED: + case DHCP6_STATE_RS: + return 0; + } + + if (max_retransmit_count && + client->retransmit_count >= max_retransmit_count) { + client_stop(client, DHCP6_EVENT_RETRANS_MAX); + return 0; + } + + r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now); + if (r < 0) + goto error; + + if (!client->retransmit_time) { + client->retransmit_time = + client_timeout_compute_random(init_retransmit_time); + } else { + if (max_retransmit_time && + client->retransmit_time > max_retransmit_time / 2) + client->retransmit_time = client_timeout_compute_random(max_retransmit_time); + else + client->retransmit_time += client_timeout_compute_random(client->retransmit_time); + } + + log_dhcp6_client(client, "Next retransmission in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, + client->retransmit_time, 0)); + + r = sd_event_add_time(client->event, &client->timeout_resend, + CLOCK_MONOTONIC, + time_now + client->retransmit_time, + 10 * USEC_PER_MSEC, client_timeout_resend, + client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->timeout_resend, + client->event_priority); + if (r < 0) + goto error; + + if (max_retransmit_duration && !client->timeout_resend_expire) { + + log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs", + max_retransmit_duration / USEC_PER_SEC); + + r = sd_event_add_time(client->event, + &client->timeout_resend_expire, + CLOCK_MONOTONIC, + time_now + max_retransmit_duration, + USEC_PER_SEC, + client_timeout_resend_expire, client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->timeout_resend_expire, + client->event_priority); + if (r < 0) + goto error; + } + +error: + if (r < 0) + client_stop(client, r); + + return 0; +} + static int client_ensure_iaid(sd_dhcp6_client *client) { const char *name = NULL; uint64_t id; @@ -180,6 +302,19 @@ static int client_start(sd_dhcp6_client *client) if (r < 0) return r; + client->state = DHCP6_STATE_SOLICITATION; + + r = sd_event_add_time(client->event, &client->timeout_resend, + CLOCK_MONOTONIC, 0, 0, client_timeout_resend, + client); + if (r < 0) + return r; + + r = sd_event_source_set_priority(client->timeout_resend, + client->event_priority); + if (r < 0) + return r; + return 0; } diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index 496501147..72267f908 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -28,6 +28,8 @@ enum { DHCP6_EVENT_STOP = 0, + DHCP6_EVENT_RESEND_EXPIRE = 10, + DHCP6_EVENT_RETRANS_MAX = 11, }; typedef struct sd_dhcp6_client sd_dhcp6_client; -- 2.30.2