]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-dhcp-server: split into small pieaces
authorYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 4 May 2026 04:24:33 +0000 (13:24 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 21 May 2026 19:30:34 +0000 (04:30 +0900)
No functional change, just several functions are moved/split/renamed.

src/libsystemd-network/dhcp-server-internal.h
src/libsystemd-network/dhcp-server-lease-internal.h
src/libsystemd-network/dhcp-server-request.c [new file with mode: 0644]
src/libsystemd-network/dhcp-server-request.h [new file with mode: 0644]
src/libsystemd-network/dhcp-server-send.c [new file with mode: 0644]
src/libsystemd-network/dhcp-server-send.h [new file with mode: 0644]
src/libsystemd-network/meson.build
src/libsystemd-network/sd-dhcp-server.c
src/libsystemd-network/test-dhcp-server.c

index d4caae66d42b7402bb6a34a8a0a52e4c60c8696e..c03ce7dddbbdd63a34873ff4f30d514e32d4d1bf 100644 (file)
@@ -5,13 +5,11 @@
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
 
-#include "sd-dhcp-lease.h"
 #include "sd-dhcp-server.h"
 
-#include "dhcp-client-id-internal.h"
 #include "dhcp-option.h"
-#include "sd-forward.h"
 #include "network-common.h"
+#include "sd-forward.h"
 #include "sparse-endian.h"
 #include "tlv-util.h"
 
@@ -74,32 +72,12 @@ typedef struct sd_dhcp_server {
         char *lease_file;
 } sd_dhcp_server;
 
-typedef struct DHCPRequest {
-        /* received message */
-        DHCPMessage *message;
-
-        /* options */
-        sd_dhcp_client_id client_id;
-        size_t max_optlen;
-        be32_t server_id;
-        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;
-        triple_timestamp timestamp;
-} DHCPRequest;
-
 int dhcp_server_set_extra_options(sd_dhcp_server *server, TLV *options);
 int dhcp_server_set_vendor_options(sd_dhcp_server *server, TLV *options);
 
-int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
-                               size_t length, const triple_timestamp *timestamp);
-int dhcp_server_send_packet(sd_dhcp_server *server,
-                            DHCPRequest *req, DHCPPacket *packet,
-                            int type, size_t optoffset);
+void dhcp_server_on_lease_change(sd_dhcp_server *server);
+bool dhcp_server_address_is_in_pool(sd_dhcp_server *server, be32_t address);
+bool dhcp_server_address_available(sd_dhcp_server *server, be32_t address);
 
 #define log_dhcp_server_errno(server, error, fmt, ...)          \
         log_interface_prefix_full_errno(                        \
index aeb099ff81878dd8c636084b56baec0213bcb4d4..6d7eb4c21368a343954afb03aab6c2e53cb082ca 100644 (file)
@@ -5,6 +5,7 @@
 
 #include "dhcp-client-id-internal.h"
 #include "dhcp-server-internal.h"
+#include "dhcp-server-request.h"
 #include "sd-forward.h"
 
 typedef struct sd_dhcp_server_lease {
diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c
new file mode 100644 (file)
index 0000000..5cccc7c
--- /dev/null
@@ -0,0 +1,536 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-event.h"
+
+#include "alloc-util.h"
+#include "dhcp-network.h"
+#include "dhcp-protocol.h"
+#include "dhcp-server-internal.h"
+#include "dhcp-server-lease-internal.h"
+#include "dhcp-server-request.h"
+#include "dhcp-server-send.h"
+#include "errno-util.h"
+#include "hashmap.h"
+#include "iovec-util.h"
+#include "memory-util.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);
+        return mfree(req);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
+
+static void dhcp_request_set_timestamp(DHCPRequest *req, const triple_timestamp *timestamp) {
+        assert(req);
+
+        if (timestamp && triple_timestamp_is_set(timestamp))
+                req->timestamp = *timestamp;
+        else
+                triple_timestamp_now(&req->timestamp);
+}
+
+int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret) {
+        assert(req);
+        assert(TRIPLE_TIMESTAMP_HAS_CLOCK(clock));
+        assert(clock_supported(clock));
+        assert(ret);
+
+        if (req->lifetime <= 0)
+                return -ENODATA;
+
+        if (!triple_timestamp_is_set(&req->timestamp))
+                return -ENODATA;
+
+        *ret = usec_add(triple_timestamp_by_clock(&req->timestamp, clock), req->lifetime);
+        return 0;
+}
+
+static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) {
+        int r;
+
+        assert(req);
+        assert(message);
+
+        req->message = message;
+
+        if (message->hlen > sizeof(message->chaddr))
+                return -EBADMSG;
+
+        /* set client id based on MAC address if client did not send an explicit one */
+        if (!sd_dhcp_client_id_is_set(&req->client_id)) {
+                if (!client_id_data_size_is_valid(message->hlen))
+                        return -EBADMSG;
+
+                r = sd_dhcp_client_id_set(&req->client_id, /* type= */ 1, message->chaddr, message->hlen);
+                if (r < 0)
+                        return r;
+        }
+
+        if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) {
+                uint8_t type;
+                const void *data;
+                size_t size;
+
+                /* See RFC2131 section 4.1.1.
+                 * hlen and chaddr may not be set for non-ethernet interface.
+                 * Let's try to retrieve it from the client ID. */
+
+                if (!sd_dhcp_client_id_is_set(&req->client_id))
+                        return -EBADMSG;
+
+                r = sd_dhcp_client_id_get(&req->client_id, &type, &data, &size);
+                if (r < 0)
+                        return r;
+
+                if (type != 1)
+                        return -EBADMSG;
+
+                if (size > sizeof(message->chaddr))
+                        return -EBADMSG;
+
+                memcpy(message->chaddr, data, size);
+                message->hlen = size;
+        }
+
+        if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
+                req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
+
+        if (req->lifetime <= 0)
+                req->lifetime = MAX(USEC_PER_SEC, server->default_lease_time);
+
+        if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time)
+                req->lifetime = server->max_lease_time;
+
+        return 0;
+}
+
+static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) {
+        DHCPRequest *req = ASSERT_PTR(userdata);
+        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));
+
+                break;
+        case SD_DHCP_OPTION_SERVER_IDENTIFIER:
+                if (len == 4)
+                        memcpy(&req->server_id, option, sizeof(be32_t));
+
+                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);
+
+                break;
+        case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
+
+                if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket))
+                        req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket);
+
+                break;
+        case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION:
+                req->agent_info_option = (uint8_t*)option - 2;
+
+                break;
+        case SD_DHCP_OPTION_HOST_NAME: {
+                _cleanup_free_ char *p = NULL;
+
+                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;
+        }
+
+        return 0;
+}
+
+static int dhcp_server_parse_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, DHCPRequest **ret, char **ret_error_message) {
+        int r;
+
+        assert(server);
+        assert(message);
+        assert(ret);
+        assert(ret_error_message);
+
+        _cleanup_(dhcp_request_freep) DHCPRequest *req = new0(DHCPRequest, 1);
+        if (!req)
+                return -ENOMEM;
+
+        _cleanup_free_ char *error_message = NULL;
+        r = dhcp_option_parse(message, length, parse_request, req, &error_message);
+        if (r < 0)
+                return r;
+        int type = r;
+
+        r = ensure_sane_request(server, req, message);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(req);
+        *ret_error_message = TAKE_PTR(error_message);
+        return type;
+}
+
+static int dhcp_server_ack(sd_dhcp_server *server, DHCPRequest *req, be32_t address) {
+        usec_t expiration;
+        int r;
+
+        assert(server);
+        assert(req);
+        assert(address != 0);
+
+        r = dhcp_request_get_lifetime_timestamp(req, CLOCK_BOOTTIME, &expiration);
+        if (r < 0)
+                return r;
+
+        r = dhcp_server_set_lease(server, address, req, expiration);
+        if (r < 0)
+                return log_dhcp_server_errno(server, r, "Failed to create new lease: %m");
+
+        r = server_send_offer_or_ack(server, req, address, DHCP_ACK);
+        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));
+
+        dhcp_server_on_lease_change(server);
+
+        return DHCP_ACK;
+}
+
+static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req) {
+        int r;
+
+        assert(server);
+        assert(req);
+
+        sd_dhcp_server_lease
+                *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));
+
+        if (server->pool_size == 0)
+                /* no pool allocated */
+                return 0;
+
+        be32_t address = INADDR_ANY;
+
+        /* for now pick a random free address from the pool */
+        if (static_lease) {
+                sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(static_lease->address));
+                if (l && l != existing_lease)
+                        /* The address is already assigned to another host. Refusing. */
+                        return 0;
+
+                /* Found a matching static lease. */
+                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. */
+                address = existing_lease->address;
+
+        else {
+                struct siphash state;
+                uint64_t hash;
+
+                /* Even with no persistence of leases, we try to offer the same client the same IP address.
+                 * We do this by using the hash of the client ID as the offset into the pool of leases when
+                 * finding the next free one. */
+
+#define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30)
+
+                siphash24_init(&state, HASH_KEY.bytes);
+                client_id_hash_func(&req->client_id, &state);
+                hash = htole64(siphash24_finalize(&state));
+
+                for (unsigned i = 0; i < server->pool_size; i++) {
+                        be32_t a = server->subnet | htobe32(server->pool_offset + (hash + i) % server->pool_size);
+                        if (dhcp_server_address_available(server, a)) {
+                                address = a;
+                                break;
+                        }
+                }
+        }
+
+        if (address == INADDR_ANY)
+                /* no free addresses left */
+                return 0;
+
+        if (server->rapid_commit && req->rapid_commit)
+                return dhcp_server_ack(server, req, address);
+
+        r = server_send_offer_or_ack(server, req, address, DHCP_OFFER);
+        if (r < 0)
+                /* 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));
+        return DHCP_OFFER;
+}
+
+static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) {
+        assert(server);
+        assert(req);
+
+        sd_dhcp_server_lease
+                *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id),
+                *static_lease = dhcp_server_get_static_lease(server, req);
+
+        be32_t address;
+        bool init_reboot = false;
+
+        /* see RFC 2131, section 4.3.2 */
+
+        if (req->server_id != 0) {
+                log_dhcp_server(server, "REQUEST (selecting) (0x%x)",
+                                be32toh(req->message->xid));
+
+                /* SELECTING */
+                if (req->server_id != server->address)
+                        /* client did not pick us */
+                        return 0;
+
+                if (req->message->ciaddr != 0)
+                        /* this MUST be zero */
+                        return 0;
+
+                if (req->requested_ip == 0)
+                        /* this must be filled in with the yiaddr
+                           from the chosen OFFER */
+                        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));
+
+                /* INIT-REBOOT */
+                if (req->message->ciaddr != 0)
+                        /* this MUST be zero */
+                        return 0;
+
+                /* TODO: check more carefully if IP is correct */
+                address = req->requested_ip;
+                init_reboot = true;
+        } else {
+                log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)",
+                                be32toh(req->message->xid));
+
+                /* REBINDING / RENEWING */
+                if (req->message->ciaddr == 0)
+                        /* this MUST be filled in with clients IP address */
+                        return 0;
+
+                address = req->message->ciaddr;
+        }
+
+        /* Silently ignore Rapid Commit option in REQUEST message. */
+        req->rapid_commit = false;
+
+        if (static_lease) {
+                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);
+
+                sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(address));
+                if (l && l != existing_lease)
+                        /* The requested address is already assigned to another host. Refusing. */
+                        return server_send_nak_or_ignore(server, init_reboot, req);
+
+                /* Found a static lease for the client ID. */
+                return dhcp_server_ack(server, req, address);
+        }
+
+        if (dhcp_server_address_is_in_pool(server, address))
+                /* The requested address is in the pool. */
+                return dhcp_server_ack(server, req, address);
+
+        /* Refuse otherwise. */
+        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) {
+        assert(server);
+        assert(req);
+
+        log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message));
+
+        /* TODO: make sure we don't offer this address again */
+
+        return 0;
+}
+
+static int dhcp_server_process_release(sd_dhcp_server *server, DHCPRequest *req) {
+        assert(server);
+        assert(req);
+
+        log_dhcp_server(server, "RELEASE (0x%x)",
+                        be32toh(req->message->xid));
+
+        sd_dhcp_server_lease *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id);
+        if (!existing_lease)
+                return 0;
+
+        if (existing_lease->address != req->message->ciaddr)
+                return 0;
+
+        sd_dhcp_server_lease_unref(existing_lease);
+        dhcp_server_on_lease_change(server);
+
+        return 0;
+}
+
+int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp) {
+        int r;
+
+        assert(server);
+        assert(message);
+
+        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);
+        if (r < 0)
+                return r;
+        int type = r;
+
+        dhcp_request_set_timestamp(req, timestamp);
+
+        r = dhcp_server_cleanup_expired_leases(server);
+        if (r < 0)
+                return r;
+
+        switch (type) {
+        case DHCP_DISCOVER:
+                return dhcp_server_process_discover(server, req);
+        case DHCP_REQUEST:
+                return dhcp_server_process_request(server, req);
+        case DHCP_DECLINE:
+                return dhcp_server_process_decline(server, req, error_message);
+        case DHCP_RELEASE:
+                return dhcp_server_process_release(server, req);
+        default:
+                return 0; /* Unsupported DHCP message type? Ignore the message silently. */
+        }
+}
+
+static int server_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        _cleanup_free_ DHCPMessage *message = NULL;
+        /* This needs to be initialized with zero. See #20741.
+         * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */
+        CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL +
+                         CMSG_SPACE(sizeof(struct in_pktinfo))) control = {};
+        sd_dhcp_server *server = ASSERT_PTR(userdata);
+        struct iovec iov = {};
+        struct msghdr msg = {
+                .msg_iov = &iov,
+                .msg_iovlen = 1,
+                .msg_control = &control,
+                .msg_controllen = sizeof(control),
+        };
+        ssize_t datagram_size, len;
+        int r;
+
+        datagram_size = next_datagram_size_fd(fd);
+        if (ERRNO_IS_NEG_TRANSIENT(datagram_size) || ERRNO_IS_NEG_DISCONNECT(datagram_size))
+                return 0;
+        if (datagram_size < 0) {
+                log_dhcp_server_errno(server, datagram_size, "Failed to determine datagram size to read, ignoring: %m");
+                return 0;
+        }
+
+        size_t buflen = datagram_size;
+        message = malloc0(buflen);
+        if (!message)
+                return -ENOMEM;
+
+        iov = IOVEC_MAKE(message, datagram_size);
+
+        len = recvmsg_safe(fd, &msg, 0);
+        if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len))
+                return 0;
+        if (len < 0) {
+                log_dhcp_server_errno(server, len, "Could not receive message, ignoring: %m");
+                return 0;
+        }
+
+        if ((size_t) len < sizeof(DHCPMessage))
+                return 0;
+
+        /* TODO: figure out if this can be done as a filter on the socket, like for IPv6 */
+        struct in_pktinfo *info = CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo);
+        if (info && info->ipi_ifindex != server->ifindex)
+                return 0;
+
+        r = dhcp_server_handle_message(server, message, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg));
+        if (r < 0)
+                log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m");
+
+        return 0;
+}
+
+int dhcp_server_setup_io_event_source(sd_dhcp_server *server) {
+        int r;
+
+        assert(server);
+        assert(server->event);
+
+        r = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+        if (r < 0) {
+                r = -errno;
+                goto on_error;
+        }
+        server->fd_raw = r;
+
+        r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1);
+        if (r < 0)
+                goto on_error;
+        server->fd = r;
+
+        r = sd_event_add_io(server->event, &server->receive_message,
+                            server->fd, EPOLLIN,
+                            server_receive_message, server);
+        if (r < 0)
+                goto on_error;
+
+        r = sd_event_source_set_priority(server->receive_message,
+                                         server->event_priority);
+        if (r < 0)
+                goto on_error;
+
+        return 0;
+
+ on_error:
+        sd_dhcp_server_stop(server);
+        return r;
+}
diff --git a/src/libsystemd-network/dhcp-server-request.h b/src/libsystemd-network/dhcp-server-request.h
new file mode 100644 (file)
index 0000000..f1e827a
--- /dev/null
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "dhcp-client-id-internal.h"
+#include "dhcp-protocol.h"
+#include "sd-forward.h"
+#include "sparse-endian.h"
+#include "time-util.h"
+
+typedef struct DHCPRequest {
+        /* received message */
+        DHCPMessage *message;
+        triple_timestamp timestamp;
+
+        /* options */
+        sd_dhcp_client_id client_id;
+        size_t max_optlen;
+        be32_t server_id;
+        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;
+} DHCPRequest;
+
+int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret);
+
+int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp);
+int dhcp_server_setup_io_event_source(sd_dhcp_server *server);
diff --git a/src/libsystemd-network/dhcp-server-send.c b/src/libsystemd-network/dhcp-server-send.c
new file mode 100644 (file)
index 0000000..67fc390
--- /dev/null
@@ -0,0 +1,552 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "dhcp-network.h"
+#include "dhcp-option.h"
+#include "dhcp-packet.h"
+#include "dhcp-server-lease-internal.h"
+#include "dhcp-server-send.h"
+#include "dns-domain.h"
+#include "errno-util.h"
+#include "hashmap.h"
+#include "in-addr-util.h"
+#include "iovec-util.h"
+#include "iovec-wrapper.h"
+#include "socket-util.h"
+
+static int dhcp_server_send_unicast_raw(
+                sd_dhcp_server *server,
+                uint8_t hlen,
+                const uint8_t *chaddr,
+                DHCPPacket *packet,
+                size_t len) {
+
+        union sockaddr_union link = {
+                .ll.sll_family = AF_PACKET,
+                .ll.sll_protocol = htobe16(ETH_P_IP),
+                .ll.sll_ifindex = server->ifindex,
+                .ll.sll_halen = hlen,
+        };
+        int r;
+
+        assert(server);
+        assert(server->ifindex > 0);
+        assert(server->address != 0);
+        assert(hlen > 0);
+        assert(chaddr);
+        assert(packet);
+        assert(len > sizeof(DHCPPacket));
+
+        memcpy(link.ll.sll_addr, chaddr, hlen);
+
+        if (len > UINT16_MAX)
+                return -EOVERFLOW;
+
+        r = dhcp_packet_append_ip_headers(
+                        packet,
+                        server->address,
+                        DHCP_PORT_SERVER,
+                        packet->dhcp.yiaddr,
+                        DHCP_PORT_CLIENT,
+                        len,
+                        /* ip_service_type= */ -1);
+        if (r < 0)
+                return r;
+
+        return dhcp_network_send_raw_socket(
+                        server->fd_raw,
+                        &link,
+                        &(struct iovec_wrapper) {
+                                .iovec = &IOVEC_MAKE(packet, len),
+                                .count = 1,
+                        });
+}
+
+static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
+                                uint16_t destination_port,
+                                DHCPMessage *message, size_t len) {
+        union sockaddr_union dest = {
+                .in.sin_family = AF_INET,
+                .in.sin_port = htobe16(destination_port),
+                .in.sin_addr.s_addr = destination,
+        };
+        struct iovec iov = {
+                .iov_base = message,
+                .iov_len = len,
+        };
+        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {};
+        struct msghdr msg = {
+                .msg_name = &dest,
+                .msg_namelen = sizeof(dest.in),
+                .msg_iov = &iov,
+                .msg_iovlen = 1,
+        };
+        struct cmsghdr *cmsg;
+        struct in_pktinfo *pktinfo;
+
+        assert(server);
+        assert(server->fd >= 0);
+        assert(message);
+        assert(len >= sizeof(DHCPMessage));
+
+        msg.msg_control = &control;
+        msg.msg_controllen = sizeof(control);
+
+        cmsg = CMSG_FIRSTHDR(&msg);
+        assert(cmsg);
+
+        cmsg->cmsg_level = IPPROTO_IP;
+        cmsg->cmsg_type = IP_PKTINFO;
+        cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+
+        pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
+        assert(pktinfo);
+
+        pktinfo->ipi_ifindex = server->ifindex;
+        pktinfo->ipi_spec_dst.s_addr = server->address;
+
+        if (sendmsg(server->fd, &msg, 0) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static bool requested_broadcast(DHCPMessage *message) {
+        assert(message);
+        return message->flags & htobe16(0x8000);
+}
+
+static int dhcp_server_send(
+                sd_dhcp_server *server,
+                uint8_t hlen,
+                const uint8_t *chaddr,
+                be32_t destination,
+                uint16_t destination_port,
+                DHCPPacket *packet,
+                size_t optoffset,
+                bool l2_broadcast) {
+
+        if (destination != INADDR_ANY)
+                return dhcp_server_send_udp(server, destination,
+                                            destination_port, &packet->dhcp,
+                                            sizeof(DHCPMessage) + optoffset);
+        else if (l2_broadcast)
+                return dhcp_server_send_udp(server, INADDR_BROADCAST,
+                                            destination_port, &packet->dhcp,
+                                            sizeof(DHCPMessage) + optoffset);
+        else
+                /* we cannot send UDP packet to specific MAC address when the
+                   address is not yet configured, so must fall back to raw
+                   packets */
+                return dhcp_server_send_unicast_raw(server, hlen, chaddr, packet,
+                                                    sizeof(DHCPPacket) + optoffset);
+}
+
+static int dhcp_server_send_packet(sd_dhcp_server *server,
+                            DHCPRequest *req, DHCPPacket *packet,
+                            int type, size_t optoffset) {
+        be32_t destination = INADDR_ANY;
+        uint16_t destination_port = DHCP_PORT_CLIENT;
+        int r;
+
+        assert(server);
+        assert(req);
+        assert(req->max_optlen > 0);
+        assert(req->message);
+        assert(optoffset <= req->max_optlen);
+        assert(packet);
+
+        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
+                               SD_DHCP_OPTION_SERVER_IDENTIFIER,
+                               4, &server->address);
+        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;
+                }
+        }
+
+        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
+                               SD_DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
+
+        /* RFC 2131 Section 4.1
+
+           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 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 ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is
+           set, then the server broadcasts DHCPOFFER and DHCPACK messages to
+           0xffffffff. If the broadcast bit is not set and ’giaddr’ is zero and
+           ’ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK
+           messages to the client’s hardware address and ’yiaddr’ address. In
+           all cases, when ’giaddr’ is zero, the server broadcasts any DHCPNAK
+           messages to 0xffffffff.
+
+           Section 4.3.2
+
+           If ’giaddr’ is set in the DHCPREQUEST message, the client is on a
+           different subnet. The server MUST set the broadcast bit in the
+           DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the
+           client, because the client may not have a correct network address
+           or subnet mask, and the client may not be answering ARP requests.
+         */
+        if (req->message->giaddr != 0) {
+                destination = req->message->giaddr;
+                destination_port = DHCP_PORT_SERVER;
+                if (type == DHCP_NAK)
+                        packet->dhcp.flags = htobe16(0x8000);
+        } else if (req->message->ciaddr != 0 && type != DHCP_NAK)
+                destination = req->message->ciaddr;
+
+        bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK;
+        return dhcp_server_send(server, req->message->hlen, req->message->chaddr,
+                                destination, destination_port, packet, optoffset, l2_broadcast);
+}
+
+static int server_message_init(
+                sd_dhcp_server *server,
+                DHCPPacket **ret,
+                uint8_t type,
+                size_t *ret_optoffset,
+                DHCPRequest *req) {
+
+        _cleanup_free_ DHCPPacket *packet = NULL;
+        size_t optoffset = 0;
+        int r;
+
+        assert(server);
+        assert(ret);
+        assert(ret_optoffset);
+        assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK));
+        assert(req);
+
+        packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
+        if (!packet)
+                return -ENOMEM;
+
+        r = dhcp_message_init(&packet->dhcp, BOOTREPLY,
+                              be32toh(req->message->xid),
+                              req->message->htype, req->message->hlen, req->message->chaddr,
+                              type, req->max_optlen, &optoffset);
+        if (r < 0)
+                return r;
+
+        packet->dhcp.flags = req->message->flags;
+        packet->dhcp.giaddr = req->message->giaddr;
+
+        *ret_optoffset = optoffset;
+        *ret = TAKE_PTR(packet);
+
+        return 0;
+}
+
+static int dhcp_server_append_static_hostname(
+                sd_dhcp_server *server,
+                DHCPPacket *packet,
+                size_t *offset,
+                DHCPRequest *req) {
+
+        sd_dhcp_server_lease *static_lease;
+        int r;
+
+        assert(server);
+        assert(packet);
+        assert(offset);
+        assert(req);
+
+        static_lease = dhcp_server_get_static_lease(server, req);
+        if (!static_lease || !static_lease->hostname)
+                return 0;
+
+        if (dns_name_is_single_label(static_lease->hostname))
+                /* Option 12 */
+                return dhcp_option_append(
+                                &packet->dhcp,
+                                req->max_optlen,
+                                offset,
+                                /* overload= */ 0,
+                                SD_DHCP_OPTION_HOST_NAME,
+                                strlen(static_lease->hostname),
+                                static_lease->hostname);
+
+
+        /* Option 81 */
+        uint8_t buffer[DHCP_MAX_FQDN_LENGTH + 3];
+
+        /* Flags: S=0 (will not update RR), O=1 (are overriding client),
+         * E=1 (using DNS wire format), N=1 (will not update DNS) */
+        buffer[0] = DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_E | DHCP_FQDN_FLAG_N;
+
+        /* RFC 4702: A server SHOULD set these to 255 when sending the option and MUST ignore them on
+         * receipt. */
+        buffer[1] = 255;
+        buffer[2] = 255;
+
+        r = dns_name_to_wire_format(static_lease->hostname, buffer + 3, sizeof(buffer) - 3, false);
+        if (r < 0)
+                return log_dhcp_server_errno(server, r, "Failed to encode FQDN for static lease: %m");
+        if (r > DHCP_MAX_FQDN_LENGTH)
+                return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EINVAL), "FQDN for static lease too long");
+
+        return dhcp_option_append(
+                        &packet->dhcp,
+                        req->max_optlen,
+                        offset,
+                        /* overload= */ 0,
+                        SD_DHCP_OPTION_FQDN,
+                        3 + r,
+                        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,
+                be32_t address,
+                uint8_t type) {
+
+        static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = {
+                [SD_DHCP_LEASE_DNS]  = SD_DHCP_OPTION_DOMAIN_NAME_SERVER,
+                [SD_DHCP_LEASE_NTP]  = SD_DHCP_OPTION_NTP_SERVER,
+                [SD_DHCP_LEASE_SIP]  = SD_DHCP_OPTION_SIP_SERVER,
+                [SD_DHCP_LEASE_POP3] = SD_DHCP_OPTION_POP3_SERVER,
+                [SD_DHCP_LEASE_SMTP] = SD_DHCP_OPTION_SMTP_SERVER,
+                [SD_DHCP_LEASE_LPR]  = SD_DHCP_OPTION_LPR_SERVER,
+        };
+
+        _cleanup_free_ DHCPPacket *packet = NULL;
+        be32_t lease_time;
+        size_t offset;
+        int r;
+
+        assert(server);
+        assert(req);
+        assert(IN_SET(type, DHCP_OFFER, DHCP_ACK));
+
+        r = server_message_init(server, &packet, type, &offset, req);
+        if (r < 0)
+                return r;
+
+        packet->dhcp.yiaddr = address;
+        packet->dhcp.siaddr = server->boot_server_address.s_addr;
+
+        lease_time = usec_to_be32_sec(req->lifetime);
+        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+                               SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4,
+                               &lease_time);
+        if (r < 0)
+                return r;
+
+        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+                               SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask);
+        if (r < 0)
+                return r;
+
+        if (server->emit_router) {
+                r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+                                       SD_DHCP_OPTION_ROUTER, 4,
+                                       in4_addr_is_set(&server->router_address) ?
+                                       &server->router_address.s_addr :
+                                       &server->address);
+                if (r < 0)
+                        return r;
+        }
+
+        if (server->boot_server_name) {
+                r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+                                       SD_DHCP_OPTION_BOOT_SERVER_NAME,
+                                       strlen(server->boot_server_name), server->boot_server_name);
+                if (r < 0)
+                        return r;
+        }
+
+        if (server->boot_filename) {
+                r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+                                       SD_DHCP_OPTION_BOOT_FILENAME,
+                                       strlen(server->boot_filename), server->boot_filename);
+                if (r < 0)
+                        return r;
+        }
+
+        for (sd_dhcp_lease_server_type_t k = 0; k < _SD_DHCP_LEASE_SERVER_TYPE_MAX; k++) {
+                if (server->servers[k].size <= 0)
+                        continue;
+
+                r = dhcp_option_append(
+                                &packet->dhcp, req->max_optlen, &offset, 0,
+                                option_map[k],
+                                sizeof(struct in_addr) * server->servers[k].size,
+                                server->servers[k].addr);
+                if (r < 0)
+                        return r;
+        }
+
+        if (server->timezone) {
+                r = dhcp_option_append(
+                                &packet->dhcp, req->max_optlen, &offset, 0,
+                                SD_DHCP_OPTION_TZDB_TIMEZONE,
+                                strlen(server->timezone), server->timezone);
+                if (r < 0)
+                        return r;
+        }
+
+        if (server->domain_name) {
+                r = dhcp_option_append(
+                                &packet->dhcp, req->max_optlen, &offset, 0,
+                                SD_DHCP_OPTION_DOMAIN_NAME,
+                                strlen(server->domain_name), server->domain_name);
+                if (r < 0)
+                        return r;
+        }
+
+        /* 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) &&
+            server->ipv6_only_preferred_usec > 0) {
+                be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec);
+
+                r = dhcp_option_append(
+                                &packet->dhcp, req->max_optlen, &offset, 0,
+                                SD_DHCP_OPTION_IPV6_ONLY_PREFERRED,
+                                sizeof(sec), &sec);
+                if (r < 0)
+                        return r;
+        }
+
+        if (server->extra_options) {
+                void *key;
+                struct iovec_wrapper *iovw;
+                HASHMAP_FOREACH_KEY(iovw, key, server->extra_options->entries) {
+                        uint32_t tag = PTR_TO_UINT32(key);
+
+                        FOREACH_ARRAY(iov, iovw->iovec, iovw->count) {
+                                r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+                                                       tag, iov->iov_len, iov->iov_base);
+                                if (r < 0)
+                                        return r;
+                        }
+                }
+        }
+
+        if (!tlv_isempty(server->vendor_options)) {
+                _cleanup_(iovec_done) struct iovec iov = {};
+                r = tlv_build(server->vendor_options, &iov);
+                if (r < 0)
+                        return r;
+
+                r = dhcp_option_append(
+                                &packet->dhcp, req->max_optlen, &offset, 0,
+                                SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION,
+                                iov.iov_len, iov.iov_base);
+                if (r < 0)
+                        return r;
+        }
+
+        if (server->rapid_commit && req->rapid_commit && type == DHCP_ACK) {
+                r = dhcp_option_append(
+                                &packet->dhcp, req->max_optlen, &offset, 0,
+                                SD_DHCP_OPTION_RAPID_COMMIT,
+                                0, NULL);
+                if (r < 0)
+                        return r;
+        }
+
+        r = dhcp_server_append_static_hostname(server, packet, &offset, req);
+        if (r < 0)
+                return r;
+
+        return dhcp_server_send_packet(server, req, packet, type, offset);
+}
+
+int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req) {
+        _cleanup_free_ DHCPPacket *packet = NULL;
+        size_t offset;
+        int r;
+
+        /* When a request is refused, RFC 2131, section 4.3.2 mentioned we should send NAK when the
+         * client is in INITREBOOT. If the client is in other state, there is nothing mentioned in the
+         * RFC whether we should send NAK or not. Hence, let's silently ignore the request. */
+
+        if (!init_reboot)
+                return 0;
+
+        r = server_message_init(server, &packet, DHCP_NAK, &offset, req);
+        if (r < 0)
+                return log_dhcp_server_errno(server, r, "Failed to create NAK message: %m");
+
+        r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset);
+        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));
+        return DHCP_NAK;
+}
+
+static int server_send_forcerenew(
+                sd_dhcp_server *server,
+                be32_t address,
+                be32_t gateway,
+                uint8_t htype,
+                uint8_t hlen,
+                const uint8_t *chaddr) {
+
+        _cleanup_free_ DHCPPacket *packet = NULL;
+        size_t optoffset = 0;
+        int r;
+
+        assert(server);
+        assert(address != INADDR_ANY);
+        assert(chaddr);
+
+        packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE);
+        if (!packet)
+                return -ENOMEM;
+
+        r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0,
+                              htype, hlen, chaddr, DHCP_FORCERENEW,
+                              DHCP_MIN_OPTIONS_SIZE, &optoffset);
+        if (r < 0)
+                return r;
+
+        r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE,
+                               &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
+
+        return dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT,
+                                    &packet->dhcp,
+                                    sizeof(DHCPMessage) + optoffset);
+}
+
+int sd_dhcp_server_forcerenew(sd_dhcp_server *server) {
+        sd_dhcp_server_lease *lease;
+        int r = 0;
+
+        assert_return(server, -EINVAL);
+
+        log_dhcp_server(server, "FORCERENEW");
+
+        HASHMAP_FOREACH(lease, server->bound_leases_by_client_id)
+                RET_GATHER(r,
+                           server_send_forcerenew(server, lease->address, lease->gateway,
+                                                  lease->htype, lease->hlen, lease->chaddr));
+        return r;
+}
diff --git a/src/libsystemd-network/dhcp-server-send.h b/src/libsystemd-network/dhcp-server-send.h
new file mode 100644 (file)
index 0000000..80fd9a6
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-forward.h"
+
+#include "dhcp-server-request.h"
+
+int server_send_offer_or_ack(
+                sd_dhcp_server *server,
+                DHCPRequest *req,
+                be32_t address,
+                uint8_t type);
+
+int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req);
index 9b36f6edf2cf6380b7471e96b41259bc80b830e4..3981ecd7d5815689b52f097a8db515eb615327ad 100644 (file)
@@ -13,6 +13,8 @@ libsystemd_network_sources = files(
         'dhcp-relay-interface.c',
         'dhcp-relay-upstream.c',
         'dhcp-route.c',
+        'dhcp-server-request.c',
+        'dhcp-server-send.c',
         'dhcp6-network.c',
         'dhcp6-option.c',
         'dhcp6-protocol.c',
index 07f15ef6dc20c158168ceb00efac3fc18a51560d..5267137540d21fd2a01027c7bc883d86ac08a7ac 100644 (file)
@@ -5,33 +5,25 @@
 
 #include "sd-dhcp-server.h"
 #include "sd-event.h"
-#include "sd-id128.h"
 
 #include "alloc-util.h"
-#include "dhcp-network.h"
 #include "dhcp-option.h"
-#include "dhcp-packet.h"
 #include "dhcp-server-internal.h"
 #include "dhcp-server-lease-internal.h"
+#include "dhcp-server-request.h"
 #include "dns-domain.h"
-#include "errno-util.h"
 #include "fd-util.h"
 #include "hashmap.h"
 #include "in-addr-util.h"
-#include "iovec-util.h"
-#include "iovec-wrapper.h"
-#include "memory-util.h"
 #include "network-common.h"
 #include "path-util.h"
-#include "siphash24.h"
 #include "socket-util.h"
 #include "string-util.h"
-#include "unaligned.h"
 
 #define DHCP_DEFAULT_LEASE_TIME_USEC USEC_PER_HOUR
 #define DHCP_MAX_LEASE_TIME_USEC (USEC_PER_HOUR*12)
 
-static void server_on_lease_change(sd_dhcp_server *server) {
+void dhcp_server_on_lease_change(sd_dhcp_server *server) {
         int r;
 
         assert(server);
@@ -288,681 +280,7 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) {
         return 0;
 }
 
-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);
-}
-
-static int dhcp_server_send_unicast_raw(
-                sd_dhcp_server *server,
-                uint8_t hlen,
-                const uint8_t *chaddr,
-                DHCPPacket *packet,
-                size_t len) {
-
-        union sockaddr_union link = {
-                .ll.sll_family = AF_PACKET,
-                .ll.sll_protocol = htobe16(ETH_P_IP),
-                .ll.sll_ifindex = server->ifindex,
-                .ll.sll_halen = hlen,
-        };
-        int r;
-
-        assert(server);
-        assert(server->ifindex > 0);
-        assert(server->address != 0);
-        assert(hlen > 0);
-        assert(chaddr);
-        assert(packet);
-        assert(len > sizeof(DHCPPacket));
-
-        memcpy(link.ll.sll_addr, chaddr, hlen);
-
-        if (len > UINT16_MAX)
-                return -EOVERFLOW;
-
-        r = dhcp_packet_append_ip_headers(
-                        packet,
-                        server->address,
-                        DHCP_PORT_SERVER,
-                        packet->dhcp.yiaddr,
-                        DHCP_PORT_CLIENT,
-                        len,
-                        /* ip_service_type= */ -1);
-        if (r < 0)
-                return r;
-
-        return dhcp_network_send_raw_socket(
-                        server->fd_raw,
-                        &link,
-                        &(struct iovec_wrapper) {
-                                .iovec = &IOVEC_MAKE(packet, len),
-                                .count = 1,
-                        });
-}
-
-static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
-                                uint16_t destination_port,
-                                DHCPMessage *message, size_t len) {
-        union sockaddr_union dest = {
-                .in.sin_family = AF_INET,
-                .in.sin_port = htobe16(destination_port),
-                .in.sin_addr.s_addr = destination,
-        };
-        struct iovec iov = {
-                .iov_base = message,
-                .iov_len = len,
-        };
-        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {};
-        struct msghdr msg = {
-                .msg_name = &dest,
-                .msg_namelen = sizeof(dest.in),
-                .msg_iov = &iov,
-                .msg_iovlen = 1,
-        };
-        struct cmsghdr *cmsg;
-        struct in_pktinfo *pktinfo;
-
-        assert(server);
-        assert(server->fd >= 0);
-        assert(message);
-        assert(len >= sizeof(DHCPMessage));
-
-        msg.msg_control = &control;
-        msg.msg_controllen = sizeof(control);
-
-        cmsg = CMSG_FIRSTHDR(&msg);
-        assert(cmsg);
-
-        cmsg->cmsg_level = IPPROTO_IP;
-        cmsg->cmsg_type = IP_PKTINFO;
-        cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
-
-        pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
-        assert(pktinfo);
-
-        pktinfo->ipi_ifindex = server->ifindex;
-        pktinfo->ipi_spec_dst.s_addr = server->address;
-
-        if (sendmsg(server->fd, &msg, 0) < 0)
-                return -errno;
-
-        return 0;
-}
-
-static bool requested_broadcast(DHCPMessage *message) {
-        assert(message);
-        return message->flags & htobe16(0x8000);
-}
-
-static int dhcp_server_send(
-                sd_dhcp_server *server,
-                uint8_t hlen,
-                const uint8_t *chaddr,
-                be32_t destination,
-                uint16_t destination_port,
-                DHCPPacket *packet,
-                size_t optoffset,
-                bool l2_broadcast) {
-
-        if (destination != INADDR_ANY)
-                return dhcp_server_send_udp(server, destination,
-                                            destination_port, &packet->dhcp,
-                                            sizeof(DHCPMessage) + optoffset);
-        else if (l2_broadcast)
-                return dhcp_server_send_udp(server, INADDR_BROADCAST,
-                                            destination_port, &packet->dhcp,
-                                            sizeof(DHCPMessage) + optoffset);
-        else
-                /* we cannot send UDP packet to specific MAC address when the
-                   address is not yet configured, so must fall back to raw
-                   packets */
-                return dhcp_server_send_unicast_raw(server, hlen, chaddr, packet,
-                                                    sizeof(DHCPPacket) + optoffset);
-}
-
-int dhcp_server_send_packet(sd_dhcp_server *server,
-                            DHCPRequest *req, DHCPPacket *packet,
-                            int type, size_t optoffset) {
-        be32_t destination = INADDR_ANY;
-        uint16_t destination_port = DHCP_PORT_CLIENT;
-        int r;
-
-        assert(server);
-        assert(req);
-        assert(req->max_optlen > 0);
-        assert(req->message);
-        assert(optoffset <= req->max_optlen);
-        assert(packet);
-
-        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
-                               SD_DHCP_OPTION_SERVER_IDENTIFIER,
-                               4, &server->address);
-        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;
-                }
-        }
-
-        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
-                               SD_DHCP_OPTION_END, 0, NULL);
-        if (r < 0)
-                return r;
-
-        /* RFC 2131 Section 4.1
-
-           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 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 ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is
-           set, then the server broadcasts DHCPOFFER and DHCPACK messages to
-           0xffffffff. If the broadcast bit is not set and ’giaddr’ is zero and
-           ’ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK
-           messages to the client’s hardware address and ’yiaddr’ address. In
-           all cases, when ’giaddr’ is zero, the server broadcasts any DHCPNAK
-           messages to 0xffffffff.
-
-           Section 4.3.2
-
-           If ’giaddr’ is set in the DHCPREQUEST message, the client is on a
-           different subnet. The server MUST set the broadcast bit in the
-           DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the
-           client, because the client may not have a correct network address
-           or subnet mask, and the client may not be answering ARP requests.
-         */
-        if (req->message->giaddr != 0) {
-                destination = req->message->giaddr;
-                destination_port = DHCP_PORT_SERVER;
-                if (type == DHCP_NAK)
-                        packet->dhcp.flags = htobe16(0x8000);
-        } else if (req->message->ciaddr != 0 && type != DHCP_NAK)
-                destination = req->message->ciaddr;
-
-        bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK;
-        return dhcp_server_send(server, req->message->hlen, req->message->chaddr,
-                                destination, destination_port, packet, optoffset, l2_broadcast);
-}
-
-static int server_message_init(
-                sd_dhcp_server *server,
-                DHCPPacket **ret,
-                uint8_t type,
-                size_t *ret_optoffset,
-                DHCPRequest *req) {
-
-        _cleanup_free_ DHCPPacket *packet = NULL;
-        size_t optoffset = 0;
-        int r;
-
-        assert(server);
-        assert(ret);
-        assert(ret_optoffset);
-        assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK));
-        assert(req);
-
-        packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
-        if (!packet)
-                return -ENOMEM;
-
-        r = dhcp_message_init(&packet->dhcp, BOOTREPLY,
-                              be32toh(req->message->xid),
-                              req->message->htype, req->message->hlen, req->message->chaddr,
-                              type, req->max_optlen, &optoffset);
-        if (r < 0)
-                return r;
-
-        packet->dhcp.flags = req->message->flags;
-        packet->dhcp.giaddr = req->message->giaddr;
-
-        *ret_optoffset = optoffset;
-        *ret = TAKE_PTR(packet);
-
-        return 0;
-}
-
-static int dhcp_server_append_static_hostname(
-                sd_dhcp_server *server,
-                DHCPPacket *packet,
-                size_t *offset,
-                DHCPRequest *req) {
-
-        sd_dhcp_server_lease *static_lease;
-        int r;
-
-        assert(server);
-        assert(packet);
-        assert(offset);
-        assert(req);
-
-        static_lease = dhcp_server_get_static_lease(server, req);
-        if (!static_lease || !static_lease->hostname)
-                return 0;
-
-        if (dns_name_is_single_label(static_lease->hostname))
-                /* Option 12 */
-                return dhcp_option_append(
-                                &packet->dhcp,
-                                req->max_optlen,
-                                offset,
-                                /* overload= */ 0,
-                                SD_DHCP_OPTION_HOST_NAME,
-                                strlen(static_lease->hostname),
-                                static_lease->hostname);
-
-
-        /* Option 81 */
-        uint8_t buffer[DHCP_MAX_FQDN_LENGTH + 3];
-
-        /* Flags: S=0 (will not update RR), O=1 (are overriding client),
-         * E=1 (using DNS wire format), N=1 (will not update DNS) */
-        buffer[0] = DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_E | DHCP_FQDN_FLAG_N;
-
-        /* RFC 4702: A server SHOULD set these to 255 when sending the option and MUST ignore them on
-         * receipt. */
-        buffer[1] = 255;
-        buffer[2] = 255;
-
-        r = dns_name_to_wire_format(static_lease->hostname, buffer + 3, sizeof(buffer) - 3, false);
-        if (r < 0)
-                return log_dhcp_server_errno(server, r, "Failed to encode FQDN for static lease: %m");
-        if (r > DHCP_MAX_FQDN_LENGTH)
-                return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EINVAL), "FQDN for static lease too long");
-
-        return dhcp_option_append(
-                        &packet->dhcp,
-                        req->max_optlen,
-                        offset,
-                        /* overload= */ 0,
-                        SD_DHCP_OPTION_FQDN,
-                        3 + r,
-                        buffer);
-}
-
-static int server_send_offer_or_ack(
-                sd_dhcp_server *server,
-                DHCPRequest *req,
-                be32_t address,
-                uint8_t type) {
-
-        static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = {
-                [SD_DHCP_LEASE_DNS]  = SD_DHCP_OPTION_DOMAIN_NAME_SERVER,
-                [SD_DHCP_LEASE_NTP]  = SD_DHCP_OPTION_NTP_SERVER,
-                [SD_DHCP_LEASE_SIP]  = SD_DHCP_OPTION_SIP_SERVER,
-                [SD_DHCP_LEASE_POP3] = SD_DHCP_OPTION_POP3_SERVER,
-                [SD_DHCP_LEASE_SMTP] = SD_DHCP_OPTION_SMTP_SERVER,
-                [SD_DHCP_LEASE_LPR]  = SD_DHCP_OPTION_LPR_SERVER,
-        };
-
-        _cleanup_free_ DHCPPacket *packet = NULL;
-        be32_t lease_time;
-        size_t offset;
-        int r;
-
-        assert(server);
-        assert(req);
-        assert(IN_SET(type, DHCP_OFFER, DHCP_ACK));
-
-        r = server_message_init(server, &packet, type, &offset, req);
-        if (r < 0)
-                return r;
-
-        packet->dhcp.yiaddr = address;
-        packet->dhcp.siaddr = server->boot_server_address.s_addr;
-
-        lease_time = usec_to_be32_sec(req->lifetime);
-        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
-                               SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4,
-                               &lease_time);
-        if (r < 0)
-                return r;
-
-        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
-                               SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask);
-        if (r < 0)
-                return r;
-
-        if (server->emit_router) {
-                r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
-                                       SD_DHCP_OPTION_ROUTER, 4,
-                                       in4_addr_is_set(&server->router_address) ?
-                                       &server->router_address.s_addr :
-                                       &server->address);
-                if (r < 0)
-                        return r;
-        }
-
-        if (server->boot_server_name) {
-                r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
-                                       SD_DHCP_OPTION_BOOT_SERVER_NAME,
-                                       strlen(server->boot_server_name), server->boot_server_name);
-                if (r < 0)
-                        return r;
-        }
-
-        if (server->boot_filename) {
-                r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
-                                       SD_DHCP_OPTION_BOOT_FILENAME,
-                                       strlen(server->boot_filename), server->boot_filename);
-                if (r < 0)
-                        return r;
-        }
-
-        for (sd_dhcp_lease_server_type_t k = 0; k < _SD_DHCP_LEASE_SERVER_TYPE_MAX; k++) {
-                if (server->servers[k].size <= 0)
-                        continue;
-
-                r = dhcp_option_append(
-                                &packet->dhcp, req->max_optlen, &offset, 0,
-                                option_map[k],
-                                sizeof(struct in_addr) * server->servers[k].size,
-                                server->servers[k].addr);
-                if (r < 0)
-                        return r;
-        }
-
-        if (server->timezone) {
-                r = dhcp_option_append(
-                                &packet->dhcp, req->max_optlen, &offset, 0,
-                                SD_DHCP_OPTION_TZDB_TIMEZONE,
-                                strlen(server->timezone), server->timezone);
-                if (r < 0)
-                        return r;
-        }
-
-        if (server->domain_name) {
-                r = dhcp_option_append(
-                                &packet->dhcp, req->max_optlen, &offset, 0,
-                                SD_DHCP_OPTION_DOMAIN_NAME,
-                                strlen(server->domain_name), server->domain_name);
-                if (r < 0)
-                        return r;
-        }
-
-        /* 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) &&
-            server->ipv6_only_preferred_usec > 0) {
-                be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec);
-
-                r = dhcp_option_append(
-                                &packet->dhcp, req->max_optlen, &offset, 0,
-                                SD_DHCP_OPTION_IPV6_ONLY_PREFERRED,
-                                sizeof(sec), &sec);
-                if (r < 0)
-                        return r;
-        }
-
-        if (server->extra_options) {
-                void *key;
-                struct iovec_wrapper *iovw;
-                HASHMAP_FOREACH_KEY(iovw, key, server->extra_options->entries) {
-                        uint32_t tag = PTR_TO_UINT32(key);
-
-                        FOREACH_ARRAY(iov, iovw->iovec, iovw->count) {
-                                r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
-                                                       tag, iov->iov_len, iov->iov_base);
-                                if (r < 0)
-                                        return r;
-                        }
-                }
-        }
-
-        if (!tlv_isempty(server->vendor_options)) {
-                _cleanup_(iovec_done) struct iovec iov = {};
-                r = tlv_build(server->vendor_options, &iov);
-                if (r < 0)
-                        return r;
-
-                r = dhcp_option_append(
-                                &packet->dhcp, req->max_optlen, &offset, 0,
-                                SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION,
-                                iov.iov_len, iov.iov_base);
-                if (r < 0)
-                        return r;
-        }
-
-        if (server->rapid_commit && req->rapid_commit && type == DHCP_ACK) {
-                r = dhcp_option_append(
-                                &packet->dhcp, req->max_optlen, &offset, 0,
-                                SD_DHCP_OPTION_RAPID_COMMIT,
-                                0, NULL);
-                if (r < 0)
-                        return r;
-        }
-
-        r = dhcp_server_append_static_hostname(server, packet, &offset, req);
-        if (r < 0)
-                return r;
-
-        return dhcp_server_send_packet(server, req, packet, type, offset);
-}
-
-static int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req) {
-        _cleanup_free_ DHCPPacket *packet = NULL;
-        size_t offset;
-        int r;
-
-        /* When a request is refused, RFC 2131, section 4.3.2 mentioned we should send NAK when the
-         * client is in INITREBOOT. If the client is in other state, there is nothing mentioned in the
-         * RFC whether we should send NAK or not. Hence, let's silently ignore the request. */
-
-        if (!init_reboot)
-                return 0;
-
-        r = server_message_init(server, &packet, DHCP_NAK, &offset, req);
-        if (r < 0)
-                return log_dhcp_server_errno(server, r, "Failed to create NAK message: %m");
-
-        r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset);
-        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));
-        return DHCP_NAK;
-}
-
-static int server_send_forcerenew(
-                sd_dhcp_server *server,
-                be32_t address,
-                be32_t gateway,
-                uint8_t htype,
-                uint8_t hlen,
-                const uint8_t *chaddr) {
-
-        _cleanup_free_ DHCPPacket *packet = NULL;
-        size_t optoffset = 0;
-        int r;
-
-        assert(server);
-        assert(address != INADDR_ANY);
-        assert(chaddr);
-
-        packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE);
-        if (!packet)
-                return -ENOMEM;
-
-        r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0,
-                              htype, hlen, chaddr, DHCP_FORCERENEW,
-                              DHCP_MIN_OPTIONS_SIZE, &optoffset);
-        if (r < 0)
-                return r;
-
-        r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE,
-                               &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL);
-        if (r < 0)
-                return r;
-
-        return dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT,
-                                    &packet->dhcp,
-                                    sizeof(DHCPMessage) + optoffset);
-}
-
-static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) {
-        DHCPRequest *req = ASSERT_PTR(userdata);
-        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));
-
-                break;
-        case SD_DHCP_OPTION_SERVER_IDENTIFIER:
-                if (len == 4)
-                        memcpy(&req->server_id, option, sizeof(be32_t));
-
-                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);
-
-                break;
-        case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
-
-                if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket))
-                        req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket);
-
-                break;
-        case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION:
-                req->agent_info_option = (uint8_t*)option - 2;
-
-                break;
-        case SD_DHCP_OPTION_HOST_NAME: {
-                _cleanup_free_ char *p = NULL;
-
-                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;
-        }
-
-        return 0;
-}
-
-static DHCPRequest* dhcp_request_free(DHCPRequest *req) {
-        if (!req)
-                return NULL;
-
-        free(req->hostname);
-        return mfree(req);
-}
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
-
-static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) {
-        int r;
-
-        assert(req);
-        assert(message);
-
-        req->message = message;
-
-        if (message->hlen > sizeof(message->chaddr))
-                return -EBADMSG;
-
-        /* set client id based on MAC address if client did not send an explicit one */
-        if (!sd_dhcp_client_id_is_set(&req->client_id)) {
-                if (!client_id_data_size_is_valid(message->hlen))
-                        return -EBADMSG;
-
-                r = sd_dhcp_client_id_set(&req->client_id, /* type= */ 1, message->chaddr, message->hlen);
-                if (r < 0)
-                        return r;
-        }
-
-        if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) {
-                uint8_t type;
-                const void *data;
-                size_t size;
-
-                /* See RFC2131 section 4.1.1.
-                 * hlen and chaddr may not be set for non-ethernet interface.
-                 * Let's try to retrieve it from the client ID. */
-
-                if (!sd_dhcp_client_id_is_set(&req->client_id))
-                        return -EBADMSG;
-
-                r = sd_dhcp_client_id_get(&req->client_id, &type, &data, &size);
-                if (r < 0)
-                        return r;
-
-                if (type != 1)
-                        return -EBADMSG;
-
-                if (size > sizeof(message->chaddr))
-                        return -EBADMSG;
-
-                memcpy(message->chaddr, data, size);
-                message->hlen = size;
-        }
-
-        if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
-                req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
-
-        if (req->lifetime <= 0)
-                req->lifetime = MAX(USEC_PER_SEC, server->default_lease_time);
-
-        if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time)
-                req->lifetime = server->max_lease_time;
-
-        return 0;
-}
-
-static void request_set_timestamp(DHCPRequest *req, const triple_timestamp *timestamp) {
-        assert(req);
-
-        if (timestamp && triple_timestamp_is_set(timestamp))
-                req->timestamp = *timestamp;
-        else
-                triple_timestamp_now(&req->timestamp);
-}
-
-static int request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret) {
-        assert(req);
-        assert(TRIPLE_TIMESTAMP_HAS_CLOCK(clock));
-        assert(clock_supported(clock));
-        assert(ret);
-
-        if (req->lifetime <= 0)
-                return -ENODATA;
-
-        if (!triple_timestamp_is_set(&req->timestamp))
-                return -ENODATA;
-
-        *ret = usec_add(triple_timestamp_by_clock(&req->timestamp, clock), req->lifetime);
-        return 0;
-}
-
-static bool address_is_in_pool(sd_dhcp_server *server, be32_t address) {
+bool dhcp_server_address_is_in_pool(sd_dhcp_server *server, be32_t address) {
         assert(server);
 
         if (server->pool_size == 0)
@@ -981,34 +299,7 @@ static bool address_is_in_pool(sd_dhcp_server *server, be32_t address) {
         return true;
 }
 
-static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, be32_t address) {
-        usec_t expiration;
-        int r;
-
-        assert(server);
-        assert(req);
-        assert(address != 0);
-
-        r = request_get_lifetime_timestamp(req, CLOCK_BOOTTIME, &expiration);
-        if (r < 0)
-                return r;
-
-        r = dhcp_server_set_lease(server, address, req, expiration);
-        if (r < 0)
-                return log_dhcp_server_errno(server, r, "Failed to create new lease: %m");
-
-        r = server_send_offer_or_ack(server, req, address, DHCP_ACK);
-        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));
-
-        server_on_lease_change(server);
-
-        return DHCP_ACK;
-}
-
-static bool address_available(sd_dhcp_server *server, be32_t address) {
+bool dhcp_server_address_available(sd_dhcp_server *server, be32_t address) {
         assert(server);
 
         if (hashmap_contains(server->bound_leases_by_address, UINT32_TO_PTR(address)) ||
@@ -1019,262 +310,6 @@ static bool address_available(sd_dhcp_server *server, be32_t address) {
         return true;
 }
 
-#define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30)
-
-int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp) {
-        _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL;
-        _cleanup_free_ char *error_message = NULL;
-        sd_dhcp_server_lease *existing_lease, *static_lease;
-        int type, r;
-
-        assert(server);
-        assert(message);
-
-        if (message->op != BOOTREQUEST)
-                return 0;
-
-        req = new0(DHCPRequest, 1);
-        if (!req)
-                return -ENOMEM;
-
-        type = dhcp_option_parse(message, length, parse_request, req, &error_message);
-        if (type < 0)
-                return type;
-
-        r = ensure_sane_request(server, req, message);
-        if (r < 0)
-                return r;
-
-        request_set_timestamp(req, timestamp);
-
-        r = dhcp_server_cleanup_expired_leases(server);
-        if (r < 0)
-                return r;
-
-        existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id);
-        static_lease = dhcp_server_get_static_lease(server, req);
-
-        switch (type) {
-
-        case DHCP_DISCOVER: {
-                be32_t address = INADDR_ANY;
-
-                log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->xid));
-
-                if (server->pool_size == 0)
-                        /* no pool allocated */
-                        return 0;
-
-                /* for now pick a random free address from the pool */
-                if (static_lease) {
-                        sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(static_lease->address));
-                        if (l && l != existing_lease)
-                                /* The address is already assigned to another host. Refusing. */
-                                return 0;
-
-                        /* Found a matching static lease. */
-                        address = static_lease->address;
-
-                } else if (existing_lease && address_is_in_pool(server, existing_lease->address))
-
-                        /* If we previously assigned an address to the host, then reuse it. */
-                        address = existing_lease->address;
-
-                else {
-                        struct siphash state;
-                        uint64_t hash;
-
-                        /* even with no persistence of leases, we try to offer the same client
-                           the same IP address. we do this by using the hash of the client id
-                           as the offset into the pool of leases when finding the next free one */
-
-                        siphash24_init(&state, HASH_KEY.bytes);
-                        client_id_hash_func(&req->client_id, &state);
-                        hash = htole64(siphash24_finalize(&state));
-
-                        for (unsigned i = 0; i < server->pool_size; i++) {
-                                be32_t tmp_address;
-
-                                tmp_address = server->subnet | htobe32(server->pool_offset + (hash + i) % server->pool_size);
-                                if (address_available(server, tmp_address)) {
-                                        address = tmp_address;
-                                        break;
-                                }
-                        }
-                }
-
-                if (address == INADDR_ANY)
-                        /* no free addresses left */
-                        return 0;
-
-                if (server->rapid_commit && req->rapid_commit)
-                        return server_ack_request(server, req, address);
-
-                r = server_send_offer_or_ack(server, req, address, DHCP_OFFER);
-                if (r < 0)
-                        /* 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));
-                return DHCP_OFFER;
-        }
-        case DHCP_DECLINE:
-                log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message));
-
-                /* TODO: make sure we don't offer this address again */
-
-                return 1;
-
-        case DHCP_REQUEST: {
-                be32_t address;
-                bool init_reboot = false;
-
-                /* see RFC 2131, section 4.3.2 */
-
-                if (req->server_id != 0) {
-                        log_dhcp_server(server, "REQUEST (selecting) (0x%x)",
-                                        be32toh(req->message->xid));
-
-                        /* SELECTING */
-                        if (req->server_id != server->address)
-                                /* client did not pick us */
-                                return 0;
-
-                        if (req->message->ciaddr != 0)
-                                /* this MUST be zero */
-                                return 0;
-
-                        if (req->requested_ip == 0)
-                                /* this must be filled in with the yiaddr
-                                   from the chosen OFFER */
-                                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));
-
-                        /* INIT-REBOOT */
-                        if (req->message->ciaddr != 0)
-                                /* this MUST be zero */
-                                return 0;
-
-                        /* TODO: check more carefully if IP is correct */
-                        address = req->requested_ip;
-                        init_reboot = true;
-                } else {
-                        log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)",
-                                        be32toh(req->message->xid));
-
-                        /* REBINDING / RENEWING */
-                        if (req->message->ciaddr == 0)
-                                /* this MUST be filled in with clients IP address */
-                                return 0;
-
-                        address = req->message->ciaddr;
-                }
-
-                /* Silently ignore Rapid Commit option in REQUEST message. */
-                req->rapid_commit = false;
-
-                if (static_lease) {
-                        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);
-
-                        sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(address));
-                        if (l && l != existing_lease)
-                                /* The requested address is already assigned to another host. Refusing. */
-                                return server_send_nak_or_ignore(server, init_reboot, req);
-
-                        /* Found a static lease for the client ID. */
-                        return server_ack_request(server, req, address);
-                }
-
-                if (address_is_in_pool(server, address))
-                        /* The requested address is in the pool. */
-                        return server_ack_request(server, req, address);
-
-                /* Refuse otherwise. */
-                return server_send_nak_or_ignore(server, init_reboot, req);
-        }
-
-        case DHCP_RELEASE: {
-                log_dhcp_server(server, "RELEASE (0x%x)",
-                                be32toh(req->message->xid));
-
-                if (!existing_lease)
-                        return 0;
-
-                if (existing_lease->address != req->message->ciaddr)
-                        return 0;
-
-                sd_dhcp_server_lease_unref(existing_lease);
-
-                server_on_lease_change(server);
-
-                return 0;
-        }}
-
-        return 0;
-}
-
-static int server_receive_message(sd_event_source *s, int fd,
-                                  uint32_t revents, void *userdata) {
-        _cleanup_free_ DHCPMessage *message = NULL;
-        /* This needs to be initialized with zero. See #20741.
-         * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */
-        CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL +
-                         CMSG_SPACE(sizeof(struct in_pktinfo))) control = {};
-        sd_dhcp_server *server = ASSERT_PTR(userdata);
-        struct iovec iov = {};
-        struct msghdr msg = {
-                .msg_iov = &iov,
-                .msg_iovlen = 1,
-                .msg_control = &control,
-                .msg_controllen = sizeof(control),
-        };
-        ssize_t datagram_size, len;
-        int r;
-
-        datagram_size = next_datagram_size_fd(fd);
-        if (ERRNO_IS_NEG_TRANSIENT(datagram_size) || ERRNO_IS_NEG_DISCONNECT(datagram_size))
-                return 0;
-        if (datagram_size < 0) {
-                log_dhcp_server_errno(server, datagram_size, "Failed to determine datagram size to read, ignoring: %m");
-                return 0;
-        }
-
-        size_t buflen = datagram_size;
-        message = malloc0(buflen);
-        if (!message)
-                return -ENOMEM;
-
-        iov = IOVEC_MAKE(message, datagram_size);
-
-        len = recvmsg_safe(fd, &msg, 0);
-        if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len))
-                return 0;
-        if (len < 0) {
-                log_dhcp_server_errno(server, len, "Could not receive message, ignoring: %m");
-                return 0;
-        }
-
-        if ((size_t) len < sizeof(DHCPMessage))
-                return 0;
-
-        /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
-        struct in_pktinfo *info = CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo);
-        if (info && info->ipi_ifindex != server->ifindex)
-                return 0;
-
-        r = dhcp_server_handle_message(server, message, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg));
-        if (r < 0)
-                log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m");
-
-        return 0;
-}
-
 static void dhcp_server_update_lease_servers(sd_dhcp_server *server) {
         assert(server);
         assert(server->address != 0);
@@ -1303,55 +338,16 @@ int sd_dhcp_server_start(sd_dhcp_server *server) {
 
         dhcp_server_update_lease_servers(server);
 
-        r = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
-        if (r < 0) {
-                r = -errno;
-                goto on_error;
-        }
-        server->fd_raw = r;
-
-        r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1);
+        r = dhcp_server_setup_io_event_source(server);
         if (r < 0)
-                goto on_error;
-        server->fd = r;
-
-        r = sd_event_add_io(server->event, &server->receive_message,
-                            server->fd, EPOLLIN,
-                            server_receive_message, server);
-        if (r < 0)
-                goto on_error;
-
-        r = sd_event_source_set_priority(server->receive_message,
-                                         server->event_priority);
-        if (r < 0)
-                goto on_error;
+                return r;
 
         r = dhcp_server_load_leases(server);
         if (r < 0)
                 log_dhcp_server_errno(server, r, "Failed to load lease file %s, ignoring: %m", strna(server->lease_file));
 
         log_dhcp_server(server, "STARTED");
-
         return 0;
-
-on_error:
-        sd_dhcp_server_stop(server);
-        return r;
-}
-
-int sd_dhcp_server_forcerenew(sd_dhcp_server *server) {
-        sd_dhcp_server_lease *lease;
-        int r = 0;
-
-        assert_return(server, -EINVAL);
-
-        log_dhcp_server(server, "FORCERENEW");
-
-        HASHMAP_FOREACH(lease, server->bound_leases_by_client_id)
-                RET_GATHER(r,
-                           server_send_forcerenew(server, lease->address, lease->gateway,
-                                                  lease->htype, lease->hlen, lease->chaddr));
-        return r;
 }
 
 int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz) {
index bbfb4f1c05d0a39727b55b465e95b0a1b23591e4..86face3406c71cf1406f469727b43926d00591f7 100644 (file)
@@ -9,6 +9,7 @@
 #include "sd-event.h"
 
 #include "dhcp-server-internal.h"
+#include "dhcp-server-request.h"
 #include "tests.h"
 
 TEST(basic) {