No functional change, just several functions are moved/split/renamed.
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"
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( \
#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 {
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
'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',
#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);
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)
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)) ||
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);
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) {
#include "sd-event.h"
#include "dhcp-server-internal.h"
+#include "dhcp-server-request.h"
#include "tests.h"
TEST(basic) {