sd-dhcp-server: use sd_dhcp_message to parse/build DHCP messages (#42240)

This commit is contained in:
Yu Watanabe
2026-05-22 15:28:52 +09:00
committed by GitHub
6 changed files with 460 additions and 555 deletions

View File

@@ -1,9 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <net/if_arp.h>
#include "sd-event.h"
#include "alloc-util.h"
#include "dhcp-protocol.h"
#include "dhcp-message.h"
#include "dhcp-server-internal.h"
#include "dhcp-server-lease-internal.h"
#include "dhcp-server-request.h"
@@ -12,17 +14,18 @@
#include "fd-util.h"
#include "hashmap.h"
#include "iovec-util.h"
#include "memory-util.h"
#include "ip-util.h"
#include "set.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);
sd_dhcp_message_unref(req->message);
set_free(req->parameter_request_list);
return mfree(req);
}
@@ -53,27 +56,22 @@ int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_
return 0;
}
static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) {
static int dhcp_request_set_client_id(DHCPRequest *req) {
assert(req);
assert(message);
assert(req->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);
/* Genuine client ID from Client Identifier option. The option may not be set. */
(void) dhcp_message_get_option_client_id(req->message, &req->client_id);
/* 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))
if (!IN_SET(req->message->header.htype, 0, UINT8_MAX))
(void) sd_dhcp_client_id_set(
&req->client_id_by_header,
req->message->htype,
req->message->chaddr,
req->message->hlen);
req->message->header.htype,
req->message->header.chaddr,
req->message->header.hlen);
/* If Client Identifier option is unspecified, use the generated one. */
if (!sd_dhcp_client_id_is_set(&req->client_id))
@@ -83,100 +81,140 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes
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;
return 0;
}
static int dhcp_request_set_server_identifier(DHCPRequest *req) {
int r;
assert(req);
assert(req->message);
bool mandatory = IN_SET(req->type, DHCP_RELEASE, DHCP_DECLINE);
be32_t a;
r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &a);
if (r < 0)
return mandatory ? r : 0;
req->server_address = a;
return 0;
}
static int dhcp_request_set_maximum_message_size(DHCPRequest *req) {
int r;
assert(req);
assert(req->message);
uint16_t sz;
r = dhcp_message_get_option_u16(req->message, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, &sz);
if (r < 0)
return r;
/* RFC 2132 section 9.10:
* The minimum legal value is 576 octets. */
if (sz < IPV4_MIN_REASSEMBLY_SIZE)
return -EBADMSG;
req->max_message_size = sz;
return 0;
}
static int dhcp_request_set_lifetime(DHCPRequest *req, sd_dhcp_server *server) {
assert(req);
assert(req->message);
assert(server);
(void) dhcp_message_get_option_sec(
req->message,
SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
/* max_as_infinity= */ true,
&req->lifetime);
/* If unset (or zero is specified...), use the default lease time. */
if (req->lifetime <= 0)
req->lifetime = MAX(USEC_PER_SEC, server->default_lease_time);
req->lifetime = MAX(30 * USEC_PER_SEC, server->default_lease_time);
/* If the requested lifetime is too long, then cap it with the maximum 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) {
static int dhcp_server_parse_message(sd_dhcp_server *server, const struct iovec *iov, DHCPRequest **ret) {
int r;
assert(server);
assert(message);
assert(iov);
assert(ret);
assert(ret_error_message);
_cleanup_(dhcp_request_freep) DHCPRequest *req = new0(DHCPRequest, 1);
_cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL;
r = dhcp_message_parse(
iov,
BOOTREQUEST,
/* xid= */ NULL,
ARPHRD_NONE,
/* hw_addr= */ NULL,
&message);
if (r < 0)
return r;
/* A DHCP relay agent is running on the interface with the same address??
* Should be malicious message. */
if (message->header.giaddr == server->address)
return -EBADMSG;
_cleanup_(dhcp_request_freep) DHCPRequest *req = new(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;
*req = (DHCPRequest) {
.message = sd_dhcp_message_ref(message),
r = ensure_sane_request(server, req, message);
/* RFC 2132 section 9.10:
* The minimum legal value is 576 octets. */
.max_message_size = IPV4_MIN_REASSEMBLY_SIZE,
};
/* client hardware address
* Note, hlen and chaddr may not be set for non-ethernet interface.
* See RFC2131 section 4.1. */
r = dhcp_message_get_hw_addr(message, &req->hw_addr);
if (r < 0)
return r;
/* Message Type: mandatory */
r = dhcp_message_get_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, &req->type);
if (r < 0)
return r;
/* Client Identifier: Mandatory. If not set, fall back to use chaddr. */
r = dhcp_request_set_client_id(req);
if (r < 0)
return r;
/* Server Identifier */
r = dhcp_request_set_server_identifier(req);
if (r < 0)
return r;
/* Maximum Message Size: optional */
(void) dhcp_request_set_maximum_message_size(req);
if (req->max_message_size >= sizeof(DHCPPacket))
req->max_optlen = req->max_message_size - sizeof(DHCPPacket);
else
req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
/* Lifetime: optional */
(void) dhcp_request_set_lifetime(req, server);
/* Parameter Request List: optional */
(void) dhcp_message_get_option_parameter_request_list(message, &req->parameter_request_list);
*ret = TAKE_PTR(req);
*ret_error_message = TAKE_PTR(error_message);
return 0;
}
@@ -189,22 +227,17 @@ static int dhcp_server_ack(sd_dhcp_server *server, DHCPRequest *req) {
r = dhcp_server_set_lease(server, req);
if (r < 0)
return log_dhcp_server_errno(server, r, "Failed to create new lease: %m");
return r;
r = server_send_offer_or_ack(server, req, DHCP_ACK);
r = dhcp_server_send_reply(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));
return r;
dhcp_server_on_lease_change(server);
return DHCP_ACK;
return r;
}
static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req) {
int r;
assert(server);
assert(req);
@@ -212,7 +245,7 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req
*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));
log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->header.xid));
/* for now pick a random free address from the pool */
if (static_lease) {
@@ -226,7 +259,6 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req
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;
@@ -257,19 +289,16 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req
/* no free addresses left */
return 0;
if (server->rapid_commit && req->rapid_commit)
if (server->rapid_commit &&
dhcp_message_get_option_flag(req->message, SD_DHCP_OPTION_RAPID_COMMIT) >= 0)
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;
return dhcp_server_send_reply(server, req, DHCP_OFFER);
}
static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) {
int r;
assert(server);
assert(req);
@@ -281,67 +310,69 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req)
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));
be32toh(req->message->header.xid));
/* SELECTING */
if (req->server_address != server->address)
/* client did not pick us */
return 0; /* The message is not for us. Let's silently ignore the packet. */
if (req->message->header.ciaddr != INADDR_ANY) /* this MUST be zero */
return 0;
if (req->message->ciaddr != 0)
/* this MUST be zero */
return 0;
/* this must be filled in with the yiaddr from the chosen OFFER */
r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &address);
if (r < 0)
return r;
if (req->requested_ip == 0)
/* this must be filled in with the yiaddr
from the chosen OFFER */
return 0;
if (address == INADDR_ANY)
return -EBADMSG;
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 {
} else if (req->message->header.ciaddr != INADDR_ANY) {
log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)",
be32toh(req->message->xid));
be32toh(req->message->header.xid));
/* REBINDING / RENEWING */
if (req->message->ciaddr == 0)
/* this MUST be filled in with clients IP address */
return 0;
address = req->message->ciaddr;
/* this must NOT be filled */
if (dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, /* ret= */ NULL) >= 0)
return -EBADMSG;
address = req->message->header.ciaddr;
} else {
log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)",
be32toh(req->message->header.xid));
/* INIT-REBOOT */
r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &address);
if (r < 0)
return r;
if (address == INADDR_ANY)
return -EBADMSG;
init_reboot = true;
}
/* Silently ignore Rapid Commit option in REQUEST message. */
req->rapid_commit = false;
/* Check if the request is consistent with the static lease. */
if (static_lease) {
/* Found a static lease for the client ID. In this case, the server is explicitly configured
* to manage the host. Hence, send NAK when the request is invalid. */
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);
return init_reboot ? dhcp_server_send_reply(server, req, DHCP_NAK) : 0;
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);
return init_reboot ? dhcp_server_send_reply(server, req, DHCP_NAK) : 0;
req->static_lease = static_lease;
req->address = address;
/* Found a static lease for the client ID. */
return dhcp_server_ack(server, req);
}
@@ -353,10 +384,13 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req)
}
/* Refuse otherwise. */
return server_send_nak_or_ignore(server, init_reboot, req);
if (init_reboot)
return dhcp_server_send_reply(server, req, DHCP_NAK);
return 0;
}
static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req, const char *error_message) {
static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req) {
assert(server);
assert(req);
@@ -365,7 +399,9 @@ static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req,
/* 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));
_cleanup_free_ char *e = NULL;
(void) dhcp_message_get_option_string(req->message, SD_DHCP_OPTION_ERROR_MESSAGE, &e);
log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->header.xid), strna(e));
return 0;
}
@@ -380,13 +416,13 @@ static int dhcp_server_process_release(sd_dhcp_server *server, DHCPRequest *req)
if (!existing_lease)
return 0;
if (existing_lease->address != req->message->ciaddr)
if (existing_lease->address != req->message->header.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));
log_dhcp_server(server, "RELEASE (0x%x)", be32toh(req->message->header.xid));
return 0;
}
@@ -396,15 +432,8 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
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);
r = dhcp_server_parse_message(server, &IOVEC_MAKE(message, length), &req);
if (r < 0)
return r;
@@ -420,7 +449,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
case DHCP_REQUEST:
return dhcp_server_process_request(server, req);
case DHCP_DECLINE:
return dhcp_server_process_decline(server, req, error_message);
return dhcp_server_process_decline(server, req);
case DHCP_RELEASE:
return dhcp_server_process_release(server, req);
default:

View File

@@ -4,6 +4,7 @@
#include "sd-dhcp-server-lease.h"
#include "dhcp-client-id-internal.h"
#include "dhcp-message.h"
#include "dhcp-protocol.h"
#include "ether-addr-util.h"
#include "sd-forward.h"
@@ -12,7 +13,7 @@
typedef struct DHCPRequest {
/* received message */
DHCPMessage *message;
sd_dhcp_message *message;
/* sender hardware address, may not be set for non-ethernet interface */
struct hw_addr_data hw_addr;
triple_timestamp timestamp;
@@ -22,14 +23,10 @@ typedef struct DHCPRequest {
sd_dhcp_client_id client_id;
sd_dhcp_client_id client_id_by_header;
size_t max_optlen;
size_t max_message_size; /* maximum message size, including IP and UDP headers */
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;
Set *parameter_request_list;
/* acquired data */
sd_dhcp_server_lease *static_lease;

View File

@@ -2,18 +2,14 @@
#include "sd-event.h"
#include "alloc-util.h"
#include "dhcp-option.h"
#include "dhcp-packet.h"
#include "dhcp-server-internal.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 "random-util.h"
#include "set.h"
#include "socket-util.h"
static int server_acquire_raw_socket(sd_dhcp_server *server) {
@@ -45,124 +41,72 @@ static int server_acquire_raw_socket(sd_dhcp_server *server) {
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;
sd_dhcp_message *message) {
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;
assert(message);
int fd = server_acquire_raw_socket(server);
if (fd < 0)
return fd;
r = dhcp_packet_append_ip_headers(
packet,
return dhcp_message_send_raw(
message,
fd,
server->ifindex,
server->address,
DHCP_PORT_SERVER,
packet->dhcp.yiaddr,
hw_addr,
message->header.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) {
static int dhcp_server_send_udp(
sd_dhcp_server *server,
be32_t address,
uint16_t port,
sd_dhcp_message *message) {
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;
return dhcp_message_send_udp(
message,
fd,
server->address,
address,
port);
}
static int dhcp_server_send_message(
sd_dhcp_server *server,
DHCPRequest *req,
uint8_t type,
DHCPPacket *packet,
size_t optoffset) {
sd_dhcp_message *message) {
int r;
assert(server);
assert(req);
assert(req->message);
assert(packet);
assert(message);
/* 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)
if (message->header.giaddr != INADDR_ANY)
return dhcp_server_send_udp(
server,
req->message->giaddr,
message->header.giaddr,
DHCP_PORT_SERVER,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
message);
/* when giaddr is zero, the server broadcasts any DHCPNAK messages to 0xffffffff. */
if (type == DHCP_NAK)
@@ -170,18 +114,16 @@ static int dhcp_server_send_message(
server,
INADDR_BROADCAST,
DHCP_PORT_CLIENT,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
message);
/* 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)
if (message->header.ciaddr != INADDR_ANY)
return dhcp_server_send_udp(
server,
req->message->ciaddr,
message->header.ciaddr,
DHCP_PORT_CLIENT,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
message);
/* If giaddr is zero and ciaddr is zero, and the broadcast bit is set, then the server
* broadcasts DHCPOFFER and DHCPACK messages to 0xffffffff.
@@ -189,166 +131,137 @@ static int dhcp_server_send_message(
* (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)
struct hw_addr_data hw_addr = {};
if (!dhcp_message_has_broadcast_flag(message) &&
message->header.yiaddr != INADDR_ANY) {
r = dhcp_message_get_hw_addr(message, &hw_addr);
if (r < 0)
return r;
}
if (hw_addr_is_null(&hw_addr))
return dhcp_server_send_udp(
server,
INADDR_BROADCAST,
DHCP_PORT_CLIENT,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
message);
/* 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 clients hardware address and yiaddr address. */
return dhcp_server_send_unicast_raw(
server,
&req->hw_addr,
packet,
sizeof(DHCPPacket) + optoffset);
&hw_addr,
message);
}
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(
static int dhcp_server_new_reply(
sd_dhcp_server *server,
DHCPRequest *req,
uint8_t type) {
uint8_t type,
sd_dhcp_message **ret) {
int r;
assert(server);
assert(req);
assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK));
assert(ret);
_cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL;
r = dhcp_message_new(&message);
if (r < 0)
return r;
r = dhcp_message_init_header(
message,
BOOTREPLY,
be32toh(req->message->header.xid),
req->message->header.htype,
&req->hw_addr);
if (r < 0)
return r;
message->header.giaddr = req->message->header.giaddr;
/* RFC 2131 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. */
dhcp_message_set_broadcast_flag(
message,
dhcp_message_has_broadcast_flag(req->message) ||
req->message->header.giaddr != INADDR_ANY ||
type == DHCP_NAK);
/* DHCP Message Type (53): Mandatory. */
r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, type);
if (r < 0)
return r;
/* Server Identifier */
r = dhcp_message_append_option_be32(
message,
SD_DHCP_OPTION_SERVER_IDENTIFIER,
server->address);
if (r < 0)
return r;
if (type == DHCP_NAK) {
*ret = TAKE_PTR(message);
return 0;
}
assert(req->address != INADDR_ANY);
message->header.yiaddr = req->address;
message->header.siaddr = server->boot_server_address.s_addr;
if (type == DHCP_ACK)
message->header.ciaddr = req->message->header.ciaddr;
r = dhcp_message_append_option_be32(
message,
SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
usec_to_be32_sec(req->lifetime));
if (r < 0)
return r;
r = dhcp_message_append_option_be32(
message,
SD_DHCP_OPTION_SUBNET_MASK,
server->netmask);
if (r < 0)
return r;
if (server->emit_router) {
r = dhcp_message_append_option_be32(
message,
SD_DHCP_OPTION_ROUTER,
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_message_append_option_string(
message,
SD_DHCP_OPTION_BOOT_SERVER_NAME,
server->boot_server_name);
if (r < 0)
return r;
}
if (server->boot_filename) {
r = dhcp_message_append_option_string(
message,
SD_DHCP_OPTION_BOOT_FILENAME,
server->boot_filename);
if (r < 0)
return r;
}
static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = {
[SD_DHCP_LEASE_DNS] = SD_DHCP_OPTION_DOMAIN_NAME_SERVER,
@@ -359,87 +272,33 @@ int server_send_offer_or_ack(
[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,
r = dhcp_message_append_option_addresses(
message,
option_map[k],
sizeof(struct in_addr) * server->servers[k].size,
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,
r = dhcp_message_append_option_string(
message,
SD_DHCP_OPTION_TZDB_TIMEZONE,
strlen(server->timezone), server->timezone);
server->timezone);
if (r < 0)
return r;
}
if (server->domain_name) {
r = dhcp_option_append(
&packet->dhcp, req->max_optlen, &offset, 0,
r = dhcp_message_append_option_string(
message,
SD_DHCP_OPTION_DOMAIN_NAME,
strlen(server->domain_name), server->domain_name);
server->domain_name);
if (r < 0)
return r;
}
@@ -447,116 +306,138 @@ int server_send_offer_or_ack(
/* 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) &&
if (set_contains(req->parameter_request_list, UINT_TO_PTR(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,
r = dhcp_message_append_option_be32(
message,
SD_DHCP_OPTION_IPV6_ONLY_PREFERRED,
sizeof(sec), &sec);
usec_to_be32_sec(server->ipv6_only_preferred_usec));
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);
r = dhcp_message_append_option_sub_tlv(
message,
SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION,
server->vendor_options);
if (r < 0)
return r;
return dhcp_server_send_packet(server, req, packet, type, offset);
if (req->static_lease) {
/* Hostname (12) or FQDN (81)
* Flags: S=0 (will not update RR), O=1 (are overriding client), N=1 (will not update DNS) */
r = dhcp_message_append_option_hostname(
message,
DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_N,
/* is_client= */ false,
req->static_lease->hostname);
if (r < 0)
return r;
}
_cleanup_(tlv_unrefp) TLV *agent_info = NULL;
r = dhcp_message_get_option_sub_tlv(
req->message,
SD_DHCP_OPTION_RELAY_AGENT_INFORMATION,
TLV_DHCP4_SUBOPTION,
&agent_info);
if (r < 0 && r != -ENODATA)
log_dhcp_server_errno(server, r, "Failed to parse %s option, ignoring: %m",
dhcp_option_code_to_string(SD_DHCP_OPTION_RELAY_AGENT_INFORMATION));
if (agent_info) {
r = dhcp_message_append_option_sub_tlv(
message,
SD_DHCP_OPTION_RELAY_AGENT_INFORMATION,
agent_info);
if (r < 0)
return r;
}
if (type == DHCP_ACK && req->type == DHCP_DISCOVER) {
assert(server->rapid_commit);
r = dhcp_message_append_option_flag(message, SD_DHCP_OPTION_RAPID_COMMIT);
if (r < 0)
return r;
}
r = dhcp_message_append_option_tlv(message, server->extra_options);
if (r < 0)
return r;
*ret = TAKE_PTR(message);
return 0;
}
int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req) {
_cleanup_free_ DHCPPacket *packet = NULL;
size_t offset;
int dhcp_server_send_reply(
sd_dhcp_server *server,
DHCPRequest *req,
uint8_t type) {
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. */
assert(server);
assert(req);
assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK));
if (!init_reboot)
return 0;
r = server_message_init(server, &packet, DHCP_NAK, &offset, req);
_cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL;
r = dhcp_server_new_reply(server, req, type, &message);
if (r < 0)
return log_dhcp_server_errno(server, r, "Failed to create NAK message: %m");
return r;
r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset);
if (dhcp_message_packet_size(message) > req->max_message_size)
return -E2BIG;
r = dhcp_server_send_message(server, type, message);
if (r < 0)
return log_dhcp_server_errno(server, r, "Could not send NAK message: %m");
return r;
log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->xid));
return DHCP_NAK;
log_dhcp_server(server, "%s (0x%x)", dhcp_message_type_to_string(type), be32toh(message->header.xid));
return type; /* Return the sent message type. To make the test easier. */
}
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);
_cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL;
r = dhcp_message_new(&message);
if (r < 0)
return r;
r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE,
&optoffset, 0, SD_DHCP_OPTION_END, 0, NULL);
r = dhcp_message_init_header(
message,
BOOTREPLY,
random_u32(),
lease->htype,
&lease->hw_addr);
if (r < 0)
return r;
return dhcp_server_send_udp(server, lease->address, DHCP_PORT_CLIENT,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
/* DHCP Message Type (53): Mandatory. */
r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_FORCERENEW);
if (r < 0)
return r;
/* Server Identifier */
r = dhcp_message_append_option_be32(
message,
SD_DHCP_OPTION_SERVER_IDENTIFIER,
server->address);
if (r < 0)
return r;
r = dhcp_server_send_udp(server, lease->address, DHCP_PORT_CLIENT, message);
if (r < 0)
return r;
log_dhcp_server(server, "%s (0x%x)", dhcp_message_type_to_string(DHCP_FORCERENEW), be32toh(message->header.xid));
return 0;
}
int sd_dhcp_server_forcerenew(sd_dhcp_server *server) {
@@ -565,8 +446,6 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) {
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;

View File

@@ -5,9 +5,7 @@
#include "dhcp-server-request.h"
int server_send_offer_or_ack(
int dhcp_server_send_reply(
sd_dhcp_server *server,
DHCPRequest *req,
uint8_t type);
int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req);

View File

@@ -70,6 +70,7 @@ int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req) {
assert(server);
assert(req);
assert(req->message);
assert(req->address != INADDR_ANY);
usec_t expiration;
@@ -105,18 +106,16 @@ int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req) {
.n_ref = 1,
.address = req->address,
.client_id = req->client_id,
.htype = req->message->htype,
.gateway = req->message->giaddr,
.htype = req->message->header.htype,
.gateway = req->message->header.giaddr,
.expiration = expiration,
};
lease->hw_addr = req->hw_addr;
if (req->hostname) {
lease->hostname = strdup(req->hostname);
if (!lease->hostname)
return -ENOMEM;
}
char *hostname;
if (dhcp_message_get_option_hostname(req->message, &hostname) >= 0)
free_and_replace(lease->hostname, hostname);
r = dhcp_server_put_lease(server, lease, /* is_static= */ false);
if (r < 0)

View File

@@ -95,6 +95,7 @@ TEST(dhcp_server_handle_message) {
.header.hlen = ETHER_ADDR_LEN,
.header.xid = htobe32(0x12345678),
.header.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' },
.header.magic = htobe32(DHCP_MAGIC_COOKIE),
.option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE,
.option_type.length = 1,
.option_type.type = DHCP_DISCOVER,
@@ -141,14 +142,14 @@ TEST(dhcp_server_handle_message) {
test.option_type.code = 0;
test.option_type.length = 0;
test.option_type.type = 0;
ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENOMSG);
ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENODATA);
test.option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE;
test.option_type.length = 1;
test.option_type.type = DHCP_DISCOVER;
ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER);
test.header.op = 0;
ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL));
ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), EBADMSG);
test.header.op = BOOTREQUEST;
ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER);
@@ -163,8 +164,10 @@ TEST(dhcp_server_handle_message) {
test.header.hlen = ETHER_ADDR_LEN;
ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER);
/* DHCPREQUEST (init-boot) without requested IP */
test.option_type.type = DHCP_REQUEST;
ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL));
ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENODATA);
test.option_requested_ip.code = SD_DHCP_OPTION_REQUESTED_IP_ADDRESS;
test.option_requested_ip.length = 4;
test.option_requested_ip.address = htobe32(0x12345678);