/* 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"
#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);
}
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);
-
- req->message = message;
+ assert(req->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))
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;
+}
- if (req->lifetime <= 0)
- req->lifetime = MAX(USEC_PER_SEC, server->default_lease_time);
+static int dhcp_request_set_server_identifier(DHCPRequest *req) {
+ int r;
- if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time)
- req->lifetime = server->max_lease_time;
+ 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 parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) {
- DHCPRequest *req = ASSERT_PTR(userdata);
+static int dhcp_request_set_maximum_message_size(DHCPRequest *req) {
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));
+ assert(req);
+ assert(req->message);
- break;
- case SD_DHCP_OPTION_SERVER_IDENTIFIER:
- if (len == 4)
- memcpy(&req->server_address, option, sizeof(be32_t));
+ uint16_t sz;
+ r = dhcp_message_get_option_u16(req->message, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, &sz);
+ if (r < 0)
+ return r;
- 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);
+ /* RFC 2132 section 9.10:
+ * The minimum legal value is 576 octets. */
+ if (sz < IPV4_MIN_REASSEMBLY_SIZE)
+ return -EBADMSG;
- break;
- case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
+ req->max_message_size = sz;
+ return 0;
+}
- if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket))
- req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket);
+static int dhcp_request_set_lifetime(DHCPRequest *req, sd_dhcp_server *server) {
+ assert(req);
+ assert(req->message);
+ assert(server);
- break;
- case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION:
- req->agent_info_option = (uint8_t*)option - 2;
+ (void) dhcp_message_get_option_sec(
+ req->message,
+ SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
+ /* max_as_infinity= */ true,
+ &req->lifetime);
- break;
- case SD_DHCP_OPTION_HOST_NAME: {
- _cleanup_free_ char *p = NULL;
+ /* If unset (or zero is specified...), use the default lease time. */
+ if (req->lifetime <= 0)
+ req->lifetime = MAX(30 * USEC_PER_SEC, server->default_lease_time);
- 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;
- }
+ /* 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 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);
+ *req = (DHCPRequest) {
+ .message = sd_dhcp_message_ref(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;
- req->type = r;
- r = ensure_sane_request(server, req, message);
+ /* 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;
}
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));
+ log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->header.xid));
dhcp_server_on_lease_change(server);
-
return DHCP_ACK;
}
*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) {
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;
/* 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);
/* 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));
+ log_dhcp_server(server, "OFFER (0x%x)", be32toh(req->message->header.xid));
return DHCP_OFFER;
}
static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) {
+ int r;
+
assert(server);
assert(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;
-
- if (req->message->ciaddr != 0)
- /* this MUST be zero */
- return 0;
+ return 0; /* The message is not for us. Let's silently ignore the packet. */
- if (req->requested_ip == 0)
- /* this must be filled in with the yiaddr
- from the chosen OFFER */
+ if (req->message->header.ciaddr != INADDR_ANY) /* this MUST be zero */
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));
+ /* 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;
- /* INIT-REBOOT */
- if (req->message->ciaddr != 0)
- /* this MUST be zero */
- return 0;
+ if (address == INADDR_ANY)
+ return -EBADMSG;
- /* 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;
- /* Silently ignore Rapid Commit option in REQUEST message. */
- req->rapid_commit = false;
+ 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;
+ }
+
+ /* 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);
req->static_lease = static_lease;
req->address = address;
- /* Found a static lease for the client ID. */
return dhcp_server_ack(server, req);
}
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) {
+static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req) {
assert(server);
assert(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;
}
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;
}
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;
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:
#include "in-addr-util.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
+#include "set.h"
#include "socket-util.h"
static int server_acquire_raw_socket(sd_dhcp_server *server) {
/* 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 (req->message->header.giaddr != INADDR_ANY)
return dhcp_server_send_udp(
server,
- req->message->giaddr,
+ req->message->header.giaddr,
DHCP_PORT_SERVER,
&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)
+ if (req->message->header.ciaddr != INADDR_ANY)
return dhcp_server_send_udp(
server,
- req->message->ciaddr,
+ req->message->header.ciaddr,
DHCP_PORT_CLIENT,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
* (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) ||
+ if (dhcp_message_has_broadcast_flag(req->message) ||
hw_addr_is_null(&req->hw_addr) ||
packet->dhcp.yiaddr == INADDR_ANY)
return dhcp_server_send_udp(
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;
- }
- }
+ _cleanup_(iovec_done) struct iovec iov = {};
+ if (dhcp_message_get_option_alloc(req->message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, &iov) >= 0 &&
+ iov.iov_len <= UINT8_MAX)
+ (void) dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
+ SD_DHCP_OPTION_RELAY_AGENT_INFORMATION,
+ iov.iov_len, iov.iov_base);
r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
SD_DHCP_OPTION_END, 0, NULL);
return -ENOMEM;
r = dhcp_message_init(&packet->dhcp, BOOTREPLY,
- be32toh(req->message->xid),
- req->message->htype, req->hw_addr.length, req->hw_addr.bytes,
+ be32toh(req->message->header.xid),
+ req->message->header.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;
+ packet->dhcp.flags = req->message->header.flags;
+ packet->dhcp.giaddr = req->message->header.giaddr;
*ret_optoffset = optoffset;
*ret = TAKE_PTR(packet);
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,
/* 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);
return r;
}
- if (server->rapid_commit && req->rapid_commit && type == DHCP_ACK) {
+ if (type == DHCP_ACK && req->type == DHCP_DISCOVER) {
+ assert(server->rapid_commit);
r = dhcp_option_append(
&packet->dhcp, req->max_optlen, &offset, 0,
SD_DHCP_OPTION_RAPID_COMMIT,
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));
+ log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->header.xid));
return DHCP_NAK;
}