From a43f92252600599045c2309b8e839ebf0c39a614 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 19 Oct 2023 23:57:44 +0900 Subject: [PATCH 1/5] sd-dhcp-client: simplify the condition in sd_dhcp_client_get_lease() The condition was outdated, e.g. SELECTING state does not have a lease. See client_handle_offer() and client_enter_requesting(). The condition based on the state may become much complex in the future. Let's use simpler condition. --- src/libsystemd-network/sd-dhcp-client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index bc73e87b95b79..6c08d28962265 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -709,7 +709,7 @@ int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v) int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { assert_return(client, -EINVAL); - if (!IN_SET(client->state, DHCP_STATE_SELECTING, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING)) + if (!client->lease) return -EADDRNOTAVAIL; if (ret) From dab96fed0cfbe0c475b64837affe3da2d69e5764 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Oct 2023 00:01:15 +0900 Subject: [PATCH 2/5] sd-dhcp-client: only send RENEW message when the client is in bound state Fixes an issue reported at https://github.com/systemd/systemd/pull/29544#issuecomment-1762742561. --- src/libsystemd-network/sd-dhcp-client.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 6c08d28962265..7e36908937109 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -2161,9 +2161,11 @@ int sd_dhcp_client_send_renew(sd_dhcp_client *client) { assert_return(sd_dhcp_client_is_running(client), -ESTALE); assert_return(client->fd >= 0, -EINVAL); - if (!client->lease) + if (client->state != DHCP_STATE_BOUND) return 0; + assert(client->lease); + client->start_delay = 0; client->attempt = 1; client_set_state(client, DHCP_STATE_RENEWING); From 74c102d7e9e04695476ca7fe1cec088a938ffb11 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Oct 2023 00:16:39 +0900 Subject: [PATCH 3/5] sd-dhcp-client: add a short comment about IPv6 only mode --- src/libsystemd-network/sd-dhcp-client.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 7e36908937109..d27609655f171 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1906,6 +1906,13 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) { client->start_delay = 0; (void) event_source_disable(client->timeout_resend); + /* 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, From 95bd6816d7f09a1505727d86cc3ce79d98f256d3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Oct 2023 01:21:42 +0900 Subject: [PATCH 4/5] sd-dhcp-client: always use sd_dhcp_client.timeout_ipv6_only_mode for delaying subsequent task Otherwise, sd_dhcp_client_set_ipv6_connectivity() may not work, as it checks if the timer event source is enabled or not. --- src/libsystemd-network/sd-dhcp-client.c | 48 +++++++++++++++++++------ 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index d27609655f171..5064bf2b4f02a 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1661,10 +1661,37 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *message, siz return 0; } +static int client_enter_requesting_now(sd_dhcp_client *client) { + assert(client); + + client_set_state(client, DHCP_STATE_REQUESTING); + client->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); + (void) event_source_disable(client->timeout_resend); + if (client->lease->ipv6_only_preferred_usec > 0) { if (client->ipv6_acquired) { log_dhcp_client(client, @@ -1675,17 +1702,16 @@ 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)); - } - client_set_state(client, DHCP_STATE_REQUESTING); - client->attempt = 0; + 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 event_reset_time_relative(client->event, &client->timeout_resend, - CLOCK_BOOTTIME, - client->lease->ipv6_only_preferred_usec, 0, - client_timeout_resend, client, - client->event_priority, "dhcp4-resend-timer", - /* force_reset = */ true); + return client_enter_requesting_now(client); } static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, size_t len) { @@ -1887,7 +1913,7 @@ static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) { return 0; } -static int client_timeout_ipv6_only_mode(sd_event_source *s, uint64_t usec, void *userdata) { +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; @@ -1927,7 +1953,7 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) { return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode, CLOCK_BOOTTIME, client->lease->ipv6_only_preferred_usec, 0, - client_timeout_ipv6_only_mode, client, + client_enter_bound_delayed, client, client->event_priority, "dhcp4-ipv6-only-mode", /* force_reset = */ true); } From 0bc30a20382d944908897c226e5675f7152b38f5 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 20 Oct 2023 01:35:03 +0900 Subject: [PATCH 5/5] network,dhcp: restart client with 'networkctl renew' when delayed by IPv6 only mode This is convenient when the server supports IPv6 only mode. Otherwise, we cannot request a new address during the client is waiting an IPv6 connectivity. Note, the minimal timespan is 5min, and a server may send a quite large value. --- src/libsystemd-network/sd-dhcp-client.c | 12 ++++++++++++ src/network/networkd-dhcp4.c | 19 +++++++++++++++++++ src/network/networkd-dhcp4.h | 1 + src/network/networkd-link-bus.c | 9 ++------- src/systemd/sd-dhcp-client.h | 1 + 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 5064bf2b4f02a..f056dcfc93698 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -2351,6 +2351,18 @@ int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int 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); + assert_return(client->fd >= 0, -EINVAL); + + if (sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) <= 0) + return 0; + + client_initialize(client); + return client_start(client); +} + int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) { int r; diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 57d40e856ee0c..5ad8128da7258 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1744,6 +1744,25 @@ int dhcp4_start_full(Link *link, bool set_ipv6_connectivity) { return 1; } +int dhcp4_renew(Link *link) { + assert(link); + + if (!link->dhcp_client) + return 0; + + /* The DHCPv4 client may have been stopped by the IPv6 only mode. Let's unconditionally restart the + * client if it is not running. */ + if (!sd_dhcp_client_is_running(link->dhcp_client)) + 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); + + /* Otherwise, send a RENEW command. */ + return sd_dhcp_client_send_renew(link->dhcp_client); +} + static int dhcp4_configure_duid(Link *link) { assert(link); assert(link->network); diff --git a/src/network/networkd-dhcp4.h b/src/network/networkd-dhcp4.h index d36b058546418..b3fe0272fc0a6 100644 --- a/src/network/networkd-dhcp4.h +++ b/src/network/networkd-dhcp4.h @@ -20,6 +20,7 @@ int dhcp4_start_full(Link *link, bool set_ipv6_connectivity); static inline int dhcp4_start(Link *link) { return dhcp4_start_full(link, true); } +int dhcp4_renew(Link *link); int dhcp4_lease_lost(Link *link); int dhcp4_check_ready(Link *link); diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c index 5fd5734ce06f1..af36b645f9481 100644 --- a/src/network/networkd-link-bus.c +++ b/src/network/networkd-link-bus.c @@ -10,6 +10,7 @@ #include "bus-message-util.h" #include "bus-polkit.h" #include "dns-domain.h" +#include "networkd-dhcp4.h" #include "networkd-json.h" #include "networkd-link-bus.h" #include "networkd-link.h" @@ -626,13 +627,7 @@ int bus_link_method_renew(sd_bus_message *message, void *userdata, sd_bus_error if (r == 0) return 1; /* Polkit will call us back */ - if (sd_dhcp_client_is_running(l->dhcp_client)) - r = sd_dhcp_client_send_renew(l->dhcp_client); - else - /* The DHCPv4 client may have been stopped by the IPv6 only mode. Let's unconditionally - * restart the client here. Note, if the DHCPv4 client is disabled, then dhcp4_start() does - * nothing and returns 0. */ - r = dhcp4_start(l); + r = dhcp4_renew(l); if (r < 0) return r; diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 372603d43ec52..0996aeeb881a8 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -153,6 +153,7 @@ int sd_dhcp_client_send_release(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); sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client); sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client);