]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
networkd: add support for resolved hook for DHCP server
authorLennart Poettering <lennart@poettering.net>
Mon, 13 Oct 2025 07:57:48 +0000 (09:57 +0200)
committerLennart Poettering <lennart@poettering.net>
Sat, 15 Nov 2025 06:52:42 +0000 (07:52 +0100)
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.

21 files changed:
man/systemd.network.xml
src/network/meson.build
src/network/networkd-dhcp-server.c
src/network/networkd-link.c
src/network/networkd-link.h
src/network/networkd-manager-varlink.c
src/network/networkd-manager-varlink.h
src/network/networkd-manager.c
src/network/networkd-manager.h
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
src/network/networkd-resolve-hook.c [new file with mode: 0644]
src/network/networkd-resolve-hook.h [new file with mode: 0644]
test/networkd-test.py
test/test-network/conf/25-dhcp-client-resolve-hook.network [new file with mode: 0644]
test/test-network/conf/25-dhcp-server-resolve-hook.network [new file with mode: 0644]
test/test-network/systemd-networkd-tests.py
units/meson.build
units/systemd-networkd-resolve-hook.socket [new file with mode: 0644]
units/systemd-networkd.service.in

index a75f89de99d40745690bcab5c90f08ec631d7d0f..dda1e117ed885d380b371c0fd530c109a78fb3a2 100644 (file)
@@ -4201,6 +4201,23 @@ ServerAddress=192.168.0.1/24</programlisting>
         </listitem>
       </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>
   </refsect1>
 
index 78a0e72b3a17cfbeeaa1ada101250b5b103ec7ca..2b66aa2743670da1c4355a5bc66b4c846dce4bf7 100644 (file)
@@ -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',
index 4b1dbe6eb948b7c94f75edfcead92aee539ac872..75ecfdca91aed7b7ec5c52b228eee2bee8773e0a 100644 (file)
@@ -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;
 }
 
index 9c3de19766e3df224cb1ac2640fdcfd363f610e2..458ddff27f88aac74d41744970bafc13c770eab7 100644 (file)
@@ -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);
+}
index 6e936e3bb13353385687d0e273bdb458a2b3494f..8c5648d8d0a25f98c42b261b20d5b4c24855d03a 100644 (file)
@@ -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);
index 871e5a4636c75c72dadd32a47d4affd276b93e4b..cc46e60e4055374f6f44bc774851544c77aafdc6 100644 (file)
@@ -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);
-}
index bd5cede4046d6b411020bcc109c211a5ba889601..0933c2a0566b9d58aa355f5a5d2893333a0c1ebd 100644 (file)
@@ -4,4 +4,3 @@
 #include "networkd-forward.h"
 
 int manager_connect_varlink(Manager *m, int fd);
-void manager_varlink_done(Manager *m);
index 4004be81c0f6df68f0c4af4d6e9350fd4417f496..762f1ca190182a4740fba74171157914c461e501 100644 (file)
@@ -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);
 
index e350fe80c5b6c6af245c753ef5a74fef4c1bc5c2..871898abe605cd49f8f1051916e8f5f853c13232 100644 (file)
@@ -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;
index 2d0b7d40641a7759e83fc5e61998b93f2dbef43c..7f3f4c531134f26e15422cf75102a9ea171131e8 100644 (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.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
index 4e8566afa675b0089a4297fc4faf0f3209ecb3b2..e1ecc15235c65a5ee536dd4d1a209da27d8fa3f2 100644 (file)
@@ -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);
index edd2177dd3bf4821e55023f7d384b7fc0fb6be57..4ec2783e99f494df97c918a9409419f25a62d219 100644 (file)
@@ -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 (file)
index 0000000..6c437be
--- /dev/null
@@ -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 (file)
index 0000000..3ad185b
--- /dev/null
@@ -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);
index 8ea6cd43a0682ccbd6c90b9ef4eded7af8747fbc..691f58b2d3aed3351aeb27c11b9db6e672788bd4 100755 (executable)
@@ -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 (file)
index 0000000..5f4ed00
--- /dev/null
@@ -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 (file)
index 0000000..ec6ee8d
--- /dev/null
@@ -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
index 32c7b5904477e4c839be1507911bb245821f050f..2210a99c074df5e1775e37f8672f633d72fa8843 100755 (executable)
@@ -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):
index 8e5b645f9166ef93c424db9b1f0f9fbb38c974cf..2e04c4aa2b2c88f18198231608c0ae596fb53620 100644 (file)
@@ -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 (file)
index 0000000..3c11b8e
--- /dev/null
@@ -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
index 44103f7daa4d6588e31048a9060bcfeee1babbac..589782effcd6d2d284fac94b9d7be7dd1e94c80e 100644 (file)
@@ -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