sd-dhcp-server: several trivial cleanups (#42235)

This should mostly not change behavior, except for some corner cases.
Just refactoring and preparation for later changes.
This commit is contained in:
Yu Watanabe
2026-05-22 06:44:07 +09:00
committed by GitHub
13 changed files with 1358 additions and 1207 deletions
+8 -29
View File
@@ -5,13 +5,11 @@
Copyright © 2013 Intel Corporation. All rights reserved.
***/
#include "sd-dhcp-lease.h"
#include "sd-dhcp-server.h"
#include "dhcp-client-id-internal.h"
#include "dhcp-option.h"
#include "sd-forward.h"
#include "network-common.h"
#include "sd-forward.h"
#include "sparse-endian.h"
#include "tlv-util.h"
@@ -31,9 +29,10 @@ typedef struct sd_dhcp_server {
sd_event *event;
int event_priority;
sd_event_source *receive_message;
int fd;
int fd_raw;
sd_event_source *io_event_source;
uint8_t ip_service_type;
int socket_fd; /* socket fd set externally, used by unit tests */
int raw_socket_fd; /* send-only raw socket fd, used on sending L2 unicast message. */
int ifindex;
char *ifname;
@@ -74,32 +73,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( \
@@ -5,6 +5,8 @@
#include "dhcp-client-id-internal.h"
#include "dhcp-server-internal.h"
#include "dhcp-server-request.h"
#include "ether-addr-util.h"
#include "sd-forward.h"
typedef struct sd_dhcp_server_lease {
@@ -15,10 +17,9 @@ typedef struct sd_dhcp_server_lease {
sd_dhcp_client_id client_id;
uint8_t htype; /* e.g. ARPHRD_ETHER */
uint8_t hlen; /* e.g. ETH_ALEN */
struct hw_addr_data hw_addr;
be32_t address;
be32_t gateway;
uint8_t chaddr[16];
usec_t expiration;
char *hostname;
} sd_dhcp_server_lease;
@@ -27,7 +28,7 @@ extern const struct hash_ops dhcp_server_lease_hash_ops;
int dhcp_server_put_lease(sd_dhcp_server *server, sd_dhcp_server_lease *lease, bool is_static);
int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *req, usec_t expiration);
int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req);
int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server);
sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req);
@@ -0,0 +1,571 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-event.h"
#include "alloc-util.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 "fd-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) {
assert(req);
assert(message);
req->message = message;
if (message->hlen > sizeof(message->chaddr))
return -EBADMSG;
req->hw_addr.length = req->message->hlen;
memcpy_safe(req->hw_addr.bytes, message->chaddr, message->hlen);
/* Fake client ID generated from the DHCP header.
* The client ID type 0 and 255 are special. So do not use if htype is 0 or 255.
* Note, Some hardware type (e.g. Infiniband) may not set chaddr field. */
if (!IN_SET(req->message->htype, 0, UINT8_MAX))
(void) sd_dhcp_client_id_set(
&req->client_id_by_header,
req->message->htype,
req->message->chaddr,
req->message->hlen);
/* If Client Identifier option is unspecified, use the generated one. */
if (!sd_dhcp_client_id_is_set(&req->client_id))
req->client_id = req->client_id_by_header;
/* We manage bound leases by client ID. Hence, at least one of them is necessary. */
if (!sd_dhcp_client_id_is_set(&req->client_id))
return -EBADMSG;
if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
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_address, 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;
req->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 0;
}
static int dhcp_server_ack(sd_dhcp_server *server, DHCPRequest *req) {
int r;
assert(server);
assert(req);
assert(req->address != INADDR_ANY);
r = dhcp_server_set_lease(server, req);
if (r < 0)
return log_dhcp_server_errno(server, r, "Failed to create new lease: %m");
r = server_send_offer_or_ack(server, req, 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;
/* 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. */
req->static_lease = static_lease;
req->address = static_lease->address;
} else if (existing_lease && dhcp_server_address_is_in_pool(server, existing_lease->address))
/* If we previously assigned an address to the host, then reuse it. */
req->address = existing_lease->address;
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)) {
req->address = a;
break;
}
}
}
if (req->address == INADDR_ANY)
/* no free addresses left */
return 0;
if (server->rapid_commit && req->rapid_commit)
return dhcp_server_ack(server, req);
r = server_send_offer_or_ack(server, req, 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_address != INADDR_ANY) {
log_dhcp_server(server, "REQUEST (selecting) (0x%x)",
be32toh(req->message->xid));
/* SELECTING */
if (req->server_address != server->address)
/* client did not pick us */
return 0;
if (req->message->ciaddr != 0)
/* this MUST be zero */
return 0;
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);
req->static_lease = static_lease;
req->address = address;
/* Found a static lease for the client ID. */
return dhcp_server_ack(server, req);
}
if (dhcp_server_address_is_in_pool(server, address)) {
/* The requested address is in the pool. */
req->address = address;
return dhcp_server_ack(server, req);
}
/* 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);
if (req->server_address != server->address)
return 0; /* The message is not for us. Let's silently ignore the packet. */
/* TODO: make sure we don't offer this address again for a while. */
log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message));
return 0;
}
static int dhcp_server_process_release(sd_dhcp_server *server, DHCPRequest *req) {
assert(server);
assert(req);
if (req->server_address != server->address)
return 0; /* The message is not for us. Let's silently ignore the packet. */
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 -EBADMSG;
sd_dhcp_server_lease_unref(existing_lease);
dhcp_server_on_lease_change(server);
log_dhcp_server(server, "RELEASE (0x%x)", be32toh(req->message->xid));
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 (length < sizeof(DHCPMessage))
return 0;
if (message->op != BOOTREQUEST)
return 0;
_cleanup_(dhcp_request_freep) DHCPRequest *req = NULL;
_cleanup_free_ char *error_message = NULL;
r = dhcp_server_parse_message(server, message, length, &req, &error_message);
if (r < 0)
return r;
dhcp_request_set_timestamp(req, timestamp);
r = dhcp_server_cleanup_expired_leases(server);
if (r < 0)
return r;
switch (req->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) {
sd_dhcp_server *server = ASSERT_PTR(userdata);
int r;
ssize_t buflen = next_datagram_size_fd(fd);
if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
return 0;
if (buflen < 0) {
log_dhcp_server_errno(server, buflen, "Failed to determine datagram size to read, ignoring: %m");
return 0;
}
_cleanup_free_ void *buf = malloc0(buflen);
if (!buf)
return -ENOMEM;
/* 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 = {};
struct iovec iov = IOVEC_MAKE(buf, buflen);
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = &control,
.msg_controllen = sizeof(control),
};
ssize_t 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;
}
/* 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, buf, (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 int server_open_socket(sd_dhcp_server *server) {
int r;
assert(server);
_cleanup_close_ int fd = RET_NERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
if (fd < 0)
return fd;
r = socket_bind_to_ifindex(fd, server->ifindex);
if (r < 0)
return r;
r = setsockopt_int(fd, SOL_SOCKET, SO_TIMESTAMP, true);
if (r < 0)
return r;
r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true);
if (r < 0)
return r;
r = setsockopt_int(fd, SOL_SOCKET, SO_BROADCAST, true);
if (r < 0)
return r;
r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(server->ip_service_type));
if (r < 0)
return r;
r = setsockopt_int(fd, IPPROTO_IP, IP_TOS, server->ip_service_type);
if (r < 0)
return r;
r = setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, true);
if (r < 0)
return r;
union sockaddr_union sa = {
.in.sin_family = AF_INET,
.in.sin_port = htobe16(DHCP_PORT_SERVER),
.in.sin_addr.s_addr = htobe32(INADDR_ANY),
};
if (bind(fd, &sa.sa, sizeof(sa.in)) < 0)
return -errno;
return TAKE_FD(fd);
}
int dhcp_server_setup_io_event_source(sd_dhcp_server *server) {
int r;
assert(server);
assert(server->event);
_cleanup_close_ int fd_close = -EBADF;
int fd;
if (server->socket_fd >= 0)
/* When a socket fd is given externally, unconditionally use it and do not close the socket
* even if we fail to set up the event source. */
fd = server->socket_fd;
else {
fd = fd_close = server_open_socket(server);
if (fd < 0)
return fd;
}
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
r = sd_event_add_io(server->event, &s, fd, EPOLLIN, server_receive_message, server);
if (r < 0)
return r;
r = sd_event_source_set_priority(s, server->event_priority);
if (r < 0)
return r;
(void) sd_event_source_set_description(s, "dhcp-server-io");
if (fd_close >= 0) {
r = sd_event_source_set_io_fd_own(s, true);
if (r < 0)
return r;
TAKE_FD(fd_close);
}
sd_event_source_disable_unref(server->io_event_source);
server->io_event_source = TAKE_PTR(s);
return 0;
}
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-dhcp-server-lease.h"
#include "dhcp-client-id-internal.h"
#include "dhcp-protocol.h"
#include "ether-addr-util.h"
#include "sd-forward.h"
#include "sparse-endian.h"
#include "time-util.h"
typedef struct DHCPRequest {
/* received message */
DHCPMessage *message;
/* sender hardware address, may not be set for non-ethernet interface */
struct hw_addr_data hw_addr;
triple_timestamp timestamp;
/* options */
uint8_t type;
sd_dhcp_client_id client_id;
sd_dhcp_client_id client_id_by_header;
size_t max_optlen;
be32_t server_address;
be32_t requested_ip;
usec_t lifetime;
const uint8_t *agent_info_option;
char *hostname;
const uint8_t *parameter_request_list;
size_t parameter_request_list_len;
bool rapid_commit;
/* acquired data */
sd_dhcp_server_lease *static_lease;
be32_t address;
} 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);
+573
View File
@@ -0,0 +1,573 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-event.h"
#include "alloc-util.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 "fd-util.h"
#include "hashmap.h"
#include "in-addr-util.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
#include "socket-util.h"
static int server_acquire_raw_socket(sd_dhcp_server *server) {
int r;
assert(server);
if (server->socket_fd >= 0)
/* When a socket fd is given externally, unconditionally use it and do not close the socket. */
return server->socket_fd;
if (server->raw_socket_fd >= 0)
/* Already opened. */
return server->raw_socket_fd;
/* This is a send-only socket, hence it is opened with protocol=0, and do not call bind().
* The interface binding will be done on send. */
_cleanup_close_ int fd = RET_NERRNO(socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
if (fd < 0)
return fd;
r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(server->ip_service_type));
if (r < 0)
return r;
return server->raw_socket_fd = TAKE_FD(fd);
}
static int dhcp_server_send_unicast_raw(
sd_dhcp_server *server,
const struct hw_addr_data *hw_addr,
DHCPPacket *packet,
size_t len) {
int r;
assert(server);
assert(server->ifindex > 0);
assert(server->address != 0);
assert(hw_addr);
assert(packet);
assert(len > sizeof(DHCPPacket));
if (len > UINT16_MAX)
return -EOVERFLOW;
int fd = server_acquire_raw_socket(server);
if (fd < 0)
return fd;
r = dhcp_packet_append_ip_headers(
packet,
server->address,
DHCP_PORT_SERVER,
packet->dhcp.yiaddr,
DHCP_PORT_CLIENT,
len,
server->ip_service_type);
if (r < 0)
return r;
union sockaddr_union sa = {
.ll.sll_family = AF_PACKET,
.ll.sll_protocol = htobe16(ETH_P_IP),
.ll.sll_ifindex = server->ifindex,
.ll.sll_halen = hw_addr->length,
};
memcpy_safe(sa.ll.sll_addr, hw_addr->bytes, hw_addr->length);
struct msghdr mh = {
.msg_name = &sa.sa,
.msg_namelen = sockaddr_ll_len(&sa.ll),
.msg_iov = &IOVEC_MAKE(packet, len),
.msg_iovlen = 1,
};
if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0)
return -errno;
return 0;
}
static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
uint16_t destination_port,
DHCPMessage *message, size_t len) {
assert(server);
assert(message);
assert(len >= sizeof(DHCPMessage));
int fd = sd_event_source_get_io_fd(server->io_event_source);
if (fd < 0)
return fd;
union sockaddr_union sa = {
.in.sin_family = AF_INET,
.in.sin_port = htobe16(destination_port),
.in.sin_addr.s_addr = destination,
};
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {};
struct msghdr msg = {
.msg_name = &sa,
.msg_namelen = sizeof(sa.in),
.msg_iov = &IOVEC_MAKE(message, len),
.msg_iovlen = 1,
.msg_control = &control,
.msg_controllen = sizeof(control),
};
struct cmsghdr *cmsg = ASSERT_PTR(CMSG_FIRSTHDR(&msg));
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
struct in_pktinfo *pktinfo = ASSERT_PTR(CMSG_TYPED_DATA(cmsg, struct in_pktinfo));
pktinfo->ipi_ifindex = server->ifindex;
pktinfo->ipi_spec_dst.s_addr = server->address;
if (sendmsg(fd, &msg, MSG_NOSIGNAL) < 0)
return -errno;
return 0;
}
static int dhcp_server_send_message(
sd_dhcp_server *server,
DHCPRequest *req,
uint8_t type,
DHCPPacket *packet,
size_t optoffset) {
assert(server);
assert(req);
assert(req->message);
assert(packet);
/* 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 (req->message->giaddr != INADDR_ANY)
return dhcp_server_send_udp(
server,
req->message->giaddr,
DHCP_PORT_SERVER,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
/* when ’giaddr’ is zero, the server broadcasts any DHCPNAK messages to 0xffffffff. */
if (type == DHCP_NAK)
return dhcp_server_send_udp(
server,
INADDR_BROADCAST,
DHCP_PORT_CLIENT,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
/* If the ’giaddr’ field is zero and the ’ciaddr’ field is nonzero, then the server unicasts
* DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. */
if (req->message->ciaddr != INADDR_ANY)
return dhcp_server_send_udp(
server,
req->message->ciaddr,
DHCP_PORT_CLIENT,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
/* If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is set, then the server
* broadcasts DHCPOFFER and DHCPACK messages to 0xffffffff.
*
* (Note, even the broadcast flag is unset, we may not know the client hardware address, e.g.
* InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, broadcast
* the message if 'yiaddr' is zero.) */
if (FLAGS_SET(be16toh(req->message->flags), 0x8000) ||
hw_addr_is_null(&req->hw_addr) ||
packet->dhcp.yiaddr == INADDR_ANY)
return dhcp_server_send_udp(
server,
INADDR_BROADCAST,
DHCP_PORT_CLIENT,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
/* 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. */
return dhcp_server_send_unicast_raw(
server,
&req->hw_addr,
packet,
sizeof(DHCPPacket) + optoffset);
}
static int dhcp_server_send_packet(sd_dhcp_server *server,
DHCPRequest *req, DHCPPacket *packet,
int type, size_t optoffset) {
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;
return dhcp_server_send_message(server, req, type, packet, optoffset);
}
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->hw_addr.length, req->hw_addr.bytes,
type, req->max_optlen, &optoffset);
if (r < 0)
return r;
packet->dhcp.flags = req->message->flags;
packet->dhcp.giaddr = req->message->giaddr;
*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) {
int r;
assert(server);
assert(packet);
assert(offset);
assert(req);
if (!req->static_lease || !req->static_lease->hostname)
return 0;
if (dns_name_is_single_label(req->static_lease->hostname))
/* Option 12 */
return dhcp_option_append(
&packet->dhcp,
req->max_optlen,
offset,
/* overload= */ 0,
SD_DHCP_OPTION_HOST_NAME,
strlen(req->static_lease->hostname),
req->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(req->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,
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 = req->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 dhcp_server_send_forcerenew(
sd_dhcp_server *server,
sd_dhcp_server_lease *lease) {
_cleanup_free_ DHCPPacket *packet = NULL;
size_t optoffset = 0;
int r;
assert(server);
assert(lease);
packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE);
if (!packet)
return -ENOMEM;
r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0,
lease->htype, lease->hw_addr.length, lease->hw_addr.bytes, 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, lease->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, dhcp_server_send_forcerenew(server, lease));
return r;
}
+13
View File
@@ -0,0 +1,13 @@
/* 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,
uint8_t type);
int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req);
+7 -6
View File
@@ -27,10 +27,10 @@ static int add_lease(sd_dhcp_server *server, const struct in_addr *server_addres
*lease = (sd_dhcp_server_lease) {
.n_ref = 1,
.address = htobe32(UINT32_C(10) << 24 | i),
.chaddr = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
.hw_addr.length = ETH_ALEN,
.hw_addr.bytes = { 3, 3, 3, 3, 3, 3, },
.expiration = usec_add(now(CLOCK_BOOTTIME), USEC_PER_DAY),
.gateway = server_address->s_addr,
.hlen = ETH_ALEN,
.htype = ARPHRD_ETHER,
.client_id.size = 2,
@@ -63,21 +63,21 @@ static int add_static_lease(sd_dhcp_server *server, uint8_t i) {
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
struct in_addr address = { .s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))};
if (size < sizeof(DHCPMessage))
return 0;
fuzz_setup_logging();
_cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
_cleanup_close_ int dir_fd = ASSERT_OK(mkdtemp_open(NULL, 0, &tmpdir));
_cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR;
ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd));
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
ASSERT_OK(sd_event_new(&event));
_cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
ASSERT_OK(sd_dhcp_server_new(&server, 1));
ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL));
server->fd = ASSERT_OK_ERRNO(open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY));
server->socket_fd = TAKE_FD(socket_fd[0]);
ASSERT_OK(sd_dhcp_server_set_lease_file(server, dir_fd, "leases"));
ASSERT_OK(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0));
@@ -90,6 +90,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
ASSERT_OK(add_static_lease(server, 4));
_cleanup_free_ uint8_t *duped = ASSERT_NOT_NULL(memdup(data, size));
ASSERT_OK(sd_dhcp_server_start(server));
(void) dhcp_server_handle_message(server, (DHCPMessage*) duped, size, NULL);
ASSERT_OK(dhcp_server_save_leases(server));
+2
View File
@@ -13,6 +13,8 @@ libsystemd_network_sources = files(
'dhcp-relay-interface.c',
'dhcp-relay-upstream.c',
'dhcp-route.c',
'dhcp-server-request.c',
'dhcp-server-send.c',
'dhcp6-network.c',
'dhcp6-option.c',
'dhcp6-protocol.c',
+75 -69
View File
@@ -16,7 +16,6 @@
#include "iovec-util.h"
#include "json-util.h"
#include "mkdir.h"
#include "string-util.h"
#include "tmpfile-util.h"
static sd_dhcp_server_lease* dhcp_server_lease_free(sd_dhcp_server_lease *lease) {
@@ -65,21 +64,25 @@ int dhcp_server_put_lease(sd_dhcp_server *server, sd_dhcp_server_lease *lease, b
return 0;
}
int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *req, usec_t expiration) {
int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req) {
_cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL;
int r;
assert(server);
assert(address != 0);
assert(req);
assert(expiration != 0);
assert(req->address != INADDR_ANY);
usec_t expiration;
r = dhcp_request_get_lifetime_timestamp(req, CLOCK_BOOTTIME, &expiration);
if (r < 0)
return r;
/* If a lease for the host already exists, update it. */
lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id);
if (lease) {
if (lease->address != address) {
if (lease->address != req->address) {
hashmap_remove_value(server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease);
lease->address = address;
lease->address = req->address;
r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease);
if (r < 0)
@@ -100,15 +103,14 @@ int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *r
*lease = (sd_dhcp_server_lease) {
.n_ref = 1,
.address = address,
.address = req->address,
.client_id = req->client_id,
.htype = req->message->htype,
.hlen = req->message->hlen,
.gateway = req->message->giaddr,
.expiration = expiration,
};
memcpy(lease->chaddr, req->message->chaddr, req->message->hlen);
lease->hw_addr = req->hw_addr;
if (req->hostname) {
lease->hostname = strdup(req->hostname);
@@ -146,27 +148,17 @@ int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) {
sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req) {
sd_dhcp_server_lease *static_lease;
sd_dhcp_client_id client_id;
assert(server);
assert(req);
static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id);
if (static_lease)
goto verify;
/* when no lease is found based on the client id fall back to chaddr */
if (!client_id_data_size_is_valid(req->message->hlen))
return NULL;
if (sd_dhcp_client_id_set(&client_id, /* type= */ 1, req->message->chaddr, req->message->hlen) < 0)
return NULL;
static_lease = hashmap_get(server->static_leases_by_client_id, &client_id);
if (!static_lease && sd_dhcp_client_id_is_set(&req->client_id_by_header))
/* when no lease is found, fall back to use the fake client ID generated from the header. */
static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id_by_header);
if (!static_lease)
return NULL;
verify:
/* Check if the address is in the same subnet. */
if ((static_lease->address & server->netmask) != server->subnet)
return NULL;
@@ -247,8 +239,8 @@ static int dhcp_server_lease_build_json(sd_dhcp_server_lease *lease, sd_json_var
JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING_NON_NULL("Address", &(struct in_addr) { .s_addr = lease->address }),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", lease->hostname),
SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressType", lease->htype),
SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressLength", lease->hlen),
SD_JSON_BUILD_PAIR_BYTE_ARRAY("HardwareAddress", lease->chaddr, sizeof(lease->chaddr)));
SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressLength", lease->hw_addr.length),
SD_JSON_BUILD_PAIR_BYTE_ARRAY("HardwareAddress", lease->hw_addr.bytes, lease->hw_addr.length));
}
int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, sd_json_variant **v) {
@@ -376,70 +368,67 @@ int dhcp_server_save_leases(sd_dhcp_server *server) {
return 0;
}
static int json_dispatch_chaddr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
uint8_t* address = ASSERT_PTR(userdata);
_cleanup_(iovec_done) struct iovec iov = {};
int r;
typedef struct LeaseParam {
sd_dhcp_client_id client_id;
uint8_t htype;
uint8_t hlen;
struct iovec hw_addr;
struct in_addr address;
usec_t exp_b;
usec_t exp_r;
char *hostname;
} LeaseParam;
r = json_dispatch_byte_array_iovec(name, variant, flags, &iov);
if (r < 0)
return r;
static void lease_param_done(LeaseParam *p) {
assert(p);
if (iov.iov_len != 16)
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name));
memcpy(address, iov.iov_base, iov.iov_len);
return 0;
iovec_done(&p->hw_addr);
free(p->hostname);
}
static int json_dispatch_dhcp_lease(sd_dhcp_server *server, sd_json_variant *v, bool use_boottime) {
static const sd_json_dispatch_field dispatch_table_boottime[] = {
{ "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), SD_JSON_MANDATORY },
{ "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), SD_JSON_MANDATORY },
{ "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 },
{ "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, htype), 0 },
{ "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, hlen), 0 },
{ "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_chaddr, offsetof(sd_dhcp_server_lease, chaddr), 0 },
{ "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), SD_JSON_MANDATORY },
{ "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, SD_JSON_MANDATORY },
{}
}, dispatch_table_realtime[] = {
{ "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), SD_JSON_MANDATORY },
{ "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), SD_JSON_MANDATORY },
{ "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 },
{ "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, htype), 0 },
{ "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, hlen), 0 },
{ "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_chaddr, offsetof(sd_dhcp_server_lease, chaddr), 0 },
{ "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, SD_JSON_MANDATORY },
{ "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), SD_JSON_MANDATORY },
static const sd_json_dispatch_field dispatch_table[] = {
{ "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(LeaseParam, client_id), SD_JSON_MANDATORY },
{ "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(LeaseParam, htype), 0 },
{ "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(LeaseParam, hlen), 0 },
{ "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(LeaseParam, hw_addr), 0 },
{ "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(LeaseParam, address), SD_JSON_MANDATORY },
{ "AddressString", SD_JSON_VARIANT_STRING, NULL, 0, 0 },
{ "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LeaseParam, exp_b), SD_JSON_MANDATORY },
{ "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LeaseParam, exp_r), SD_JSON_MANDATORY },
{ "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(LeaseParam, hostname), 0 },
{}
};
_cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL;
usec_t now_b;
int r;
assert(server);
assert(v);
lease = new(sd_dhcp_server_lease, 1);
if (!lease)
return -ENOMEM;
*lease = (sd_dhcp_server_lease) {
.n_ref = 1,
};
r = sd_json_dispatch(v, use_boottime ? dispatch_table_boottime : dispatch_table_realtime, SD_JSON_ALLOW_EXTENSIONS, lease);
_cleanup_(lease_param_done) LeaseParam p = {};
r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
if (r < 0)
return r;
if (p.hlen > HW_ADDR_MAX_SIZE)
return -EINVAL;
if (p.hlen > p.hw_addr.iov_len)
return -EINVAL;
if (!in4_addr_is_set(&p.address))
return -EINVAL;
if (!sd_dhcp_client_id_is_set(&p.client_id))
return -EINVAL;
usec_t now_b;
r = sd_event_now(server->event, CLOCK_BOOTTIME, &now_b);
if (r < 0)
return r;
if (use_boottime) {
if (lease->expiration < now_b)
if (p.exp_b < now_b)
return 0; /* already expired */
} else {
usec_t now_r;
@@ -448,12 +437,29 @@ static int json_dispatch_dhcp_lease(sd_dhcp_server *server, sd_json_variant *v,
if (r < 0)
return r;
if (lease->expiration < now_r)
if (p.exp_r < now_r)
return 0; /* already expired */
lease->expiration = map_clock_usec_raw(lease->expiration, now_r, now_b);
p.exp_b = map_clock_usec_raw(p.exp_r, now_r, now_b);
}
_cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = new(sd_dhcp_server_lease, 1);
if (!lease)
return -ENOMEM;
*lease = (sd_dhcp_server_lease) {
.n_ref = 1,
.client_id = p.client_id,
.htype = p.htype,
.hw_addr.length = p.hlen,
.address = p.address.s_addr,
.expiration = p.exp_b,
.hostname = TAKE_PTR(p.hostname),
};
memcpy_safe(lease->hw_addr.bytes, p.hw_addr.iov_base, p.hlen);
r = dhcp_server_put_lease(server, lease, /* is_static= */ false);
if (r == -EEXIST)
return 0;
File diff suppressed because it is too large Load Diff
+35 -71
View File
@@ -9,45 +9,32 @@
#include "sd-event.h"
#include "dhcp-server-internal.h"
#include "dhcp-server-request.h"
#include "fd-util.h"
#include "tests.h"
static void test_pool(struct in_addr *address, unsigned size, int ret) {
_cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
ASSERT_OK(sd_dhcp_server_new(&server, 1));
if (ret >= 0)
ASSERT_RETURN_IS_CRITICAL(true, ASSERT_OK_EQ(sd_dhcp_server_configure_pool(server, address, 8, 0, size), ret));
else
ASSERT_RETURN_IS_CRITICAL(false, ASSERT_ERROR(sd_dhcp_server_configure_pool(server, address, 8, 0, size), -ret));
}
static int test_basic(void) {
_cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
TEST(basic) {
struct in_addr address_lo = {
.s_addr = htobe32(INADDR_LOOPBACK),
};
struct in_addr address_any = {
.s_addr = htobe32(INADDR_ANY),
};
int r;
log_debug("/* %s */", __func__);
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
ASSERT_OK(sd_event_new(&event));
/* attach to loopback interface */
ASSERT_OK(sd_dhcp_server_new(&server, 1));
_cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
ASSERT_OK(sd_dhcp_server_new(&server, 4242));
ASSERT_NOT_NULL(server);
ASSERT_OK(sd_dhcp_server_attach_event(server, event, 0));
ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, event, 0), EBUSY));
ASSERT_TRUE(sd_dhcp_server_get_event(server) == event); /* ASSERT_EQ() doesn't work here. */
ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL));
ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL), EBUSY));
ASSERT_PTR_EQ(sd_dhcp_server_get_event(server), event);
ASSERT_OK(sd_dhcp_server_detach_event(server));
ASSERT_NULL(sd_dhcp_server_get_event(server));
ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, 0));
ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, NULL, 0), EBUSY));
ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, SD_EVENT_PRIORITY_NORMAL));
ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, NULL, SD_EVENT_PRIORITY_NORMAL), EBUSY));
ASSERT_TRUE(sd_dhcp_server_ref(server) == server);
ASSERT_NULL(sd_dhcp_server_unref(server));
@@ -58,27 +45,22 @@ static int test_basic(void) {
ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_configure_pool(server, &address_lo, 38, 0, 0), ERANGE));
ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0));
ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0));
ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_configure_pool(server, &address_any, 8, 0, 1), EINVAL));
ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 1));
test_pool(&address_any, 1, -EINVAL);
test_pool(&address_lo, 1, 0);
_cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR;
ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd));
r = sd_dhcp_server_start(server);
/* skip test if running in an environment with no full networking support, CONFIG_PACKET not
* compiled in kernel, nor af_packet module available. */
if (IN_SET(r, -EPERM, -EAFNOSUPPORT))
return r;
ASSERT_OK(r);
server->socket_fd = TAKE_FD(socket_fd[0]);
ASSERT_OK(sd_dhcp_server_start(server));
ASSERT_OK(sd_dhcp_server_start(server));
ASSERT_OK(sd_dhcp_server_stop(server));
ASSERT_OK(sd_dhcp_server_stop(server));
ASSERT_OK(sd_dhcp_server_start(server));
return 0;
}
static void test_message_handler(void) {
_cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
TEST(dhcp_server_handle_message) {
struct {
DHCPMessageHeader header;
struct {
@@ -128,11 +110,15 @@ static void test_message_handler(void) {
.s_addr = htobe32(INADDR_LOOPBACK + 42),
};
static uint8_t static_lease_client_id[7] = {0x01, 'A', 'B', 'C', 'D', 'E', 'G' };
int r;
log_debug("/* %s */", __func__);
_cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR;
ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd));
ASSERT_OK(sd_dhcp_server_new(&server, 1));
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
ASSERT_OK(sd_event_new(&event));
_cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
ASSERT_OK(sd_dhcp_server_new(&server, 4242));
ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
@@ -140,13 +126,11 @@ static void test_message_handler(void) {
static_lease_client_id,
ELEMENTSOF(static_lease_client_id),
/* hostname= */ NULL));
ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, 0));
ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL));
server->socket_fd = TAKE_FD(socket_fd[0]);
ASSERT_OK(sd_dhcp_server_start(server));
r = dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL);
if (r == -ENETDOWN)
return (void) log_tests_skipped("Network is not available");
ASSERT_OK_EQ(r, DHCP_OFFER);
ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER);
test.end = 0;
/* TODO, shouldn't this fail? */
@@ -168,8 +152,9 @@ static void test_message_handler(void) {
test.header.op = BOOTREQUEST;
ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER);
/* Neither client ID nor hardware type is set. There is no way to manage the bound lease for the request. */
test.header.htype = 0;
ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER);
ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), EBADMSG);
test.header.htype = ARPHRD_ETHER;
ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER);
@@ -282,12 +267,9 @@ static void test_message_handler(void) {
ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK);
}
static void test_static_lease(void) {
TEST(sd_dhcp_server_set_static_lease) {
_cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
log_debug("/* %s */", __func__);
ASSERT_OK(sd_dhcp_server_new(&server, 1));
ASSERT_OK(sd_dhcp_server_new(&server, 4242));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
@@ -363,12 +345,9 @@ static void test_static_lease(void) {
/* hostname= */ NULL));
}
static void test_domain_name(void) {
TEST(sd_dhcp_server_set_domain_name) {
_cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL;
log_debug("/* %s */", __func__);
ASSERT_OK(sd_dhcp_server_new(&server, 1));
ASSERT_OK(sd_dhcp_server_new(&server, 4242));
/* Test setting domain name */
ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "example.com"));
@@ -401,19 +380,4 @@ static void test_domain_name(void) {
ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "local"));
}
int main(int argc, char *argv[]) {
int r;
test_setup_logging(LOG_DEBUG);
test_static_lease();
test_domain_name();
r = test_basic();
if (r < 0)
return log_tests_skipped_errno(r, "cannot start dhcp server");
test_message_handler();
return 0;
}
DEFINE_TEST_MAIN(LOG_DEBUG);
+1 -1
View File
@@ -55,7 +55,7 @@ static int property_get_leases(
if (r < 0)
return r;
r = sd_bus_message_append_array(reply, 'y', &lease->chaddr, sizeof(lease->chaddr));
r = sd_bus_message_append_array(reply, 'y', &lease->hw_addr.bytes, lease->hw_addr.length);
if (r < 0)
return r;
+1
View File
@@ -53,6 +53,7 @@ int sd_dhcp_server_start(sd_dhcp_server *server);
int sd_dhcp_server_stop(sd_dhcp_server *server);
int sd_dhcp_server_configure_pool(sd_dhcp_server *server, const struct in_addr *address, unsigned char prefixlen, uint32_t offset, uint32_t size);
int sd_dhcp_server_set_ip_service_type(sd_dhcp_server *server, uint8_t type);
int sd_dhcp_server_set_boot_server_address(sd_dhcp_server *server, const struct in_addr *address);
int sd_dhcp_server_set_boot_server_name(sd_dhcp_server *server, const char *name);