mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 08:25:20 +09:00
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
This commit is contained in:
@@ -3992,6 +3992,34 @@ ServerAddress=192.168.0.1/24</programlisting>
|
||||
<xi:include href="version-info.xml" xpointer="v226"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>EmitDomain=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean. Configures whether the DHCP leases handed out
|
||||
to clients shall contain domain name information (DHCP option 15). Defaults to
|
||||
<literal>no</literal>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>Domain=</varname></term>
|
||||
|
||||
<listitem><para>Takes a domain name (such as <literal>example.com</literal>)
|
||||
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.</para>
|
||||
|
||||
<para>When <varname>EmitDomain=yes</varname> is set but <varname>Domain=</varname>
|
||||
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
|
||||
<literal>host.example.com</literal>, the domain <literal>example.com</literal>
|
||||
will be sent to clients. If the system's hostname does not contain a domain part
|
||||
(e.g., hostname is just <literal>host</literal>), no domain name will be sent to
|
||||
DHCP clients. When empty or unset, defaults to no domain name.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>BootServerAddress=</varname></term>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user