From a7fa29b1b52210e33f4e43efc1a2f06b7c7233c0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Oct 2025 09:57:48 +0200 Subject: [PATCH] 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. --- man/systemd.network.xml | 17 ++ src/network/meson.build | 1 + src/network/networkd-dhcp-server.c | 3 + src/network/networkd-link.c | 22 ++ src/network/networkd-link.h | 2 + src/network/networkd-manager-varlink.c | 6 - src/network/networkd-manager-varlink.h | 1 - src/network/networkd-manager.c | 25 +- src/network/networkd-manager.h | 2 + src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 + src/network/networkd-resolve-hook.c | 261 ++++++++++++++++++ src/network/networkd-resolve-hook.h | 7 + test/networkd-test.py | 16 +- .../conf/25-dhcp-client-resolve-hook.network | 10 + .../conf/25-dhcp-server-resolve-hook.network | 13 + test/test-network/systemd-networkd-tests.py | 26 ++ units/meson.build | 4 + units/systemd-networkd-resolve-hook.socket | 26 ++ units/systemd-networkd.service.in | 4 +- 21 files changed, 434 insertions(+), 15 deletions(-) create mode 100644 src/network/networkd-resolve-hook.c create mode 100644 src/network/networkd-resolve-hook.h create mode 100644 test/test-network/conf/25-dhcp-client-resolve-hook.network create mode 100644 test/test-network/conf/25-dhcp-server-resolve-hook.network create mode 100644 units/systemd-networkd-resolve-hook.socket diff --git a/man/systemd.network.xml b/man/systemd.network.xml index a75f89de99d..dda1e117ed8 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -4201,6 +4201,23 @@ ServerAddress=192.168.0.1/24 + + LocalLeaseDomain= + + Takes a DNS domain name as argument. If specified, + systemd-networkd.service8 + will integrate with + systemd-resolved.service8 + 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. + + Note that this purely about hostname resolution on the local system, i.e. from programs with + access to the same systemd-resolved instances via D-Bus IPC, Varlink IPC, or + the local DNS stub. + + + + diff --git a/src/network/meson.build b/src/network/meson.build index 78a0e72b3a1..2b66aa27436 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -74,6 +74,7 @@ systemd_networkd_extract_sources = files( 'networkd-ntp.c', 'networkd-queue.c', 'networkd-radv.c', + 'networkd-resolve-hook.c', 'networkd-route.c', 'networkd-route-metric.c', 'networkd-route-nexthop.c', diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index 4b1dbe6eb94..75ecfdca91a 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -25,6 +25,7 @@ #include "networkd-network.h" #include "networkd-ntp.h" #include "networkd-queue.h" +#include "networkd-resolve-hook.h" #include "networkd-route-util.h" #include "path-util.h" #include "set.h" @@ -747,6 +748,8 @@ static int dhcp4_server_configure(Link *link) { if (r < 0) return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m"); + manager_notify_hook_filters(link->manager); + return 0; } diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 9c3de19766e..458ddff27f8 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -23,6 +23,7 @@ #include "arphrd-util.h" #include "bitfield.h" #include "device-util.h" +#include "dns-domain.h" #include "errno-util.h" #include "ethtool-util.h" #include "event-util.h" @@ -53,6 +54,7 @@ #include "networkd-nexthop.h" #include "networkd-queue.h" #include "networkd-radv.h" +#include "networkd-resolve-hook.h" #include "networkd-route.h" #include "networkd-route-util.h" #include "networkd-routing-policy-rule.h" @@ -1070,6 +1072,8 @@ static Link *link_drop(Link *link) { assert(link->manager); + bool notify = link_has_local_lease_domain(link); + link_set_state(link, LINK_STATE_LINGER); /* 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. */ 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); } @@ -1351,6 +1359,8 @@ static void link_enter_unmanaged(Link *link) { if (link->state == LINK_STATE_UNMANAGED) return; + bool notify = link_has_local_lease_domain(link); + log_link_full(link, link->state == LINK_STATE_INITIALIZED ? LOG_DEBUG : LOG_INFO, "Unmanaging interface."); @@ -1367,6 +1377,9 @@ static void link_enter_unmanaged(Link *link) { link->network = network_unref(link->network); link_set_state(link, LINK_STATE_UNMANAGED); + + if (notify) + manager_notify_hook_filters(link->manager); } 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); + +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); +} diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 6e936e3bb13..8c5648d8d0a 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -257,3 +257,5 @@ const char* kernel_operstate_to_string(int t) _const_; void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret); AddressFamily link_required_family_for_online(Link *link); + +bool link_has_local_lease_domain(Link *link); diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index 871e5a4636c..cc46e60e405 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -335,9 +335,3 @@ int manager_connect_varlink(Manager *m, int fd) { m->varlink_server = TAKE_PTR(s); return 0; } - -void manager_varlink_done(Manager *m) { - assert(m); - - m->varlink_server = sd_varlink_server_unref(m->varlink_server); -} diff --git a/src/network/networkd-manager-varlink.h b/src/network/networkd-manager-varlink.h index bd5cede4046..0933c2a0566 100644 --- a/src/network/networkd-manager-varlink.h +++ b/src/network/networkd-manager-varlink.h @@ -4,4 +4,3 @@ #include "networkd-forward.h" int manager_connect_varlink(Manager *m, int fd); -void manager_varlink_done(Manager *m); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 4004be81c0f..762f1ca1901 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -8,6 +8,7 @@ #include "sd-event.h" #include "sd-netlink.h" #include "sd-resolve.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "bus-error.h" @@ -37,6 +38,7 @@ #include "networkd-neighbor.h" #include "networkd-nexthop.h" #include "networkd-queue.h" +#include "networkd-resolve-hook.h" #include "networkd-route.h" #include "networkd-routing-policy-rule.h" #include "networkd-serialize.h" @@ -205,13 +207,14 @@ static int manager_connect_udev(Manager *m) { 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; - int n, rtnl_fd = -EBADF, varlink_fd = -EBADF; + int n, rtnl_fd = -EBADF, varlink_fd = -EBADF, resolve_hook_fd = -EBADF; assert(m); assert(ret_rtnl_fd); assert(ret_varlink_fd); + assert(ret_resolve_hook_fd); n = sd_listen_fds_with_names(/* unset_environment = */ true, &names); if (n < 0) @@ -235,6 +238,11 @@ static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd) continue; } + if (streq(names[i], "resolve-hook")) { + resolve_hook_fd = fd; + continue; + } + if (manager_set_serialization_fd(m, fd, names[i]) >= 0) 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_varlink_fd = varlink_fd; + *ret_resolve_hook_fd = resolve_hook_fd; return 0; } @@ -543,7 +552,7 @@ static int manager_set_keep_configuration(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; assert(m); @@ -567,7 +576,7 @@ int manager_setup(Manager *m) { if (r < 0) 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) return r; @@ -590,6 +599,10 @@ int manager_setup(Manager *m) { if (r < 0) return r; + r = manager_varlink_init_resolve_hook(m, TAKE_FD(resolve_hook_fd)); + if (r < 0) + return r; + r = manager_connect_bus(m); if (r < 0) return r; @@ -737,7 +750,9 @@ Manager* manager_free(Manager *m) { 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); sd_bus_flush_close_unref(m->bus); diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index e350fe80c5b..871898abe60 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -22,6 +22,8 @@ typedef struct Manager { sd_resolve *resolve; sd_bus *bus; sd_varlink_server *varlink_server; + sd_varlink_server *varlink_resolve_hook_server; + Set *query_filter_subscriptions; sd_device_monitor *device_monitor; Hashmap *polkit_registry; int ethtool_fd; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 2d0b7d40641..7f3f4c53113 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -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.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.LocalLeaseDomain, config_parse_dns_name, 0, offsetof(Network, dhcp_server_local_lease_domain) 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 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 4e8566afa67..e1ecc15235c 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -761,6 +761,7 @@ static Network *network_free(Network *network) { free(network->dhcp_server_emit[t].addresses); ordered_hashmap_free(network->dhcp_server_send_options); ordered_hashmap_free(network->dhcp_server_send_vendor_options); + free(network->dhcp_server_local_lease_domain); /* DHCP client */ free(network->dhcp_vendor_class_identifier); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index edd2177dd3b..4ec2783e99f 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -233,6 +233,7 @@ typedef struct Network { usec_t dhcp_server_ipv6_only_preferred_usec; bool dhcp_server_rapid_commit; DHCPServerPersistLeases dhcp_server_persist_leases; + char *dhcp_server_local_lease_domain; /* link-local addressing support */ AddressFamily link_local; diff --git a/src/network/networkd-resolve-hook.c b/src/network/networkd-resolve-hook.c new file mode 100644 index 00000000000..6c437be9db6 --- /dev/null +++ b/src/network/networkd-resolve-hook.c @@ -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; +} diff --git a/src/network/networkd-resolve-hook.h b/src/network/networkd-resolve-hook.h new file mode 100644 index 00000000000..3ad185be529 --- /dev/null +++ b/src/network/networkd-resolve-hook.h @@ -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); diff --git a/test/networkd-test.py b/test/networkd-test.py index 8ea6cd43a06..691f58b2d3a 100755 --- a/test/networkd-test.py +++ b/test/networkd-test.py @@ -65,6 +65,7 @@ def setUpModule(): for u in [ 'systemd-networkd.socket', 'systemd-networkd-varlink.socket', + 'systemd-networkd-resolve-hook.socket', 'systemd-networkd.service', 'systemd-resolved-varlink.socket', 'systemd-resolved-monitor.socket', @@ -90,12 +91,15 @@ def setUpModule(): subprocess.call(['useradd', '--system', '--no-create-home', '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]) tmpmounts.append(d) if os.path.isdir('/run/systemd/resolve'): os.chmod('/run/systemd/resolve', 0o755) 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'): os.chmod('/run/systemd/netif', 0o755) shutil.chown('/run/systemd/netif', 'systemd-network', 'systemd-network') @@ -278,6 +282,8 @@ Gateway=192.168.250.1 def tearDown(self): 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(['ip', 'link', 'del', 'mybridge']) subprocess.check_call(['ip', 'link', 'del', 'port1']) @@ -373,6 +379,8 @@ class ClientTestBase(NetworkdTestingUtilities): def tearDown(self): self.shutdown_iface() 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(['ip', 'link', 'del', 'dummy0'], stderr=subprocess.DEVNULL) @@ -930,9 +938,11 @@ class NetworkdClientTest(ClientTestBase, unittest.TestCase): set -eu mkdir -p /run/systemd/network mkdir -p /run/systemd/netif +mkdir -p /run/systemd/resolve.hook mkdir -p /var/lib/systemd/network mount -t tmpfs none /run/systemd/network mount -t tmpfs none /run/systemd/netif +mount -t tmpfs none /run/systemd/resolve.hook mount -t tmpfs none /var/lib/systemd/network [ ! -e /run/dbus ] || mount -t tmpfs none /run/dbus # create router/client veth pair @@ -966,6 +976,9 @@ EOF # Hence, 'networkctl persistent-storage yes' cannot be used. 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. 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=-/run/systemd/network', '-p', 'InaccessibleDirectories=-/run/systemd/netif', + '-p', 'InaccessibleDirectories=-/run/systemd/resolve.hook', '-p', 'InaccessibleDirectories=-/var/lib/systemd/network', '--service-type=notify', script]) diff --git a/test/test-network/conf/25-dhcp-client-resolve-hook.network b/test/test-network/conf/25-dhcp-client-resolve-hook.network new file mode 100644 index 00000000000..5f4ed00d750 --- /dev/null +++ b/test/test-network/conf/25-dhcp-client-resolve-hook.network @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=veth99 + +[Network] +DHCP=yes + +[DHCPv4] +SendHostname=yes +Hostname=flummy diff --git a/test/test-network/conf/25-dhcp-server-resolve-hook.network b/test/test-network/conf/25-dhcp-server-resolve-hook.network new file mode 100644 index 00000000000..ec6ee8d895a --- /dev/null +++ b/test/test-network/conf/25-dhcp-server-resolve-hook.network @@ -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 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 32c7b590447..2210a99c074 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -426,6 +426,7 @@ def save_active_units(): for u in [ 'systemd-networkd.socket', 'systemd-networkd-varlink.socket', + 'systemd-networkd-resolve-hook.socket', 'systemd-networkd.service', 'systemd-resolved-monitor.socket', 'systemd-resolved-varlink.socket', @@ -449,6 +450,10 @@ def restore_active_units(): call('systemctl stop systemd-networkd-varlink.socket') 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: call('systemctl stop systemd-resolved-monitor.socket') has_resolve_socket = True @@ -525,6 +530,7 @@ def setup_system_units(): 'systemd-networkd.service', 'systemd-networkd.socket', 'systemd-networkd-varlink.socket', + 'systemd-networkd-resolve-hook.socket', 'systemd-networkd-persistent-storage.service', 'systemd-resolved.service', 'systemd-timesyncd.service', @@ -572,6 +578,13 @@ def setup_system_units(): 'StartLimitIntervalSec=0', ] ) + create_unit_dropin( + 'systemd-networkd-resolve-hook.socket', + [ + '[Unit]', + 'StartLimitIntervalSec=0', + ] + ) create_unit_dropin( 'systemd-networkd-persistent-storage.service', [ @@ -604,6 +617,7 @@ def clear_system_units(): rm_unit('systemd-networkd.service') rm_unit('systemd-networkd.socket') rm_unit('systemd-networkd-varlink.socket') + rm_unit('systemd-networkd-resolve-hook.socket') rm_unit('systemd-networkd-persistent-storage.service') rm_unit('systemd-resolved.service') rm_unit('systemd-timesyncd.service') @@ -990,10 +1004,12 @@ def stop_networkd(show_logs=True, check_failed=True): if check_failed: check_output('systemctl stop systemd-networkd.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') else: call('systemctl stop systemd-networkd.socket') call('systemctl stop systemd-networkd-varlink.socket') + call('systemctl stop systemd-networkd-resolve-hook.socket') call('systemctl stop systemd-networkd.service') if show_logs: @@ -7366,6 +7382,16 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities): data = json.loads(output) 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): def setUp(self): diff --git a/units/meson.build b/units/meson.build index 8e5b645f916..2e04c4aa2b2 100644 --- a/units/meson.build +++ b/units/meson.build @@ -497,6 +497,10 @@ units = [ 'file' : 'systemd-networkd-varlink.socket', 'conditions' : ['ENABLE_NETWORKD'], }, + { + 'file' : 'systemd-networkd-resolve-hook.socket', + 'conditions' : ['ENABLE_NETWORKD'], + }, { 'file' : 'systemd-networkd-wait-online.service.in', 'conditions' : ['ENABLE_NETWORKD'], diff --git a/units/systemd-networkd-resolve-hook.socket b/units/systemd-networkd-resolve-hook.socket new file mode 100644 index 00000000000..3c11b8e8de1 --- /dev/null +++ b/units/systemd-networkd-resolve-hook.socket @@ -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 diff --git a/units/systemd-networkd.service.in b/units/systemd-networkd.service.in index 44103f7daa4..589782effcd 100644 --- a/units/systemd-networkd.service.in +++ b/units/systemd-networkd.service.in @@ -46,7 +46,7 @@ RestrictRealtime=yes RestrictSUIDSGID=yes RuntimeDirectory=systemd/netif 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 SystemCallErrorNumber=EPERM SystemCallFilter=@system-service bpf @@ -56,7 +56,7 @@ User=systemd-network [Install] 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 # The output from this generator is used by udevd and networkd. Enable it by -- 2.47.3