diff options
author | Yu Watanabe <watanabe.yu+github@gmail.com> | 2023-09-20 07:02:51 +0200 |
---|---|---|
committer | Yu Watanabe <watanabe.yu+github@gmail.com> | 2023-10-11 14:42:13 +0200 |
commit | a91b888fffc20f6d7c5d07b597cc27b750c0c67a (patch) | |
tree | 01941f2bcaf9a7f4812d7f154396687056429898 /src/libsystemd-network/sd-dhcp-client.c | |
parent | network/dhcp-server: allow to configure IPv6 only preferred option (diff) | |
download | systemd-a91b888fffc20f6d7c5d07b597cc27b750c0c67a.tar.xz systemd-a91b888fffc20f6d7c5d07b597cc27b750c0c67a.zip |
sd-dhcp-client: support IPv6 only mode
This makes sd-dhcp-client optionally request IPv6 only preferred
option (RFC 8925).
Diffstat (limited to 'src/libsystemd-network/sd-dhcp-client.c')
-rw-r--r-- | src/libsystemd-network/sd-dhcp-client.c | 99 |
1 files changed, 94 insertions, 5 deletions
diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index facb2b2424..7ecd207993 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -116,6 +116,7 @@ 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; @@ -125,6 +126,7 @@ struct sd_dhcp_client { int ip_service_type; int socket_priority; bool socket_priority_set; + bool ipv6_acquired; }; static const uint8_t default_req_opts[] = { @@ -280,6 +282,12 @@ int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) { return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option)); } +static int client_request_contains(sd_dhcp_client *client, uint8_t option) { + assert(client); + + return set_contains(client->req_opts, UINT8_TO_PTR(option)); +} + int sd_dhcp_client_set_request_address( sd_dhcp_client *client, const struct in_addr *last_addr) { @@ -778,6 +786,7 @@ static int client_initialize(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); client->attempt = 0; @@ -1424,6 +1433,8 @@ static int client_initialize_time_events(sd_dhcp_client *client) { assert(client); assert(client->event); + (void) event_source_disable(client->timeout_ipv6_only_mode); + if (client->start_delay > 0) { assert_se(sd_event_now(client->event, CLOCK_BOOTTIME, &usec) >= 0); usec = usec_add(usec, client->start_delay); @@ -1613,6 +1624,16 @@ static int client_parse_message( return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), "received lease lacks subnet mask, and a fallback one cannot be generated, ignoring."); + /* RFC 8925 section 3.2 + * If the client did not include the IPv6-Only Preferred option code in the Parameter Request List in + * the DHCPDISCOVER or DHCPREQUEST message, it MUST ignore the IPv6-Only Preferred option in any + * messages received from the server. */ + if (lease->ipv6_only_preferred_usec > 0 && + !client_request_contains(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) { + log_dhcp_client(client, "Received message with unrequested IPv6-only preferred option, ignoring the option."); + lease->ipv6_only_preferred_usec = 0; + } + *ret = TAKE_PTR(lease); return 0; } @@ -1639,13 +1660,26 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *message, siz static int client_enter_requesting(sd_dhcp_client *client) { assert(client); + assert(client->lease); + + if (client->lease->ipv6_only_preferred_usec > 0) { + 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."); + return sd_dhcp_client_stop(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_resend, CLOCK_BOOTTIME, - 0, 0, + client->lease->ipv6_only_preferred_usec, 0, client_timeout_resend, client, client->event_priority, "dhcp4-resend-timer", /* force_reset = */ true); @@ -1814,7 +1848,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { return 0; } -static int client_enter_bound(sd_dhcp_client *client, int notify_event) { +static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) { int r; assert(client); @@ -1822,9 +1856,6 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) { if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING)) notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; - client->start_delay = 0; - (void) event_source_disable(client->timeout_resend); - client_set_state(client, DHCP_STATE_BOUND); client->attempt = 0; @@ -1853,6 +1884,47 @@ static int client_enter_bound(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) { + 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->start_delay = 0; + (void) event_source_disable(client->timeout_resend); + + 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_timeout_ipv6_only_mode, client, + client->event_priority, "dhcp4-ipv6-only-mode", + /* force_reset = */ true); + } + + return client_enter_bound_now(client, notify_event); +} + static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) { DHCP_CLIENT_DONT_DESTROY(client); int r; @@ -2108,6 +2180,9 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { assert_return(client, -EINVAL); + /* Note, do not reset the flag in client_initialize(), as it is also called on expire. */ + client->ipv6_acquired = false; + r = client_initialize(client); if (r < 0) return r; @@ -2224,6 +2299,20 @@ int sd_dhcp_client_stop(sd_dhcp_client *client) { return 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_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) { int r; |