From be40a31f5c7adbd27afcb55867494d8f691e7899 Mon Sep 17 00:00:00 2001 From: Colin Foster Date: Wed, 11 Jun 2025 05:50:20 -0500 Subject: [PATCH 1/8] dhcp: relocate type field The type field is a DHCP-specific parameter. Relocate the parameter so there is a clearer separation between DHCP and BOOTP parameters. --- src/libsystemd-network/dhcp-packet.c | 2 +- src/libsystemd-network/dhcp-packet.h | 2 +- src/libsystemd-network/sd-dhcp-client.c | 4 ++-- src/libsystemd-network/sd-dhcp-server.c | 6 +++--- src/libsystemd-network/test-dhcp-option.c | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index 46317c997c..f08eef01c9 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -17,10 +17,10 @@ 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) { diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h index a7f2e12b4f..c289daa038 100644 --- a/src/libsystemd-network/dhcp-packet.h +++ b/src/libsystemd-network/dhcp-packet.h @@ -8,10 +8,10 @@ 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); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 2cb60b92ce..7e2ce91a31 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -789,9 +789,9 @@ static int client_message_init( if (!packet) return -ENOMEM; - r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, type, + r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, client->arp_type, client->hw_addr.length, client->hw_addr.bytes, - optlen, &optoffset); + type, optlen, &optoffset); if (r < 0) return r; diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index af87b15238..099362d1ae 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -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; diff --git a/src/libsystemd-network/test-dhcp-option.c b/src/libsystemd-network/test-dhcp-option.c index 9e36e03859..05572e0b21 100644 --- a/src/libsystemd-network/test-dhcp-option.c +++ b/src/libsystemd-network/test-dhcp-option.c @@ -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)); From 556f641fb5617789bb4874abe7de41a1ba8bd83b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 13 Jun 2025 05:44:23 +0900 Subject: [PATCH 2/8] dhcp: several coding style cleanups - rename arguments for storing results, - reorder arguments to move ret_xyz at the end, - add several missing assertions. --- src/libsystemd-network/dhcp-packet.c | 7 ++++--- src/libsystemd-network/dhcp-packet.h | 2 +- src/libsystemd-network/sd-dhcp-client.c | 28 ++++++++++++------------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index f08eef01c9..4384a05d96 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -22,13 +22,15 @@ int dhcp_message_init( const uint8_t *chaddr, uint8_t type, size_t optlen, - size_t *optoffset) { + size_t *ret_optoffset) { size_t offset = 0; int r; + assert(message); assert(IN_SET(op, BOOTREQUEST, BOOTREPLY)); assert(chaddr || hlen == 0); + assert(ret_optoffset); message->op = op; message->htype = arp_type; @@ -55,8 +57,7 @@ int dhcp_message_init( if (r < 0) return r; - *optoffset = offset; - + *ret_optoffset = offset; return 0; } diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h index c289daa038..ab29ab616a 100644 --- a/src/libsystemd-network/dhcp-packet.h +++ b/src/libsystemd-network/dhcp-packet.h @@ -13,7 +13,7 @@ int dhcp_message_init( 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); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 7e2ce91a31..c7565aec79 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -764,10 +764,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 +776,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; @@ -900,9 +899,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; } @@ -1020,8 +1019,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; @@ -1077,7 +1075,7 @@ static int client_send_request(sd_dhcp_client *client) { assert(client); - 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; @@ -2224,7 +2222,7 @@ int sd_dhcp_client_send_release(sd_dhcp_client *client) { if (!sd_dhcp_client_is_running(client) || !client->lease) 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 +2260,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; From 98a9b0f92afb4cbe74b1490ec89f38e38620b568 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 13 Jun 2025 05:59:56 +0900 Subject: [PATCH 3/8] sd-dhcp-client: move comment to relevant place --- src/libsystemd-network/sd-dhcp-client.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index c7565aec79..f14fd09bf4 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1055,10 +1055,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; @@ -2207,6 +2203,9 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { if (client->last_addr && !client->anonymize) 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); From 4ad29bad7b9936f993699a61a1f1d9974891740f Mon Sep 17 00:00:00 2001 From: Colin Foster Date: Tue, 29 Oct 2024 20:50:58 -0500 Subject: [PATCH 4/8] 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 4384a05d96..7732f25b8f 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 ab29ab616a..ba058e06c1 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 f14fd09bf4..f77d3f696c 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 4e288f0828..0009a25742 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); From 2871f967cc90a42ff952a82fd1db11007e1a9ab4 Mon Sep 17 00:00:00 2001 From: Colin Foster Date: Mon, 28 Oct 2024 19:50:06 -0500 Subject: [PATCH 5/8] test-dhcp-client: add test for bootp clients Verify that BOOTP replies are successfully handled by the sd-dhcp-client when configured for BOOTP. Co-authored-by: Avram Dorfman --- src/libsystemd-network/test-dhcp-client.c | 211 ++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 72d8ad4d0c..cc08c3fbd2 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -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 From 0dbb5139a7d42fcf938e0091bb8f14a758aab666 Mon Sep 17 00:00:00 2001 From: Colin Foster Date: Tue, 22 Oct 2024 13:26:36 -0500 Subject: [PATCH 6/8] network/dhcp4: add ability to use BOOTP 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. Co-authored-by: Avram Dorfman --- man/systemd.network.xml | 10 ++++++++++ src/network/networkd-dhcp4.c | 5 +++++ src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.h | 1 + 4 files changed, 17 insertions(+) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 53221cbd5e..c3836b0446 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2603,6 +2603,16 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix + + BOOTP= + + Takes a boolean. When enabled, the DHCPv4 client will be configured to communicate with BOOTP + servers, rather than with DHCP servers. Defaults to off. + + + + + diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 7bb203e70a..981b28b777 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -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"); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 54d2783c80..36b833ef36 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -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) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 0808fee694..51c9160760 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -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; From 1f87275736daab5cc3ea06a8fc75fc16abc07c24 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 13 Jun 2025 07:24:04 +0900 Subject: [PATCH 7/8] network/dhcp4: release previously acquired DHCP lease when BOOTP will be enabled --- src/network/networkd-dhcp4.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 981b28b777..4b0263ea72 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1860,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); @@ -1879,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. */ From fb9076b9941faac62afc1d67526a6ad119714894 Mon Sep 17 00:00:00 2001 From: Avram Dorfman Date: Wed, 18 Dec 2024 13:56:44 -0500 Subject: [PATCH 8/8] test-network: add test case for bootp Co-authored-by: Yu Watanabe --- .../test-network/conf/25-bootp-client.network | 10 ++++ test/test-network/systemd-networkd-tests.py | 56 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 test/test-network/conf/25-bootp-client.network diff --git a/test/test-network/conf/25-bootp-client.network b/test/test-network/conf/25-bootp-client.network new file mode 100644 index 0000000000..8e9b574b55 --- /dev/null +++ b/test/test-network/conf/25-bootp-client.network @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=veth99 + +[Network] +IPv6AcceptRA=no +DHCP=ipv4 + +[DHCPv4] +BOOTP=yes diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 91e51ba142..7d8a7c8f3f 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -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',