sd-dhcp-client: simplify the implementation of IPv6 Only mode support
This drop delay after ACK, as it has many problems. See comment in sd_dhcp_client_is_waiting_for_ipv6_connectivity() for more details. This way, the logic becomes much much simpler. Also, do not restart the client if we lost IPv6 connectivity in sd_dhcp_client side, but restart the client by networkd. As, sd_dhcp_client does not know if we can start the client or not, e.g., the interface may be currently down.
This commit is contained in:
@@ -70,7 +70,6 @@ struct sd_dhcp_client {
|
||||
sd_event_source *timeout_t1;
|
||||
sd_event_source *timeout_t2;
|
||||
sd_event_source *timeout_expire;
|
||||
sd_event_source *timeout_ipv6_only_mode;
|
||||
sd_dhcp_client_callback_t callback;
|
||||
void *userdata;
|
||||
sd_dhcp_client_callback_t state_callback;
|
||||
|
||||
@@ -635,7 +635,6 @@ static void client_disable_event_sources(sd_dhcp_client *client) {
|
||||
(void) event_source_disable(client->timeout_t1);
|
||||
(void) event_source_disable(client->timeout_t2);
|
||||
(void) event_source_disable(client->timeout_expire);
|
||||
(void) event_source_disable(client->timeout_ipv6_only_mode);
|
||||
}
|
||||
|
||||
static void client_initialize(sd_dhcp_client *client) {
|
||||
@@ -1293,8 +1292,6 @@ static int client_initialize_time_events(sd_dhcp_client *client) {
|
||||
assert(client);
|
||||
assert(client->event);
|
||||
|
||||
(void) event_source_disable(client->timeout_ipv6_only_mode);
|
||||
|
||||
return event_reset_time_relative(
|
||||
client->event,
|
||||
&client->timeout_resend,
|
||||
@@ -1566,39 +1563,17 @@ static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int client_enter_requesting_now(sd_dhcp_client *client) {
|
||||
assert(client);
|
||||
|
||||
client_set_state(client, DHCP_STATE_REQUESTING);
|
||||
client->discover_attempt = 0;
|
||||
client->request_attempt = 0;
|
||||
|
||||
return event_reset_time(client->event, &client->timeout_resend,
|
||||
CLOCK_BOOTTIME, 0, 0,
|
||||
client_timeout_resend, client,
|
||||
client->event_priority, "dhcp4-resend-timer",
|
||||
/* force_reset= */ true);
|
||||
}
|
||||
|
||||
static int client_enter_requesting_delayed(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||
sd_dhcp_client *client = ASSERT_PTR(userdata);
|
||||
DHCP_CLIENT_DONT_DESTROY(client);
|
||||
int r;
|
||||
|
||||
r = client_enter_requesting_now(client);
|
||||
if (r < 0)
|
||||
client_stop(client, r);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int client_enter_requesting(sd_dhcp_client *client) {
|
||||
assert(client);
|
||||
assert(client->lease);
|
||||
|
||||
client_disable_event_sources(client);
|
||||
|
||||
if (client->lease->ipv6_only_preferred_usec > 0) {
|
||||
client_set_state(client, DHCP_STATE_REQUESTING);
|
||||
client->discover_attempt = 0;
|
||||
client->request_attempt = 0;
|
||||
|
||||
if (sd_dhcp_client_is_waiting_for_ipv6_connectivity(client)) {
|
||||
if (client->ipv6_acquired) {
|
||||
log_dhcp_client(client,
|
||||
"Received an OFFER with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client.");
|
||||
@@ -1608,16 +1583,19 @@ static int client_enter_requesting(sd_dhcp_client *client) {
|
||||
log_dhcp_client(client,
|
||||
"Received an OFFER with IPv6-only preferred option, delaying to send REQUEST with %s.",
|
||||
FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC));
|
||||
|
||||
return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode,
|
||||
CLOCK_BOOTTIME,
|
||||
client->lease->ipv6_only_preferred_usec, 0,
|
||||
client_enter_requesting_delayed, client,
|
||||
client->event_priority, "dhcp4-ipv6-only-mode-timer",
|
||||
/* force_reset= */ true);
|
||||
}
|
||||
|
||||
return client_enter_requesting_now(client);
|
||||
return event_reset_time_relative(
|
||||
client->event,
|
||||
&client->timeout_resend,
|
||||
CLOCK_BOOTTIME,
|
||||
client->lease->ipv6_only_preferred_usec,
|
||||
/* accuracy= */ 0,
|
||||
client_timeout_resend,
|
||||
client,
|
||||
client->event_priority,
|
||||
"dhcp4-resend-timer",
|
||||
/* force_reset= */ true);
|
||||
}
|
||||
|
||||
static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) {
|
||||
@@ -1770,14 +1748,19 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) {
|
||||
static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
|
||||
int r;
|
||||
|
||||
assert(client);
|
||||
assert(client->lease);
|
||||
|
||||
if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING))
|
||||
notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
|
||||
|
||||
client_disable_event_sources(client);
|
||||
|
||||
client->start_delay = 0;
|
||||
|
||||
client_set_state(client, DHCP_STATE_BOUND);
|
||||
client->discover_attempt = 0;
|
||||
client->request_attempt = 0;
|
||||
@@ -1786,61 +1769,12 @@ static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) {
|
||||
|
||||
r = client_set_lease_timeouts(client);
|
||||
if (r < 0)
|
||||
log_dhcp_client_errno(client, r, "could not set lease timeouts: %m");
|
||||
log_dhcp_client_errno(client, r, "Failed to set lease timeouts: %m");
|
||||
|
||||
client_notify(client, notify_event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int client_enter_bound_delayed(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||
sd_dhcp_client *client = ASSERT_PTR(userdata);
|
||||
DHCP_CLIENT_DONT_DESTROY(client);
|
||||
int r;
|
||||
|
||||
r = client_enter_bound_now(client, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE);
|
||||
if (r < 0)
|
||||
client_stop(client, r);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
|
||||
assert(client);
|
||||
assert(client->lease);
|
||||
|
||||
client_disable_event_sources(client);
|
||||
|
||||
client->start_delay = 0;
|
||||
|
||||
/* RFC 8925 section 3.2
|
||||
* If the client is in the INIT-REBOOT state, it SHOULD stop the DHCPv4 configuration process or
|
||||
* disable the IPv4 stack completely for V6ONLY_WAIT seconds or until the network attachment event,
|
||||
* whichever happens first.
|
||||
*
|
||||
* In the below, the condition uses REBOOTING, instead of INIT-REBOOT, as the client state has
|
||||
* already transitioned from INIT-REBOOT to REBOOTING after sending a DHCPREQUEST message. */
|
||||
if (client->state == DHCP_STATE_REBOOTING && client->lease->ipv6_only_preferred_usec > 0) {
|
||||
if (client->ipv6_acquired) {
|
||||
log_dhcp_client(client,
|
||||
"Received an ACK with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client.");
|
||||
return sd_dhcp_client_stop(client);
|
||||
}
|
||||
|
||||
log_dhcp_client(client,
|
||||
"Received an ACK with IPv6-only preferred option, delaying to enter bound state with %s.",
|
||||
FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC));
|
||||
|
||||
return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode,
|
||||
CLOCK_BOOTTIME,
|
||||
client->lease->ipv6_only_preferred_usec, 0,
|
||||
client_enter_bound_delayed, client,
|
||||
client->event_priority, "dhcp4-ipv6-only-mode",
|
||||
/* force_reset= */ true);
|
||||
}
|
||||
|
||||
return client_enter_bound_now(client, notify_event);
|
||||
}
|
||||
|
||||
static int client_restart(sd_dhcp_client *client) {
|
||||
int r;
|
||||
assert(client);
|
||||
@@ -2107,9 +2041,6 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
|
||||
assert_return(client->event, -EINVAL);
|
||||
assert_return(client->ifindex > 0, -EINVAL);
|
||||
|
||||
/* Note, do not reset the flag in client_initialize(), as it is also called on expire. */
|
||||
client->ipv6_acquired = false;
|
||||
|
||||
/* If no client identifier exists, construct an RFC 4361-compliant one */
|
||||
if (!sd_dhcp_client_id_is_set(&client->client_id)) {
|
||||
r = sd_dhcp_client_set_iaid_duid_en(client, /* iaid_set= */ false, /* iaid= */ 0);
|
||||
@@ -2245,30 +2176,51 @@ int sd_dhcp_client_stop(sd_dhcp_client *client) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sd_dhcp_client_is_waiting_for_ipv6_connectivity(sd_dhcp_client *client) {
|
||||
/* Note that we intentionally do not implement the following behavior:
|
||||
*
|
||||
* RFC 8925, section 3.2:
|
||||
* If the client is in the INIT-REBOOT state, it SHOULD stop the DHCPv4 configuration process or
|
||||
* disable the IPv4 stack completely for V6ONLY_WAIT seconds or until the next network attachment
|
||||
* event, whichever occurs first.
|
||||
*
|
||||
* Delaying the application of an acquired IPv4 address after DHCPACK introduces several issues:
|
||||
*
|
||||
* - If T1 is reached before the address is assigned to the interface, the client cannot send a
|
||||
* unicast DHCPREQUEST during RENEWING.
|
||||
*
|
||||
* - If the client is stopped before the address is configured, it cannot send a DHCPRELEASE message,
|
||||
* which also requires a valid source address.
|
||||
*
|
||||
* While these issues could be worked around, doing so would significantly complicate the
|
||||
* implementation and violate assumptions in the DHCP state machine as defined in RFC 2131.
|
||||
*
|
||||
* Instead, we only honor the IPv6-Only Preferred delay (Option 108) in the REQUESTING state, i.e.
|
||||
* before any DHCPREQUEST has been sent. */
|
||||
|
||||
return
|
||||
client &&
|
||||
client->state == DHCP_STATE_REQUESTING &&
|
||||
client->request_attempt == 0 &&
|
||||
client->lease &&
|
||||
client->lease->ipv6_only_preferred_usec > 0;
|
||||
}
|
||||
|
||||
int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have) {
|
||||
if (!client)
|
||||
return 0;
|
||||
|
||||
/* We have already received a message with IPv6-Only preferred option, and are waiting for IPv6
|
||||
* connectivity or timeout, let's stop the client. */
|
||||
if (have && sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) > 0)
|
||||
return sd_dhcp_client_stop(client);
|
||||
|
||||
/* Otherwise, save that the host already has IPv6 connectivity. */
|
||||
client->ipv6_acquired = have;
|
||||
|
||||
if (have && sd_dhcp_client_is_waiting_for_ipv6_connectivity(client)) {
|
||||
log_dhcp_client(client,
|
||||
"Acquired IPv6 connectivity before sending REQUEST, stopping DHCPv4 client.");
|
||||
return sd_dhcp_client_stop(client);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sd_dhcp_client_interrupt_ipv6_only_mode(sd_dhcp_client *client) {
|
||||
assert_return(client, -EINVAL);
|
||||
assert_return(sd_dhcp_client_is_running(client), -ESTALE);
|
||||
|
||||
if (sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) <= 0)
|
||||
return 0;
|
||||
|
||||
return client_start(client);
|
||||
}
|
||||
|
||||
int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) {
|
||||
int r;
|
||||
|
||||
@@ -2322,7 +2274,6 @@ static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) {
|
||||
sd_event_source_unref(client->timeout_t1);
|
||||
sd_event_source_unref(client->timeout_t2);
|
||||
sd_event_source_unref(client->timeout_expire);
|
||||
sd_event_source_unref(client->timeout_ipv6_only_mode);
|
||||
|
||||
sd_dhcp_client_detach_event(client);
|
||||
|
||||
|
||||
@@ -1733,6 +1733,8 @@ int dhcp4_update_mac(Link *link) {
|
||||
}
|
||||
|
||||
int dhcp4_update_ipv6_connectivity(Link *link) {
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
if (!link->network)
|
||||
@@ -1744,16 +1746,20 @@ int dhcp4_update_ipv6_connectivity(Link *link) {
|
||||
if (!link->dhcp_client)
|
||||
return 0;
|
||||
|
||||
/* If the client is running, set the current connectivity. */
|
||||
if (sd_dhcp_client_is_running(link->dhcp_client))
|
||||
return sd_dhcp_client_set_ipv6_connectivity(link->dhcp_client, link_has_ipv6_connectivity(link));
|
||||
bool have = link_has_ipv6_connectivity(link);
|
||||
r = sd_dhcp_client_set_ipv6_connectivity(link->dhcp_client, have);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If the client has been already stopped or not started yet, let's check the current connectivity
|
||||
* and start the client if necessary. */
|
||||
if (link_has_ipv6_connectivity(link))
|
||||
return 0;
|
||||
/* If we do not have IPv6 connectivity, and the client has been already stopped or not started yet,
|
||||
* let's start the client if possible. */
|
||||
if (!have && !sd_dhcp_client_is_running(link->dhcp_client)) {
|
||||
r = dhcp4_start_full(link, /* set_ipv6_connectivity= */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return dhcp4_start_full(link, /* set_ipv6_connectivity= */ false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dhcp4_start_full(Link *link, bool set_ipv6_connectivity) {
|
||||
@@ -1805,8 +1811,10 @@ int dhcp4_renew(Link *link) {
|
||||
return dhcp4_start(link);
|
||||
|
||||
/* The client may be waiting for IPv6 connectivity. Let's restart the client in that case. */
|
||||
if (dhcp_client_get_state(link->dhcp_client) != DHCP_STATE_BOUND)
|
||||
return sd_dhcp_client_interrupt_ipv6_only_mode(link->dhcp_client);
|
||||
if (sd_dhcp_client_is_waiting_for_ipv6_connectivity(link->dhcp_client)) {
|
||||
sd_dhcp_client_stop(link->dhcp_client);
|
||||
return dhcp4_start(link);
|
||||
}
|
||||
|
||||
/* Otherwise, send a RENEW command. */
|
||||
return sd_dhcp_client_send_renew(link->dhcp_client);
|
||||
|
||||
@@ -159,7 +159,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client);
|
||||
int sd_dhcp_client_send_decline(sd_dhcp_client *client);
|
||||
int sd_dhcp_client_send_renew(sd_dhcp_client *client);
|
||||
int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have);
|
||||
int sd_dhcp_client_interrupt_ipv6_only_mode(sd_dhcp_client *client);
|
||||
int sd_dhcp_client_is_waiting_for_ipv6_connectivity(sd_dhcp_client *client);
|
||||
|
||||
_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user