]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-dhcp-client: add ability to support bootp
authorColin Foster <colin.foster@in-advantage.com>
Wed, 30 Oct 2024 01:50:58 +0000 (20:50 -0500)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 13 Jun 2025 05:14:33 +0000 (14:14 +0900)
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>
src/libsystemd-network/dhcp-packet.c
src/libsystemd-network/dhcp-packet.h
src/libsystemd-network/sd-dhcp-client.c
src/systemd/sd-dhcp-client.h

index 4384a05d96af4f87b8ae8c5d9f32480c12c29623..7732f25b8f70656e3b5f8063421eccb6d9c14885 100644 (file)
 
 #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)
index ab29ab616a29bf14719267ccb1da95bfe337dd39..ba058e06c16132d1269b366f40022c33e44cd04c 100644 (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,
index f14fd09bf4855c28e700e9048ccb91252e764b5d..f77d3f696c853aec0898929eaf7d1d2207d69b8a 100644 (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);
index 4e288f08285449b568e83803f7e414fc56b6c36f..0009a25742b9bef47105db8927eb559283060cfb 100644 (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);