From: Govind Venugopal Date: Wed, 15 Oct 2025 09:20:41 +0000 (-0700) Subject: network: add DHCP server domain name option support (#39260) X-Git-Tag: v259-rc1~323 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3eb7b881bd7e5ebe54910fe343d0ee0963879aab;p=thirdparty%2Fsystemd.git network: add DHCP server domain name option support (#39260) Implements DHCP option 15 (Domain Name) for systemd-networkd's DHCP server, allowing administrators to configure the DNS default domain that clients should use. This addresses the feature request in issue #37077, where users needed to manually configure domain names using SendOption=15:string:example.com as a workaround. This adds two new configuration options to the [DHCPServer] section: - EmitDomain= (boolean): whether to send domain name to clients - Domain= (string): the domain name to send (e.g., "example.com") Example configuration: [DHCPServer] EmitDomain=yes Domain=example.com This eliminates the need for manual workarounds using SendOption=15:string:... Fixes #37077 --- diff --git a/man/systemd.network.xml b/man/systemd.network.xml index eefaa5572c2..370ff3f969f 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -3992,6 +3992,34 @@ ServerAddress=192.168.0.1/24 + + EmitDomain= + + Takes a boolean. Configures whether the DHCP leases handed out + to clients shall contain domain name information (DHCP option 15). Defaults to + no. + + + + + + Domain= + + Takes a domain name (such as example.com) + to pass to DHCP clients. This configures the DNS default domain for DHCP clients. + When set, DHCP clients will use this as their DNS search domain. + + When EmitDomain=yes is set but Domain= + is not configured, the domain name will be automatically derived from the system's + fully qualified hostname. For example, if the system's hostname is + host.example.com, the domain example.com + will be sent to clients. If the system's hostname does not contain a domain part + (e.g., hostname is just host), no domain name will be sent to + DHCP clients. When empty or unset, defaults to no domain name. + + + + BootServerAddress= diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index 1da18cce603..eeaed11096b 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -46,6 +46,7 @@ typedef struct sd_dhcp_server { uint32_t pool_size; char *timezone; + char *domain_name; DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX]; struct in_addr boot_server_address; diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 335f01f1645..43c7c241758 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -128,6 +128,7 @@ static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) { free(server->boot_server_name); free(server->boot_filename); free(server->timezone); + free(server->domain_name); for (sd_dhcp_lease_server_type_t i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++) free(server->servers[i].addr); @@ -625,6 +626,15 @@ static int server_send_offer_or_ack( return r; } + if (server->domain_name) { + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_DOMAIN_NAME, + strlen(server->domain_name), server->domain_name); + if (r < 0) + return r; + } + /* RFC 8925 section 3.3. DHCPv4 Server Behavior * The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if * the option was not present in the Parameter Request List sent by the client. */ @@ -1415,6 +1425,22 @@ int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz) { return 1; } +int sd_dhcp_server_set_domain_name(sd_dhcp_server *server, const char *domain_name) { + int r; + + assert_return(server, -EINVAL); + + if (domain_name) { + r = dns_name_is_valid(domain_name); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + } + + return free_and_strdup(&server->domain_name, domain_name); +} + int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint64_t t) { assert_return(server, -EINVAL); diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index 61189fe5452..17c5cddd541 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -316,6 +316,44 @@ static void test_static_lease(void) { (uint8_t*) &(uint32_t) { 0x01020306 }, sizeof(uint32_t))); } +static void test_domain_name(void) { + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; + + log_debug("/* %s */", __func__); + + ASSERT_OK(sd_dhcp_server_new(&server, 1)); + + /* Test setting domain name */ + ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "example.com")); + + /* Test setting same domain name (should return 0 - no change) */ + ASSERT_OK_ZERO(sd_dhcp_server_set_domain_name(server, "example.com")); + + /* Test changing domain name */ + ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "test.local")); + + /* Test clearing domain name */ + ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, NULL)); + + /* Test clearing again (should return 0 - already cleared) */ + ASSERT_OK_ZERO(sd_dhcp_server_set_domain_name(server, NULL)); + + /* Test invalid domain name */ + ASSERT_ERROR(sd_dhcp_server_set_domain_name(server, "invalid..domain"), EINVAL); + + /* Test empty string (treated differently from NULL) */ + ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "")); + + /* Test clearing domain name with NULL */ + ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, NULL)); + + /* Test valid domain with subdomain */ + ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "sub.example.com")); + + /* Test single-label domain */ + ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "local")); +} + int main(int argc, char *argv[]) { int r; @@ -323,6 +361,7 @@ int main(int argc, char *argv[]) { test_client_id_hash(); test_static_lease(); + test_domain_name(); r = test_basic(true); if (r < 0) diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index e36d80e03fc..f27cb28cefd 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -13,6 +13,7 @@ #include "fd-util.h" #include "fileio.h" #include "hashmap.h" +#include "hostname-setup.h" #include "network-common.h" #include "networkd-address.h" #include "networkd-dhcp-server.h" @@ -31,6 +32,30 @@ #include "string-util.h" #include "strv.h" +static int get_hostname_domain(char **ret) { + _cleanup_free_ char *hostname = NULL; + const char *domain; + int r; + + assert(ret); + + /* Get the full hostname (FQDN if available) */ + r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &hostname); + if (r < 0) + return r; + + /* Find the first dot to extract the domain part */ + domain = strchr(hostname, '.'); + if (!domain) + return -ENOENT; /* No domain part in hostname */ + + domain++; /* Skip the dot */ + if (isempty(domain)) + return -ENOENT; /* Empty domain after dot */ + + return strdup_to(ret, domain); +} + static bool link_dhcp4_server_enabled(Link *link) { assert(link); @@ -678,6 +703,29 @@ static int dhcp4_server_configure(Link *link) { } } + if (link->network->dhcp_server_emit_domain) { + _cleanup_free_ char *buffer = NULL; + const char *domain = NULL; + + if (link->network->dhcp_server_domain) + domain = link->network->dhcp_server_domain; + else { + r = get_hostname_domain(&buffer); + if (r < 0) + log_link_warning_errno(link, r, "Failed to determine domain name from host's hostname, will not send domain in DHCP leases: %m"); + else { + domain = buffer; + log_link_debug(link, "Using autodetected domain name '%s' for DHCP server.", domain); + } + } + + if (domain) { + r = sd_dhcp_server_set_domain_name(link->dhcp_server, domain); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set domain name for DHCP server: %m"); + } + } + ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_options) { r = sd_dhcp_server_add_option(link->dhcp_server, p); if (r == -EEXIST) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index b9e3fc6a290..40b97300e65 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -381,6 +381,8 @@ DHCPServer.EmitRouter, config_parse_bool, DHCPServer.Router, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_router) DHCPServer.EmitTimezone, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_timezone) DHCPServer.Timezone, config_parse_timezone, 0, offsetof(Network, dhcp_server_timezone) +DHCPServer.EmitDomain, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_domain) +DHCPServer.Domain, config_parse_dns_name, 0, offsetof(Network, dhcp_server_domain) DHCPServer.PoolOffset, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_offset) DHCPServer.PoolSize, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_size) DHCPServer.SendVendorOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_server_send_vendor_options) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index e27fabea81a..4e8566afa67 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -755,6 +755,7 @@ static Network *network_free(Network *network) { free(network->dhcp_server_boot_server_name); free(network->dhcp_server_boot_filename); free(network->dhcp_server_timezone); + free(network->dhcp_server_domain); free(network->dhcp_server_uplink_name); for (sd_dhcp_lease_server_type_t t = 0; t < _SD_DHCP_LEASE_SERVER_TYPE_MAX; t++) free(network->dhcp_server_emit[t].addresses); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 677d337e0f2..edd2177dd3b 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -220,6 +220,8 @@ typedef struct Network { struct in_addr dhcp_server_router; bool dhcp_server_emit_timezone; char *dhcp_server_timezone; + bool dhcp_server_emit_domain; + char *dhcp_server_domain; usec_t dhcp_server_default_lease_time_usec, dhcp_server_max_lease_time_usec; uint32_t dhcp_server_pool_offset; uint32_t dhcp_server_pool_size; diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index d6940ac7f86..b188e1f8393 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -61,6 +61,7 @@ int sd_dhcp_server_set_boot_server_name(sd_dhcp_server *server, const char *name int sd_dhcp_server_set_boot_filename(sd_dhcp_server *server, const char *filename); int sd_dhcp_server_set_bind_to_interface(sd_dhcp_server *server, int enabled); int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *timezone); +int sd_dhcp_server_set_domain_name(sd_dhcp_server *server, const char *domain_name); int sd_dhcp_server_set_router(sd_dhcp_server *server, const struct in_addr *address); int sd_dhcp_server_set_servers(