diff --git a/man/systemd.network.xml b/man/systemd.network.xml index a75f89de99..dda1e117ed 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 78a0e72b3a..2b66aa2743 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 4b1dbe6eb9..75ecfdca91 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 9c3de19766..458ddff27f 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 6e936e3bb1..8c5648d8d0 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 871e5a4636..cc46e60e40 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 bd5cede404..0933c2a056 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 4004be81c0..762f1ca190 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 e350fe80c5..871898abe6 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 2d0b7d4064..7f3f4c5311 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 4e8566afa6..e1ecc15235 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 edd2177dd3..4ec2783e99 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 0000000000..6c437be9db --- /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 0000000000..3ad185be52 --- /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 8ea6cd43a0..691f58b2d3 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 0000000000..5f4ed00d75 --- /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 0000000000..ec6ee8d895 --- /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 32c7b59044..2210a99c07 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 8e5b645f91..2e04c4aa2b 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 0000000000..3c11b8e8de --- /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 44103f7daa..589782effc 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