From: Luca Boccassi Date: Fri, 8 Mar 2024 23:02:19 +0000 (+0000) Subject: resolved: support reloading configuration at runtime X-Git-Tag: v256-rc1~391^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=14a52176798bc2d013297b503ac6fa49a64e2725;p=thirdparty%2Fsystemd.git resolved: support reloading configuration at runtime 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 --- diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index 8520a97e424..13c0da987fe 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -421,6 +421,16 @@ search foobar.com barbar.com + + + SIGHUP + + Upon reception of the SIGHUP process signal + systemd-resolved will flush all caches it maintains, drop all open TCP + connections (if any), and reload its configuration files. + + + diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index be2fdca21fe..986a90f9f23 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -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, diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 504da9ebca3..4441ee27c80 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -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) { diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index ca768bb2d9f..5eea6bd54b9 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -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); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 957e6618b44..340f11f4f49 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -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; diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index ed6560fa9d8..ef76bbc878b 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -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); diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c index 8790755d3b6..7f8f99717c0 100644 --- a/src/resolve/resolved-dnssd.c +++ b/src/resolve/resolved-dnssd.c @@ -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; diff --git a/src/resolve/resolved-dnssd.h b/src/resolve/resolved-dnssd.h index 970f2ba3c86..e7f2add397d 100644 --- a/src/resolve/resolved-dnssd.h +++ b/src/resolve/resolved-dnssd.h @@ -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); diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c index 65562dc9339..656bdd9d8e0 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -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; diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index dd5daddce48..bb43a73de42 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -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) { diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 5a14e64fe5c..568ee00280c 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -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); diff --git a/test/units/testsuite-75.sh b/test/units/testsuite-75.sh index 0a3951e30b3..275bf296b8d 100755 --- a/test/units/testsuite-75.sh +++ b/test/units/testsuite-75.sh @@ -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 diff --git a/units/systemd-resolved.service.in b/units/systemd-resolved.service.in index 820aecfef6c..717f572bc53 100644 --- a/units/systemd-resolved.service.in +++ b/units/systemd-resolved.service.in @@ -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