mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
network/dhcpv4: add ability to use BOOTP (#34888)
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. Add the following network option to enable BOOTP: [DHCPv4] BOOTP=yes This will allow a two message request / reply sequence that doesn't require DHCP message types. Fixes: #34885
This commit is contained in:
@@ -2603,6 +2603,16 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>BOOTP=</varname></term>
|
||||
<listitem>
|
||||
<para>Takes a boolean. When enabled, the DHCPv4 client will be configured to communicate with BOOTP
|
||||
servers, rather than with DHCP servers. Defaults to off.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<!-- How to use the DHCP lease -->
|
||||
|
||||
<varlistentry>
|
||||
|
||||
@@ -13,20 +13,15 @@
|
||||
|
||||
#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312
|
||||
|
||||
int dhcp_message_init(
|
||||
int bootp_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) {
|
||||
|
||||
size_t offset = 0;
|
||||
int r;
|
||||
const uint8_t *chaddr) {
|
||||
|
||||
assert(message);
|
||||
assert(IN_SET(op, BOOTREQUEST, BOOTREPLY));
|
||||
assert(chaddr || hlen == 0);
|
||||
|
||||
@@ -50,13 +45,37 @@ 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)
|
||||
return r;
|
||||
|
||||
*optoffset = offset;
|
||||
|
||||
*ret_optoffset = offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,16 +4,24 @@
|
||||
#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,
|
||||
uint32_t xid,
|
||||
uint8_t type,
|
||||
uint16_t arp_type,
|
||||
uint8_t hlen,
|
||||
const uint8_t *chaddr,
|
||||
uint8_t type,
|
||||
size_t optlen,
|
||||
size_t *optoffset);
|
||||
size_t *ret_optoffset);
|
||||
|
||||
uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -764,10 +774,10 @@ static int cmp_uint8(const uint8_t *a, const uint8_t *b) {
|
||||
|
||||
static int client_message_init(
|
||||
sd_dhcp_client *client,
|
||||
DHCPPacket **ret,
|
||||
uint8_t type,
|
||||
size_t *_optlen,
|
||||
size_t *_optoffset) {
|
||||
DHCPPacket **ret_packet,
|
||||
size_t *ret_optlen,
|
||||
size_t *ret_optoffset) {
|
||||
|
||||
_cleanup_free_ DHCPPacket *packet = NULL;
|
||||
size_t optlen, optoffset, size;
|
||||
@@ -776,11 +786,10 @@ static int client_message_init(
|
||||
int r;
|
||||
|
||||
assert(client);
|
||||
assert(client->start_time);
|
||||
assert(ret);
|
||||
assert(_optlen);
|
||||
assert(_optoffset);
|
||||
assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE));
|
||||
assert(ret_packet);
|
||||
assert(ret_optlen);
|
||||
assert(ret_optoffset);
|
||||
|
||||
optlen = DHCP_MIN_OPTIONS_SIZE;
|
||||
size = sizeof(DHCPPacket) + optlen;
|
||||
@@ -788,10 +797,18 @@ static int client_message_init(
|
||||
packet = malloc0(size);
|
||||
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) {
|
||||
/* 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;
|
||||
|
||||
@@ -821,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,
|
||||
@@ -900,9 +924,9 @@ static int client_message_init(
|
||||
return r;
|
||||
}
|
||||
|
||||
*_optlen = optlen;
|
||||
*_optoffset = optoffset;
|
||||
*ret = TAKE_PTR(packet);
|
||||
*ret_optlen = optlen;
|
||||
*ret_optoffset = optoffset;
|
||||
*ret_packet = TAKE_PTR(packet);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1012,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;
|
||||
@@ -1020,8 +1044,7 @@ static int client_send_discover(sd_dhcp_client *client) {
|
||||
assert(client);
|
||||
assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING));
|
||||
|
||||
r = client_message_init(client, &discover, DHCP_DISCOVER,
|
||||
&optlen, &optoffset);
|
||||
r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1057,10 +1080,6 @@ static int client_send_discover(sd_dhcp_client *client) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* We currently ignore:
|
||||
The client SHOULD wait a random time between one and ten seconds to
|
||||
desynchronize the use of DHCP at startup.
|
||||
*/
|
||||
r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1070,14 +1089,52 @@ 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, &request, DHCP_REQUEST, &optlen, &optoffset);
|
||||
r = client_message_init(client, DHCP_REQUEST, &request, &optlen, &optoffset);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1267,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;
|
||||
@@ -1276,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;
|
||||
@@ -1478,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)
|
||||
@@ -1561,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),
|
||||
@@ -1848,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);
|
||||
|
||||
@@ -2162,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;
|
||||
@@ -2206,9 +2334,12 @@ 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:
|
||||
* The client SHOULD wait a random time between one and ten seconds to desynchronize the use of
|
||||
* DHCP at startup. */
|
||||
r = client_start(client);
|
||||
if (r >= 0)
|
||||
log_dhcp_client(client, "STARTED on ifindex %i", client->ifindex);
|
||||
@@ -2221,10 +2352,10 @@ 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, &release, DHCP_RELEASE, &optlen, &optoffset);
|
||||
r = client_message_init(client, DHCP_RELEASE, &release, &optlen, &optoffset);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -2262,7 +2393,7 @@ int sd_dhcp_client_send_decline(sd_dhcp_client *client) {
|
||||
if (!sd_dhcp_client_is_running(client) || !client->lease)
|
||||
return 0; /* do nothing */
|
||||
|
||||
r = client_message_init(client, &release, DHCP_DECLINE, &optlen, &optoffset);
|
||||
r = client_message_init(client, DHCP_DECLINE, &release, &optlen, &optoffset);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -518,9 +518,9 @@ static int server_message_init(
|
||||
return -ENOMEM;
|
||||
|
||||
r = dhcp_message_init(&packet->dhcp, BOOTREPLY,
|
||||
be32toh(req->message->xid), type,
|
||||
be32toh(req->message->xid),
|
||||
req->message->htype, req->message->hlen, req->message->chaddr,
|
||||
req->max_optlen, &optoffset);
|
||||
type, req->max_optlen, &optoffset);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -713,7 +713,7 @@ static int server_send_forcerenew(
|
||||
return -ENOMEM;
|
||||
|
||||
r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0,
|
||||
DHCP_FORCERENEW, htype, hlen, chaddr,
|
||||
htype, hlen, chaddr, DHCP_FORCERENEW,
|
||||
DHCP_MIN_OPTIONS_SIZE, &optoffset);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -35,6 +35,14 @@ static struct hw_addr_data hw_addr = {
|
||||
};
|
||||
typedef int (*test_callback_recv_t)(size_t size, DHCPMessage *dhcp);
|
||||
|
||||
struct bootp_addr_data {
|
||||
uint8_t *offer_buf;
|
||||
size_t offer_len;
|
||||
int netmask_offset;
|
||||
int ip_offset;
|
||||
};
|
||||
struct bootp_addr_data *bootp_test_context;
|
||||
|
||||
static bool verbose = true;
|
||||
static int test_fd[2];
|
||||
static test_callback_recv_t callback_recv;
|
||||
@@ -531,6 +539,202 @@ static void test_addr_acq(sd_event *e) {
|
||||
xid = 0;
|
||||
}
|
||||
|
||||
static uint8_t test_addr_bootp_reply[] = {
|
||||
0x45, 0x00, 0x01, 0x48, 0x00, 0x00, 0x40, 0x00,
|
||||
0xff, 0x11, 0x70, 0xa3, 0x0a, 0x00, 0x00, 0x02,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x43, 0x00, 0x44,
|
||||
0x01, 0x2c, 0x2b, 0x91, 0x02, 0x01, 0x06, 0x00,
|
||||
0x69, 0xd3, 0x79, 0x11, 0x17, 0x00, 0x80, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0a, 0x46, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x50, 0x2d, 0xf4, 0x1f, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x63, 0x82, 0x53, 0x63, 0x01, 0x04, 0xff, 0x00,
|
||||
0x00, 0x00, 0x36, 0x04, 0x0a, 0x00, 0x00, 0x02,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
};
|
||||
|
||||
static uint8_t test_addr_bootp_reply_bootpd[] = {
|
||||
0x45, 0x00, 0x01, 0x48, 0xbe, 0xad, 0x40, 0x00,
|
||||
0x40, 0x11, 0x73, 0x43, 0xc0, 0xa8, 0x43, 0x31,
|
||||
0xc0, 0xa8, 0x43, 0x32, 0x00, 0x43, 0x00, 0x44,
|
||||
0x01, 0x34, 0x08, 0xfa, 0x02, 0x01, 0x06, 0x00,
|
||||
0x82, 0x57, 0xda, 0xf1, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x43, 0x32,
|
||||
0xc0, 0xa8, 0x43, 0x31, 0x00, 0x00, 0x00, 0x00,
|
||||
0xc2, 0x3e, 0xa5, 0x53, 0x57, 0x72, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x64, 0x65, 0x62, 0x69, 0x61, 0x6e, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x63, 0x82, 0x53, 0x63, 0x01, 0x04, 0xff, 0xff,
|
||||
0xff, 0xf0, 0x03, 0x04, 0xc0, 0xa8, 0x43, 0x31,
|
||||
0x06, 0x04, 0x0a, 0x00, 0x01, 0x01, 0x0c, 0x15,
|
||||
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x64,
|
||||
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2d, 0x74,
|
||||
0x72, 0x69, 0x78, 0x69, 0x65, 0xff, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static struct bootp_addr_data bootp_addr_data[] = {
|
||||
{
|
||||
.offer_buf = test_addr_bootp_reply,
|
||||
.offer_len = sizeof(test_addr_bootp_reply),
|
||||
.netmask_offset = 270,
|
||||
.ip_offset = 44,
|
||||
},
|
||||
{
|
||||
.offer_buf = test_addr_bootp_reply_bootpd,
|
||||
.offer_len = sizeof(test_addr_bootp_reply_bootpd),
|
||||
.netmask_offset = 270,
|
||||
.ip_offset = 44,
|
||||
},
|
||||
};
|
||||
|
||||
static int test_bootp_acquired(sd_dhcp_client *client, int event,
|
||||
void *userdata) {
|
||||
sd_dhcp_lease *lease = NULL;
|
||||
sd_event *e = userdata;
|
||||
struct in_addr addr;
|
||||
|
||||
ASSERT_NOT_NULL(client);
|
||||
assert_se(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING));
|
||||
|
||||
ASSERT_OK(sd_dhcp_client_get_lease(client, &lease));
|
||||
ASSERT_NOT_NULL(lease);
|
||||
|
||||
ASSERT_OK(sd_dhcp_lease_get_address(lease, &addr));
|
||||
ASSERT_EQ(memcmp(&addr.s_addr, &bootp_test_context->offer_buf[bootp_test_context->ip_offset],
|
||||
sizeof(addr.s_addr)), 0);
|
||||
|
||||
ASSERT_OK(sd_dhcp_lease_get_netmask(lease, &addr));
|
||||
ASSERT_EQ(memcmp(&addr.s_addr, &bootp_test_context->offer_buf[bootp_test_context->netmask_offset],
|
||||
sizeof(addr.s_addr)), 0);
|
||||
|
||||
if (verbose)
|
||||
log_info(" BOOTP address acquired");
|
||||
|
||||
sd_event_exit(e, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_bootp_recv_request(size_t size, DHCPMessage *request) {
|
||||
uint16_t udp_check = 0;
|
||||
size_t res;
|
||||
|
||||
xid = request->xid;
|
||||
|
||||
if (verbose)
|
||||
log_info(" recv BOOTP Request 0x%08x", be32toh(xid));
|
||||
|
||||
callback_recv = NULL;
|
||||
|
||||
memcpy(&bootp_test_context->offer_buf[26], &udp_check, sizeof(udp_check));
|
||||
memcpy(&bootp_test_context->offer_buf[32], &xid, sizeof(xid));
|
||||
memcpy(&bootp_test_context->offer_buf[56], hw_addr.bytes, hw_addr.length);
|
||||
|
||||
res = write(test_fd[1], bootp_test_context->offer_buf,
|
||||
bootp_test_context->offer_len);
|
||||
ASSERT_EQ(res, bootp_test_context->offer_len);
|
||||
|
||||
if (verbose)
|
||||
log_info(" sent BOOTP Reply");
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
static void test_acquire_bootp(sd_event *e) {
|
||||
sd_dhcp_client *client = NULL;
|
||||
int res;
|
||||
|
||||
if (verbose)
|
||||
log_info("* %s", __func__);
|
||||
|
||||
ASSERT_OK(sd_dhcp_client_new(&client, false));
|
||||
ASSERT_NOT_NULL(client);
|
||||
|
||||
ASSERT_OK(sd_dhcp_client_attach_event(client, e, 0));
|
||||
|
||||
ASSERT_OK(sd_dhcp_client_set_bootp(client, true));
|
||||
|
||||
ASSERT_OK(sd_dhcp_client_set_ifindex(client, 42));
|
||||
ASSERT_OK(sd_dhcp_client_set_mac(client, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, ARPHRD_ETHER));
|
||||
|
||||
ASSERT_OK(sd_dhcp_client_set_callback(client, test_bootp_acquired, e));
|
||||
|
||||
callback_recv = test_bootp_recv_request;
|
||||
|
||||
ASSERT_OK(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
|
||||
30 * USEC_PER_SEC, 0,
|
||||
NULL, INT_TO_PTR(-ETIMEDOUT)));
|
||||
|
||||
res = sd_dhcp_client_start(client);
|
||||
assert_se(IN_SET(res, 0, -EINPROGRESS));
|
||||
|
||||
ASSERT_OK(sd_event_loop(e));
|
||||
|
||||
ASSERT_OK(sd_dhcp_client_set_callback(client, NULL, NULL));
|
||||
ASSERT_OK(sd_dhcp_client_stop(client));
|
||||
client = sd_dhcp_client_unref(client);
|
||||
ASSERT_NULL(client);
|
||||
|
||||
test_fd[1] = safe_close(test_fd[1]);
|
||||
|
||||
callback_recv = NULL;
|
||||
xid = 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
_cleanup_(sd_event_unrefp) sd_event *e;
|
||||
|
||||
@@ -548,6 +752,13 @@ int main(int argc, char *argv[]) {
|
||||
test_discover_message(e);
|
||||
test_addr_acq(e);
|
||||
|
||||
FOREACH_ELEMENT(i, bootp_addr_data) {
|
||||
sd_event_unref(e);
|
||||
ASSERT_OK(sd_event_new(&e));
|
||||
bootp_test_context = i;
|
||||
test_acquire_bootp(e);
|
||||
}
|
||||
|
||||
#if HAVE_VALGRIND_VALGRIND_H
|
||||
/* Make sure the async_close thread has finished.
|
||||
* valgrind would report some of the phread_* structures
|
||||
|
||||
@@ -87,7 +87,7 @@ static void test_message_init(void) {
|
||||
message = malloc0(len);
|
||||
|
||||
assert_se(dhcp_message_init(message, BOOTREQUEST, 0x12345678,
|
||||
DHCP_DISCOVER, ARPHRD_ETHER, ETH_ALEN, (uint8_t[16]){},
|
||||
ARPHRD_ETHER, ETH_ALEN, (uint8_t[16]){}, DHCP_DISCOVER,
|
||||
optlen, &optoffset) >= 0);
|
||||
|
||||
assert_se(message->xid == htobe32(0x12345678));
|
||||
|
||||
@@ -1498,6 +1498,11 @@ static int dhcp4_configure(Link *link) {
|
||||
if (r < 0)
|
||||
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to allocate DHCPv4 client: %m");
|
||||
|
||||
r = sd_dhcp_client_set_bootp(link->dhcp_client, link->network->dhcp_use_bootp);
|
||||
if (r < 0)
|
||||
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to %s BOOTP: %m",
|
||||
enable_disable(link->network->dhcp_use_bootp));
|
||||
|
||||
r = sd_dhcp_client_attach_event(link->dhcp_client, link->manager->event, 0);
|
||||
if (r < 0)
|
||||
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to attach event to DHCPv4 client: %m");
|
||||
@@ -1855,7 +1860,7 @@ int link_request_dhcp4_client(Link *link) {
|
||||
}
|
||||
|
||||
int link_drop_dhcp4_config(Link *link, Network *network) {
|
||||
int ret = 0;
|
||||
int r, ret = 0;
|
||||
|
||||
assert(link);
|
||||
assert(link->network);
|
||||
@@ -1874,6 +1879,17 @@ int link_drop_dhcp4_config(Link *link, Network *network) {
|
||||
RET_GATHER(ret, dhcp4_remove_address_and_routes(link, /* only_marked = */ false));
|
||||
}
|
||||
|
||||
if (link->dhcp_client && link->network->dhcp_use_bootp &&
|
||||
network && !network->dhcp_use_bootp && network->dhcp_send_release) {
|
||||
/* If the client was enabled as a DHCP client, and is now enabled as a BOOTP client, release
|
||||
* the previous lease. Note, this can be easily fail, e.g. when the interface is down. Hence,
|
||||
* ignore any failures here. */
|
||||
r = sd_dhcp_client_send_release(link->dhcp_client);
|
||||
if (r < 0)
|
||||
log_link_full_errno(link, ERRNO_IS_DISCONNECT(r) ? LOG_DEBUG : LOG_WARNING, r,
|
||||
"Failed to send DHCP RELEASE, ignoring: %m");
|
||||
}
|
||||
|
||||
/* Even if the client is currently enabled and also enabled in the new .network file, detailed
|
||||
* settings for the client may be different. Let's unref() the client. But do not unref() the lease.
|
||||
* it will be unref()ed later when a new lease is acquired. */
|
||||
|
||||
@@ -269,6 +269,7 @@ DHCPv4.QuickAck, config_parse_bool,
|
||||
DHCPv4.RequestOptions, config_parse_dhcp_request_options, AF_INET, 0
|
||||
DHCPv4.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize)
|
||||
DHCPv4.SendHostname, config_parse_dhcp_send_hostname, AF_INET, 0
|
||||
DHCPv4.BOOTP, config_parse_bool, 0, offsetof(Network, dhcp_use_bootp)
|
||||
DHCPv4.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname)
|
||||
DHCPv4.Label, config_parse_dhcp_label, 0, offsetof(Network, dhcp_label)
|
||||
DHCPv4.RequestBroadcast, config_parse_tristate, 0, offsetof(Network, dhcp_broadcast)
|
||||
|
||||
@@ -114,6 +114,7 @@ typedef struct Network {
|
||||
/* DHCP Client Support */
|
||||
AddressFamily dhcp;
|
||||
struct in_addr dhcp_request_address;
|
||||
bool dhcp_use_bootp;
|
||||
DHCPClientIdentifier dhcp_client_identifier;
|
||||
DUID dhcp_duid;
|
||||
uint32_t dhcp_iaid;
|
||||
|
||||
@@ -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);
|
||||
|
||||
10
test/test-network/conf/25-bootp-client.network
Normal file
10
test/test-network/conf/25-bootp-client.network
Normal file
@@ -0,0 +1,10 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
[Match]
|
||||
Name=veth99
|
||||
|
||||
[Network]
|
||||
IPv6AcceptRA=no
|
||||
DHCP=ipv4
|
||||
|
||||
[DHCPv4]
|
||||
BOOTP=yes
|
||||
@@ -7746,6 +7746,62 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
|
||||
self.assertNotIn('DHCPREQUEST(veth-peer)', output)
|
||||
self.assertIn('DHCPACK(veth-peer)', output)
|
||||
|
||||
def check_bootp_client(self, check_log):
|
||||
self.wait_online('veth99:routable', 'veth-peer:routable')
|
||||
output = check_output('ip -4 address show dev veth99')
|
||||
print(output)
|
||||
self.assertRegex(output, r'inet 192.168.5.[0-9]*/24')
|
||||
|
||||
state = get_dhcp4_client_state('veth99')
|
||||
print(f"DHCPv4 client state = {state}")
|
||||
self.assertEqual(state, 'bound')
|
||||
|
||||
if check_log:
|
||||
output = read_dnsmasq_log_file()
|
||||
print(output)
|
||||
self.assertIn('BOOTP(veth-peer)', output)
|
||||
self.assertNotIn('DHCPDISCOVER(veth-peer)', output)
|
||||
self.assertNotIn('DHCPOFFER(veth-peer)', output)
|
||||
self.assertNotIn('DHCPREQUEST(veth-peer)', output)
|
||||
self.assertNotIn('DHCPACK(veth-peer)', output)
|
||||
|
||||
def test_bootp_client(self):
|
||||
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-bootp-client.network')
|
||||
start_networkd()
|
||||
self.wait_online('veth-peer:carrier')
|
||||
start_dnsmasq('--dhcp-host=12:34:56:78:9a:bc,192.168.5.42,trixie-mule')
|
||||
self.check_bootp_client(check_log=True)
|
||||
|
||||
touch_network_unit('25-bootp-client.network')
|
||||
networkctl_reload()
|
||||
self.check_bootp_client(check_log=True)
|
||||
|
||||
with open(os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8') as f:
|
||||
f.write('[DHCPv4]\nBOOTP=no\n')
|
||||
|
||||
networkctl_reload()
|
||||
self.check_bootp_client(check_log=False)
|
||||
|
||||
output = read_dnsmasq_log_file()
|
||||
print(output)
|
||||
# Note, on reload, the DHCP client will be started from INIT-REBOOT state,
|
||||
# hence DISCOVER and OFFER message will not be sent/received.
|
||||
self.assertNotIn('DHCPDISCOVER(veth-peer)', output)
|
||||
self.assertNotIn('DHCPOFFER(veth-peer)', output)
|
||||
self.assertIn('DHCPREQUEST(veth-peer)', output)
|
||||
self.assertIn('DHCPACK(veth-peer)', output)
|
||||
|
||||
with open(os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8') as f:
|
||||
f.write('[DHCPv4]\nBOOTP=yes\n')
|
||||
|
||||
since = datetime.datetime.now()
|
||||
|
||||
networkctl_reload()
|
||||
self.check_bootp_client(check_log=False)
|
||||
|
||||
# Check if the client send RELEASE message of the previous lease
|
||||
self.check_networkd_log('veth99: DHCPv4 client: RELEASE', since=since)
|
||||
|
||||
def test_dhcp_client_ipv6_only_mode_without_ipv6_connectivity(self):
|
||||
copy_network_unit('25-veth.netdev',
|
||||
'25-dhcp-server-ipv6-only-mode.network',
|
||||
|
||||
Reference in New Issue
Block a user