From 4ad29bad7b9936f993699a61a1f1d9974891740f Mon Sep 17 00:00:00 2001 From: Colin Foster Date: Tue, 29 Oct 2024 20:50:58 -0500 Subject: [PATCH] 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 Co-authored-by: Yu Watanabe --- src/libsystemd-network/dhcp-packet.c | 36 +++-- src/libsystemd-network/dhcp-packet.h | 8 + src/libsystemd-network/sd-dhcp-client.c | 194 ++++++++++++++++++++---- src/systemd/sd-dhcp-client.h | 3 + 4 files changed, 202 insertions(+), 39 deletions(-) diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index 4384a05d96a..7732f25b8f7 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -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) diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h index ab29ab616a2..ba058e06c16 100644 --- a/src/libsystemd-network/dhcp-packet.h +++ b/src/libsystemd-network/dhcp-packet.h @@ -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, diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index f14fd09bf48..f77d3f696c8 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -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); diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 4e288f08285..0009a25742b 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -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); -- 2.47.3