<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>
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) {
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;
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");
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;
#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"
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;
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;
.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;
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,
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);
}
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));
/* 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 */
/* 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 */
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_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020305 },
- (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)));
+ 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),
+ /* 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) {
#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"
config_section_free(static_lease->section);
free(static_lease->client_id);
+ free(static_lease->hostname);
return mfree(static_lease);
}
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;
+}
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);
}
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");
}
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);
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;
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)
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,
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);
[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
--- /dev/null
+# 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
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Match]
+Name=veth99
+
+[Network]
+DHCP=ipv4
+IPv6AcceptRA=no
--- /dev/null
+# 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
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):