]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: support reloading configuration at runtime 31951/head
authorLuca Boccassi <bluca@debian.org>
Fri, 8 Mar 2024 23:02:19 +0000 (23:02 +0000)
committerLuca Boccassi <bluca@debian.org>
Tue, 26 Mar 2024 13:36:42 +0000 (13:36 +0000)
Drop connections and caches and reload config from files, to allow
for low-interruptions updates, and hook up to the usual SIGHUP and
ExecReload=. Mark servers and services configured directly via D-Bus
so that they can be kept around, and only the configuration file
settings are dropped and reloaded.

Fixes https://github.com/systemd/systemd/issues/17503
Fixes https://github.com/systemd/systemd/issues/20604

13 files changed:
man/systemd-resolved.service.xml
src/resolve/resolved-bus.c
src/resolve/resolved-conf.c
src/resolve/resolved-conf.h
src/resolve/resolved-dns-server.c
src/resolve/resolved-dns-server.h
src/resolve/resolved-dnssd.c
src/resolve/resolved-dnssd.h
src/resolve/resolved-link-bus.c
src/resolve/resolved-link.c
src/resolve/resolved-manager.c
test/units/testsuite-75.sh
units/systemd-resolved.service.in

index 8520a97e42469e51f3a4f34d9e550fcfada600ec..13c0da987fe6fc0dbbd3116a4682ec548dba42b4 100644 (file)
@@ -421,6 +421,16 @@ search foobar.com barbar.com
 
         <xi:include href="version-info.xml" xpointer="v235"/></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><constant>SIGHUP</constant></term>
+
+        <listitem><para>Upon reception of the <constant>SIGHUP</constant> process signal
+        <command>systemd-resolved</command> will flush all caches it maintains, drop all open TCP
+        connections (if any), and reload its configuration files.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index be2fdca21fe85d54f8b2fadb6182908e6b2e37cb..986a90f9f233ced2c3bd390305655895544c67bb 100644 (file)
@@ -1890,6 +1890,7 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata,
         if (r < 0)
                 return r;
         service->originator = euid;
+        service->config_source = RESOLVE_CONFIG_SOURCE_DBUS;
 
         r = sd_bus_message_read(message, "sssqqq", &name, &name_template, &type,
                                 &service->port, &service->priority,
index 504da9ebca31ed0eb655ee939be5d1ace8e99156..4441ee27c803cb2026494574292061743d10d9bf 100644 (file)
@@ -55,7 +55,7 @@ static int manager_add_dns_server_by_string(Manager *m, DnsServerType type, cons
                 return 0;
         }
 
-        return dns_server_new(m, NULL, type, NULL, family, &address, port, ifindex, server_name);
+        return dns_server_new(m, NULL, type, NULL, family, &address, port, ifindex, server_name, RESOLVE_CONFIG_SOURCE_FILE);
 }
 
 int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) {
index ca768bb2d9fadfd4ae79e769e6c2a610bff5fa70..5eea6bd54b91d6eebd049894c1e6203715955d97 100644 (file)
@@ -3,6 +3,14 @@
 
 #include "conf-parser.h"
 
+typedef enum ResolveConfigSource {
+        RESOLVE_CONFIG_SOURCE_FILE,
+        RESOLVE_CONFIG_SOURCE_NETWORKD,
+        RESOLVE_CONFIG_SOURCE_DBUS,
+        _RESOLVE_CONFIG_SOURCE_MAX,
+        _RESOLVE_CONFIG_SOURCE_INVALID = -EINVAL,
+} ResolveConfigSource;
+
 #include "resolved-dns-server.h"
 
 int manager_parse_config_file(Manager *m);
index 957e6618b44dfcb384e3b92298e8a360452ec8ae..340f11f4f494c4566860b1b8a83b531b04650686 100644 (file)
@@ -28,7 +28,8 @@ int dns_server_new(
                 const union in_addr_union *in_addr,
                 uint16_t port,
                 int ifindex,
-                const char *server_name) {
+                const char *server_name,
+                ResolveConfigSource config_source) {
 
         _cleanup_free_ char *name = NULL;
         DnsServer *s;
@@ -67,6 +68,7 @@ int dns_server_new(
                 .port = port,
                 .ifindex = ifindex,
                 .server_name = TAKE_PTR(name),
+                .config_source = config_source,
         };
 
         dns_server_reset_features(s);
@@ -794,6 +796,17 @@ void dns_server_unlink_all(DnsServer *first) {
         dns_server_unlink_all(next);
 }
 
+void dns_server_unlink_on_reload(DnsServer *server) {
+        while (server) {
+                DnsServer *next = server->servers_next;
+
+                if (server->config_source == RESOLVE_CONFIG_SOURCE_FILE)
+                        dns_server_unlink(server);
+
+                server = next;
+        }
+}
+
 bool dns_server_unlink_marked(DnsServer *server) {
         bool changed = false;
 
index ed6560fa9d8a65f9dc33f2767541658c0f8323a6..ef76bbc878bd38040734d0467013390491c1db28 100644 (file)
@@ -24,6 +24,8 @@ typedef enum DnsServerType {
         _DNS_SERVER_TYPE_INVALID = -EINVAL,
 } DnsServerType;
 
+#include "resolved-conf.h"
+
 const char* dns_server_type_to_string(DnsServerType i) _const_;
 DnsServerType dns_server_type_from_string(const char *s) _pure_;
 
@@ -100,6 +102,9 @@ struct DnsServer {
         /* If linked is set, then this server appears in the servers linked list */
         bool linked:1;
         LIST_FIELDS(DnsServer, servers);
+
+        /* Servers registered via D-Bus are not removed on reload */
+        ResolveConfigSource config_source;
 };
 
 int dns_server_new(
@@ -111,7 +116,8 @@ int dns_server_new(
                 const union in_addr_union *address,
                 uint16_t port,
                 int ifindex,
-                const char *server_string);
+                const char *server_string,
+                ResolveConfigSource config_source);
 
 DnsServer* dns_server_ref(DnsServer *s);
 DnsServer* dns_server_unref(DnsServer *s);
@@ -145,6 +151,7 @@ void dns_server_warn_downgrade(DnsServer *server);
 DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, uint16_t port, int ifindex, const char *name);
 
 void dns_server_unlink_all(DnsServer *first);
+void dns_server_unlink_on_reload(DnsServer *server);
 bool dns_server_unlink_marked(DnsServer *first);
 void dns_server_mark_all(DnsServer *first);
 
index 8790755d3b6044ad6abe158a86a4848bddbe06bc..7f8f99717c03ac00fc2eeeabf9c47087c4918572 100644 (file)
@@ -57,6 +57,16 @@ DnssdService *dnssd_service_free(DnssdService *service) {
         return mfree(service);
 }
 
+void dnssd_service_clear_on_reload(Hashmap *services) {
+        DnssdService *service;
+
+        HASHMAP_FOREACH(service, services)
+                if (service->config_source == RESOLVE_CONFIG_SOURCE_FILE) {
+                        hashmap_remove(services, service->name);
+                        dnssd_service_free(service);
+                }
+}
+
 static int dnssd_service_load(Manager *manager, const char *filename) {
         _cleanup_(dnssd_service_freep) DnssdService *service = NULL;
         _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
index 970f2ba3c86764c0a32d2d14bafe9eb540c71a39..e7f2add397d976b2076b94374019a9ae3d3a7f53 100644 (file)
@@ -3,6 +3,7 @@
 #pragma once
 
 #include "list.h"
+#include "resolved-conf.h"
 
 typedef struct DnssdService DnssdService;
 typedef struct DnssdTxtData DnssdTxtData;
@@ -44,6 +45,9 @@ struct DnssdService {
 
         Manager *manager;
 
+        /* Services registered via D-Bus are not removed on reload */
+        ResolveConfigSource config_source;
+
         bool withdrawn:1;
         uid_t originator;
 };
@@ -51,6 +55,7 @@ struct DnssdService {
 DnssdService *dnssd_service_free(DnssdService *service);
 DnssdTxtData *dnssd_txtdata_free(DnssdTxtData *txt_data);
 DnssdTxtData *dnssd_txtdata_free_all(DnssdTxtData *txt_data);
+void dnssd_service_clear_on_reload(Hashmap *services);
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdService*, dnssd_service_free);
 DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdTxtData*, dnssd_txtdata_free);
index 65562dc93390c8d77f3b2e47f6b44e3d8361f822..656bdd9d8e05de14b99c45afeda18885ea05101f 100644 (file)
@@ -274,7 +274,7 @@ static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, voi
                 if (s)
                         dns_server_move_back_and_unmark(s);
                 else {
-                        r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i]->family, &dns[i]->address, dns[i]->port, 0, dns[i]->server_name);
+                        r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i]->family, &dns[i]->address, dns[i]->port, 0, dns[i]->server_name, RESOLVE_CONFIG_SOURCE_DBUS);
                         if (r < 0) {
                                 dns_server_unlink_all(l->dns_servers);
                                 goto finalize;
index dd5daddce486f166f2123a46d2ebcca06c89e413..bb43a73de42dcf65afb92f0029e8562361264aff 100644 (file)
@@ -273,7 +273,7 @@ static int link_update_dns_server_one(Link *l, const char *str) {
                 return 0;
         }
 
-        return dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a, port, 0, name);
+        return dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a, port, 0, name, RESOLVE_CONFIG_SOURCE_NETWORKD);
 }
 
 static int link_update_dns_servers(Link *l) {
index 5a14e64fe5c4a1f5a74b08fab71bf767e8741242..568ee00280c6b287e3cba1b14569f47c92f7b0ab 100644 (file)
@@ -11,6 +11,7 @@
 #include "af-list.h"
 #include "alloc-util.h"
 #include "bus-polkit.h"
+#include "daemon-util.h"
 #include "dirent-util.h"
 #include "dns-domain.h"
 #include "event-util.h"
@@ -565,6 +566,73 @@ static int manager_memory_pressure_listen(Manager *m) {
         return 0;
 }
 
+static void manager_set_defaults(Manager *m) {
+        assert(m);
+
+        m->llmnr_support = DEFAULT_LLMNR_MODE;
+        m->mdns_support = DEFAULT_MDNS_MODE;
+        m->dnssec_mode = DEFAULT_DNSSEC_MODE;
+        m->dns_over_tls_mode = DEFAULT_DNS_OVER_TLS_MODE;
+        m->enable_cache = DNS_CACHE_MODE_YES;
+        m->dns_stub_listener_mode = DNS_STUB_LISTENER_YES;
+        m->read_etc_hosts = true;
+        m->resolve_unicast_single_label = false;
+        m->cache_from_localhost = false;
+        m->stale_retention_usec = 0;
+}
+
+static int manager_dispatch_reload_signal(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
+        Manager *m = ASSERT_PTR(userdata);
+        int r;
+
+        (void) notify_reloading();
+
+        manager_set_defaults(m);
+
+        dns_server_unlink_on_reload(m->dns_servers);
+        dns_server_unlink_on_reload(m->fallback_dns_servers);
+        m->dns_extra_stub_listeners = ordered_set_free(m->dns_extra_stub_listeners);
+        dnssd_service_clear_on_reload(m->dnssd_services);
+        m->unicast_scope = dns_scope_free(m->unicast_scope);
+
+        dns_trust_anchor_flush(&m->trust_anchor);
+
+        r = dns_trust_anchor_load(&m->trust_anchor);
+        if (r < 0)
+                return r;
+
+        r = manager_parse_config_file(m);
+        if (r < 0)
+                log_warning_errno(r, "Failed to parse config file on reload: %m");
+        else
+                log_info("Config file reloaded.");
+
+        r = dnssd_load(m);
+        if (r < 0)
+                log_warning_errno(r, "Failed to load DNS-SD configuration files: %m");
+
+        /* The default scope configuration is influenced by the manager's configuration (modes, etc.), so
+         * recreate it on reload. */
+        r = dns_scope_new(m, &m->unicast_scope, NULL, DNS_PROTOCOL_DNS, AF_UNSPEC);
+        if (r < 0)
+                return r;
+
+        /* The configuration has changed, so reload the per-interface configuration too in order to take
+         * into account any changes (e.g.: enable/disable DNSSEC). */
+        r = on_network_event(/* sd_event_source= */ NULL, -EBADF, /* revents= */ 0, m);
+        if (r < 0)
+                log_warning_errno(r, "Failed to update network information: %m");
+
+        /* We have new configuration, which means potentially new servers, so close all connections and drop
+         * all caches, so that we can start fresh. */
+        (void) dns_stream_disconnect_all(m);
+        manager_flush_caches(m, LOG_INFO);
+        manager_verify_all(m);
+
+        (void) sd_notify(/* unset= */ false, NOTIFY_READY);
+        return 0;
+}
+
 int manager_new(Manager **ret) {
         _cleanup_(manager_freep) Manager *m = NULL;
         int r;
@@ -584,21 +652,16 @@ int manager_new(Manager **ret) {
                 .mdns_ipv6_fd = -EBADF,
                 .hostname_fd = -EBADF,
 
-                .llmnr_support = DEFAULT_LLMNR_MODE,
-                .mdns_support = DEFAULT_MDNS_MODE,
-                .dnssec_mode = DEFAULT_DNSSEC_MODE,
-                .dns_over_tls_mode = DEFAULT_DNS_OVER_TLS_MODE,
-                .enable_cache = DNS_CACHE_MODE_YES,
-                .dns_stub_listener_mode = DNS_STUB_LISTENER_YES,
                 .read_resolv_conf = true,
                 .need_builtin_fallbacks = true,
                 .etc_hosts_last = USEC_INFINITY,
-                .read_etc_hosts = true,
 
                 .sigrtmin18_info.memory_pressure_handler = manager_memory_pressure,
                 .sigrtmin18_info.memory_pressure_userdata = m,
         };
 
+        manager_set_defaults(m);
+
         r = dns_trust_anchor_load(&m->trust_anchor);
         if (r < 0)
                 return r;
@@ -619,6 +682,7 @@ int manager_new(Manager **ret) {
 
         (void) sd_event_add_signal(m->event, NULL, SIGTERM, NULL,  NULL);
         (void) sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
+        (void) sd_event_add_signal(m->event, NULL, SIGHUP | SD_EVENT_SIGNAL_PROCMASK, manager_dispatch_reload_signal, m);
 
         (void) sd_event_set_watchdog(m->event, true);
 
index 0a3951e30b30cc023f527ebc563867c1d3e52105..275bf296b8d9fb17bfbd81fc9a4f87f106640c0d 100755 (executable)
@@ -102,12 +102,21 @@ assert_in '_localdnsproxy' "$(dig @127.0.0.53 -x 127.0.0.54)"
 mkdir -p /run/systemd/resolved.conf.d
 {
     echo "[Resolve]"
-    echo "MulticastDNS=yes"
-    echo "LLMNR=yes"
+    echo "MulticastDNS=no"
+    echo "LLMNR=no"
 } >/run/systemd/resolved.conf.d/mdns-llmnr.conf
 restart_resolved
 # make sure networkd is not running.
 systemctl stop systemd-networkd.service
+assert_in 'no' "$(resolvectl mdns hoge)"
+assert_in 'no' "$(resolvectl llmnr hoge)"
+# Tests that reloading works
+{
+    echo "[Resolve]"
+    echo "MulticastDNS=yes"
+    echo "LLMNR=yes"
+} >/run/systemd/resolved.conf.d/mdns-llmnr.conf
+systemctl reload systemd-resolved.service
 # defaults to yes (both the global and per-link settings are yes)
 assert_in 'yes' "$(resolvectl mdns hoge)"
 assert_in 'yes' "$(resolvectl llmnr hoge)"
@@ -130,7 +139,7 @@ assert_in 'no' "$(resolvectl llmnr hoge)"
     echo "MulticastDNS=resolve"
     echo "LLMNR=resolve"
 } >/run/systemd/resolved.conf.d/mdns-llmnr.conf
-restart_resolved
+systemctl reload systemd-resolved.service
 # set per-link setting
 resolvectl mdns hoge yes
 resolvectl llmnr hoge yes
@@ -150,7 +159,7 @@ assert_in 'no' "$(resolvectl llmnr hoge)"
     echo "MulticastDNS=no"
     echo "LLMNR=no"
 } >/run/systemd/resolved.conf.d/mdns-llmnr.conf
-restart_resolved
+systemctl reload systemd-resolved.service
 # set per-link setting
 resolvectl mdns hoge yes
 resolvectl llmnr hoge yes
@@ -254,7 +263,8 @@ ln -svf /etc/bind.keys /etc/bind/bind.keys
 # Start the services
 systemctl unmask systemd-networkd
 systemctl start systemd-networkd
-restart_resolved
+/usr/lib/systemd/systemd-networkd-wait-online --interface=dns1:routable --timeout=60
+systemctl reload systemd-resolved
 systemctl start resolved-dummy-server
 # Create knot's runtime dir, since from certain version it's provided only by
 # the package and not created by tmpfiles/systemd
@@ -720,7 +730,7 @@ if command -v nft >/dev/null; then
         echo "StaleRetentionSec=1d"
     } >/run/systemd/resolved.conf.d/test.conf
     ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
-    restart_resolved
+    systemctl reload systemd-resolved.service
 
     run dig stale1.unsigned.test -t A
     grep -qE "NOERROR" "$RUN_OUT"
@@ -850,6 +860,21 @@ test "$(resolvectl --json=short query -t A localhost)" == '{"key":{"class":1,"ty
 test "$(varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveRecord '{"name":"localhost","type":1}' --json=short)" == '{"rrs":[{"ifindex":1,"rr":{"key":{"class":1,"type":1,"name":"localhost"},"address":[127,0,0,1]},"raw":"CWxvY2FsaG9zdAAAAQABAAAAAAAEfwAAAQ=="}],"flags":786945}'
 test "$(varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveRecord '{"name":"localhost","type":28}' --json=short)" == '{"rrs":[{"ifindex":1,"rr":{"key":{"class":1,"type":28,"name":"localhost"},"address":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]},"raw":"CWxvY2FsaG9zdAAAHAABAAAAAAAQAAAAAAAAAAAAAAAAAAAAAQ=="}],"flags":786945}'
 
+# Ensure that reloading keeps the manually configured address
+{
+    echo "[Resolve]"
+    echo "DNS=8.8.8.8"
+} >/run/systemd/resolved.conf.d/reload.conf
+resolvectl dns dns0 1.1.1.1
+systemctl reload systemd-resolved.service
+resolvectl status
+resolvectl dns dns0 | grep -qF "1.1.1.1"
+# For some reason piping this last command to grep fails with:
+# 'resolvectl[1378]: Failed to print table: Broken pipe'
+# so use an intermediate file in /tmp/
+resolvectl >/tmp/output
+grep -qF "DNS Servers: 8.8.8.8" /tmp/output
+
 # Check if resolved exits cleanly.
 restart_resolved
 
index 820aecfef6cc9072b6b1fe53aff9dbc887f644b6..717f572bc53ed97eb134ed0fe99be92c3fd89074 100644 (file)
@@ -48,7 +48,7 @@ RuntimeDirectoryPreserve=yes
 SystemCallArchitectures=native
 SystemCallErrorNumber=EPERM
 SystemCallFilter=@system-service
-Type=notify
+Type=notify-reload
 User=systemd-resolve
 ImportCredential=network.dns
 ImportCredential=network.search_domains