networkd: add support for resolved hook for DHCP server

Let's synthesize DNS RRs for leases handed out by our DHCP server. This
way local VMs can have resolvable hostnames locally.

This does not implement reverse look ups for now. We can add this
later in similar fashion.
This commit is contained in:
Lennart Poettering
2025-10-13 09:57:48 +02:00
parent f041d40dee
commit a7fa29b1b5
21 changed files with 434 additions and 15 deletions

View File

@@ -4201,6 +4201,23 @@ ServerAddress=192.168.0.1/24</programlisting>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>LocalLeaseDomain=</varname></term>
<listitem>
<para>Takes a DNS domain name as argument. If specified,
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
will integrate with
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
and ensure that the hostnames associated with each handed out DHCP lease may be resolved to the IP
addresses of the lease. The hostnames are suffixed with the specified domain name.</para>
<para>Note that this purely about hostname resolution on the local system, i.e. from programs with
access to the same <filename>systemd-resolved</filename> instances via D-Bus IPC, Varlink IPC, or
the local DNS stub.</para>
<xi:include href="version-info.xml" xpointer="v259"/>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>

View File

@@ -74,6 +74,7 @@ systemd_networkd_extract_sources = files(
'networkd-ntp.c', 'networkd-ntp.c',
'networkd-queue.c', 'networkd-queue.c',
'networkd-radv.c', 'networkd-radv.c',
'networkd-resolve-hook.c',
'networkd-route.c', 'networkd-route.c',
'networkd-route-metric.c', 'networkd-route-metric.c',
'networkd-route-nexthop.c', 'networkd-route-nexthop.c',

View File

@@ -25,6 +25,7 @@
#include "networkd-network.h" #include "networkd-network.h"
#include "networkd-ntp.h" #include "networkd-ntp.h"
#include "networkd-queue.h" #include "networkd-queue.h"
#include "networkd-resolve-hook.h"
#include "networkd-route-util.h" #include "networkd-route-util.h"
#include "path-util.h" #include "path-util.h"
#include "set.h" #include "set.h"
@@ -747,6 +748,8 @@ static int dhcp4_server_configure(Link *link) {
if (r < 0) if (r < 0)
return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m"); return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m");
manager_notify_hook_filters(link->manager);
return 0; return 0;
} }

View File

@@ -23,6 +23,7 @@
#include "arphrd-util.h" #include "arphrd-util.h"
#include "bitfield.h" #include "bitfield.h"
#include "device-util.h" #include "device-util.h"
#include "dns-domain.h"
#include "errno-util.h" #include "errno-util.h"
#include "ethtool-util.h" #include "ethtool-util.h"
#include "event-util.h" #include "event-util.h"
@@ -53,6 +54,7 @@
#include "networkd-nexthop.h" #include "networkd-nexthop.h"
#include "networkd-queue.h" #include "networkd-queue.h"
#include "networkd-radv.h" #include "networkd-radv.h"
#include "networkd-resolve-hook.h"
#include "networkd-route.h" #include "networkd-route.h"
#include "networkd-route-util.h" #include "networkd-route-util.h"
#include "networkd-routing-policy-rule.h" #include "networkd-routing-policy-rule.h"
@@ -1070,6 +1072,8 @@ static Link *link_drop(Link *link) {
assert(link->manager); assert(link->manager);
bool notify = link_has_local_lease_domain(link);
link_set_state(link, LINK_STATE_LINGER); link_set_state(link, LINK_STATE_LINGER);
/* Drop all references from other links and manager. Note that async netlink calls may have /* Drop all references from other links and manager. Note that async netlink calls may have
@@ -1098,6 +1102,10 @@ static Link *link_drop(Link *link) {
/* The following must be called at last. */ /* The following must be called at last. */
assert_se(hashmap_remove(link->manager->links_by_index, INT_TO_PTR(link->ifindex)) == link); assert_se(hashmap_remove(link->manager->links_by_index, INT_TO_PTR(link->ifindex)) == link);
if (notify)
manager_notify_hook_filters(link->manager);
return link_unref(link); return link_unref(link);
} }
@@ -1351,6 +1359,8 @@ static void link_enter_unmanaged(Link *link) {
if (link->state == LINK_STATE_UNMANAGED) if (link->state == LINK_STATE_UNMANAGED)
return; return;
bool notify = link_has_local_lease_domain(link);
log_link_full(link, link->state == LINK_STATE_INITIALIZED ? LOG_DEBUG : LOG_INFO, log_link_full(link, link->state == LINK_STATE_INITIALIZED ? LOG_DEBUG : LOG_INFO,
"Unmanaging interface."); "Unmanaging interface.");
@@ -1367,6 +1377,9 @@ static void link_enter_unmanaged(Link *link) {
link->network = network_unref(link->network); link->network = network_unref(link->network);
link_set_state(link, LINK_STATE_UNMANAGED); link_set_state(link, LINK_STATE_UNMANAGED);
if (notify)
manager_notify_hook_filters(link->manager);
} }
static int link_managed_by_us(Link *link) { static int link_managed_by_us(Link *link) {
@@ -3061,3 +3074,12 @@ static const char * const kernel_operstate_table[] = {
}; };
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(kernel_operstate, int); DEFINE_STRING_TABLE_LOOKUP_TO_STRING(kernel_operstate, int);
bool link_has_local_lease_domain(Link *link) {
assert(link);
return link->dhcp_server &&
link->network &&
link->network->dhcp_server_local_lease_domain &&
!dns_name_is_root(link->network->dhcp_server_local_lease_domain);
}

View File

@@ -257,3 +257,5 @@ const char* kernel_operstate_to_string(int t) _const_;
void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret); void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret);
AddressFamily link_required_family_for_online(Link *link); AddressFamily link_required_family_for_online(Link *link);
bool link_has_local_lease_domain(Link *link);

View File

@@ -335,9 +335,3 @@ int manager_connect_varlink(Manager *m, int fd) {
m->varlink_server = TAKE_PTR(s); m->varlink_server = TAKE_PTR(s);
return 0; return 0;
} }
void manager_varlink_done(Manager *m) {
assert(m);
m->varlink_server = sd_varlink_server_unref(m->varlink_server);
}

View File

@@ -4,4 +4,3 @@
#include "networkd-forward.h" #include "networkd-forward.h"
int manager_connect_varlink(Manager *m, int fd); int manager_connect_varlink(Manager *m, int fd);
void manager_varlink_done(Manager *m);

View File

@@ -8,6 +8,7 @@
#include "sd-event.h" #include "sd-event.h"
#include "sd-netlink.h" #include "sd-netlink.h"
#include "sd-resolve.h" #include "sd-resolve.h"
#include "sd-varlink.h"
#include "alloc-util.h" #include "alloc-util.h"
#include "bus-error.h" #include "bus-error.h"
@@ -37,6 +38,7 @@
#include "networkd-neighbor.h" #include "networkd-neighbor.h"
#include "networkd-nexthop.h" #include "networkd-nexthop.h"
#include "networkd-queue.h" #include "networkd-queue.h"
#include "networkd-resolve-hook.h"
#include "networkd-route.h" #include "networkd-route.h"
#include "networkd-routing-policy-rule.h" #include "networkd-routing-policy-rule.h"
#include "networkd-serialize.h" #include "networkd-serialize.h"
@@ -205,13 +207,14 @@ static int manager_connect_udev(Manager *m) {
return 0; return 0;
} }
static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd) { static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd, int *ret_resolve_hook_fd) {
_cleanup_strv_free_ char **names = NULL; _cleanup_strv_free_ char **names = NULL;
int n, rtnl_fd = -EBADF, varlink_fd = -EBADF; int n, rtnl_fd = -EBADF, varlink_fd = -EBADF, resolve_hook_fd = -EBADF;
assert(m); assert(m);
assert(ret_rtnl_fd); assert(ret_rtnl_fd);
assert(ret_varlink_fd); assert(ret_varlink_fd);
assert(ret_resolve_hook_fd);
n = sd_listen_fds_with_names(/* unset_environment = */ true, &names); n = sd_listen_fds_with_names(/* unset_environment = */ true, &names);
if (n < 0) if (n < 0)
@@ -235,6 +238,11 @@ static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd)
continue; continue;
} }
if (streq(names[i], "resolve-hook")) {
resolve_hook_fd = fd;
continue;
}
if (manager_set_serialization_fd(m, fd, names[i]) >= 0) if (manager_set_serialization_fd(m, fd, names[i]) >= 0)
continue; continue;
@@ -250,6 +258,7 @@ static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd)
*ret_rtnl_fd = rtnl_fd; *ret_rtnl_fd = rtnl_fd;
*ret_varlink_fd = varlink_fd; *ret_varlink_fd = varlink_fd;
*ret_resolve_hook_fd = resolve_hook_fd;
return 0; return 0;
} }
@@ -543,7 +552,7 @@ static int manager_set_keep_configuration(Manager *m) {
} }
int manager_setup(Manager *m) { int manager_setup(Manager *m) {
_cleanup_close_ int rtnl_fd = -EBADF, varlink_fd = -EBADF; _cleanup_close_ int rtnl_fd = -EBADF, varlink_fd = -EBADF, resolve_hook_fd = -EBADF;
int r; int r;
assert(m); assert(m);
@@ -567,7 +576,7 @@ int manager_setup(Manager *m) {
if (r < 0) if (r < 0)
return r; return r;
r = manager_listen_fds(m, &rtnl_fd, &varlink_fd); r = manager_listen_fds(m, &rtnl_fd, &varlink_fd, &resolve_hook_fd);
if (r < 0) if (r < 0)
return r; return r;
@@ -590,6 +599,10 @@ int manager_setup(Manager *m) {
if (r < 0) if (r < 0)
return r; return r;
r = manager_varlink_init_resolve_hook(m, TAKE_FD(resolve_hook_fd));
if (r < 0)
return r;
r = manager_connect_bus(m); r = manager_connect_bus(m);
if (r < 0) if (r < 0)
return r; return r;
@@ -737,7 +750,9 @@ Manager* manager_free(Manager *m) {
sd_device_monitor_unref(m->device_monitor); sd_device_monitor_unref(m->device_monitor);
manager_varlink_done(m); m->varlink_server = sd_varlink_server_unref(m->varlink_server);
m->varlink_resolve_hook_server = sd_varlink_server_unref(m->varlink_resolve_hook_server);
m->query_filter_subscriptions = set_free(m->query_filter_subscriptions);
hashmap_free(m->polkit_registry); hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus); sd_bus_flush_close_unref(m->bus);

View File

@@ -22,6 +22,8 @@ typedef struct Manager {
sd_resolve *resolve; sd_resolve *resolve;
sd_bus *bus; sd_bus *bus;
sd_varlink_server *varlink_server; sd_varlink_server *varlink_server;
sd_varlink_server *varlink_resolve_hook_server;
Set *query_filter_subscriptions;
sd_device_monitor *device_monitor; sd_device_monitor *device_monitor;
Hashmap *polkit_registry; Hashmap *polkit_registry;
int ethtool_fd; int ethtool_fd;

View File

@@ -393,6 +393,7 @@ DHCPServer.BootServerName, config_parse_dns_name,
DHCPServer.BootFilename, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, dhcp_server_boot_filename) DHCPServer.BootFilename, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, dhcp_server_boot_filename)
DHCPServer.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp_server_rapid_commit) DHCPServer.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp_server_rapid_commit)
DHCPServer.PersistLeases, config_parse_dhcp_server_persist_leases, 0, offsetof(Network, dhcp_server_persist_leases) DHCPServer.PersistLeases, config_parse_dhcp_server_persist_leases, 0, offsetof(Network, dhcp_server_persist_leases)
DHCPServer.LocalLeaseDomain, config_parse_dns_name, 0, offsetof(Network, dhcp_server_local_lease_domain)
DHCPServerStaticLease.Address, config_parse_dhcp_static_lease_address, 0, 0 DHCPServerStaticLease.Address, config_parse_dhcp_static_lease_address, 0, 0
DHCPServerStaticLease.MACAddress, config_parse_dhcp_static_lease_hwaddr, 0, 0 DHCPServerStaticLease.MACAddress, config_parse_dhcp_static_lease_hwaddr, 0, 0
DHCPServerStaticLease.Hostname, config_parse_dhcp_static_lease_hostname, 0, 0 DHCPServerStaticLease.Hostname, config_parse_dhcp_static_lease_hostname, 0, 0

View File

@@ -761,6 +761,7 @@ static Network *network_free(Network *network) {
free(network->dhcp_server_emit[t].addresses); free(network->dhcp_server_emit[t].addresses);
ordered_hashmap_free(network->dhcp_server_send_options); ordered_hashmap_free(network->dhcp_server_send_options);
ordered_hashmap_free(network->dhcp_server_send_vendor_options); ordered_hashmap_free(network->dhcp_server_send_vendor_options);
free(network->dhcp_server_local_lease_domain);
/* DHCP client */ /* DHCP client */
free(network->dhcp_vendor_class_identifier); free(network->dhcp_vendor_class_identifier);

View File

@@ -233,6 +233,7 @@ typedef struct Network {
usec_t dhcp_server_ipv6_only_preferred_usec; usec_t dhcp_server_ipv6_only_preferred_usec;
bool dhcp_server_rapid_commit; bool dhcp_server_rapid_commit;
DHCPServerPersistLeases dhcp_server_persist_leases; DHCPServerPersistLeases dhcp_server_persist_leases;
char *dhcp_server_local_lease_domain;
/* link-local addressing support */ /* link-local addressing support */
AddressFamily link_local; AddressFamily link_local;

View File

@@ -0,0 +1,261 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-dhcp-server.h"
#include "sd-json.h"
#include "sd-varlink.h"
#include "alloc-util.h"
#include "dns-answer.h"
#include "dns-domain.h"
#include "dns-packet.h"
#include "dns-question.h"
#include "dns-rr.h"
#include "env-util.h"
#include "fd-util.h"
#include "networkd-link.h"
#include "networkd-manager.h"
#include "networkd-resolve-hook.h"
#include "resolve-hook-util.h"
#include "set.h"
#include "varlink-io.systemd.Resolve.Hook.h"
#include "varlink-util.h"
static int manager_make_domain_array(Manager *m, sd_json_variant **ret) {
int r;
assert(m);
assert(ret);
_cleanup_(set_freep) Set *domains = NULL;
Link *link;
HASHMAP_FOREACH(link, m->links_by_index) {
if (!link_has_local_lease_domain(link))
continue;
r = set_put_strdup_full(&domains, &dns_name_hash_ops_free, link->network->dhcp_server_local_lease_domain);
if (r < 0 && r != -EEXIST)
return r;
}
_cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
char *s;
SET_FOREACH(s, domains) {
r = sd_json_variant_append_arrayb(&array, SD_JSON_BUILD_STRING(s));
if (r < 0)
return r;
}
if (!array)
return sd_json_variant_new_array(ret, /* array= */ NULL, /* n= */ 0);
*ret = TAKE_PTR(array);
return 0;
}
int manager_notify_hook_filters(Manager *m) {
int r;
assert(m);
/* Called whenever a machine is added or dropped from the list */
if (set_isempty(m->query_filter_subscriptions))
return 0;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
r = manager_make_domain_array(m, &array);
if (r < 0)
return log_error_errno(r, "Failed to generate JSON array with machine names: %m");
r = varlink_many_notifybo(m->query_filter_subscriptions, SD_JSON_BUILD_PAIR_VARIANT("filterDomains", array));
if (r < 0)
return log_error_errno(r, "Failed to notify filter subscribers: %m");
return 0;
}
static int vl_method_query_filter(
sd_varlink *link,
sd_json_variant *parameters,
sd_varlink_method_flags_t flags,
void *userdata) {
Manager *m = ASSERT_PTR(userdata);
int r;
assert(link);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
r = manager_make_domain_array(m, &array);
if (r < 0)
return r;
if (flags & SD_VARLINK_METHOD_MORE) {
/* If 'more' is set, this is a subscription request, keep track of the link */
r = sd_varlink_notifybo(link, SD_JSON_BUILD_PAIR_VARIANT("filterDomains", array));
if (r < 0)
return log_error_errno(r, "Failed to notify filter subscribers: %m");
r = set_ensure_put(&m->query_filter_subscriptions, &varlink_hash_ops, link);
if (r < 0)
return r;
sd_varlink_ref(link);
} else {
r = sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_VARIANT("filterDomains", array));
if (r < 0)
return log_error_errno(r, "Failed to notify filter subscribers: %m");
}
return 0;
}
static int vl_method_resolve_record(
sd_varlink *link,
sd_json_variant *parameters,
sd_varlink_method_flags_t flags,
void *userdata) {
Manager *m = ASSERT_PTR(userdata);
int r;
assert(link);
_cleanup_(resolve_record_parameters_done) ResolveRecordParameters p = {};
r = sd_varlink_dispatch(link, parameters, resolve_record_parameters_dispatch_table, &p);
if (r != 0)
return r;
if (dns_question_isempty(p.question))
return sd_varlink_error_invalid_parameter_name(link, "question");
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
bool found_address = false, found_domain = false;
DnsResourceKey *key;
DNS_QUESTION_FOREACH(key, p.question) {
const char *name = dns_resource_key_name(key);
Link *l;
HASHMAP_FOREACH(l, m->links_by_index) {
if (!link_has_local_lease_domain(l))
continue;
/* Try to strip the local lease domain suffix from name, so that we have the short hostname left. */
_cleanup_free_ char *prefix = NULL;
r = dns_name_change_suffix(name, l->network->dhcp_server_local_lease_domain, /* new_suffix= */ NULL, &prefix);
if (r <= 0) /* no match? */
continue;
found_domain = true;
struct in_addr address;
r = sd_dhcp_server_get_lease_address_by_name(l->dhcp_server, prefix, &address);
if (r <= 0)
continue;
/* The domain exists, so we can give a positive reply. But only for A lookups we have addresses to return. */
if (key->type != DNS_TYPE_A)
continue;
found_address = true;
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
r = dns_resource_record_new_address(&rr, AF_INET, (union in_addr_union*) &address, name);
if (r < 0)
return r;
r = dns_answer_add_extend(
&answer,
rr,
l->ifindex,
DNS_ANSWER_AUTHENTICATED,
/* rrsig= */ NULL);
if (r < 0)
return r;
}
}
if (!found_address) {
/* If this was a lookup in one of our domains, return NXDOMAIN, we are authoritative on that */
if (found_domain)
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_INTEGER("rcode", DNS_RCODE_NXDOMAIN));
/* Otherwise we return an empty response, which means: continue with the usual lookup */
return sd_varlink_reply(link, /* parameters= */ NULL);
}
_cleanup_(sd_json_variant_unrefp) sd_json_variant *ja = NULL;
r = dns_answer_to_json(answer, &ja);
if (r < 0)
return r;
return sd_varlink_replybo(
link,
SD_JSON_BUILD_PAIR_INTEGER("rcode", DNS_RCODE_SUCCESS),
SD_JSON_BUILD_PAIR_VARIANT("answer", ja));
}
static void on_resolve_hook_disconnect(sd_varlink_server *server, sd_varlink *link, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
if (set_remove(m->query_filter_subscriptions, link))
sd_varlink_unref(link);
}
int manager_varlink_init_resolve_hook(Manager *m, int fd) {
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL;
_unused_ _cleanup_close_ int fd_close = fd; /* take possession */
int r;
assert(m);
if (m->varlink_resolve_hook_server)
return 0;
r = getenv_bool("SYSTEMD_NETWORK_RESOLVE_HOOK");
if (r < 0 && r != -ENXIO)
log_warning_errno(r, "Failed to parse $SYSTEMD_NETWORK_RESOLVE_HOOK, ignoring: %m");
if (r == 0) {
log_notice("Resolve hook disabled via $SYSTEMD_NETWORK_RESOLVE_HOOK.");
return 0;
}
r = varlink_server_new(&s, SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA, m);
if (r < 0)
return log_error_errno(r, "Failed to allocate varlink server object: %m");
(void) sd_varlink_server_set_description(s, "varlink-resolve-hook");
r = sd_varlink_server_add_interface(s, &vl_interface_io_systemd_Resolve_Hook);
if (r < 0)
return log_error_errno(r, "Failed to add Resolve.Hook interface to varlink server: %m");
r = sd_varlink_server_bind_method_many(
s,
"io.systemd.Resolve.Hook.QueryFilter", vl_method_query_filter,
"io.systemd.Resolve.Hook.ResolveRecord", vl_method_resolve_record);
if (r < 0)
return log_error_errno(r, "Failed to register varlink methods: %m");
r = sd_varlink_server_bind_disconnect(s, on_resolve_hook_disconnect);
if (r < 0)
return log_error_errno(r, "Failed to bind on resolve hook disconnection events: %m");
if (fd < 0)
r = sd_varlink_server_listen_address(s, "/run/systemd/resolve.hook/io.systemd.Network", 0666 | SD_VARLINK_SERVER_MODE_MKDIR_0755);
else
r = sd_varlink_server_listen_fd(s, fd);
if (r < 0)
return log_error_errno(r, "Failed to bind to systemd-resolved hook Varlink socket: %m");
TAKE_FD(fd_close);
r = sd_varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0)
return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
m->varlink_resolve_hook_server = TAKE_PTR(s);
return 0;
}

View File

@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "networkd-forward.h"
int manager_notify_hook_filters(Manager *m);
int manager_varlink_init_resolve_hook(Manager *m, int fd);

View File

@@ -65,6 +65,7 @@ def setUpModule():
for u in [ for u in [
'systemd-networkd.socket', 'systemd-networkd.socket',
'systemd-networkd-varlink.socket', 'systemd-networkd-varlink.socket',
'systemd-networkd-resolve-hook.socket',
'systemd-networkd.service', 'systemd-networkd.service',
'systemd-resolved-varlink.socket', 'systemd-resolved-varlink.socket',
'systemd-resolved-monitor.socket', 'systemd-resolved-monitor.socket',
@@ -90,12 +91,15 @@ def setUpModule():
subprocess.call(['useradd', '--system', '--no-create-home', 'systemd-network']) subprocess.call(['useradd', '--system', '--no-create-home', 'systemd-network'])
for d in ['/etc/systemd/network', '/run/systemd/network', for d in ['/etc/systemd/network', '/run/systemd/network',
'/run/systemd/netif', '/run/systemd/resolve']: '/run/systemd/netif', '/run/systemd/resolve', '/run/systemd/resolve.hook']:
subprocess.check_call(["mount", "-m", "-t", "tmpfs", "none", d]) subprocess.check_call(["mount", "-m", "-t", "tmpfs", "none", d])
tmpmounts.append(d) tmpmounts.append(d)
if os.path.isdir('/run/systemd/resolve'): if os.path.isdir('/run/systemd/resolve'):
os.chmod('/run/systemd/resolve', 0o755) os.chmod('/run/systemd/resolve', 0o755)
shutil.chown('/run/systemd/resolve', 'systemd-resolve', 'systemd-resolve') shutil.chown('/run/systemd/resolve', 'systemd-resolve', 'systemd-resolve')
if os.path.isdir('/run/systemd/resolve.hook'):
os.chmod('/run/systemd/resolve.hook', 0o755)
shutil.chown('/run/systemd/resolve.hook', 'systemd-network', 'systemd-network')
if os.path.isdir('/run/systemd/netif'): if os.path.isdir('/run/systemd/netif'):
os.chmod('/run/systemd/netif', 0o755) os.chmod('/run/systemd/netif', 0o755)
shutil.chown('/run/systemd/netif', 'systemd-network', 'systemd-network') shutil.chown('/run/systemd/netif', 'systemd-network', 'systemd-network')
@@ -278,6 +282,8 @@ Gateway=192.168.250.1
def tearDown(self): def tearDown(self):
subprocess.check_call(['systemctl', 'stop', 'systemd-networkd.socket']) subprocess.check_call(['systemctl', 'stop', 'systemd-networkd.socket'])
subprocess.check_call(['systemctl', 'stop', 'systemd-networkd-varlink.socket'])
subprocess.check_call(['systemctl', 'stop', 'systemd-networkd-resolve-hook.socket'])
subprocess.check_call(['systemctl', 'stop', 'systemd-networkd.service']) subprocess.check_call(['systemctl', 'stop', 'systemd-networkd.service'])
subprocess.check_call(['ip', 'link', 'del', 'mybridge']) subprocess.check_call(['ip', 'link', 'del', 'mybridge'])
subprocess.check_call(['ip', 'link', 'del', 'port1']) subprocess.check_call(['ip', 'link', 'del', 'port1'])
@@ -373,6 +379,8 @@ class ClientTestBase(NetworkdTestingUtilities):
def tearDown(self): def tearDown(self):
self.shutdown_iface() self.shutdown_iface()
subprocess.call(['systemctl', 'stop', 'systemd-networkd.socket']) subprocess.call(['systemctl', 'stop', 'systemd-networkd.socket'])
subprocess.call(['systemctl', 'stop', 'systemd-networkd-varlink.socket'])
subprocess.call(['systemctl', 'stop', 'systemd-networkd-resolve-hook.socket'])
subprocess.call(['systemctl', 'stop', 'systemd-networkd.service']) subprocess.call(['systemctl', 'stop', 'systemd-networkd.service'])
subprocess.call(['ip', 'link', 'del', 'dummy0'], subprocess.call(['ip', 'link', 'del', 'dummy0'],
stderr=subprocess.DEVNULL) stderr=subprocess.DEVNULL)
@@ -930,9 +938,11 @@ class NetworkdClientTest(ClientTestBase, unittest.TestCase):
set -eu set -eu
mkdir -p /run/systemd/network mkdir -p /run/systemd/network
mkdir -p /run/systemd/netif mkdir -p /run/systemd/netif
mkdir -p /run/systemd/resolve.hook
mkdir -p /var/lib/systemd/network mkdir -p /var/lib/systemd/network
mount -t tmpfs none /run/systemd/network mount -t tmpfs none /run/systemd/network
mount -t tmpfs none /run/systemd/netif mount -t tmpfs none /run/systemd/netif
mount -t tmpfs none /run/systemd/resolve.hook
mount -t tmpfs none /var/lib/systemd/network mount -t tmpfs none /var/lib/systemd/network
[ ! -e /run/dbus ] || mount -t tmpfs none /run/dbus [ ! -e /run/dbus ] || mount -t tmpfs none /run/dbus
# create router/client veth pair # create router/client veth pair
@@ -966,6 +976,9 @@ EOF
# Hence, 'networkctl persistent-storage yes' cannot be used. # Hence, 'networkctl persistent-storage yes' cannot be used.
export SYSTEMD_NETWORK_PERSISTENT_STORAGE_READY=1 export SYSTEMD_NETWORK_PERSISTENT_STORAGE_READY=1
# Don't try to register resolved hook for our testcase
export SYSTEMD_NETWORK_RESOLVE_HOOK=0
# Generate debugging logs. # Generate debugging logs.
export SYSTEMD_LOG_LEVEL=debug export SYSTEMD_LOG_LEVEL=debug
@@ -982,6 +995,7 @@ exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ {{ s/^.*=/
'-p', 'InaccessibleDirectories=-/etc/systemd/network', '-p', 'InaccessibleDirectories=-/etc/systemd/network',
'-p', 'InaccessibleDirectories=-/run/systemd/network', '-p', 'InaccessibleDirectories=-/run/systemd/network',
'-p', 'InaccessibleDirectories=-/run/systemd/netif', '-p', 'InaccessibleDirectories=-/run/systemd/netif',
'-p', 'InaccessibleDirectories=-/run/systemd/resolve.hook',
'-p', 'InaccessibleDirectories=-/var/lib/systemd/network', '-p', 'InaccessibleDirectories=-/var/lib/systemd/network',
'--service-type=notify', script]) '--service-type=notify', script])

View File

@@ -0,0 +1,10 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth99
[Network]
DHCP=yes
[DHCPv4]
SendHostname=yes
Hostname=flummy

View File

@@ -0,0 +1,13 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth-peer
[Network]
IPv6AcceptRA=false
DHCPServer=yes
[DHCPServer]
ServerAddress=192.168.5.1/24
PoolOffset=20
PoolSize=10
LocalLeaseDomain=_networkdtest

View File

@@ -426,6 +426,7 @@ def save_active_units():
for u in [ for u in [
'systemd-networkd.socket', 'systemd-networkd.socket',
'systemd-networkd-varlink.socket', 'systemd-networkd-varlink.socket',
'systemd-networkd-resolve-hook.socket',
'systemd-networkd.service', 'systemd-networkd.service',
'systemd-resolved-monitor.socket', 'systemd-resolved-monitor.socket',
'systemd-resolved-varlink.socket', 'systemd-resolved-varlink.socket',
@@ -449,6 +450,10 @@ def restore_active_units():
call('systemctl stop systemd-networkd-varlink.socket') call('systemctl stop systemd-networkd-varlink.socket')
has_network_socket = True has_network_socket = True
if 'systemd-networkd-resolve-hook.socket' in active_units:
call('systemctl stop systemd-networkd-resolve-hook.socket')
has_network_socket = True
if 'systemd-resolved-monitor.socket' in active_units: if 'systemd-resolved-monitor.socket' in active_units:
call('systemctl stop systemd-resolved-monitor.socket') call('systemctl stop systemd-resolved-monitor.socket')
has_resolve_socket = True has_resolve_socket = True
@@ -525,6 +530,7 @@ def setup_system_units():
'systemd-networkd.service', 'systemd-networkd.service',
'systemd-networkd.socket', 'systemd-networkd.socket',
'systemd-networkd-varlink.socket', 'systemd-networkd-varlink.socket',
'systemd-networkd-resolve-hook.socket',
'systemd-networkd-persistent-storage.service', 'systemd-networkd-persistent-storage.service',
'systemd-resolved.service', 'systemd-resolved.service',
'systemd-timesyncd.service', 'systemd-timesyncd.service',
@@ -572,6 +578,13 @@ def setup_system_units():
'StartLimitIntervalSec=0', 'StartLimitIntervalSec=0',
] ]
) )
create_unit_dropin(
'systemd-networkd-resolve-hook.socket',
[
'[Unit]',
'StartLimitIntervalSec=0',
]
)
create_unit_dropin( create_unit_dropin(
'systemd-networkd-persistent-storage.service', 'systemd-networkd-persistent-storage.service',
[ [
@@ -604,6 +617,7 @@ def clear_system_units():
rm_unit('systemd-networkd.service') rm_unit('systemd-networkd.service')
rm_unit('systemd-networkd.socket') rm_unit('systemd-networkd.socket')
rm_unit('systemd-networkd-varlink.socket') rm_unit('systemd-networkd-varlink.socket')
rm_unit('systemd-networkd-resolve-hook.socket')
rm_unit('systemd-networkd-persistent-storage.service') rm_unit('systemd-networkd-persistent-storage.service')
rm_unit('systemd-resolved.service') rm_unit('systemd-resolved.service')
rm_unit('systemd-timesyncd.service') rm_unit('systemd-timesyncd.service')
@@ -990,10 +1004,12 @@ def stop_networkd(show_logs=True, check_failed=True):
if check_failed: if check_failed:
check_output('systemctl stop systemd-networkd.socket') check_output('systemctl stop systemd-networkd.socket')
check_output('systemctl stop systemd-networkd-varlink.socket') check_output('systemctl stop systemd-networkd-varlink.socket')
check_output('systemctl stop systemd-networkd-resolve-hook.socket')
check_output('systemctl stop systemd-networkd.service') check_output('systemctl stop systemd-networkd.service')
else: else:
call('systemctl stop systemd-networkd.socket') call('systemctl stop systemd-networkd.socket')
call('systemctl stop systemd-networkd-varlink.socket') call('systemctl stop systemd-networkd-varlink.socket')
call('systemctl stop systemd-networkd-resolve-hook.socket')
call('systemctl stop systemd-networkd.service') call('systemctl stop systemd-networkd.service')
if show_logs: if show_logs:
@@ -7366,6 +7382,16 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
data = json.loads(output) data = json.loads(output)
self.assertEqual(data['DHCPv4Client']['Lease']['Hostname'], 'fqdn.example.com') self.assertEqual(data['DHCPv4Client']['Lease']['Hostname'], 'fqdn.example.com')
def test_dhcp_server_resolve_hook(self):
copy_network_unit('25-veth.netdev', '25-dhcp-client-resolve-hook.network', '25-dhcp-server-resolve-hook.network')
start_networkd()
self.wait_online('veth99:routable', 'veth-peer:routable')
output = check_output('resolvectl query flummy._networkdtest')
print(output)
self.assertIn('192.168.5.2', output)
class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities): class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities):
def setUp(self): def setUp(self):

View File

@@ -497,6 +497,10 @@ units = [
'file' : 'systemd-networkd-varlink.socket', 'file' : 'systemd-networkd-varlink.socket',
'conditions' : ['ENABLE_NETWORKD'], 'conditions' : ['ENABLE_NETWORKD'],
}, },
{
'file' : 'systemd-networkd-resolve-hook.socket',
'conditions' : ['ENABLE_NETWORKD'],
},
{ {
'file' : 'systemd-networkd-wait-online.service.in', 'file' : 'systemd-networkd-wait-online.service.in',
'conditions' : ['ENABLE_NETWORKD'], 'conditions' : ['ENABLE_NETWORKD'],

View File

@@ -0,0 +1,26 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Network Management Resolve Hook Socket
Documentation=man:systemd-networkd.service(8)
ConditionCapability=CAP_NET_ADMIN
DefaultDependencies=no
Before=sockets.target shutdown.target
Conflicts=shutdown.target
[Socket]
ListenStream=/run/systemd/resolve.hook/io.systemd.Network
FileDescriptorName=resolve-hook
SocketMode=0666
Service=systemd-networkd.service
RemoveOnStop=yes
[Install]
WantedBy=sockets.target

View File

@@ -46,7 +46,7 @@ RestrictRealtime=yes
RestrictSUIDSGID=yes RestrictSUIDSGID=yes
RuntimeDirectory=systemd/netif RuntimeDirectory=systemd/netif
RuntimeDirectoryPreserve=yes RuntimeDirectoryPreserve=yes
Sockets=systemd-networkd.socket systemd-networkd-varlink.socket Sockets=systemd-networkd.socket systemd-networkd-varlink.socket systemd-networkd-resolve-hook.socket
SystemCallArchitectures=native SystemCallArchitectures=native
SystemCallErrorNumber=EPERM SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service bpf SystemCallFilter=@system-service bpf
@@ -56,7 +56,7 @@ User=systemd-network
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
Also=systemd-networkd.socket systemd-networkd-varlink.socket Also=systemd-networkd.socket systemd-networkd-varlink.socket systemd-networkd-resolve-hook.socket
Alias=dbus-org.freedesktop.network1.service Alias=dbus-org.freedesktop.network1.service
# The output from this generator is used by udevd and networkd. Enable it by # The output from this generator is used by udevd and networkd. Enable it by