]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dhcp-client-send: introduce dhcp_client_send_message()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 18 Mar 2026 16:01:50 +0000 (01:01 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 15 May 2026 23:03:15 +0000 (08:03 +0900)
This internally uses sd_dhcp_message object, and replaces functions
for creating and sending DHCP messages.

By using sd_dhcp_message internally, now we can correctly send long
(> 255 bytes) option data that cannot be fit in a single DHCP option TLV.

This also fixes the value in DHCP option 57 (Maximum Message Size).
Previously the IP and UDP header size is subtracted from the interface
MTU, but it should not.

Except for the above, this should not change any effective behaviors.

src/libsystemd-network/dhcp-client-send.c
src/libsystemd-network/dhcp-client-send.h
src/libsystemd-network/sd-dhcp-client.c
src/libsystemd-network/test-dhcp-client.c

index d1f20fef46e691466706c0f941adea0b1d79afd9..e8b91a3e7c0bddf3cbc8dfa29105c6f0abe9c36f 100644 (file)
@@ -1,15 +1,17 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <net/if_arp.h>
+
 #include "sd-event.h"
 
 #include "dhcp-client-internal.h"
 #include "dhcp-client-send.h"
 #include "dhcp-lease-internal.h"  /* IWYU pragma: keep */
+#include "dhcp-message.h"
 #include "dhcp-network.h"
-#include "dhcp-packet.h"
 #include "fd-util.h"
-#include "iovec-util.h"
 #include "iovec-wrapper.h"
+#include "ip-util.h"
 #include "socket-util.h"
 
 static int client_get_socket(sd_dhcp_client *client, int domain) {
@@ -70,17 +72,16 @@ static int client_setup_io_event(
         return 0;
 }
 
-int dhcp_client_send_raw(
+static int client_send_raw(
                 sd_dhcp_client *client,
-                bool expect_reply,
-                DHCPPacket *packet,
-                size_t optoffset) {
+                sd_dhcp_message *message,
+                bool expect_reply) {
 
         _cleanup_close_ int fd_close = -EBADF;
         int r, fd;
 
         assert(client);
-        assert(packet);
+        assert(message);
 
         fd = client_get_socket(client, AF_PACKET);
         if (fd < 0) {
@@ -100,24 +101,39 @@ int dhcp_client_send_raw(
                 fd_close = fd;
         }
 
-        r = dhcp_packet_append_ip_headers(
-                        packet,
+        _cleanup_(iovw_done_free) struct iovec_wrapper payload = {};
+        r = dhcp_message_build(message, &payload);
+        if (r < 0)
+                return r;
+
+        struct iphdr ip;
+        struct udphdr udp;
+        r = udp_packet_build(
                         INADDR_ANY,
                         client->port,
                         INADDR_BROADCAST,
                         client->server_port,
-                        sizeof(DHCPPacket) + optoffset,
-                        client->ip_service_type);
+                        client->ip_service_type,
+                        &payload,
+                        &ip,
+                        &udp);
         if (r < 0)
                 return r;
 
-        r = dhcp_network_send_raw_socket(
-                        fd,
-                        &client->link,
-                        &(struct iovec_wrapper) {
-                                .iovec = &IOVEC_MAKE(packet, sizeof(DHCPPacket) + optoffset),
-                                .count = 1,
-                        });
+        _cleanup_(iovw_done) struct iovec_wrapper iovw = {};
+        r = iovw_put(&iovw, &ip, sizeof(struct iphdr));
+        if (r < 0)
+                return r;
+
+        r = iovw_put(&iovw, &udp, sizeof(struct udphdr));
+        if (r < 0)
+                return r;
+
+        r = iovw_put_iovw(&iovw, &payload);
+        if (r < 0)
+                return r;
+
+        r = dhcp_network_send_raw_socket(fd, &client->link, &iovw);
         if (r < 0)
                 return r;
 
@@ -138,17 +154,16 @@ int dhcp_client_send_raw(
         return 0;
 }
 
-int dhcp_client_send_udp(
+static int client_send_udp(
                 sd_dhcp_client *client,
-                bool expect_reply,
-                DHCPPacket *packet,
-                size_t optoffset) {
+                sd_dhcp_message *message,
+                bool expect_reply) {
 
         _cleanup_close_ int fd_close = -EBADF;
         int r, fd;
 
         assert(client);
-        assert(packet);
+        assert(message);
 
         if (!client->lease || client->lease->address == 0)
                 return -EADDRNOTAVAIL;
@@ -166,14 +181,16 @@ int dhcp_client_send_udp(
                 fd_close = fd;
         }
 
+        _cleanup_(iovw_done_free) struct iovec_wrapper payload = {};
+        r = dhcp_message_build(message, &payload);
+        if (r < 0)
+                return r;
+
         r = dhcp_network_send_udp_socket(
                         fd,
                         client->lease->server_address,
                         client->server_port,
-                        &(struct iovec_wrapper) {
-                                .iovec = &IOVEC_MAKE(&packet->dhcp, sizeof(DHCPMessage) + optoffset),
-                                .count = 1,
-                        });
+                        &payload);
         if (r < 0)
                 return r;
 
@@ -193,3 +210,323 @@ int dhcp_client_send_udp(
         TAKE_FD(fd_close);
         return 0;
 }
+
+static int client_new_message(sd_dhcp_client *client, uint8_t type, sd_dhcp_message **ret) {
+        int r;
+
+        assert(client);
+        assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE));
+        assert(ret);
+
+        _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL;
+        r = dhcp_message_new(&message);
+        if (r < 0)
+                return r;
+
+        r = dhcp_message_init_header(
+                        message,
+                        BOOTREQUEST,
+                        client->xid,
+                        client->arp_type,
+                        &client->hw_addr);
+        if (r < 0)
+                return r;
+
+        /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers refuse to issue a DHCP lease
+         * if 'secs' is set to zero. */
+        usec_t time_now;
+        r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
+        if (r < 0)
+                return r;
+        assert(time_now >= client->start_time);
+
+        /* Seconds between sending first and last DISCOVER must always be strictly positive to deal with
+         * broken servers. */
+        message->header.secs = usec_to_be16_sec(usec_sub_unsigned(time_now, client->start_time) ?: 1 * USEC_PER_SEC);
+
+        /* RFC2131 section 4.1
+         * A client that cannot receive unicast IP datagrams until its protocol software has been configured
+         * with an IP address SHOULD set the BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or
+         * DHCPREQUEST messages that client sends. The BROADCAST bit will provide a hint to the DHCP server
+         * and BOOTP relay agent to broadcast any messages to the client on the client's subnet.
+         *
+         * Note: some interfaces needs this to be enabled, but some networks need this to be disabled as
+         * broadcasts are filtered, so this needs to be configurable. */
+        dhcp_message_set_broadcast_flag(message, client->request_broadcast || client->arp_type != ARPHRD_ETHER);
+
+        /* We append no vendor options on BOOTP mode. */
+        if (client->bootp) {
+                *ret = TAKE_PTR(message);
+                return 0;
+        }
+
+        /* DHCP Message Type (53): Mandatory. */
+        r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, type);
+        if (r < 0)
+                return r;
+
+        /* Server Identifier (54): mandatory in DHCPREQUEST on REQUESTING state. It is also mandatory when
+         * DHCPRELEASE and DHCPDECLINE. */
+        if ((type == DHCP_REQUEST && client->state == DHCP_STATE_REQUESTING) ||
+            IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)) {
+                r = dhcp_message_append_option_be32(
+                                message,
+                                SD_DHCP_OPTION_SERVER_IDENTIFIER,
+                                ASSERT_PTR(client->lease)->server_address);
+                if (r < 0)
+                        return r;
+        }
+
+        /* Client Identifier (61): Not mandatory, but some DHCP servers will reject messages without client
+         * identifier option. Hence, we always set it. */
+        r = dhcp_message_append_option_client_id(message, &client->client_id);
+        if (r < 0)
+                return r;
+
+        /* Requested IP Address option (50) or ciaddr
+         *
+         * See RFC2131 section 4.3.2 (note that there is a typo in the RFC, SELECTING should be REQUESTING). */
+        be32_t addr = INADDR_ANY;
+        switch (type) {
+        case DHCP_DISCOVER:
+                /* the client may suggest values for the network address and lease time in the DHCPDISCOVER
+                 * message. The client may include the ’requested IP address’ option to suggest that a
+                 * particular IP address be assigned, and may include the ’IP address lease time’ option to
+                 * suggest the lease time it would like.
+                 *
+                 * RFC7844 section 3:
+                 * SHOULD NOT contain any other option (when running on anonymize mode). */
+                if (!client->anonymize)
+                        addr = client->last_addr;
+                break;
+
+        case DHCP_REQUEST:
+                switch (client->state) {
+
+                case DHCP_STATE_REQUESTING:
+                        /* ’ciaddr’ MUST be zero, ’requested IP address’ MUST be filled in with the
+                         * yiaddr value from the chosen DHCPOFFER. */
+                        addr = ASSERT_PTR(client->lease)->address;
+                        break;
+
+                case DHCP_STATE_REBOOTING:
+                        /* ’requested IP address’ option MUST be filled in with client’s notion of its
+                         * previously assigned address. ’ciaddr’ MUST be zero. */
+                        addr = client->last_addr;
+                        break;
+
+                case DHCP_STATE_RENEWING:
+                case DHCP_STATE_REBINDING:
+                        /* ’requested IP address’ option MUST NOT be filled in, ’ciaddr’ MUST be filled
+                         * in with client’s IP address. */
+                        message->header.ciaddr = ASSERT_PTR(client->lease)->address;
+                        break;
+
+                default:
+                        assert_not_reached();
+                }
+                break;
+
+        case DHCP_RELEASE:
+                /* The acquired address must be set in ciaddr. */
+                message->header.ciaddr = ASSERT_PTR(client->lease)->address;
+                break;
+
+        case DHCP_DECLINE:
+                /* The acquired address must be set in Requested IP Address option. */
+                addr = ASSERT_PTR(client->lease)->address;
+                break;
+
+        default:
+                assert_not_reached();
+        }
+
+        if (addr != INADDR_ANY) {
+                r = dhcp_message_append_option_be32(message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr);
+                if (r < 0)
+                        return r;
+        }
+
+        /* DHCPRELEASE and DHCPDECLINE MUST NOT contain any other options. */
+        if (IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)) {
+                *ret = TAKE_PTR(message);
+                return 0;
+        }
+
+        /* Parameter Request List (55)
+         *
+         * RFC2131 section 3.5:
+         * in its initial DHCPDISCOVER or DHCPREQUEST message, a client may provide the server with a list of
+         * specific parameters the client is interested in. If the client includes a list of parameters in a
+         * DHCPDISCOVER message, it MUST include that list in any subsequent DHCPREQUEST messages.
+         *
+         * RFC7844 section 3:
+         * MAY contain the Parameter Request List option.
+         *
+         * RFC7844 section 3.6:
+         * The client intending to protect its privacy SHOULD only request a minimal number of options in the
+         * PRL and SHOULD also randomly shuffle the ordering of option codes in the PRL. If this random
+         * ordering cannot be implemented, the client MAY order the option codes in the PRL by option code
+         * number (lowest to highest).
+         *
+         * NOTE: using PRL options that Windows 10 RFC7844 implementation uses. */
+        if (client->anonymize) {
+                static const uint8_t default_req_opts_anonymize[] = {
+                        SD_DHCP_OPTION_SUBNET_MASK,                     /* 1 */
+                        SD_DHCP_OPTION_ROUTER,                          /* 3 */
+                        SD_DHCP_OPTION_DOMAIN_NAME_SERVER,              /* 6 */
+                        SD_DHCP_OPTION_DOMAIN_NAME,                     /* 15 */
+                        SD_DHCP_OPTION_ROUTER_DISCOVERY,                /* 31 */
+                        SD_DHCP_OPTION_STATIC_ROUTE,                    /* 33 */
+                        SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION,     /* 43 */
+                        SD_DHCP_OPTION_NETBIOS_NAME_SERVER,             /* 44 */
+                        SD_DHCP_OPTION_NETBIOS_NODE_TYPE,               /* 46 */
+                        SD_DHCP_OPTION_NETBIOS_SCOPE,                   /* 47 */
+                        SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,          /* 121 */
+                        SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE,  /* 249 */
+                        SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,     /* 252 */
+                };
+
+                r = dhcp_message_append_option(
+                                message,
+                                SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
+                                ELEMENTSOF(default_req_opts_anonymize),
+                                default_req_opts_anonymize);
+                if (r < 0)
+                        return r;
+
+                /* RFC7844 section 3:
+                 * SHOULD NOT contain any other option (when running on anonymize mode). */
+                *ret = TAKE_PTR(message);
+                return 0;
+        }
+
+        /* When not on anonymized mode, use the default + user requested options. */
+        r = dhcp_message_append_option_parameter_request_list(message, client->req_opts);
+        if (r < 0)
+                return r;
+
+        /* Maximum Message Size (57)
+         *
+         * RFC2131 section 3.5:
+         * The client SHOULD include the ’maximum DHCP message size’ option to let the server know how
+         * large the server may make its DHCP messages.
+         *
+         * Note (from ConnMan): Some DHCP servers will send bigger DHCP packets than the defined default size
+         * unless the Maximum Message Size option is explicitly set.
+         *
+         * RFC3442 "Requirements to Avoid Sizing Constraints":
+         * Because a full routing table can be quite large, the standard 576 octet maximum size for a DHCP
+         * message may be too short to contain some legitimate Classless Static Route options. Because of
+         * this, clients implementing the Classless Static Route option SHOULD send a Maximum DHCP Message
+         * Size [4] option if the DHCP client's TCP/IP stack is capable of receiving larger IP datagrams.
+         * In this case, the client SHOULD set the value of this option to at least the MTU of the interface
+         * that the client is configuring. The client MAY set the value of this option higher, up to the size
+         * of the largest UDP packet it is prepared to accept. (Note that the value specified in the Maximum
+         * DHCP Message Size option is the total maximum packet size, including IP and UDP headers.) */
+        r = dhcp_message_append_option_u16(
+                        message,
+                        SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
+                        CLAMP(client->mtu, (uint32_t) IPV4_MIN_REASSEMBLY_SIZE, (uint32_t) UINT16_MAX));
+        if (r < 0)
+                return r;
+
+        /* Hostname (12) or FQDN (81)
+         *
+         * Note, it is unclear from RFC 2131 if client should send hostname in DHCPDISCOVER but dhclient does
+         * and so we do as well. */
+        r = dhcp_message_append_option_hostname(
+                        message,
+                        DHCP_FQDN_FLAG_S, /* Request server to perform A RR DNS updates */
+                        /* is_client= */ true,
+                        client->hostname);
+        if (r < 0)
+                return r;
+
+        /* Vendor Specific (43) */
+        r = dhcp_message_append_option_sub_tlv(
+                        message,
+                        SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION,
+                        client->vendor_options);
+        if (r < 0)
+                return r;
+
+        /* Vendor Class Identifier (60) */
+        r = dhcp_message_append_option_string(
+                        message,
+                        SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER,
+                        client->vendor_class_identifier);
+        if (r < 0)
+                return r;
+
+        /* User Class (77) */
+        r = dhcp_message_append_option_length_prefixed_data(
+                        message,
+                        SD_DHCP_OPTION_USER_CLASS,
+                        /* length_size= */ 1,
+                        &client->user_class);
+        if (r < 0)
+                return r;
+
+        /* Rapid Commit (80): only for DHCPDISCOVER */
+        if (client->rapid_commit && type == DHCP_DISCOVER) {
+                r = dhcp_message_append_option_flag(message, SD_DHCP_OPTION_RAPID_COMMIT);
+                if (r < 0)
+                        return r;
+        }
+
+        /* MUD URL (161) */
+        r = dhcp_message_append_option_string(message, SD_DHCP_OPTION_MUD_URL, client->mudurl);
+        if (r < 0)
+                return r;
+
+        r = dhcp_message_append_option_tlv(message, client->extra_options);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(message);
+        return 0;
+}
+
+int dhcp_client_send_message(sd_dhcp_client *client, uint8_t type) {
+        int r;
+
+        assert(client);
+
+        _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL;
+        r = client_new_message(client, type, &message);
+        if (r < 0)
+                return r;
+
+        switch (type) {
+        case DHCP_DISCOVER:
+                r = client_send_raw(client, message, /* expect_reply= */ true);
+                break;
+        case DHCP_REQUEST:
+                if (client->state == DHCP_STATE_RENEWING)
+                        r = client_send_udp(client, message, /* expect_reply= */ true);
+                else
+                        r = client_send_raw(client, message, /* expect_reply= */ true);
+                break;
+        case DHCP_RELEASE:
+                r = client_send_udp(client, message, /* expect_reply= */ false);
+                break;
+        case DHCP_DECLINE:
+                r = client_send_raw(client, message, /* expect_reply= */ false);
+                break;
+        default:
+                r = -EINVAL;
+        }
+        if (r < 0)
+                return r;
+
+        if (client->bootp)
+                log_dhcp_client(client, "BOOTREQUEST");
+        else if (type == DHCP_REQUEST)
+                log_dhcp_client(client, "%s (%s)",
+                                dhcp_message_type_to_string(type),
+                                dhcp_state_to_string(client->state));
+        else
+                log_dhcp_client(client, "%s", dhcp_message_type_to_string(type));
+        return 0;
+}
index 2dbb6a0878dcce96eb442c294877070e44781072..d5d65a31cce505e7105ff7dec24616dd8af6b1be 100644 (file)
@@ -3,16 +3,4 @@
 
 #include "sd-forward.h"
 
-#include "dhcp-protocol.h"
-
-int dhcp_client_send_raw(
-                sd_dhcp_client *client,
-                bool expect_reply,
-                DHCPPacket *packet,
-                size_t optoffset);
-
-int dhcp_client_send_udp(
-                sd_dhcp_client *client,
-                bool expect_reply,
-                DHCPPacket *packet,
-                size_t optoffset);
+int dhcp_client_send_message(sd_dhcp_client *client, uint8_t type);
index 463f95ea29bf1cd2a9cd2d0192695f8fba147fdf..e2513567a2f58d3ce30c6065f12d508e6238a2b0 100644 (file)
@@ -24,7 +24,6 @@
 #include "network-common.h"
 #include "random-util.h"
 #include "set.h"
-#include "sort-util.h"
 #include "string-table.h"
 #include "string-util.h"
 #include "time-util.h"
@@ -715,495 +714,6 @@ static usec_t client_compute_reacquisition_timeout(usec_t now_usec, usec_t expir
         return MAX(usec_sub_unsigned(expire, now_usec) / 2, 60 * USEC_PER_SEC);
 }
 
-static int cmp_uint8(const uint8_t *a, const uint8_t *b) {
-        assert(a);
-        assert(b);
-
-        return CMP(*a, *b);
-}
-
-static int client_message_init(
-                sd_dhcp_client *client,
-                uint8_t type,
-                DHCPPacket **ret_packet,
-                size_t *ret_optlen,
-                size_t *ret_optoffset) {
-
-        _cleanup_free_ DHCPPacket *packet = NULL;
-        size_t optlen, optoffset, size;
-        usec_t time_now;
-        uint16_t secs;
-        int r;
-
-        assert(client);
-        assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE));
-        assert(ret_packet);
-        assert(ret_optlen);
-        assert(ret_optoffset);
-
-        optlen = DHCP_MIN_OPTIONS_SIZE;
-        size = sizeof(DHCPPacket) + optlen;
-
-        packet = malloc0(size);
-        if (!packet)
-                return -ENOMEM;
-        if (client->bootp) {
-                /* BOOTP supports options, but only DHCP_OPTION_END is used. The rest of the 64-byte buffer
-                 * is set to zero, per RFC1542. Allow for this by initialaizing optoffset to 0. */
-                optoffset = 0;
-                r = bootp_message_init(
-                                &packet->dhcp, BOOTREQUEST, client->xid, client->arp_type,
-                                client->hw_addr.length, client->hw_addr.bytes);
-        } else
-                r = dhcp_message_init(
-                                &packet->dhcp, BOOTREQUEST, client->xid, client->arp_type,
-                                client->hw_addr.length, client->hw_addr.bytes,
-                                type, optlen, &optoffset);
-        if (r < 0)
-                return r;
-
-        /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers
-           refuse to issue an DHCP lease if 'secs' is set to zero */
-        r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
-        if (r < 0)
-                return r;
-        assert(time_now >= client->start_time);
-
-        /* seconds between sending first and last DISCOVER
-         * must always be strictly positive to deal with broken servers */
-        secs = ((time_now - client->start_time) / USEC_PER_SEC) ?: 1;
-        packet->dhcp.secs = htobe16(secs);
-
-        /* RFC2131 section 4.1
-           A client that cannot receive unicast IP datagrams until its protocol
-           software has been configured with an IP address SHOULD set the
-           BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or
-           DHCPREQUEST messages that client sends.  The BROADCAST bit will
-           provide a hint to the DHCP server and BOOTP relay agent to broadcast
-           any messages to the client on the client's subnet.
-
-           Note: some interfaces needs this to be enabled, but some networks
-           needs this to be disabled as broadcasts are filteretd, so this
-           needs to be configurable */
-        if (client->request_broadcast || client->arp_type != ARPHRD_ETHER)
-                packet->dhcp.flags = htobe16(0x8000);
-
-        if (client->bootp) {
-                *ret_optlen = optlen;
-                *ret_optoffset = optoffset;
-                *ret_packet = TAKE_PTR(packet);
-                return 0;
-        }
-
-        /* Some DHCP servers will refuse to issue an DHCP lease if the Client
-           Identifier option is not set */
-        r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
-                               SD_DHCP_OPTION_CLIENT_IDENTIFIER,
-                               client->client_id.size,
-                               client->client_id.raw);
-        if (r < 0)
-                return r;
-
-        /* RFC2131 section 3.5:
-           in its initial DHCPDISCOVER or DHCPREQUEST message, a
-           client may provide the server with a list of specific
-           parameters the client is interested in. If the client
-           includes a list of parameters in a DHCPDISCOVER message,
-           it MUST include that list in any subsequent DHCPREQUEST
-           messages.
-         */
-
-        /* RFC7844 section 3:
-           MAY contain the Parameter Request List option. */
-        /* NOTE: in case that there would be an option to do not send
-         * any PRL at all, the size should be checked before sending */
-        if (!set_isempty(client->req_opts) && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) {
-                _cleanup_free_ uint8_t *opts = NULL;
-                size_t n_opts, i = 0;
-                void *val;
-
-                n_opts = set_size(client->req_opts);
-                opts = new(uint8_t, n_opts);
-                if (!opts)
-                        return -ENOMEM;
-
-                SET_FOREACH(val, client->req_opts)
-                        opts[i++] = PTR_TO_UINT8(val);
-                assert(i == n_opts);
-
-                /* For anonymizing the request, let's sort the options. */
-                typesafe_qsort(opts, n_opts, cmp_uint8);
-
-                r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
-                                       SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
-                                       n_opts, opts);
-                if (r < 0)
-                        return r;
-        }
-
-        /* RFC2131 section 3.5:
-           The client SHOULD include the ’maximum DHCP message size’ option to
-           let the server know how large the server may make its DHCP messages.
-
-           Note (from ConnMan): Some DHCP servers will send bigger DHCP packets
-           than the defined default size unless the Maximum Message Size option
-           is explicitly set
-
-           RFC3442 "Requirements to Avoid Sizing Constraints":
-           Because a full routing table can be quite large, the standard 576
-           octet maximum size for a DHCP message may be too short to contain
-           some legitimate Classless Static Route options.  Because of this,
-           clients implementing the Classless Static Route option SHOULD send a
-           Maximum DHCP Message Size [4] option if the DHCP client's TCP/IP
-           stack is capable of receiving larger IP datagrams.  In this case, the
-           client SHOULD set the value of this option to at least the MTU of the
-           interface that the client is configuring.  The client MAY set the
-           value of this option higher, up to the size of the largest UDP packet
-           it is prepared to accept.  (Note that the value specified in the
-           Maximum DHCP Message Size option is the total maximum packet size,
-           including IP and UDP headers.)
-         */
-        /* RFC7844 section 3:
-           SHOULD NOT contain any other option. */
-        if (!client->anonymize && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) {
-                be16_t max_size = htobe16(MIN(client->mtu - DHCP_IP_UDP_SIZE, (uint32_t) UINT16_MAX));
-                r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
-                                       SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
-                                       2, &max_size);
-                if (r < 0)
-                        return r;
-        }
-
-        *ret_optlen = optlen;
-        *ret_optoffset = optoffset;
-        *ret_packet = TAKE_PTR(packet);
-
-        return 0;
-}
-
-static int client_append_fqdn_option(
-                DHCPMessage *message,
-                size_t optlen,
-                size_t *optoffset,
-                const char *fqdn) {
-
-        uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH];
-        int r;
-
-        buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */
-                    DHCP_FQDN_FLAG_E;  /* Canonical wire format */
-        buffer[1] = 0;                 /* RCODE1 (deprecated) */
-        buffer[2] = 0;                 /* RCODE2 (deprecated) */
-
-        r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false);
-        if (r > 0)
-                r = dhcp_option_append(message, optlen, optoffset, 0,
-                                       SD_DHCP_OPTION_FQDN, 3 + r, buffer);
-
-        return r;
-}
-
-static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) {
-        int r;
-
-        assert(client);
-
-        if (client->hostname) {
-                /* According to RFC 4702 "clients that send the Client FQDN option in
-                   their messages MUST NOT also send the Host Name option". Just send
-                   one of the two depending on the hostname type.
-                */
-                if (dns_name_is_single_label(client->hostname)) {
-                        /* it is unclear from RFC 2131 if client should send hostname in
-                           DHCPDISCOVER but dhclient does and so we do as well
-                        */
-                        r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
-                                               SD_DHCP_OPTION_HOST_NAME,
-                                               strlen(client->hostname), client->hostname);
-                } else
-                        r = client_append_fqdn_option(&packet->dhcp, optlen, optoffset,
-                                                      client->hostname);
-                if (r < 0)
-                        return r;
-        }
-
-        if (client->vendor_class_identifier) {
-                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
-                                       SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER,
-                                       strlen(client->vendor_class_identifier),
-                                       client->vendor_class_identifier);
-                if (r < 0)
-                        return r;
-        }
-
-        if (client->mudurl) {
-                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
-                                       SD_DHCP_OPTION_MUD_URL,
-                                       strlen(client->mudurl),
-                                       client->mudurl);
-                if (r < 0)
-                        return r;
-        }
-
-        if (!iovw_isempty(&client->user_class)) {
-                size_t sz = iovw_size(&client->user_class) + client->user_class.count;
-                if (sz <= UINT8_MAX) {
-                        _cleanup_free_ uint8_t *buf = new(uint8_t, sz);
-                        if (!buf)
-                                return -ENOMEM;
-
-                        uint8_t *p = buf;
-                        FOREACH_ARRAY(iovec, client->user_class.iovec, client->user_class.count) {
-                                assert(iovec->iov_len > 0 && iovec->iov_len <= UINT8_MAX);
-                                *p++ = iovec->iov_len;
-                                p = mempcpy(p, iovec->iov_base, iovec->iov_len);
-                        }
-
-                        r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
-                                               SD_DHCP_OPTION_USER_CLASS,
-                                               sz, buf);
-                        if (r < 0)
-                                return r;
-                }
-        }
-
-        if (client->extra_options) {
-                void *key;
-                struct iovec_wrapper *iovw;
-                HASHMAP_FOREACH_KEY(iovw, key, client->extra_options->entries) {
-                        uint32_t tag = PTR_TO_UINT32(key);
-
-                        FOREACH_ARRAY(iov, iovw->iovec, iovw->count) {
-                                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
-                                               tag, iov->iov_len, iov->iov_base);
-                                if (r < 0)
-                                        return r;
-                        }
-                }
-        }
-
-        if (!tlv_isempty(client->vendor_options)) {
-                _cleanup_(iovec_done) struct iovec iov = {};
-                r = tlv_build(client->vendor_options, &iov);
-                if (r < 0)
-                        return r;
-
-                r = dhcp_option_append(
-                                &packet->dhcp, optlen, optoffset, 0,
-                                SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION,
-                                iov.iov_len, iov.iov_base);
-                if (r < 0)
-                        return r;
-        }
-
-        return 0;
-}
-
-static int client_send_dhcp_discover(sd_dhcp_client *client) {
-        _cleanup_free_ DHCPPacket *discover = NULL;
-        size_t optoffset, optlen;
-        int r;
-
-        assert(client);
-
-        r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset);
-        if (r < 0)
-                return r;
-
-        /* the client may suggest values for the network address
-           and lease time in the DHCPDISCOVER message. The client may include
-           the ’requested IP address’ option to suggest that a particular IP
-           address be assigned, and may include the ’IP address lease time’
-           option to suggest the lease time it would like.
-         */
-        /* RFC7844 section 3:
-           SHOULD NOT contain any other option. */
-        if (!client->anonymize && client->last_addr != INADDR_ANY) {
-                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
-                                       SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
-                                       4, &client->last_addr);
-                if (r < 0)
-                        return r;
-        }
-
-        if (client->rapid_commit) {
-                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
-                                       SD_DHCP_OPTION_RAPID_COMMIT, 0, NULL);
-                if (r < 0)
-                        return r;
-        }
-
-        r = client_append_common_discover_request_options(client, discover, &optoffset, optlen);
-        if (r < 0)
-                return r;
-
-        r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
-                               SD_DHCP_OPTION_END, 0, NULL);
-        if (r < 0)
-                return r;
-
-        r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset);
-        if (r < 0)
-                return r;
-
-        log_dhcp_client(client, "DISCOVER");
-        return 0;
-}
-
-static int client_send_bootp_discover(sd_dhcp_client *client) {
-        _cleanup_free_ DHCPPacket *discover = NULL;
-        size_t optoffset, optlen;
-        int r;
-
-        assert(client);
-
-        r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset);
-        if (r < 0)
-                return r;
-
-        r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL);
-        if (r < 0)
-                return r;
-
-        /* RFC1542 section 3.5:
-         * if the client has no information to communicate to the server, the octet immediately following the
-         * magic cookie SHOULD be set to the "End" tag (255) and the remaining octets of the 'vend' field
-         * SHOULD be set to zero.
-         *
-         * Use this RFC, along with the fact that some BOOTP servers require a 64-byte vend field, to suggest
-         * that we always zero and send 64 bytes in the options field. The first four bites are the "magic"
-         * field, so this only needs to add 60 bytes. */
-        if (optoffset < 60 && optlen >= 60) {
-                memzero(&discover->dhcp.options[optoffset], optlen - optoffset);
-                optoffset = 60;
-        }
-
-        r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset);
-        if (r < 0)
-                return r;
-
-        log_dhcp_client(client, "DISCOVER");
-        return 0;
-}
-
-static int client_send_discover(sd_dhcp_client *client) {
-        assert(client);
-        assert(client->state == DHCP_STATE_SELECTING);
-
-        return client->bootp ?
-                client_send_bootp_discover(client) :
-                client_send_dhcp_discover(client);
-}
-
-static int client_send_request(sd_dhcp_client *client) {
-        _cleanup_free_ DHCPPacket *request = NULL;
-        size_t optoffset, optlen;
-        int r;
-
-        assert(client);
-        assert(!client->bootp);
-
-        r = client_message_init(client, DHCP_REQUEST, &request, &optlen, &optoffset);
-        if (r < 0)
-                return r;
-
-        switch (client->state) {
-        /* See RFC2131 section 4.3.2 (note that there is a typo in the RFC,
-           SELECTING should be REQUESTING)
-         */
-
-        case DHCP_STATE_REQUESTING:
-                /* Client inserts the address of the selected server in ’server
-                   identifier’, ’ciaddr’ MUST be zero, ’requested IP address’ MUST be
-                   filled in with the yiaddr value from the chosen DHCPOFFER.
-                 */
-
-                r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
-                                       SD_DHCP_OPTION_SERVER_IDENTIFIER,
-                                       4, &client->lease->server_address);
-                if (r < 0)
-                        return r;
-
-                r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
-                                       SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
-                                       4, &client->lease->address);
-                if (r < 0)
-                        return r;
-                break;
-
-        case DHCP_STATE_REBOOTING:
-                /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
-                   option MUST be filled in with client’s notion of its previously
-                   assigned address. ’ciaddr’ MUST be zero.
-                 */
-                r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
-                                       SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
-                                       4, &client->last_addr);
-                if (r < 0)
-                        return r;
-                break;
-
-        case DHCP_STATE_RENEWING:
-                /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
-                   option MUST NOT be filled in, ’ciaddr’ MUST be filled in with
-                   client’s IP address.
-                */
-
-        case DHCP_STATE_REBINDING:
-                /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
-                   option MUST NOT be filled in, ’ciaddr’ MUST be filled in with
-                   client’s IP address.
-
-                   This message MUST be broadcast to the 0xffffffff IP broadcast address.
-                 */
-                request->dhcp.ciaddr = client->lease->address;
-                break;
-
-        default:
-                assert_not_reached();
-        }
-
-        r = client_append_common_discover_request_options(client, request, &optoffset, optlen);
-        if (r < 0)
-                return r;
-
-        r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
-                               SD_DHCP_OPTION_END, 0, NULL);
-        if (r < 0)
-                return r;
-
-        if (client->state == DHCP_STATE_RENEWING)
-                r = dhcp_client_send_udp(client, /* expect_reply= */ true, request, optoffset);
-        else
-                r = dhcp_client_send_raw(client, /* expect_reply= */ true, request, optoffset);
-        if (r < 0)
-                return r;
-
-        switch (client->state) {
-
-        case DHCP_STATE_REQUESTING:
-                log_dhcp_client(client, "REQUEST (requesting)");
-                break;
-
-        case DHCP_STATE_REBOOTING:
-                log_dhcp_client(client, "REQUEST (rebooting)");
-                break;
-
-        case DHCP_STATE_RENEWING:
-                log_dhcp_client(client, "REQUEST (renewing)");
-                break;
-
-        case DHCP_STATE_REBINDING:
-                log_dhcp_client(client, "REQUEST (rebinding)");
-                break;
-
-        default:
-                assert_not_reached();
-        }
-
-        return 0;
-}
-
 static int client_timeout_resend(
                 sd_event_source *s,
                 uint64_t usec,
@@ -1282,7 +792,7 @@ static int client_timeout_resend(
 
         switch (client->state) {
         case DHCP_STATE_SELECTING:
-                r = client_send_discover(client);
+                r = dhcp_client_send_message(client, DHCP_DISCOVER);
                 if (r < 0 && client->discover_attempt >= client->max_discover_attempts)
                         goto error;
 
@@ -1291,7 +801,7 @@ static int client_timeout_resend(
                 break;
 
         case DHCP_STATE_REBOOTING:
-                r = client_send_request(client);
+                r = dhcp_client_send_message(client, DHCP_REQUEST);
                 if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS_ON_REBOOTING)
                         goto restart;
                 break;
@@ -1299,7 +809,7 @@ static int client_timeout_resend(
         case DHCP_STATE_REQUESTING:
         case DHCP_STATE_RENEWING:
         case DHCP_STATE_REBINDING:
-                r = client_send_request(client);
+                r = dhcp_client_send_message(client, DHCP_REQUEST);
                 if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS)
                         goto restart;
                 break;
@@ -2109,99 +1619,31 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
         return r;
 }
 
-static int client_send_release_or_decline(sd_dhcp_client *client, uint8_t type) {
+int sd_dhcp_client_send_decline(sd_dhcp_client *client) {
         int r;
 
-        assert(IN_SET(type, DHCP_RELEASE, DHCP_DECLINE));
-
         if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp)
-                return 0; /* there is nothing to release or decline */
-
-        const char *name = type == DHCP_RELEASE ? "RELEASE" : "DECLINE";
-
-        _cleanup_free_ DHCPPacket *packet = NULL;
-        size_t optoffset, optlen;
-        r = client_message_init(client, type, &packet, &optlen, &optoffset);
-        if (r < 0)
-                return log_dhcp_client_errno(client, r, "Failed to initialize DHCP %s message: %m", name);
-
-        /* See RFC 2131, Table 5 */
-        switch (type) {
-        case DHCP_RELEASE:
-                /* On release, the acquired address must be set in ciaddr. */
-                packet->dhcp.ciaddr = client->lease->address;
-                break;
-
-        case DHCP_DECLINE:
-                /* On decline, the acquired address must be set in Requested IP Address option. */
-                r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0,
-                                       SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
-                                       4, &client->lease->address);
-                if (r < 0)
-                        return log_dhcp_client_errno(
-                                        client, r,
-                                        "Failed to append Requested IP Address option to DHCP %s message: %m",
-                                        name);
-                break;
-
-        default:
-                assert_not_reached();
-        }
-
-        /* In both cases, the server identifier must be set. */
-        r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0,
-                               SD_DHCP_OPTION_SERVER_IDENTIFIER,
-                               4, &client->lease->server_address);
-        if (r < 0)
-                return log_dhcp_client_errno(
-                                client, r,
-                                "Failed to append Server Identifier option to DHCP %s message: %m",
-                                name);
+                return 0; /* there is nothing to decline */
 
-        r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, /* overload= */ 0,
-                               SD_DHCP_OPTION_END, /* optlen= */ 0, /* optval= */ NULL);
+        r = dhcp_client_send_message(client, DHCP_DECLINE);
         if (r < 0)
-                return log_dhcp_client_errno(
-                                client, r,
-                                "Failed to finalize DHCP %s message: %m",
-                                name);
-
-        switch (type) {
-        case DHCP_RELEASE:
-                r = dhcp_client_send_udp(client, /* expect_reply= */ false, packet, optoffset);
-                break;
-        case DHCP_DECLINE:
-                r = dhcp_client_send_raw(client, /* expect_reply= */ false, packet, optoffset);
-                break;
-        default:
-                assert_not_reached();
-        }
-        if (r < 0)
-                return log_dhcp_client_errno(
-                                client, r,
-                                "Failed to send DHCP %s message: %m",
-                                name);
+                return r;
 
-        log_dhcp_client(client, "%s", name);
-        return 1; /* sent */
+        /* This function is mostly called when the acquired address conflicts with another host.
+         * Restarting the daemon to acquire another address. */
+        return client_restart(client);
 }
 
-int sd_dhcp_client_send_decline(sd_dhcp_client *client) {
-        int r;
-
-        r = client_send_release_or_decline(client, DHCP_DECLINE);
-        if (r <= 0)
-                return r;
+static int client_send_release(sd_dhcp_client *client) {
+        assert(client);
 
-        log_dhcp_client(client, "DECLINE");
+        if (!client->send_release)
+                return 0;
 
-        /* This function is mostly called when the acquired address conflicts with another host.
-         * Restarting the daemon to acquire another address. */
-        r = client_restart(client);
-        if (r < 0)
-                return r;
+        if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp)
+                return 0; /* there is nothing to release */
 
-        return 1; /* sent and restarted. */
+        return dhcp_client_send_message(client, DHCP_RELEASE);
 }
 
 int sd_dhcp_client_stop(sd_dhcp_client *client) {
@@ -2210,8 +1652,7 @@ int sd_dhcp_client_stop(sd_dhcp_client *client) {
 
         DHCP_CLIENT_DONT_DESTROY(client);
 
-        if (client->send_release)
-                (void) client_send_release_or_decline(client, DHCP_RELEASE);
+        (void) client_send_release(client);
 
         client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
         return 0;
index d7dd01cc813ae45ac0087f3ab37253b923602af3..c62f46222ab0ec437e82d141f4d1e5d4fb9ba546 100644 (file)
@@ -352,7 +352,8 @@ static void test_addr_acq_recv_request(size_t size, DHCPMessage *request) {
         ASSERT_OK_EQ(dhcp_option_parse(request, size, check_options, NULL, NULL), DHCP_REQUEST);
         ASSERT_EQ(request->xid, xid);
 
-        ASSERT_EQ(msg_bytes[size - 1], SD_DHCP_OPTION_END);
+        uint8_t *end = ASSERT_NOT_NULL(memrchr(msg_bytes, SD_DHCP_OPTION_END, size));
+        ASSERT_TRUE(memeqzero(end + 1, msg_bytes + size - end - 1));
 
         log_info("  recv DHCP Request  0x%08x", be32toh(xid));
 
@@ -374,7 +375,8 @@ static void test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) {
 
         ASSERT_OK_EQ(dhcp_option_parse(discover, size, check_options, NULL, NULL), DHCP_DISCOVER);
 
-        ASSERT_EQ(msg_bytes[size - 1], SD_DHCP_OPTION_END);
+        uint8_t *end = ASSERT_NOT_NULL(memrchr(msg_bytes, SD_DHCP_OPTION_END, size));
+        ASSERT_TRUE(memeqzero(end + 1, msg_bytes + size - end - 1));
 
         xid = discover->xid;