]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-dhcp-client: support IPv6 only mode
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 20 Sep 2023 05:02:51 +0000 (14:02 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 11 Oct 2023 12:42:13 +0000 (21:42 +0900)
This makes sd-dhcp-client optionally request IPv6 only preferred
option (RFC 8925).

src/libsystemd-network/dhcp-lease-internal.h
src/libsystemd-network/sd-dhcp-client.c
src/libsystemd-network/sd-dhcp-lease.c
src/systemd/sd-dhcp-client.h

index 7c20e52209bef1a47bc74bd8730eed3f615cf672..54facc9c658e30ece89f2e64b3a7b64f3410cb42 100644 (file)
@@ -35,6 +35,7 @@ struct sd_dhcp_lease {
         usec_t t2;
         usec_t lifetime;
         triple_timestamp timestamp;
+        usec_t ipv6_only_preferred_usec;
 
         /* each 0 if unset */
         be32_t address;
index facb2b2424aafe8bd984142cae682edbb32aac61..7ecd20799354d5b6f474ee9bc0188ce9ffab68ac 100644 (file)
@@ -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;
 
index db8ac7947ae6301bcf8f1f7a80f5dfd2fe1ba691..d82ab938f73beeccf7777641e44171bdf46509b6 100644 (file)
@@ -884,6 +884,16 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
                         log_debug_errno(r, "Failed to parse 6rd option, ignoring: %m");
                 break;
 
+        case SD_DHCP_OPTION_IPV6_ONLY_PREFERRED:
+                r = lease_parse_be32_seconds(option, len, /* max_as_infinity = */ false, &lease->ipv6_only_preferred_usec);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse IPv6 only preferred option, ignoring: %m");
+
+                else if (lease->ipv6_only_preferred_usec < MIN_V6ONLY_WAIT_USEC &&
+                         !network_test_mode_enabled())
+                        lease->ipv6_only_preferred_usec = MIN_V6ONLY_WAIT_USEC;
+                break;
+
         case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST:
                 r = dhcp_lease_insert_private_option(lease, code, option, len);
                 if (r < 0)
index 19efd3bd87c84474d8b81eaf6370fe7a95150f1b..372603d43ec52dca0e0a8953f4e2650fbf720f2d 100644 (file)
@@ -152,6 +152,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client);
 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);
 
 sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client);
 sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client);