sd-dhcp-server: split into small pieaces

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