]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-dhcp-server: use sd_dhcp_message to parse received messages
authorYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 7 May 2026 04:30:00 +0000 (13:30 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 22 May 2026 00:33:15 +0000 (09:33 +0900)
This is mostly refactoring. This does not change basic behavior, but
changes/fixes some minor/corner cases, e.g.

- extend the minimum default lease time from 1 second to 30 seconds, as
  1 second is too short and causes the network unstable (though 30
  seconds is stll too short, but hopefully that does not make the
  network unstable).
- error code on broken/malicious message received may be changed.

src/libsystemd-network/dhcp-server-request.c
src/libsystemd-network/dhcp-server-request.h
src/libsystemd-network/dhcp-server-send.c
src/libsystemd-network/sd-dhcp-server-lease.c
src/libsystemd-network/test-dhcp-server.c

index 9aab83d0dd4e5fc1d74f369ebd5b10dfe63587d4..b83cb01d8b8d7ae9fda0d405031a457b148bb7d6 100644 (file)
@@ -1,9 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <net/if_arp.h>
+
 #include "sd-event.h"
 
 #include "alloc-util.h"
-#include "dhcp-protocol.h"
+#include "dhcp-message.h"
 #include "dhcp-server-internal.h"
 #include "dhcp-server-lease-internal.h"
 #include "dhcp-server-request.h"
 #include "fd-util.h"
 #include "hashmap.h"
 #include "iovec-util.h"
-#include "memory-util.h"
+#include "ip-util.h"
+#include "set.h"
 #include "siphash24.h"
 #include "socket-util.h"
 #include "string-util.h"
-#include "unaligned.h"
 
 static DHCPRequest* dhcp_request_free(DHCPRequest *req) {
         if (!req)
                 return NULL;
 
-        free(req->hostname);
+        sd_dhcp_message_unref(req->message);
+        set_free(req->parameter_request_list);
         return mfree(req);
 }
 
@@ -53,27 +56,22 @@ int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_
         return 0;
 }
 
-static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) {
+static int dhcp_request_set_client_id(DHCPRequest *req) {
         assert(req);
-        assert(message);
-
-        req->message = message;
+        assert(req->message);
 
-        if (message->hlen > sizeof(message->chaddr))
-                return -EBADMSG;
-
-        req->hw_addr.length = req->message->hlen;
-        memcpy_safe(req->hw_addr.bytes, message->chaddr, message->hlen);
+        /* Genuine client ID from Client Identifier option. The option may not be set. */
+        (void) dhcp_message_get_option_client_id(req->message, &req->client_id);
 
         /* Fake client ID generated from the DHCP header.
          * The client ID type 0 and 255 are special. So do not use if htype is 0 or 255.
          * Note, Some hardware type (e.g. Infiniband) may not set chaddr field. */
-        if (!IN_SET(req->message->htype, 0, UINT8_MAX))
+        if (!IN_SET(req->message->header.htype, 0, UINT8_MAX))
                 (void) sd_dhcp_client_id_set(
                                 &req->client_id_by_header,
-                                req->message->htype,
-                                req->message->chaddr,
-                                req->message->hlen);
+                                req->message->header.htype,
+                                req->message->header.chaddr,
+                                req->message->header.hlen);
 
         /* If Client Identifier option is unspecified, use the generated one. */
         if (!sd_dhcp_client_id_is_set(&req->client_id))
@@ -83,100 +81,140 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes
         if (!sd_dhcp_client_id_is_set(&req->client_id))
                 return -EBADMSG;
 
-        if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
-                req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
+        return 0;
+}
 
-        if (req->lifetime <= 0)
-                req->lifetime = MAX(USEC_PER_SEC, server->default_lease_time);
+static int dhcp_request_set_server_identifier(DHCPRequest *req) {
+        int r;
 
-        if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time)
-                req->lifetime = server->max_lease_time;
+        assert(req);
+        assert(req->message);
+
+        bool mandatory = IN_SET(req->type, DHCP_RELEASE, DHCP_DECLINE);
 
+        be32_t a;
+        r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &a);
+        if (r < 0)
+                return mandatory ? r : 0;
+
+        req->server_address = a;
         return 0;
 }
 
-static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) {
-        DHCPRequest *req = ASSERT_PTR(userdata);
+static int dhcp_request_set_maximum_message_size(DHCPRequest *req) {
         int r;
 
-        switch (code) {
-        case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
-                if (len == 4)
-                        req->lifetime = unaligned_be32_sec_to_usec(option, /* max_as_infinity= */ true);
-
-                break;
-        case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS:
-                if (len == 4)
-                        memcpy(&req->requested_ip, option, sizeof(be32_t));
+        assert(req);
+        assert(req->message);
 
-                break;
-        case SD_DHCP_OPTION_SERVER_IDENTIFIER:
-                if (len == 4)
-                        memcpy(&req->server_address, option, sizeof(be32_t));
+        uint16_t sz;
+        r = dhcp_message_get_option_u16(req->message, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, &sz);
+        if (r < 0)
+                return r;
 
-                break;
-        case SD_DHCP_OPTION_CLIENT_IDENTIFIER:
-                if (client_id_size_is_valid(len))
-                        (void) sd_dhcp_client_id_set_raw(&req->client_id, option, len);
+        /* RFC 2132 section 9.10:
+         * The minimum legal value is 576 octets. */
+        if (sz < IPV4_MIN_REASSEMBLY_SIZE)
+                return -EBADMSG;
 
-                break;
-        case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
+        req->max_message_size = sz;
+        return 0;
+}
 
-                if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket))
-                        req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket);
+static int dhcp_request_set_lifetime(DHCPRequest *req, sd_dhcp_server *server) {
+        assert(req);
+        assert(req->message);
+        assert(server);
 
-                break;
-        case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION:
-                req->agent_info_option = (uint8_t*)option - 2;
+        (void) dhcp_message_get_option_sec(
+                        req->message,
+                        SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
+                        /* max_as_infinity= */ true,
+                        &req->lifetime);
 
-                break;
-        case SD_DHCP_OPTION_HOST_NAME: {
-                _cleanup_free_ char *p = NULL;
+        /* If unset (or zero is specified...), use the default lease time. */
+        if (req->lifetime <= 0)
+                req->lifetime = MAX(30 * USEC_PER_SEC, server->default_lease_time);
 
-                r = dhcp_option_parse_hostname(option, len, &p);
-                if (r < 0)
-                        log_debug_errno(r, "Failed to parse hostname, ignoring: %m");
-                else
-                        free_and_replace(req->hostname, p);
-                break;
-        }
-        case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST:
-                req->parameter_request_list = option;
-                req->parameter_request_list_len = len;
-                break;
-
-        case SD_DHCP_OPTION_RAPID_COMMIT:
-                req->rapid_commit = true;
-                break;
-        }
+        /* If the requested lifetime is too long, then cap it with the maximum lease time. */
+        if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time)
+                req->lifetime = server->max_lease_time;
 
         return 0;
 }
 
-static int dhcp_server_parse_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, DHCPRequest **ret, char **ret_error_message) {
+static int dhcp_server_parse_message(sd_dhcp_server *server, const struct iovec *iov, DHCPRequest **ret) {
         int r;
 
         assert(server);
-        assert(message);
+        assert(iov);
         assert(ret);
-        assert(ret_error_message);
 
-        _cleanup_(dhcp_request_freep) DHCPRequest *req = new0(DHCPRequest, 1);
+        _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL;
+        r = dhcp_message_parse(
+                        iov,
+                        BOOTREQUEST,
+                        /* xid= */ NULL,
+                        ARPHRD_NONE,
+                        /* hw_addr= */ NULL,
+                        &message);
+        if (r < 0)
+                return r;
+
+        /* A DHCP relay agent is running on the interface with the same address??
+         * Should be malicious message. */
+        if (message->header.giaddr == server->address)
+                return -EBADMSG;
+
+        _cleanup_(dhcp_request_freep) DHCPRequest *req = new(DHCPRequest, 1);
         if (!req)
                 return -ENOMEM;
 
-        _cleanup_free_ char *error_message = NULL;
-        r = dhcp_option_parse(message, length, parse_request, req, &error_message);
+        *req = (DHCPRequest) {
+                .message = sd_dhcp_message_ref(message),
+
+                /* RFC 2132 section 9.10:
+                 * The minimum legal value is 576 octets. */
+                .max_message_size = IPV4_MIN_REASSEMBLY_SIZE,
+        };
+
+        /* client hardware address
+         * Note, hlen and chaddr may not be set for non-ethernet interface.
+         * See RFC2131 section 4.1. */
+        r = dhcp_message_get_hw_addr(message, &req->hw_addr);
+        if (r < 0)
+                return r;
+
+        /* Message Type: mandatory */
+        r = dhcp_message_get_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, &req->type);
         if (r < 0)
                 return r;
-        req->type = r;
 
-        r = ensure_sane_request(server, req, message);
+        /* Client Identifier: Mandatory. If not set, fall back to use chaddr. */
+        r = dhcp_request_set_client_id(req);
         if (r < 0)
                 return r;
 
+        /* Server Identifier */
+        r = dhcp_request_set_server_identifier(req);
+        if (r < 0)
+                return r;
+
+        /* Maximum Message Size: optional */
+        (void) dhcp_request_set_maximum_message_size(req);
+
+        if (req->max_message_size >= sizeof(DHCPPacket))
+                req->max_optlen = req->max_message_size - sizeof(DHCPPacket);
+        else
+                req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
+
+        /* Lifetime: optional */
+        (void) dhcp_request_set_lifetime(req, server);
+
+        /* Parameter Request List: optional */
+        (void) dhcp_message_get_option_parameter_request_list(message, &req->parameter_request_list);
+
         *ret = TAKE_PTR(req);
-        *ret_error_message = TAKE_PTR(error_message);
         return 0;
 }
 
@@ -195,10 +233,9 @@ static int dhcp_server_ack(sd_dhcp_server *server, DHCPRequest *req) {
         if (r < 0)
                 return log_dhcp_server_errno(server, r, "Could not send ACK: %m");
 
-        log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->xid));
+        log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->header.xid));
 
         dhcp_server_on_lease_change(server);
-
         return DHCP_ACK;
 }
 
@@ -212,7 +249,7 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req
                 *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id),
                 *static_lease = dhcp_server_get_static_lease(server, req);
 
-        log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->xid));
+        log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->header.xid));
 
         /* for now pick a random free address from the pool */
         if (static_lease) {
@@ -226,7 +263,6 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req
                 req->address = static_lease->address;
 
         } else if (existing_lease && dhcp_server_address_is_in_pool(server, existing_lease->address))
-
                 /* If we previously assigned an address to the host, then reuse it. */
                 req->address = existing_lease->address;
 
@@ -257,7 +293,8 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req
                 /* no free addresses left */
                 return 0;
 
-        if (server->rapid_commit && req->rapid_commit)
+        if (server->rapid_commit &&
+            dhcp_message_get_option_flag(req->message, SD_DHCP_OPTION_RAPID_COMMIT) >= 0)
                 return dhcp_server_ack(server, req);
 
         r = server_send_offer_or_ack(server, req, DHCP_OFFER);
@@ -265,11 +302,13 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req
                 /* this only fails on critical errors */
                 return log_dhcp_server_errno(server, r, "Could not send offer: %m");
 
-        log_dhcp_server(server, "OFFER (0x%x)", be32toh(req->message->xid));
+        log_dhcp_server(server, "OFFER (0x%x)", be32toh(req->message->header.xid));
         return DHCP_OFFER;
 }
 
 static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) {
+        int r;
+
         assert(server);
         assert(req);
 
@@ -281,54 +320,57 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req)
         bool init_reboot = false;
 
         /* see RFC 2131, section 4.3.2 */
-
         if (req->server_address != INADDR_ANY) {
                 log_dhcp_server(server, "REQUEST (selecting) (0x%x)",
-                                be32toh(req->message->xid));
+                                be32toh(req->message->header.xid));
 
                 /* SELECTING */
                 if (req->server_address != server->address)
-                        /* client did not pick us */
-                        return 0;
-
-                if (req->message->ciaddr != 0)
-                        /* this MUST be zero */
-                        return 0;
+                        return 0; /* The message is not for us. Let's silently ignore the packet. */
 
-                if (req->requested_ip == 0)
-                        /* this must be filled in with the yiaddr
-                           from the chosen OFFER */
+                if (req->message->header.ciaddr != INADDR_ANY) /* this MUST be zero */
                         return 0;
 
-                address = req->requested_ip;
-        } else if (req->requested_ip != 0) {
-                log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)",
-                                be32toh(req->message->xid));
+                /* this must be filled in with the yiaddr from the chosen OFFER */
+                r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &address);
+                if (r < 0)
+                        return r;
 
-                /* INIT-REBOOT */
-                if (req->message->ciaddr != 0)
-                        /* this MUST be zero */
-                        return 0;
+                if (address == INADDR_ANY)
+                        return -EBADMSG;
 
-                /* TODO: check more carefully if IP is correct */
-                address = req->requested_ip;
-                init_reboot = true;
-        } else {
+        } else if (req->message->header.ciaddr != INADDR_ANY) {
                 log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)",
-                                be32toh(req->message->xid));
+                                be32toh(req->message->header.xid));
 
                 /* REBINDING / RENEWING */
-                if (req->message->ciaddr == 0)
-                        /* this MUST be filled in with clients IP address */
-                        return 0;
 
-                address = req->message->ciaddr;
-        }
+                /* this must NOT be filled */
+                if (dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, /* ret= */ NULL) >= 0)
+                        return -EBADMSG;
 
-        /* Silently ignore Rapid Commit option in REQUEST message. */
-        req->rapid_commit = false;
+                address = req->message->header.ciaddr;
 
+        } else {
+                log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)",
+                                be32toh(req->message->header.xid));
+
+                /* INIT-REBOOT */
+                r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &address);
+                if (r < 0)
+                        return r;
+
+                if (address == INADDR_ANY)
+                        return -EBADMSG;
+
+                init_reboot = true;
+        }
+
+        /* Check if the request is consistent with the static lease. */
         if (static_lease) {
+                /* Found a static lease for the client ID. In this case, the server is explicitly configured
+                 * to manage the host. Hence, send NAK when the request is invalid. */
+
                 if (static_lease->address != address)
                         /* The client requested an address which is different from the static lease. Refusing. */
                         return server_send_nak_or_ignore(server, init_reboot, req);
@@ -341,7 +383,6 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req)
                 req->static_lease = static_lease;
                 req->address = address;
 
-                /* Found a static lease for the client ID. */
                 return dhcp_server_ack(server, req);
         }
 
@@ -356,7 +397,7 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req)
         return server_send_nak_or_ignore(server, init_reboot, req);
 }
 
-static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req, const char *error_message) {
+static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req) {
         assert(server);
         assert(req);
 
@@ -365,7 +406,9 @@ static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req,
 
         /* TODO: make sure we don't offer this address again for a while. */
 
-        log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message));
+        _cleanup_free_ char *e = NULL;
+        (void) dhcp_message_get_option_string(req->message, SD_DHCP_OPTION_ERROR_MESSAGE, &e);
+        log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->header.xid), strna(e));
         return 0;
 }
 
@@ -380,13 +423,13 @@ static int dhcp_server_process_release(sd_dhcp_server *server, DHCPRequest *req)
         if (!existing_lease)
                 return 0;
 
-        if (existing_lease->address != req->message->ciaddr)
+        if (existing_lease->address != req->message->header.ciaddr)
                 return -EBADMSG;
 
         sd_dhcp_server_lease_unref(existing_lease);
         dhcp_server_on_lease_change(server);
 
-        log_dhcp_server(server, "RELEASE (0x%x)", be32toh(req->message->xid));
+        log_dhcp_server(server, "RELEASE (0x%x)", be32toh(req->message->header.xid));
         return 0;
 }
 
@@ -396,15 +439,8 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
         assert(server);
         assert(message);
 
-        if (length < sizeof(DHCPMessage))
-                return 0;
-
-        if (message->op != BOOTREQUEST)
-                return 0;
-
         _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL;
-        _cleanup_free_ char *error_message = NULL;
-        r = dhcp_server_parse_message(server, message, length, &req, &error_message);
+        r = dhcp_server_parse_message(server, &IOVEC_MAKE(message, length), &req);
         if (r < 0)
                 return r;
 
@@ -420,7 +456,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
         case DHCP_REQUEST:
                 return dhcp_server_process_request(server, req);
         case DHCP_DECLINE:
-                return dhcp_server_process_decline(server, req, error_message);
+                return dhcp_server_process_decline(server, req);
         case DHCP_RELEASE:
                 return dhcp_server_process_release(server, req);
         default:
index e5738978a2ddc2504daa6978a8f6b1cfe0b5e975..f37d81bd145a6e49edc8c12be94c396cdb7ec630 100644 (file)
@@ -4,6 +4,7 @@
 #include "sd-dhcp-server-lease.h"
 
 #include "dhcp-client-id-internal.h"
+#include "dhcp-message.h"
 #include "dhcp-protocol.h"
 #include "ether-addr-util.h"
 #include "sd-forward.h"
@@ -12,7 +13,7 @@
 
 typedef struct DHCPRequest {
         /* received message */
-        DHCPMessage *message;
+        sd_dhcp_message *message;
         /* sender hardware address, may not be set for non-ethernet interface */
         struct hw_addr_data hw_addr;
         triple_timestamp timestamp;
@@ -22,14 +23,10 @@ typedef struct DHCPRequest {
         sd_dhcp_client_id client_id;
         sd_dhcp_client_id client_id_by_header;
         size_t max_optlen;
+        size_t max_message_size; /* maximum message size, including IP and UDP headers */
         be32_t server_address;
-        be32_t requested_ip;
         usec_t lifetime;
-        const uint8_t *agent_info_option;
-        char *hostname;
-        const uint8_t *parameter_request_list;
-        size_t parameter_request_list_len;
-        bool rapid_commit;
+        Set *parameter_request_list;
 
         /* acquired data */
         sd_dhcp_server_lease *static_lease;
index 9f52ff4cc8df9c2c2d1c053aef1869fff01e68df..27bfc7f32aa02f34d842588ceb5aec4c45c5f658 100644 (file)
@@ -14,6 +14,7 @@
 #include "in-addr-util.h"
 #include "iovec-util.h"
 #include "iovec-wrapper.h"
+#include "set.h"
 #include "socket-util.h"
 
 static int server_acquire_raw_socket(sd_dhcp_server *server) {
@@ -156,10 +157,10 @@ static int dhcp_server_send_message(
         /* If the ’giaddr’ field in a DHCP message from a client is non-zero, the server sends any
          * return messages to the ’DHCP server’ port on the BOOTP relay agent whose address appears
          * in ’giaddr’. */
-        if (req->message->giaddr != INADDR_ANY)
+        if (req->message->header.giaddr != INADDR_ANY)
                 return dhcp_server_send_udp(
                                 server,
-                                req->message->giaddr,
+                                req->message->header.giaddr,
                                 DHCP_PORT_SERVER,
                                 &packet->dhcp,
                                 sizeof(DHCPMessage) + optoffset);
@@ -175,10 +176,10 @@ static int dhcp_server_send_message(
 
         /* If the ’giaddr’ field is zero and the ’ciaddr’ field is nonzero, then the server unicasts
          * DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. */
-        if (req->message->ciaddr != INADDR_ANY)
+        if (req->message->header.ciaddr != INADDR_ANY)
                 return dhcp_server_send_udp(
                                 server,
-                                req->message->ciaddr,
+                                req->message->header.ciaddr,
                                 DHCP_PORT_CLIENT,
                                 &packet->dhcp,
                                 sizeof(DHCPMessage) + optoffset);
@@ -189,7 +190,7 @@ static int dhcp_server_send_message(
          * (Note, even the broadcast flag is unset, we may not know the client hardware address, e.g.
          * InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, broadcast
          * the message if 'yiaddr' is zero.) */
-        if (FLAGS_SET(be16toh(req->message->flags), 0x8000) ||
+        if (dhcp_message_has_broadcast_flag(req->message) ||
             hw_addr_is_null(&req->hw_addr) ||
             packet->dhcp.yiaddr == INADDR_ANY)
                 return dhcp_server_send_udp(
@@ -226,14 +227,12 @@ static int dhcp_server_send_packet(sd_dhcp_server *server,
         if (r < 0)
                 return r;
 
-        if (req->agent_info_option) {
-                size_t opt_full_length = *(req->agent_info_option + 1) + 2;
-                /* there must be space left for SD_DHCP_OPTION_END */
-                if (optoffset + opt_full_length < req->max_optlen) {
-                        memcpy(packet->dhcp.options + optoffset, req->agent_info_option, opt_full_length);
-                        optoffset += opt_full_length;
-                }
-        }
+        _cleanup_(iovec_done) struct iovec iov = {};
+        if (dhcp_message_get_option_alloc(req->message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, &iov) >= 0 &&
+            iov.iov_len <= UINT8_MAX)
+                (void) dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
+                                          SD_DHCP_OPTION_RELAY_AGENT_INFORMATION,
+                                          iov.iov_len, iov.iov_base);
 
         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
                                SD_DHCP_OPTION_END, 0, NULL);
@@ -265,14 +264,14 @@ static int server_message_init(
                 return -ENOMEM;
 
         r = dhcp_message_init(&packet->dhcp, BOOTREPLY,
-                              be32toh(req->message->xid),
-                              req->message->htype, req->hw_addr.length, req->hw_addr.bytes,
+                              be32toh(req->message->header.xid),
+                              req->message->header.htype, req->hw_addr.length, req->hw_addr.bytes,
                               type, req->max_optlen, &optoffset);
         if (r < 0)
                 return r;
 
-        packet->dhcp.flags = req->message->flags;
-        packet->dhcp.giaddr = req->message->giaddr;
+        packet->dhcp.flags = req->message->header.flags;
+        packet->dhcp.giaddr = req->message->header.giaddr;
 
         *ret_optoffset = optoffset;
         *ret = TAKE_PTR(packet);
@@ -336,15 +335,6 @@ static int dhcp_server_append_static_hostname(
                         buffer);
 }
 
-static bool dhcp_request_contains(DHCPRequest *req, uint8_t option) {
-        assert(req);
-
-        if (!req->parameter_request_list)
-                return false;
-
-        return memchr(req->parameter_request_list, option, req->parameter_request_list_len);
-}
-
 int server_send_offer_or_ack(
                 sd_dhcp_server *server,
                 DHCPRequest *req,
@@ -447,7 +437,7 @@ int server_send_offer_or_ack(
         /* RFC 8925 section 3.3. DHCPv4 Server Behavior
          * The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if
          * the option was not present in the Parameter Request List sent by the client. */
-        if (dhcp_request_contains(req, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED) &&
+        if (set_contains(req->parameter_request_list, UINT_TO_PTR(SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) &&
             server->ipv6_only_preferred_usec > 0) {
                 be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec);
 
@@ -488,7 +478,8 @@ int server_send_offer_or_ack(
                         return r;
         }
 
-        if (server->rapid_commit && req->rapid_commit && type == DHCP_ACK) {
+        if (type == DHCP_ACK && req->type == DHCP_DISCOVER) {
+                assert(server->rapid_commit);
                 r = dhcp_option_append(
                                 &packet->dhcp, req->max_optlen, &offset, 0,
                                 SD_DHCP_OPTION_RAPID_COMMIT,
@@ -524,7 +515,7 @@ int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequ
         if (r < 0)
                 return log_dhcp_server_errno(server, r, "Could not send NAK message: %m");
 
-        log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->xid));
+        log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->header.xid));
         return DHCP_NAK;
 }
 
index 4ab789726407d9ea12908949fd3946a4aef0da2e..3fe40537585bf272a0b269e7f15b94bd16ea7b59 100644 (file)
@@ -70,6 +70,7 @@ int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req) {
 
         assert(server);
         assert(req);
+        assert(req->message);
         assert(req->address != INADDR_ANY);
 
         usec_t expiration;
@@ -105,18 +106,16 @@ int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req) {
                 .n_ref = 1,
                 .address = req->address,
                 .client_id = req->client_id,
-                .htype = req->message->htype,
-                .gateway = req->message->giaddr,
+                .htype = req->message->header.htype,
+                .gateway = req->message->header.giaddr,
                 .expiration = expiration,
         };
 
         lease->hw_addr = req->hw_addr;
 
-        if (req->hostname) {
-                lease->hostname = strdup(req->hostname);
-                if (!lease->hostname)
-                        return -ENOMEM;
-        }
+        char *hostname;
+        if (dhcp_message_get_option_hostname(req->message, &hostname) >= 0)
+                free_and_replace(lease->hostname, hostname);
 
         r = dhcp_server_put_lease(server, lease, /* is_static= */ false);
         if (r < 0)
index a4f025bd412b1503b1c3960a6d1e2499296fd1d8..a112c0632ad07c6752e22c3e538fccbe78993e34 100644 (file)
@@ -95,6 +95,7 @@ TEST(dhcp_server_handle_message) {
                 .header.hlen = ETHER_ADDR_LEN,
                 .header.xid = htobe32(0x12345678),
                 .header.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' },
+                .header.magic = htobe32(DHCP_MAGIC_COOKIE),
                 .option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE,
                 .option_type.length = 1,
                 .option_type.type = DHCP_DISCOVER,
@@ -141,14 +142,14 @@ TEST(dhcp_server_handle_message) {
         test.option_type.code = 0;
         test.option_type.length = 0;
         test.option_type.type = 0;
-        ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENOMSG);
+        ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENODATA);
         test.option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE;
         test.option_type.length = 1;
         test.option_type.type = DHCP_DISCOVER;
         ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER);
 
         test.header.op = 0;
-        ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL));
+        ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), EBADMSG);
         test.header.op = BOOTREQUEST;
         ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER);
 
@@ -163,8 +164,10 @@ TEST(dhcp_server_handle_message) {
         test.header.hlen = ETHER_ADDR_LEN;
         ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER);
 
+        /* DHCPREQUEST (init-boot) without requested IP */
         test.option_type.type = DHCP_REQUEST;
-        ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL));
+        ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENODATA);
+
         test.option_requested_ip.code = SD_DHCP_OPTION_REQUESTED_IP_ADDRESS;
         test.option_requested_ip.length = 4;
         test.option_requested_ip.address = htobe32(0x12345678);