From: Yu Watanabe Date: Tue, 14 Apr 2026 23:47:42 +0000 (+0900) Subject: sd-dhcp-client: simplify the implementation of IPv6 Only mode support X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bb5b2f9e5a340407394226a60af21658e88c64a2;p=thirdparty%2Fsystemd.git 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. --- diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index e08ea3deda0..fab4ff24aaf 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -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; diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 7a08861eb81..76558bdd72e 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -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,28 +2176,49 @@ 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; - 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; + 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 client_start(client); + return 0; } int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) { @@ -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); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index f274a0c4d94..dc6b78e326c 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -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); diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 033d5ad894e..378271aef88 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -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);