sd-dhcp-server: Add Hostname= option to static leases

This adds a new `Hostname=` option to the [DHCPServerStaticLease]
section in .network files, allowing an administrator to assign a
specific hostname to a client receiving a static lease.

We automatically select the correct DHCP option to use based on the
format of the provided string:

- Single DNS labels are sent as Option 12.
- Names with multiple DNS labels are sent as Option 81 in wire format.

Fixes: #39634
This commit is contained in:
Chris Down
2025-11-10 00:59:59 +08:00
committed by Yu Watanabe
parent c83f3f0837
commit 7f9c0c31d2
18 changed files with 409 additions and 36 deletions

View File

@@ -4227,6 +4227,25 @@ ServerAddress=192.168.0.1/24</programlisting>
<xi:include href="version-info.xml" xpointer="v249"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>Hostname=</varname></term>
<listitem><para>The hostname to send to the client in DHCP replies. This can be either a simple
hostname (e.g., <literal>mydevice</literal>) or a fully qualified domain name (e.g.,
<literal>mydevice.example.com</literal>), following RFC 1123 naming conventions. Each label can be
up to 63 characters, with a total maximum length of 253 characters for FQDNs. When this option is
set, the DHCP server will include the hostname in DHCP replies (both OFFER and ACK) to the client
with the matched MAC address.</para>
<para>The server automatically selects the appropriate DHCP option based on the hostname format:
simple hostnames (single DNS label) are sent via option 12 (Host Name) per RFC 2132, while FQDNs
with multiple labels are sent via option 81 (Client FQDN) per RFC 4702 using DNS wire format
encoding. The configured hostname is sent unconditionally, any hostname requested by the client in
its DHCP message is ignored.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@@ -56,9 +56,11 @@ static int add_static_lease(sd_dhcp_server *server, uint8_t i) {
assert(server);
return sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = htobe32(UINT32_C(10) << 24 | i)},
id, ELEMENTSOF(id));
server,
&(struct in_addr) { .s_addr = htobe32(UINT32_C(10) << 24 | i) },
id,
ELEMENTSOF(id),
/* hostname= */ NULL);
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {

View File

@@ -505,6 +505,60 @@ static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) {
return 0;
}
static int lease_parse_fqdn(const uint8_t *option, size_t len, char **hostname) {
_cleanup_free_ char *name = NULL, *normalized = NULL;
int r;
assert(option);
assert(hostname);
/* RFC 4702 Section 2
*
* Byte 0: Flags (S: server should perform A RR updates, O: override existing A RR,
* E: encoding (0=ASCII, 1=Wire format), N: no server updates)
* Byte 1: RCODE1 (ignored on receipt)
* Byte 2: RCODE2 (ignored on receipt)
* Bytes 3+: Domain Name */
if (len <= 3)
return -EBADMSG;
size_t data_len = len - 3;
const uint8_t *data = option + 3;
/* In practice, many servers send DNS wire format regardless of the E flag, so ignore and try wire
* format first, then fall back to ASCII if that fails. */
r = dns_name_from_wire_format(&data, &data_len, &name);
if (r < 0) {
if (FLAGS_SET(option[0], DHCP_FQDN_FLAG_E))
return -EBADMSG;
/* Wire format failed, try ASCII format */
r = dhcp_option_parse_string(option + 3, len - 3, &name);
if (r < 0)
return r;
}
if (!name) {
*hostname = mfree(*hostname);
return 0;
}
r = dns_name_normalize(name, 0, &normalized);
if (r < 0)
return r;
if (is_localhost(normalized))
return -EINVAL;
if (dns_name_is_root(normalized))
return -EINVAL;
free_and_replace(*hostname, normalized);
return 0;
}
static int lease_parse_captive_portal(const uint8_t *option, size_t len, char **ret) {
_cleanup_free_ char *uri = NULL;
int r;
@@ -967,6 +1021,12 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
break;
case SD_DHCP_OPTION_HOST_NAME:
/* FQDN option (81) always takes precedence. If it was already set, do not overwrite it. */
if (lease->hostname) {
log_debug("Hostname already set via FQDN, ignoring hostname option.");
break;
}
r = lease_parse_domain(option, len, &lease->hostname);
if (r < 0) {
log_debug_errno(r, "Failed to parse hostname, ignoring: %m");
@@ -975,6 +1035,15 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
break;
case SD_DHCP_OPTION_FQDN:
r = lease_parse_fqdn(option, len, &lease->hostname);
if (r < 0) {
log_debug_errno(r, "Failed to parse FQDN, ignoring: %m");
return 0;
}
break;
case SD_DHCP_OPTION_ROOT_PATH: {
_cleanup_free_ char *p = NULL;

View File

@@ -7,6 +7,7 @@
#include "alloc-util.h"
#include "dhcp-server-lease-internal.h"
#include "dns-domain.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fs-util.h"
@@ -181,7 +182,8 @@ int sd_dhcp_server_set_static_lease(
sd_dhcp_server *server,
const struct in_addr *address,
uint8_t *client_id_raw,
size_t client_id_size) {
size_t client_id_size,
const char *hostname) {
_cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL;
sd_dhcp_client_id client_id;
@@ -203,6 +205,14 @@ int sd_dhcp_server_set_static_lease(
return 0;
}
if (hostname) {
r = dns_name_is_valid_ldh(hostname);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
}
lease = new(sd_dhcp_server_lease, 1);
if (!lease)
return -ENOMEM;
@@ -213,6 +223,12 @@ int sd_dhcp_server_set_static_lease(
.client_id = client_id,
};
if (hostname) {
lease->hostname = strdup(hostname);
if (!lease->hostname)
return -ENOMEM;
}
r = dhcp_server_put_lease(server, lease, /* is_static = */ true);
if (r < 0)
return r;

View File

@@ -534,6 +534,64 @@ static int server_message_init(
return 0;
}
static int dhcp_server_append_static_hostname(
sd_dhcp_server *server,
DHCPPacket *packet,
size_t *offset,
DHCPRequest *req) {
sd_dhcp_server_lease *static_lease;
int r;
assert(server);
assert(packet);
assert(offset);
assert(req);
static_lease = dhcp_server_get_static_lease(server, req);
if (!static_lease || !static_lease->hostname)
return 0;
if (dns_name_is_single_label(static_lease->hostname))
/* Option 12 */
return dhcp_option_append(
&packet->dhcp,
req->max_optlen,
offset,
/* overload= */ 0,
SD_DHCP_OPTION_HOST_NAME,
strlen(static_lease->hostname),
static_lease->hostname);
/* Option 81 */
uint8_t buffer[DHCP_MAX_FQDN_LENGTH + 3];
/* Flags: S=0 (will not update RR), O=1 (are overriding client),
* E=1 (using DNS wire format), N=1 (will not update DNS) */
buffer[0] = DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_E | DHCP_FQDN_FLAG_N;
/* RFC 4702: A server SHOULD set these to 255 when sending the option and MUST ignore them on
* receipt. */
buffer[1] = 255;
buffer[2] = 255;
r = dns_name_to_wire_format(static_lease->hostname, buffer + 3, sizeof(buffer) - 3, false);
if (r < 0)
return log_dhcp_server_errno(server, r, "Failed to encode FQDN for static lease: %m");
if (r > DHCP_MAX_FQDN_LENGTH)
return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EINVAL), "FQDN for static lease too long");
return dhcp_option_append(
&packet->dhcp,
req->max_optlen,
offset,
/* overload= */ 0,
SD_DHCP_OPTION_FQDN,
3 + r,
buffer);
}
static int server_send_offer_or_ack(
sd_dhcp_server *server,
DHCPRequest *req,
@@ -675,6 +733,10 @@ static int server_send_offer_or_ack(
return r;
}
r = dhcp_server_append_static_hostname(server, packet, &offset, req);
if (r < 0)
return r;
return dhcp_server_send_packet(server, req, packet, type, offset);
}

View File

@@ -139,8 +139,12 @@ static void test_message_handler(void) {
ASSERT_OK(sd_dhcp_server_new(&server, 1));
ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0));
ASSERT_OK(sd_dhcp_server_set_static_lease(server, &static_lease_address, static_lease_client_id,
ELEMENTSOF(static_lease_client_id)));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
&static_lease_address,
static_lease_client_id,
ELEMENTSOF(static_lease_client_id),
/* hostname= */ NULL));
ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, 0));
ASSERT_OK(sd_dhcp_server_start(server));
@@ -216,8 +220,12 @@ static void test_message_handler(void) {
/* add the static lease for the client ID */
ASSERT_OK(sd_dhcp_server_stop(server));
ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr){ .s_addr = htobe32(INADDR_LOOPBACK + 31) },
(uint8_t[7]){ 0x01, 'A', 'B', 'C', 'D', 'E', 'F' }, 7));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = htobe32(INADDR_LOOPBACK + 31) },
(uint8_t[7]) { 0x01, 'A', 'B', 'C', 'D', 'E', 'F' },
7,
/* hostname= */ NULL));
ASSERT_OK(sd_dhcp_server_start(server));
/* discover */
@@ -244,7 +252,12 @@ static void test_message_handler(void) {
/* drop the static lease for the client ID */
ASSERT_OK(sd_dhcp_server_stop(server));
ASSERT_OK(sd_dhcp_server_set_static_lease(server, NULL, (uint8_t[7]){ 0x01, 'A', 'B', 'C', 'D', 'E', 'F' }, 7));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
/* address= */ NULL,
(uint8_t[7]) { 0x01, 'A', 'B', 'C', 'D', 'E', 'F' },
7,
/* hostname= */ NULL));
ASSERT_OK(sd_dhcp_server_start(server));
/* request a new non-static address */
@@ -324,35 +337,78 @@ static void test_static_lease(void) {
ASSERT_OK(sd_dhcp_server_new(&server, 1));
ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020304 },
(uint8_t*) &(uint32_t) { 0x01020304 }, sizeof(uint32_t)));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = 0x01020304 },
(uint8_t *) &(uint32_t) { 0x01020304 },
sizeof(uint32_t),
/* hostname= */ NULL));
/* Duplicated entry. */
ASSERT_ERROR(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020304 },
(uint8_t*) &(uint32_t) { 0x01020304 }, sizeof(uint32_t)), EEXIST);
ASSERT_ERROR(sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = 0x01020304 },
(uint8_t *) &(uint32_t) { 0x01020304 },
sizeof(uint32_t),
/* hostname= */ NULL),
EEXIST);
/* Address is conflicted. */
ASSERT_ERROR(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020304 },
(uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)), EEXIST);
ASSERT_ERROR(sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = 0x01020304 },
(uint8_t *) &(uint32_t) { 0x01020305 },
sizeof(uint32_t),
/* hostname= */ NULL),
EEXIST);
/* Client ID is conflicted. */
ASSERT_ERROR(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020305 },
(uint8_t*) &(uint32_t) { 0x01020304 }, sizeof(uint32_t)), EEXIST);
ASSERT_ERROR(sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = 0x01020305 },
(uint8_t *) &(uint32_t) { 0x01020304 },
sizeof(uint32_t),
/* hostname= */ NULL),
EEXIST);
ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020305 },
(uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = 0x01020305 },
(uint8_t *) &(uint32_t) { 0x01020305 },
sizeof(uint32_t),
/* hostname= */ NULL));
/* Remove the previous entry. */
ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 },
(uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = 0x00000000 },
(uint8_t *) &(uint32_t) { 0x01020305 },
sizeof(uint32_t),
/* hostname= */ NULL));
/* Then, set a different address. */
ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020306 },
(uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = 0x01020306 },
(uint8_t *) &(uint32_t) { 0x01020305 },
sizeof(uint32_t),
/* hostname= */ NULL));
/* Remove again. */
ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 },
(uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = 0x00000000 },
(uint8_t *) &(uint32_t) { 0x01020305 },
sizeof(uint32_t),
/* hostname= */ NULL));
/* Try to remove non-existent entry. */
ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 },
(uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = 0x00000000 },
(uint8_t *) &(uint32_t) { 0x01020305 },
sizeof(uint32_t),
/* hostname= */ NULL));
/* Try to remove non-existent entry. */
ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 },
(uint8_t*) &(uint32_t) { 0x01020306 }, sizeof(uint32_t)));
ASSERT_OK(sd_dhcp_server_set_static_lease(
server,
&(struct in_addr) { .s_addr = 0x00000000 },
(uint8_t *) &(uint32_t) { 0x01020306 },
sizeof(uint32_t),
/* hostname= */ NULL));
}
static void test_domain_name(void) {

View File

@@ -2,6 +2,7 @@
#include "alloc-util.h"
#include "conf-parser.h"
#include "dns-domain.h"
#include "ether-addr-util.h"
#include "hashmap.h"
#include "networkd-dhcp-server-static-lease.h"
@@ -17,6 +18,7 @@ static DHCPStaticLease* dhcp_static_lease_free(DHCPStaticLease *static_lease) {
config_section_free(static_lease->section);
free(static_lease->client_id);
free(static_lease->hostname);
return mfree(static_lease);
}
@@ -215,3 +217,55 @@ int config_parse_dhcp_static_lease_hwaddr(
TAKE_PTR(lease);
return 0;
}
int config_parse_dhcp_static_lease_hostname(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_(dhcp_static_lease_free_or_set_invalidp) DHCPStaticLease *lease = NULL;
Network *network = ASSERT_PTR(userdata);
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
r = lease_new_static(network, filename, section_line, &lease);
if (r < 0)
return log_oom();
if (isempty(rvalue)) {
lease->hostname = mfree(lease->hostname);
TAKE_PTR(lease);
return 0;
}
r = dns_name_is_valid_ldh(rvalue);
if (r < 0)
return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
if (r == 0) {
log_syntax(unit,
LOG_WARNING,
filename,
line,
0,
"Invalid hostname for DHCPv4 static lease, ignoring assignment: %s",
rvalue);
return 0;
}
r = free_and_strdup(&lease->hostname, rvalue);
if (r < 0)
return log_oom();
TAKE_PTR(lease);
return 0;
}

View File

@@ -13,9 +13,11 @@ typedef struct DHCPStaticLease {
struct in_addr address;
uint8_t *client_id;
size_t client_id_size;
char *hostname;
} DHCPStaticLease;
void network_drop_invalid_static_leases(Network *network);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_static_lease_address);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_static_lease_hwaddr);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_static_lease_hostname);

View File

@@ -733,7 +733,12 @@ static int dhcp4_server_configure(Link *link) {
}
HASHMAP_FOREACH(static_lease, link->network->dhcp_static_leases_by_section) {
r = sd_dhcp_server_set_static_lease(link->dhcp_server, &static_lease->address, static_lease->client_id, static_lease->client_id_size);
r = sd_dhcp_server_set_static_lease(
link->dhcp_server,
&static_lease->address,
static_lease->client_id,
static_lease->client_id_size,
static_lease->hostname);
if (r < 0)
return log_link_error_errno(link, r, "Failed to set DHCPv4 static lease for DHCP server: %m");
}

View File

@@ -1300,6 +1300,7 @@ static int dhcp6_client_append_json(Link *link, sd_json_variant **v) {
static int dhcp_client_lease_append_json(Link *link, sd_json_variant **v) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
usec_t lease_timestamp_usec = USEC_INFINITY, t1 = USEC_INFINITY, t2 = USEC_INFINITY;
const char *hostname = NULL;
int r;
assert(link);
@@ -1320,10 +1321,16 @@ static int dhcp_client_lease_append_json(Link *link, sd_json_variant **v) {
if (r < 0 && r != -ENODATA)
return r;
r = sd_json_buildo(&w,
JSON_BUILD_PAIR_FINITE_USEC("LeaseTimestampUSec", lease_timestamp_usec),
JSON_BUILD_PAIR_FINITE_USEC("Timeout1USec", t1),
JSON_BUILD_PAIR_FINITE_USEC("Timeout2USec", t2));
r = sd_dhcp_lease_get_hostname(link->dhcp_lease, &hostname);
if (r < 0 && r != -ENODATA)
return r;
r = sd_json_buildo(
&w,
JSON_BUILD_PAIR_FINITE_USEC("LeaseTimestampUSec", lease_timestamp_usec),
JSON_BUILD_PAIR_FINITE_USEC("Timeout1USec", t1),
JSON_BUILD_PAIR_FINITE_USEC("Timeout2USec", t2),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", hostname));
if (r < 0)
return r;

View File

@@ -395,6 +395,7 @@ DHCPServer.RapidCommit, config_parse_bool,
DHCPServer.PersistLeases, config_parse_dhcp_server_persist_leases, 0, offsetof(Network, dhcp_server_persist_leases)
DHCPServerStaticLease.Address, config_parse_dhcp_static_lease_address, 0, 0
DHCPServerStaticLease.MACAddress, config_parse_dhcp_static_lease_hwaddr, 0, 0
DHCPServerStaticLease.Hostname, config_parse_dhcp_static_lease_hostname, 0, 0
Bridge.Cost, config_parse_uint32, 0, offsetof(Network, cost)
Bridge.UseBPDU, config_parse_tristate, 0, offsetof(Network, use_bpdu)
Bridge.HairPin, config_parse_tristate, 0, offsetof(Network, hairpin)

View File

@@ -351,7 +351,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_FIELD_COMMENT("T1 timeout (lease renewal time) in microseconds"),
SD_VARLINK_DEFINE_FIELD(Timeout1USec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("T2 timeout (lease rebinding time) in microseconds"),
SD_VARLINK_DEFINE_FIELD(Timeout2USec, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
SD_VARLINK_DEFINE_FIELD(Timeout2USec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Hostname received from DHCP server"),
SD_VARLINK_DEFINE_FIELD(Hostname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
PrivateOption,

View File

@@ -79,7 +79,12 @@ int sd_dhcp_server_set_smtp(sd_dhcp_server *server, const struct in_addr smtp[],
int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v);
int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v);
int sd_dhcp_server_set_static_lease(sd_dhcp_server *server, const struct in_addr *address, uint8_t *client_id, size_t client_id_size);
int sd_dhcp_server_set_static_lease(
sd_dhcp_server *server,
const struct in_addr *address,
uint8_t *client_id,
size_t client_id_size,
const char *hostname);
int sd_dhcp_server_set_lease_file(sd_dhcp_server *server, int dir_fd, const char *path);
int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint64_t t);

View File

@@ -16,10 +16,12 @@ DNS=9.9.9.9
[DHCPServerStaticLease]
MACAddress=12:34:56:78:9a:bc
Address=10.1.1.2
Hostname=testhost
[DHCPServerStaticLease]
MACAddress=12:34:56:78:9a:bc
Address=10.1.1.3
Hostname=device.example.com
[DHCPServerStaticLease]
Address=10.1.1.4

View File

@@ -0,0 +1,11 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth99
[Network]
DHCP=ipv4
IPv6AcceptRA=no
[Link]
# This MAC overrides the default to match the second static lease
MACAddress=92:12:01:87:11:19

View File

@@ -0,0 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth99
[Network]
DHCP=ipv4
IPv6AcceptRA=no

View File

@@ -0,0 +1,27 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth-peer
[Network]
Address=10.1.1.1/24
DHCPServer=yes
IPv6AcceptRA=no
[DHCPServer]
PoolOffset=100
PoolSize=50
DefaultLeaseTimeSec=60
# Scenario 1: Option 12
# Matches veth99's default MAC (from 25-veth.netdev)
[DHCPServerStaticLease]
MACAddress=12:34:56:78:9a:bc
Address=10.1.1.200
Hostname=simple-host
# Scenario 2: Option 81
# Matches the MAC set by 25-dhcp-client-fqdn-hostname.network
[DHCPServerStaticLease]
MACAddress=92:12:01:87:11:19
Address=10.1.1.201
Hostname=fqdn.example.com

View File

@@ -7340,6 +7340,32 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
self.assertIn('Address: 10.1.1.200 (DHCPv4 via 10.1.1.1)', output)
self.assertRegex(output, 'DHCPv4 Client ID: IAID:[0-9a-z]*/DUID')
def test_dhcp_server_static_lease_hostname_simple(self):
copy_network_unit('25-veth.netdev',
'25-dhcp-client-simple-hostname.network',
'25-dhcp-server-static-hostname.network')
start_networkd()
self.wait_online('veth99:routable', 'veth-peer:routable')
output = networkctl_json('veth99')
check_json(output)
print(output)
data = json.loads(output)
self.assertEqual(data['DHCPv4Client']['Lease']['Hostname'], 'simple-host')
def test_dhcp_server_static_lease_hostname_fqdn(self):
copy_network_unit('25-veth.netdev',
'25-dhcp-client-fqdn-hostname.network',
'25-dhcp-server-static-hostname.network')
start_networkd()
self.wait_online('veth99:routable', 'veth-peer:routable')
output = networkctl_json('veth99')
check_json(output)
print(output)
data = json.loads(output)
self.assertEqual(data['DHCPv4Client']['Lease']['Hostname'], 'fqdn.example.com')
class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities):
def setUp(self):