Skip to content

Backport BOOTP from systemd/main (v257-8) to debian/systemd v252 Bookworm .deb #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: agd-debian/252.31-1_deb12u1-base
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
systemd (252.31-1~deb12u1.1) UNRELEASED; urgency=medium

* Non-maintainer upload.
* Backport BOOTP support from systemd/main v258

-- Avram Dorfman <[email protected]> Tue, 14 Jan 2025 13:27:10 -0500

systemd (252.31-1~deb12u1) bookworm; urgency=medium

* New upstream version 252.31
Expand Down
674 changes: 674 additions & 0 deletions debian/patches/backport-bootp-from-v258-main.patch

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions debian/patches/series
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ debian/Move-sysusers.d-sysctl.d-binfmt.d-modules-load.d-back-to-.patch
debian/systemctl-do-not-shutdown-immediately-on-scheduled-shutdo.patch
debian/Downgrade-a-couple-of-warnings-to-debug.patch
debian/Skip-flaky-test_resolved_domain_restricted_dns-in-network.patch
backport-bootp-from-v258-main.patch
9 changes: 9 additions & 0 deletions man/systemd.network.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1903,6 +1903,15 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
</listitem>
</varlistentry>

<varlistentry>
<term><varname>BOOTP=</varname></term>
<listitem>
<para>Takes a boolean. The DHCPv4 client can be configured to communicate with BOOP servers that
don't accept Option 53, DHCP Message Type. In this configuration, a BOOTP Request is sent without
any options by default. Expects a BOOTREPLY that contains a permanent unique IP assignment.</para>
</listitem>
</varlistentry>

<!-- How to use the DHCP lease -->

<varlistentry>
Expand Down
3 changes: 3 additions & 0 deletions src/libsystemd-network/dhcp-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ typedef int (*dhcp_option_callback_t)(uint8_t code, uint8_t len,

int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **error_message);

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, uint32_t xid,
uint8_t type, uint16_t arp_type, uint8_t hlen, const uint8_t *chaddr,
size_t optlen, size_t *optoffset);
Expand Down
27 changes: 27 additions & 0 deletions src/libsystemd-network/dhcp-packet.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,33 @@

#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312

int bootp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, uint16_t arp_type, uint8_t hlen, const uint8_t *chaddr) {
assert(IN_SET(op, BOOTREQUEST, BOOTREPLY));
assert(chaddr || hlen == 0);

message->op = op;
message->htype = arp_type;

/* RFC2131 section 4.1.1:
The client MUST include its hardware address in the ’chaddr’ field, if
necessary for delivery of DHCP reply messages.

RFC 4390 section 2.1:
A DHCP client, when working over an IPoIB interface, MUST follow the
following rules:
"htype" (hardware address type) MUST be 32 [ARPPARAM].
"hlen" (hardware address length) MUST be 0.
"chaddr" (client hardware address) field MUST be zeroed.
*/
message->hlen = arp_type == ARPHRD_INFINIBAND ? 0 : hlen;
memcpy_safe(message->chaddr, chaddr, message->hlen);

message->xid = htobe32(xid);
message->magic = htobe32(DHCP_MAGIC_COOKIE);

return 0;
}

int dhcp_message_init(
DHCPMessage *message,
uint8_t op,
Expand Down
167 changes: 124 additions & 43 deletions src/libsystemd-network/sd-dhcp-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ struct sd_dhcp_client {
sd_dhcp_lease *lease;
usec_t start_delay;
int ip_service_type;
bool bootp;

/* Ignore ifindex when generating iaid. See dhcp_identifier_set_iaid(). */
bool test_mode;
Expand Down Expand Up @@ -651,6 +652,19 @@ int sd_dhcp_client_set_fallback_lease_lifetime(sd_dhcp_client *client, uint32_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;

/* For BOOTP mode, we don't want to send any request options by default. */
set_free(client->req_opts);
client->req_opts = NULL;

return 0;
}

static int client_notify(sd_dhcp_client *client, int event) {
assert(client);

Expand Down Expand Up @@ -759,9 +773,15 @@ static int client_message_init(
if (!packet)
return -ENOMEM;

r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, type,
client->arp_type, client->hw_addr.length, client->hw_addr.bytes,
optlen, &optoffset);
if (client->bootp) {
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, type,
client->arp_type, client->hw_addr.length, client->hw_addr.bytes,
optlen, &optoffset);

if (r < 0)
return r;

Expand Down Expand Up @@ -791,35 +811,38 @@ static int client_message_init(
if (client->request_broadcast || client->arp_type != ARPHRD_ETHER)
packet->dhcp.flags = htobe16(0x8000);

/* If no client identifier exists, construct an RFC 4361-compliant one */
if (client->client_id_len == 0) {
size_t duid_len;
if (!client->bootp) {
/* Some DHCP servers will refuse to issue an DHCP lease if the Client
Identifier option is not set */

client->client_id.type = 255;
/* If no client identifier exists, construct an RFC 4361-compliant one */
if (client->client_id_len == 0) {
size_t duid_len;

r = dhcp_identifier_set_iaid(client->ifindex, &client->hw_addr,
/* legacy_unstable_byteorder = */ true,
/* use_mac = */ client->test_mode,
&client->client_id.ns.iaid);
if (r < 0)
return r;
client->client_id.type = 255;

r = dhcp_identifier_set_duid_en(client->test_mode, &client->client_id.ns.duid, &duid_len);
if (r < 0)
return r;
r = dhcp_identifier_set_iaid(client->ifindex, &client->hw_addr,
/* legacy_unstable_byteorder = */ true,
/* use_mac = */ client->test_mode,
&client->client_id.ns.iaid);
if (r < 0)
return r;

client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + duid_len;
}
r = dhcp_identifier_set_duid_en(client->test_mode, &client->client_id.ns.duid, &duid_len);
if (r < 0)
return r;

/* Some DHCP servers will refuse to issue an DHCP lease if the Client
Identifier option is not set */
if (client->client_id_len) {
r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
SD_DHCP_OPTION_CLIENT_IDENTIFIER,
client->client_id_len,
&client->client_id);
if (r < 0)
return r;
client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + duid_len;
}

if (client->client_id_len) {
r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
SD_DHCP_OPTION_CLIENT_IDENTIFIER,
client->client_id_len,
&client->client_id);
if (r < 0)
return r;
}
}

/* RFC2131 section 3.5:
Expand All @@ -835,7 +858,7 @@ static int client_message_init(
MAY contain the Parameter Request List option. */
/* NOTE: in case that there would be an option to do not send
* any PRL at all, the size should be checked before sending */
if (!set_isempty(client->req_opts) && type != DHCP_RELEASE) {
if (!set_isempty(client->req_opts) && type != DHCP_RELEASE && !client->bootp) {
_cleanup_free_ uint8_t *opts = NULL;
size_t n_opts, i = 0;
void *val;
Expand Down Expand Up @@ -883,7 +906,7 @@ static int client_message_init(
*/
/* RFC7844 section 3:
SHOULD NOT contain any other option. */
if (!client->anonymize && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) {
if (!client->bootp && !client->anonymize && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a style choice but it looks like this block and the one on 861 could all be inside the if block on 814? Alternately you could add !client->bootp to the if blocks on lines on 819 and 838 - that would not be my first choice unless your goal is to minimize the diff.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you are right, nice catch. It wasn't always like that, there was a big refactor towards the end after I discovered that my initial attempt at keeping things simple... wasn't.

I'll give this a try. There's a part of me that's loathe to go back to that stage, rebuild, and then do significant verification. But logically it kinda seems like it's pretty straight forward that the logic is identical and short of seeing it compile I don't really need to retest anything.

be16_t max_size = htobe16(MIN(client->mtu - DHCP_IP_UDP_SIZE, (uint32_t) UINT16_MAX));
r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
Expand Down Expand Up @@ -1026,23 +1049,42 @@ static int client_send_discover(sd_dhcp_client *client) {
*/
/* RFC7844 section 3:
SHOULD NOT contain any other option. */
if (!client->anonymize && client->last_addr != INADDR_ANY) {
if (!client->bootp && !client->anonymize && client->last_addr != INADDR_ANY) {
r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
4, &client->last_addr);
if (r < 0)
return r;
}

r = client_append_common_discover_request_options(client, discover, &optoffset, optlen);
if (r < 0)
return r;
if (!client->bootp) {
r = client_append_common_discover_request_options(client, discover, &optoffset, optlen);
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 (client->bootp)
if (optoffset < 60 && optlen >= 60) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

condense this down to a single if?

memset(&discover->dhcp.options[optoffset], 0, optlen - optoffset);
optoffset = 60;
}

/* We currently ignore:
The client SHOULD wait a random time between one and ten seconds to
desynchronize the use of DHCP at startup.
Expand Down Expand Up @@ -1465,10 +1507,28 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_

r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease, NULL);
if (r != DHCP_OFFER) {
log_dhcp_client(client, "received message was not an OFFER, ignoring");
return -ENOMSG;
if (r == -ENOMSG && client->bootp) {
// Treat a non-DHCP BOOTREPLY like a DHCP ACK, so keep processing
log_dhcp_client(client, "BOOTREPLY received");
r = DHCP_ACK;
} else {
log_dhcp_client(client, "received message was not an OFFER, ignoring");
return -ENOMSG;
}
}

if (client->bootp) {
// This was USEC_INFINITY in systemd v258, but that's a long unsigned int; casting doesn't seem like a good idea
lease->lifetime = UINT32_MAX;
log_dhcp_client(client, "Using infinite lease. BOOTP siaddr=(%#x), DHCP Server Identifier=(%#x)",
offer->siaddr,
lease->server_address);

lease->server_address = offer->siaddr ? offer->siaddr : lease->server_address;
lease->next_server = 0;
} else
lease->next_server = offer->siaddr;

lease->next_server = offer->siaddr;
lease->address = offer->yiaddr;

Expand All @@ -1478,7 +1538,11 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_
if (lease->address == 0 ||
lease->server_address == 0 ||
lease->lifetime == 0) {
log_dhcp_client(client, "received lease lacks address, server address or lease lifetime, ignoring");
log_dhcp_client(client, "received lease lacks address(%#x), server address(%#x) or lease lifetime(%#llx), ignoring.",
lease->address,
lease->server_address,
(unsigned long long) lease->lifetime
);
return -ENOMSG;
}

Expand All @@ -1498,7 +1562,10 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_
if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0)
return -ENOMSG;

log_dhcp_client(client, "OFFER");
if (client->bootp)
log_dhcp_client(client, "BOOTREPLY");
else
log_dhcp_client(client, "OFFER");

return 0;
}
Expand Down Expand Up @@ -1551,8 +1618,8 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t le

if (client->client_id_len) {
r = dhcp_lease_set_client_id(lease,
(uint8_t *) &client->client_id,
client->client_id_len);
(uint8_t *) &client->client_id,
client->client_id_len);
if (r < 0)
return r;
}
Expand All @@ -1575,8 +1642,11 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t le
if (lease->address == INADDR_ANY ||
lease->server_address == INADDR_ANY ||
lease->lifetime == 0) {
log_dhcp_client(client, "received lease lacks address, server "
"address or lease lifetime, ignoring");
log_dhcp_client(client, "received lease lacks address(%#x), server address(%#x) or lease lifetime(%#llx), ignoring.",
lease->address,
lease->server_address,
(unsigned long long) lease->lifetime
);
return -ENOMSG;
}

Expand Down Expand Up @@ -1729,14 +1799,25 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
0, 0,
client_timeout_resend, client,
client->event_priority, "dhcp4-resend-timer", true);
break;

// Treat a non-DHCP BOOTREPLY like a DHCP ACK, so allow
// fall through to the next case for these.
if (! client->bootp)
break;
[[fallthrough]];

case DHCP_STATE_REBOOTING:
case DHCP_STATE_REQUESTING:
case DHCP_STATE_RENEWING:
case DHCP_STATE_REBINDING:
if (client->bootp)
if (client->state == DHCP_STATE_REQUESTING)
r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
else
r = SD_DHCP_CLIENT_EVENT_RENEW;
else
r = client_handle_ack(client, message, len);

r = client_handle_ack(client, message, len);
if (r == -ENOMSG)
return 0; /* invalid message, let's ignore it */
if (r == -EADDRNOTAVAIL) {
Expand Down
Loading