From: Yu Watanabe Date: Mon, 4 May 2026 04:24:33 +0000 (+0900) Subject: sd-dhcp-server: split into small pieaces X-Git-Tag: v261-rc1~22^2~8 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=3f8d0004e9467daeed21b392bfabd53e65912191;p=thirdparty%2Fsystemd.git sd-dhcp-server: split into small pieaces No functional change, just several functions are moved/split/renamed. --- diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index d4caae66d42..c03ce7dddbb 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -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( \ diff --git a/src/libsystemd-network/dhcp-server-lease-internal.h b/src/libsystemd-network/dhcp-server-lease-internal.h index aeb099ff818..6d7eb4c2136 100644 --- a/src/libsystemd-network/dhcp-server-lease-internal.h +++ b/src/libsystemd-network/dhcp-server-lease-internal.h @@ -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 index 00000000000..5cccc7c2f64 --- /dev/null +++ b/src/libsystemd-network/dhcp-server-request.c @@ -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 index 00000000000..f1e827a0e13 --- /dev/null +++ b/src/libsystemd-network/dhcp-server-request.h @@ -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 index 00000000000..67fc390bca3 --- /dev/null +++ b/src/libsystemd-network/dhcp-server-send.c @@ -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 index 00000000000..80fd9a6b94f --- /dev/null +++ b/src/libsystemd-network/dhcp-server-send.h @@ -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); diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 9b36f6edf2c..3981ecd7d58 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -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', diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 07f15ef6dc2..5267137540d 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -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) { diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index bbfb4f1c05d..86face3406c 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -9,6 +9,7 @@ #include "sd-event.h" #include "dhcp-server-internal.h" +#include "dhcp-server-request.h" #include "tests.h" TEST(basic) {