]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
send dhcpv6 release when stopping
authorchris <str77@pm.me>
Sat, 7 Jan 2023 20:11:28 +0000 (21:11 +0100)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 17 Jan 2023 12:26:18 +0000 (21:26 +0900)
12 files changed:
NEWS
man/systemd.network.xml
src/libsystemd-network/dhcp6-internal.h
src/libsystemd-network/dhcp6-protocol.c
src/libsystemd-network/dhcp6-protocol.h
src/libsystemd-network/sd-dhcp6-client.c
src/libsystemd-network/test-dhcp6-client.c
src/network/networkd-dhcp6.c
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
src/systemd/sd-dhcp6-client.h

diff --git a/NEWS b/NEWS
index fe5741610eeb4e8e48a7c1024e00d911ce620830..eb7c24c830e487a8c569e33a4e469291e73784e4 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -131,6 +131,11 @@ CHANGES WITH 253 in spe:
 
         * systemd-networkd-wait-online now supports alternative interface names.
 
+        * The [DHCPv6] section in .network file gained new SendRelease=
+          setting which enables the DHCPv6 client to send release when
+          it stops. This is the analog of the [DHCPv4] SendRelease= setting.
+          It is enabled by default.
+
         Changes in systemd-dissect:
 
         * systemd-dissect gained a new option --list, to print the paths fo the
index 9c0b9929dc589a6ff511f1801ecefd1302c6531a..01d88976384731b79ebbee60b8ea382ca565c384 100644 (file)
@@ -2292,6 +2292,7 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
         <term><varname>UseHostname=</varname></term>
         <term><varname>UseDomains=</varname></term>
         <term><varname>NetLabel=</varname></term>
+        <term><varname>SendRelease=</varname></term>
         <listitem>
           <para>As in the [DHCPv4] section.</para>
         </listitem>
index 2afcda3eec13ed820835520b1de32b56c7f8d57b..fa43f28eb55e09e92ce332e004235c0f215b3a86 100644 (file)
@@ -79,6 +79,7 @@ struct sd_dhcp6_client {
 
         sd_dhcp6_client_callback_t callback;
         void *userdata;
+        bool send_release;
 
         /* Ignore machine-ID when generating DUID. See dhcp_identifier_set_duid_en(). */
         bool test_mode;
index f965ea40fea9c882ccc6f0c68f441d154ede7a23..be0f651f1ab4ac92260d6c7c400f85b46b8ade8d 100644 (file)
@@ -11,6 +11,7 @@ static const char * const dhcp6_state_table[_DHCP6_STATE_MAX] = {
         [DHCP6_STATE_BOUND]               = "bound",
         [DHCP6_STATE_RENEW]               = "renew",
         [DHCP6_STATE_REBIND]              = "rebind",
+        [DHCP6_STATE_STOPPING]            = "stopping",
 };
 
 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_state, DHCP6State);
index 18217691b7511afd8ccce48dd420bb1c4e42c89b..c70f93203d199b985311af2334a9d3c598f273e9 100644 (file)
@@ -58,6 +58,7 @@ typedef enum DHCP6State {
         DHCP6_STATE_BOUND,
         DHCP6_STATE_RENEW,
         DHCP6_STATE_REBIND,
+        DHCP6_STATE_STOPPING,
         _DHCP6_STATE_MAX,
         _DHCP6_STATE_INVALID = -EINVAL,
 } DHCP6State;
index 29cd0035063000c638d98f7cd668d4d06ac4d187..40edfd390816f363832eb54b86e75b4b8a5357b1 100644 (file)
@@ -498,6 +498,14 @@ int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable) {
         return 0;
 }
 
+int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable) {
+        assert_return(client, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+        client->send_release = enable;
+        return 0;
+}
+
 int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
         assert_return(client, -EINVAL);
 
@@ -586,7 +594,8 @@ static int client_append_common_options_in_managed_mode(
                       DHCP6_STATE_SOLICITATION,
                       DHCP6_STATE_REQUEST,
                       DHCP6_STATE_RENEW,
-                      DHCP6_STATE_REBIND));
+                      DHCP6_STATE_REBIND,
+                      DHCP6_STATE_STOPPING));
         assert(buf);
         assert(*buf);
         assert(offset);
@@ -603,9 +612,11 @@ static int client_append_common_options_in_managed_mode(
                         return r;
         }
 
-        r = dhcp6_option_append_fqdn(buf, offset, client->fqdn);
-        if (r < 0)
-                return r;
+        if (client->state != DHCP6_STATE_STOPPING) {
+                r = dhcp6_option_append_fqdn(buf, offset, client->fqdn);
+                if (r < 0)
+                        return r;
+        }
 
         r = dhcp6_option_append_user_class(buf, offset, client->user_class);
         if (r < 0)
@@ -636,6 +647,8 @@ static DHCP6MessageType client_message_type_from_state(sd_dhcp6_client *client)
                 return DHCP6_MESSAGE_RENEW;
         case DHCP6_STATE_REBIND:
                 return DHCP6_MESSAGE_REBIND;
+        case DHCP6_STATE_STOPPING:
+                return DHCP6_MESSAGE_RELEASE;
         default:
                 assert_not_reached();
         }
@@ -679,6 +692,9 @@ static int client_append_oro(sd_dhcp6_client *client, uint8_t **buf, size_t *off
                 req_opts = p;
                 break;
 
+        case DHCP6_STATE_STOPPING:
+                return 0;
+
         default:
                 n = client->n_req_opts;
                 req_opts = client->req_opts;
@@ -690,6 +706,22 @@ static int client_append_oro(sd_dhcp6_client *client, uint8_t **buf, size_t *off
         return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_ORO, n * sizeof(be16_t), req_opts);
 }
 
+static int client_append_mudurl(sd_dhcp6_client *client, uint8_t **buf, size_t *offset) {
+        assert(client);
+        assert(buf);
+        assert(*buf);
+        assert(offset);
+
+        if (!client->mudurl)
+                return 0;
+
+        if (client->state == DHCP6_STATE_STOPPING)
+                return 0;
+
+        return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_MUD_URL_V6,
+                                   strlen(client->mudurl), client->mudurl);
+}
+
 int dhcp6_client_send_message(sd_dhcp6_client *client) {
         _cleanup_free_ uint8_t *buf = NULL;
         struct in6_addr all_servers =
@@ -735,7 +767,7 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) {
 
         case DHCP6_STATE_REQUEST:
         case DHCP6_STATE_RENEW:
-
+        case DHCP6_STATE_STOPPING:
                 r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_SERVERID,
                                         client->lease->serverid_len,
                                         client->lease->serverid);
@@ -753,18 +785,15 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) {
                         return r;
                 break;
 
-        case DHCP6_STATE_STOPPED:
         case DHCP6_STATE_BOUND:
+        case DHCP6_STATE_STOPPED:
         default:
                 assert_not_reached();
         }
 
-        if (client->mudurl) {
-                r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_MUD_URL_V6,
-                                        strlen(client->mudurl), client->mudurl);
-                if (r < 0)
-                        return r;
-        }
+        r = client_append_mudurl(client, &buf, &offset);
+        if (r < 0)
+                return r;
 
         r = client_append_oro(client, &buf, &offset);
         if (r < 0)
@@ -856,6 +885,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
                 break;
 
         case DHCP6_STATE_STOPPED:
+        case DHCP6_STATE_STOPPING:
         case DHCP6_STATE_BOUND:
         default:
                 assert_not_reached();
@@ -911,6 +941,7 @@ static int client_start_transaction(sd_dhcp6_client *client, DHCP6State state) {
                 assert(IN_SET(client->state, DHCP6_STATE_BOUND, DHCP6_STATE_RENEW));
                 break;
         case DHCP6_STATE_STOPPED:
+        case DHCP6_STATE_STOPPING:
         case DHCP6_STATE_BOUND:
         default:
                 assert_not_reached();
@@ -1319,6 +1350,7 @@ static int client_receive_message(
 
         case DHCP6_STATE_BOUND:
         case DHCP6_STATE_STOPPED:
+        case DHCP6_STATE_STOPPING:
         default:
                 assert_not_reached();
         }
@@ -1326,10 +1358,37 @@ static int client_receive_message(
         return 0;
 }
 
+static int client_send_release(sd_dhcp6_client *client) {
+        sd_dhcp6_lease *lease;
+
+        assert(client);
+
+        if (!client->send_release)
+                return 0;
+
+        if (sd_dhcp6_client_get_lease(client, &lease) < 0)
+                return 0;
+
+        if (!lease->ia_na && !lease->ia_pd)
+                return 0;
+
+        client_set_state(client, DHCP6_STATE_STOPPING);
+        return dhcp6_client_send_message(client);
+}
+
 int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
+        int r;
+
         if (!client)
                 return 0;
 
+        /* Intentionally ignoring failure to send DHCP6 release. The DHCPv6 client
+           engine is about to release its UDP socket inconditionally. */
+        r = client_send_release(client);
+        if (r < 0)
+                log_dhcp6_client_errno(client, r,
+                                       "Failed to send DHCP6 release message, ignoring: %m");
+
         client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
 
         client->receive_message = sd_event_source_unref(client->receive_message);
index 3e1c52a306136fe386250ad8e24a358ed5d5ba51..00c11909c92e9c95ca3df4daf2d04e7ee70ad0b2 100644 (file)
@@ -602,6 +602,62 @@ static const uint8_t msg_request[] = {
         0x00, 0x00,
 };
 
+/* RFC 3315 section 18.1.6. The DHCP6 Release message must include:
+    - transaction id
+    - server identifier
+    - client identifier
+    - all released IA with addresses included
+    - elapsed time (required for all messages).
+    All other options aren't required. */
+static const uint8_t msg_release[] = {
+        /* Message type */
+        DHCP6_MESSAGE_RELEASE,
+        /* Transaction ID */
+        0x00, 0x00, 0x00,
+        /* Server ID */
+        0x00, SD_DHCP6_OPTION_SERVERID, 0x00, 0x0e,
+        SERVER_ID_BYTES,
+        /* IA_NA */
+        0x00, SD_DHCP6_OPTION_IA_NA, 0x00, 0x44,
+        IA_ID_BYTES,
+        0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+        0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+        /* IA_NA (IAADDR suboption) */
+        0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+        IA_NA_ADDRESS1_BYTES,
+        0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+        0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+        /* IA_NA (IAADDR suboption) */
+        0x00, SD_DHCP6_OPTION_IAADDR, 0x00, 0x18,
+        IA_NA_ADDRESS2_BYTES,
+        0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+        0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+        /* IA_PD */
+        0x00, SD_DHCP6_OPTION_IA_PD, 0x00, 0x46,
+        IA_ID_BYTES,
+        0x00, 0x00, 0x00, 0x00, /* lifetime T1 */
+        0x00, 0x00, 0x00, 0x00, /* lifetime T2 */
+        /* IA_PD (IA_PD_PREFIX suboption) */
+        0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+        0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+        0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+        0x40, /* prefixlen */
+        IA_PD_PREFIX1_BYTES,
+        /* IA_PD (IA_PD_PREFIX suboption) */
+        0x00, SD_DHCP6_OPTION_IA_PD_PREFIX, 0x00, 0x19,
+        0x00, 0x00, 0x00, 0x00, /* preferred lifetime */
+        0x00, 0x00, 0x00, 0x00, /* valid lifetime */
+        0x40, /* prefixlen */
+        IA_PD_PREFIX2_BYTES,
+        /* Client ID */
+        0x00, SD_DHCP6_OPTION_CLIENTID, 0x00, 0x0e,
+        CLIENT_ID_BYTES,
+        /* Extra options */
+        /* Elapsed time */
+        0x00, SD_DHCP6_OPTION_ELAPSED_TIME, 0x00, 0x02,
+        0x00, 0x00,
+};
+
 static const uint8_t msg_reply[] = {
         /* Message type */
         DHCP6_MESSAGE_REPLY,
@@ -775,13 +831,24 @@ static void test_client_verify_solicit(const DHCP6Message *msg, size_t len) {
         assert_se(memcmp(msg, msg_solicit, len - sizeof(be16_t)) == 0);
 }
 
+static void test_client_verify_release(const DHCP6Message *msg, size_t len) {
+        log_debug("/* %s */", __func__);
+
+        assert_se(len == sizeof(msg_release));
+        assert_se(msg->type == DHCP6_MESSAGE_RELEASE);
+        /* The transaction ID and elapsed time value are not deterministic. Skip them. */
+        assert_se(memcmp(msg->options, msg_release + offsetof(DHCP6Message, options),
+                         len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0);
+}
+
 static void test_client_verify_request(const DHCP6Message *msg, size_t len) {
         log_debug("/* %s */", __func__);
 
         assert_se(len == sizeof(msg_request));
         assert_se(msg->type == DHCP6_MESSAGE_REQUEST);
         /* The transaction ID and elapsed time value are not deterministic. Skip them. */
-        assert_se(memcmp(msg->options, msg_request + offsetof(DHCP6Message, options), len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0);
+        assert_se(memcmp(msg->options, msg_request + offsetof(DHCP6Message, options),
+                         len - offsetof(DHCP6Message, options) - sizeof(be16_t)) == 0);
 }
 
 static void test_lease_common(sd_dhcp6_client *client) {
@@ -905,7 +972,7 @@ static void test_client_callback(sd_dhcp6_client *client, int event, void *userd
         case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE:
                 log_debug("/* %s (event=ip-acquire) */", __func__);
 
-                assert_se(IN_SET(test_client_sent_message_count, 3, 4));
+                assert_se(IN_SET(test_client_sent_message_count, 3, 5));
 
                 test_lease_managed(client);
 
@@ -916,7 +983,7 @@ static void test_client_callback(sd_dhcp6_client *client, int event, void *userd
                         assert_se(dhcp6_client_set_transaction_id(client, ((const DHCP6Message*) msg_reply)->transaction_id) >= 0);
                         break;
 
-                case 4:
+                case 5:
                         assert_se(sd_event_exit(sd_dhcp6_client_get_event(client), 0) >= 0);
                         break;
 
@@ -974,6 +1041,12 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *a, const void *packet,
                 break;
 
         case 3:
+                test_client_verify_release(packet, len);
+                /* when stopping, dhcp6 client doesn't wait for release server reply */
+                assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply));
+                break;
+
+        case 4:
                 test_client_verify_solicit(packet, len);
                 assert_se(write(test_fd[1], msg_reply, sizeof(msg_reply)) == sizeof(msg_reply));
                 break;
@@ -1010,6 +1083,7 @@ TEST(dhcp6_client) {
         assert_se(sd_dhcp6_client_set_local_address(client, &local_address) >= 0);
         assert_se(sd_dhcp6_client_set_fqdn(client, "host.lab.intra") >= 0);
         assert_se(sd_dhcp6_client_set_iaid(client, unaligned_read_be32((uint8_t[]) { IA_ID_BYTES })) >= 0);
+        assert_se(sd_dhcp6_client_set_send_release(client, true) >= 0);
         dhcp6_client_set_test_mode(client, true);
 
         assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER) >= 0);
@@ -1028,7 +1102,7 @@ TEST(dhcp6_client) {
 
         assert_se(sd_event_loop(e) >= 0);
 
-        assert_se(test_client_sent_message_count == 4);
+        assert_se(test_client_sent_message_count == 5);
 
         assert_se(!sd_dhcp6_client_unref(client_ref));
         test_fd[1] = safe_close(test_fd[1]);
index c44c37f3aa4c9b0057266210c9c36b049ff07dd5..43be98837768393c836795fdb7f66ab694fc0431 100644 (file)
@@ -707,6 +707,12 @@ static int dhcp6_configure(Link *link) {
                                             "DHCPv6 CLIENT: Failed to %s rapid commit: %m",
                                             enable_disable(link->network->dhcp6_use_rapid_commit));
 
+        r = sd_dhcp6_client_set_send_release(client, link->network->dhcp6_send_release);
+        if (r < 0)
+                return log_link_debug_errno(link, r,
+                                            "DHCPv6 CLIENT: Failed to %s sending release message on stop: %m",
+                                            enable_disable(link->network->dhcp6_send_release));
+
         link->dhcp6_client = TAKE_PTR(client);
 
         return 0;
index 59d352b4d1e1f3ef844ca457b5f25464d30536c4..716904cc3446afed1c12e4416e64fa70bf65afc7 100644 (file)
@@ -270,6 +270,7 @@ DHCPv6.DUIDType,                             config_parse_duid_type,
 DHCPv6.DUIDRawData,                          config_parse_duid_rawdata,                                0,                             offsetof(Network, dhcp6_duid)
 DHCPv6.RapidCommit,                          config_parse_bool,                                        0,                             offsetof(Network, dhcp6_use_rapid_commit)
 DHCPv6.NetLabel,                             config_parse_string,                                      CONFIG_PARSE_STRING_SAFE,      offsetof(Network, dhcp6_netlabel)
+DHCPv6.SendRelease,                          config_parse_bool,                                        0,                             offsetof(Network, dhcp6_send_release)
 IPv6AcceptRA.UseGateway,                     config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_gateway)
 IPv6AcceptRA.UseRoutePrefix,                 config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_route_prefix)
 IPv6AcceptRA.UseAutonomousPrefix,            config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
index d881889316868f8fa7d8a1e6b0c2bc5dcc6fc633..7c5d691afab1771dea1737291e877d66e7d3ea5c 100644 (file)
@@ -416,6 +416,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
                 .dhcp6_use_rapid_commit = true,
                 .dhcp6_duid.type = _DUID_TYPE_INVALID,
                 .dhcp6_client_start_mode = _DHCP6_CLIENT_START_MODE_INVALID,
+                .dhcp6_send_release = true,
 
                 .dhcp_pd = -1,
                 .dhcp_pd_announce = true,
index 986e06790262e58df2a1dbad1303e20376ec196b..7685c98f652901b6f8b71c43463eeac5672236bf 100644 (file)
@@ -186,6 +186,7 @@ struct Network {
         OrderedHashmap *dhcp6_client_send_vendor_options;
         Set *dhcp6_request_options;
         char *dhcp6_netlabel;
+        bool dhcp6_send_release;
 
         /* DHCP Server Support */
         bool dhcp_server;
index 497b2afb2f097f630b786ab477e8fcaba3282a34..a9fa78569d22097b7a353989216c181ed444ba82 100644 (file)
@@ -264,6 +264,7 @@ int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client,
 int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client,
                                       sd_dhcp6_option *v);
 int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable);
+int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable);
 
 int sd_dhcp6_client_get_lease(
                 sd_dhcp6_client *client,