sd-dhcp-client: add ability to support bootp

BOOTP can be used to sign a static IP to clients. Instead of using the
four message exchange, and Option 53 (DHCP Message Type) there is only a
two message exchange. This adds the support for this exchange.

Co-authored-by: Avram Dorfman <dorfman@est.org>
Co-authored-by: Yu Watanabe <watanabe.yu+github@gmail.com>
This commit is contained in:
Colin Foster
2025-06-13 14:14:33 +09:00
committed by Yu Watanabe
co-authored by Avram Dorfman Yu Watanabe
parent 98a9b0f92a
commit 4ad29bad7b
4 changed files with 202 additions and 39 deletions
+27 -9
View File
@@ -13,24 +13,17 @@
#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312
int dhcp_message_init(
int bootp_message_init(
DHCPMessage *message,
uint8_t op,
uint32_t xid,
uint16_t arp_type,
uint8_t hlen,
const uint8_t *chaddr,
uint8_t type,
size_t optlen,
size_t *ret_optoffset) {
size_t offset = 0;
int r;
const uint8_t *chaddr) {
assert(message);
assert(IN_SET(op, BOOTREQUEST, BOOTREPLY));
assert(chaddr || hlen == 0);
assert(ret_optoffset);
message->op = op;
message->htype = arp_type;
@@ -52,6 +45,31 @@ int dhcp_message_init(
message->xid = htobe32(xid);
message->magic = htobe32(DHCP_MAGIC_COOKIE);
return 0;
}
int dhcp_message_init(
DHCPMessage *message,
uint8_t op,
uint32_t xid,
uint16_t arp_type,
uint8_t hlen,
const uint8_t *chaddr,
uint8_t type,
size_t optlen,
size_t *ret_optoffset) {
size_t offset = 0;
int r;
assert(message);
assert(chaddr || hlen == 0);
assert(ret_optoffset);
r = bootp_message_init(message, op, xid, arp_type, hlen, chaddr);
if (r < 0)
return r;
r = dhcp_option_append(message, optlen, &offset, 0,
SD_DHCP_OPTION_MESSAGE_TYPE, 1, &type);
if (r < 0)
+8
View File
@@ -4,6 +4,14 @@
#include "dhcp-protocol.h"
#include "forward.h"
int bootp_message_init(
DHCPMessage *message,
uint8_t op,
uint32_t xid,
uint16_t arp_type,
uint8_t hlen,
const uint8_t *chaddr);
int dhcp_message_init(
DHCPMessage *message,
uint8_t op,
+164 -30
View File
@@ -102,6 +102,7 @@ struct sd_dhcp_client {
int socket_priority;
bool socket_priority_set;
bool ipv6_acquired;
bool bootp;
};
static const uint8_t default_req_opts[] = {
@@ -653,6 +654,15 @@ int sd_dhcp_client_set_fallback_lease_lifetime(sd_dhcp_client *client, uint64_t
return 0;
}
int sd_dhcp_client_set_bootp(sd_dhcp_client *client, int bootp) {
assert_return(client, -EINVAL);
assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
client->bootp = bootp;
return 0;
}
static void client_set_state(sd_dhcp_client *client, DHCPState state) {
assert(client);
@@ -787,10 +797,18 @@ static int client_message_init(
packet = malloc0(size);
if (!packet)
return -ENOMEM;
r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid,
client->arp_type, client->hw_addr.length, client->hw_addr.bytes,
type, optlen, &optoffset);
if (client->bootp) {
/* BOOTP supports options, but only DHCP_OPTION_END is used. The rest of the 64-byte buffer
* is set to zero, per RFC1542. Allow for this by initialaizing optoffset to 0. */
optoffset = 0;
r = bootp_message_init(
&packet->dhcp, BOOTREQUEST, client->xid, client->arp_type,
client->hw_addr.length, client->hw_addr.bytes);
} else
r = dhcp_message_init(
&packet->dhcp, BOOTREQUEST, client->xid, client->arp_type,
client->hw_addr.length, client->hw_addr.bytes,
type, optlen, &optoffset);
if (r < 0)
return r;
@@ -820,6 +838,13 @@ static int client_message_init(
if (client->request_broadcast || client->arp_type != ARPHRD_ETHER)
packet->dhcp.flags = htobe16(0x8000);
if (client->bootp) {
*ret_optlen = optlen;
*ret_optoffset = optoffset;
*ret_packet = TAKE_PTR(packet);
return 0;
}
/* Some DHCP servers will refuse to issue an DHCP lease if the Client
Identifier option is not set */
r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
@@ -1011,7 +1036,7 @@ static int client_append_common_discover_request_options(sd_dhcp_client *client,
return 0;
}
static int client_send_discover(sd_dhcp_client *client) {
static int client_send_dhcp_discover(sd_dhcp_client *client) {
_cleanup_free_ DHCPPacket *discover = NULL;
size_t optoffset, optlen;
int r;
@@ -1064,12 +1089,50 @@ static int client_send_discover(sd_dhcp_client *client) {
return 0;
}
static int client_send_bootp_discover(sd_dhcp_client *client) {
_cleanup_free_ DHCPPacket *discover = NULL;
size_t optoffset, optlen;
int r;
assert(client);
assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING));
r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset);
if (r < 0)
return r;
r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL);
if (r < 0)
return r;
/* RFC1542 section 3.5:
* if the client has no information to communicate to the server, the octet immediately following the
* magic cookie SHOULD be set to the "End" tag (255) and the remaining octets of the 'vend' field
* SHOULD be set to zero.
*
* Use this RFC, along with the fact that some BOOTP servers require a 64-byte vend field, to suggest
* that we always zero and send 64 bytes in the options field. The first four bites are the "magic"
* field, so this only needs to add 60 bytes. */
if (optoffset < 60 && optlen >= 60) {
memzero(&discover->dhcp.options[optoffset], optlen - optoffset);
optoffset = 60;
}
r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset);
if (r < 0)
return r;
log_dhcp_client(client, "DISCOVER");
return 0;
}
static int client_send_request(sd_dhcp_client *client) {
_cleanup_free_ DHCPPacket *request = NULL;
size_t optoffset, optlen;
int r;
assert(client);
assert(!client->bootp);
r = client_message_init(client, DHCP_REQUEST, &request, &optlen, &optoffset);
if (r < 0)
@@ -1261,7 +1324,10 @@ static int client_timeout_resend(
switch (client->state) {
case DHCP_STATE_INIT:
r = client_send_discover(client);
if (client->bootp)
r = client_send_bootp_discover(client);
else
r = client_send_dhcp_discover(client);
if (r >= 0) {
client_set_state(client, DHCP_STATE_SELECTING);
client->discover_attempt = 0;
@@ -1270,7 +1336,10 @@ static int client_timeout_resend(
break;
case DHCP_STATE_SELECTING:
r = client_send_discover(client);
if (client->bootp)
r = client_send_bootp_discover(client);
else
r = client_send_dhcp_discover(client);
if (r < 0 && client->discover_attempt >= client->max_discover_attempts)
goto error;
break;
@@ -1472,29 +1541,18 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata)
return client_initialize_time_events(client);
}
static int client_parse_message(
static int dhcp_option_parse_and_verify(
sd_dhcp_client *client,
DHCPMessage *message,
size_t len,
sd_dhcp_lease **ret) {
sd_dhcp_lease *lease) {
_cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
_cleanup_free_ char *error_message = NULL;
int r;
assert(client);
assert(message);
assert(ret);
r = dhcp_lease_new(&lease);
if (r < 0)
return r;
if (sd_dhcp_client_id_is_set(&client->client_id)) {
r = dhcp_lease_set_client_id(lease, &client->client_id);
if (r < 0)
return r;
}
assert(lease);
r = dhcp_option_parse(message, len, dhcp_lease_parse_options, lease, &error_message);
if (r < 0)
@@ -1555,6 +1613,77 @@ static int client_parse_message(
return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
"received lease lacks address, server address or lease lifetime, ignoring.");
return 0;
}
static int bootp_option_parse_and_verify(
sd_dhcp_client *client,
DHCPMessage *message,
size_t len,
sd_dhcp_lease *lease) {
int r;
assert(client);
assert(message);
assert(lease);
r = dhcp_option_parse(message, len, dhcp_lease_parse_options, lease, /* ret_error_message = */ NULL);
if (r == -ENOMSG)
r = DHCP_ACK; /* BOOTP messages don't have a DHCP message type option */
else if (r < 0)
return log_dhcp_client_errno(client, r, "Failed to parse BOOTP options, ignoring: %m");
else
return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), "Received unexpected message, ignoring.");
log_dhcp_client(client, "BOOTP identified, using infinite lease. BOOTP siaddr=(%#x), DHCP Server Identifier=(%#x)",
message->siaddr, lease->server_address);
lease->lifetime = USEC_INFINITY;
lease->address = message->yiaddr;
if (lease->server_address == 0)
lease->server_address = message->siaddr;
/* BOOTP protocol does not have any OFFER and REQUEST process. Hence, it is mostly equivalent to
* Rapid Commit process in DHCP. */
lease->rapid_commit = true;
if (lease->address == 0)
return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), "received lease lacks address, ignoring.");
return 0;
}
static int client_parse_message(
sd_dhcp_client *client,
DHCPMessage *message,
size_t len,
sd_dhcp_lease **ret) {
_cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
int r;
assert(client);
assert(message);
assert(ret);
r = dhcp_lease_new(&lease);
if (r < 0)
return r;
if (sd_dhcp_client_id_is_set(&client->client_id)) {
r = dhcp_lease_set_client_id(lease, &client->client_id);
if (r < 0)
return r;
}
if (client->bootp)
r = bootp_option_parse_and_verify(client, message, len, lease);
else
r = dhcp_option_parse_and_verify(client, message, len, lease);
if (r < 0)
return r;
r = dhcp_lease_set_default_subnet_mask(lease);
if (r < 0)
return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
@@ -1842,13 +1971,18 @@ static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) {
if (r < 0)
log_dhcp_client_errno(client, r, "could not set lease timeouts: %m");
r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type);
if (r < 0)
return log_dhcp_client_errno(client, r, "could not bind UDP socket: %m");
if (client->bootp) {
client->receive_message = sd_event_source_disable_unref(client->receive_message);
client->fd = safe_close(client->fd);
} else {
r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type);
if (r < 0)
return log_dhcp_client_errno(client, r, "could not bind UDP socket: %m");
client->receive_message = sd_event_source_disable_unref(client->receive_message);
close_and_replace(client->fd, r);
client_initialize_io_events(client, client_receive_message_udp);
client->receive_message = sd_event_source_disable_unref(client->receive_message);
close_and_replace(client->fd, r);
client_initialize_io_events(client, client_receive_message_udp);
}
client_notify(client, notify_event);
@@ -2156,7 +2290,7 @@ static int client_receive_message_raw(
}
int sd_dhcp_client_send_renew(sd_dhcp_client *client) {
if (!sd_dhcp_client_is_running(client) || client->state != DHCP_STATE_BOUND)
if (!sd_dhcp_client_is_running(client) || client->state != DHCP_STATE_BOUND || client->bootp)
return 0; /* do nothing */
client->start_delay = 0;
@@ -2200,7 +2334,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
previously connected, and if the link-layer address did not change,
the client MAY issue a DHCPREQUEST to try to reclaim the current
address. */
if (client->last_addr && !client->anonymize)
if (client->last_addr && !client->anonymize && !client->bootp)
client_set_state(client, DHCP_STATE_INIT_REBOOT);
/* We currently ignore:
@@ -2218,7 +2352,7 @@ int sd_dhcp_client_send_release(sd_dhcp_client *client) {
size_t optoffset, optlen;
int r;
if (!sd_dhcp_client_is_running(client) || !client->lease)
if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp)
return 0; /* do nothing */
r = client_message_init(client, DHCP_RELEASE, &release, &optlen, &optoffset);
+3
View File
@@ -145,6 +145,9 @@ int sd_dhcp_client_set_socket_priority(
int sd_dhcp_client_set_fallback_lease_lifetime(
sd_dhcp_client *client,
uint64_t fallback_lease_lifetime);
int sd_dhcp_client_set_bootp(
sd_dhcp_client *client,
int bootp);
int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v);
int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v);