diff --git a/NEWS b/NEWS
index ac6f23c37d..fdd0b88077 100644
--- a/NEWS
+++ b/NEWS
@@ -209,6 +209,17 @@ CHANGES WITH 235:
too. Note that while the other databases are world-readable
(i.e. 0644), btmp is not and remains more restrictive.
+ * The systemd-resolve tool gained a new --reset-server-features
+ switch. When invoked like this systemd-resolved will forget
+ everything it learnt about the features supported by the configured
+ upstream DNS servers, and restarts the feature probing logic on the
+ next resolver look-up for them at the highest feature level
+ again.
+
+ * The status dump systemd-resolved sends to the logs upon receiving
+ SIGUSR1 now also includes information about all DNS servers it is
+ configured to use, and the features levels it probed for them.
+
Contributions from: Abdó Roig-Maranges, Alan Jenkins, Alexander
Kuleshov, Andreas Rammhold, Andrew Jeddeloh, Andrew Soutar, Ansgar
Burchardt, b1tninja, bengal, Benjamin Berg, Benjamin Robin, Charles
diff --git a/man/systemd-resolve.xml b/man/systemd-resolve.xml
index 47c90f2e48..53f843ff93 100644
--- a/man/systemd-resolve.xml
+++ b/man/systemd-resolve.xml
@@ -299,7 +299,18 @@
- Flushes all DNS resource record caches the service maintains locally.
+ Flushes all DNS resource record caches the service maintains locally. This is mostly equivalent
+ to sending the SIGUSR2 to the systemd-resolved
+ service.
+
+
+
+
+
+ Flushes all feature level information the resolver learnt about specific servers, and ensures
+ that the server feature probing logic is started from the beginning with the next look-up request. This is
+ mostly equivalent to sending the SIGRTMIN+1 to the systemd-resolved
+ service.
diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml
index fd717e4963..d07d1968b4 100644
--- a/man/systemd-resolved.service.xml
+++ b/man/systemd-resolved.service.xml
@@ -202,19 +202,38 @@
SIGUSR1
- Upon reception of the SIGUSR1 process signal systemd-resolved will dump the
- contents of all DNS resource record caches it maintains into the system logs.
+ Upon reception of the SIGUSR1 process signal
+ systemd-resolved will dump the contents of all DNS resource record caches it maintains, as
+ well as all feature level information it learnt about configured DNS servers into the system
+ logs.
SIGUSR2
- Upon reception of the SIGUSR2 process signal systemd-resolved will flush all
- caches it maintains. Note that it should normally not be necessary to request this explicitly – except for
- debugging purposes – as systemd-resolved flushes the caches automatically anyway any time
- the host's network configuration changes.
+ Upon reception of the SIGUSR2 process signal
+ systemd-resolved will flush all caches it maintains. Note that it should normally not be
+ necessary to request this explicitly – except for debugging purposes – as systemd-resolved
+ flushes the caches automatically anyway any time the host's network configuration changes. Sending this signal
+ to systemd-resolved is equivalent to the systemd-resolve --flush-caches
+ command, however the latter is recommended since it operates in a synchronous way.
+
+
+
+ SIGRTMIN+1
+
+ Upon reception of the SIGRTMIN+1 process signal
+ systemd-resolved will forget everything it learnt about the configured DNS
+ servers. Specifically any information about server feature support is flushed out, and the server feature
+ probing logic is restarted on the next request, starting with the most fully featured level. Note that it
+ should normally not be necessary to request this explicitly – except for debugging purposes – as
+ systemd-resolved automatically forgets learnt information any time the DNS server
+ configuration changes. Sending this signal to systemd-resolved is equivalent to the
+ systemd-resolve --reset-server-features command, however the latter is recommended since it
+ operates in a synchronous way.
+
diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c
index 195555c3c0..708378573f 100644
--- a/src/resolve/resolve-tool.c
+++ b/src/resolve/resolve-tool.c
@@ -72,6 +72,7 @@ static enum {
MODE_STATISTICS,
MODE_RESET_STATISTICS,
MODE_FLUSH_CACHES,
+ MODE_RESET_SERVER_FEATURES,
MODE_STATUS,
} arg_mode = MODE_RESOLVE_HOST;
@@ -1055,6 +1056,24 @@ static int flush_caches(sd_bus *bus) {
return 0;
}
+static int reset_server_features(sd_bus *bus) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ r = sd_bus_call_method(bus,
+ "org.freedesktop.resolve1",
+ "/org/freedesktop/resolve1",
+ "org.freedesktop.resolve1.Manager",
+ "ResetServerFeatures",
+ &error,
+ NULL,
+ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to reset server features: %s", bus_error_message(&error, r));
+
+ return 0;
+}
+
static int map_link_dns_servers(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
char ***l = userdata;
int r;
@@ -1588,6 +1607,8 @@ static void help(void) {
" --reset-statistics Reset resolver statistics\n"
" --status Show link and server status\n"
" --flush-caches Flush all local DNS caches\n"
+ " --reset-server-features\n"
+ " Forget learnt DNS server feature levels\n"
, program_invocation_short_name);
}
@@ -1607,30 +1628,32 @@ static int parse_argv(int argc, char *argv[]) {
ARG_RESET_STATISTICS,
ARG_STATUS,
ARG_FLUSH_CACHES,
+ ARG_RESET_SERVER_FEATURES,
ARG_NO_PAGER,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "type", required_argument, NULL, 't' },
- { "class", required_argument, NULL, 'c' },
- { "legend", required_argument, NULL, ARG_LEGEND },
- { "interface", required_argument, NULL, 'i' },
- { "protocol", required_argument, NULL, 'p' },
- { "cname", required_argument, NULL, ARG_CNAME },
- { "service", no_argument, NULL, ARG_SERVICE },
- { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS },
- { "service-txt", required_argument, NULL, ARG_SERVICE_TXT },
- { "openpgp", no_argument, NULL, ARG_OPENPGP },
- { "tlsa", optional_argument, NULL, ARG_TLSA },
- { "raw", optional_argument, NULL, ARG_RAW },
- { "search", required_argument, NULL, ARG_SEARCH },
- { "statistics", no_argument, NULL, ARG_STATISTICS, },
- { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS },
- { "status", no_argument, NULL, ARG_STATUS },
- { "flush-caches", no_argument, NULL, ARG_FLUSH_CACHES },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "type", required_argument, NULL, 't' },
+ { "class", required_argument, NULL, 'c' },
+ { "legend", required_argument, NULL, ARG_LEGEND },
+ { "interface", required_argument, NULL, 'i' },
+ { "protocol", required_argument, NULL, 'p' },
+ { "cname", required_argument, NULL, ARG_CNAME },
+ { "service", no_argument, NULL, ARG_SERVICE },
+ { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS },
+ { "service-txt", required_argument, NULL, ARG_SERVICE_TXT },
+ { "openpgp", no_argument, NULL, ARG_OPENPGP },
+ { "tlsa", optional_argument, NULL, ARG_TLSA },
+ { "raw", optional_argument, NULL, ARG_RAW },
+ { "search", required_argument, NULL, ARG_SEARCH },
+ { "statistics", no_argument, NULL, ARG_STATISTICS, },
+ { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS },
+ { "status", no_argument, NULL, ARG_STATUS },
+ { "flush-caches", no_argument, NULL, ARG_FLUSH_CACHES },
+ { "reset-server-features", no_argument, NULL, ARG_RESET_SERVER_FEATURES },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
{}
};
@@ -1814,6 +1837,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_mode = MODE_FLUSH_CACHES;
break;
+ case ARG_RESET_SERVER_FEATURES:
+ arg_mode = MODE_RESET_SERVER_FEATURES;
+ break;
+
case ARG_STATUS:
arg_mode = MODE_STATUS;
break;
@@ -1999,6 +2026,16 @@ int main(int argc, char **argv) {
r = flush_caches(bus);
break;
+ case MODE_RESET_SERVER_FEATURES:
+ if (argc > optind) {
+ log_error("Too many arguments.");
+ r = -EINVAL;
+ goto finish;
+ }
+
+ r = reset_server_features(bus);
+ break;
+
case MODE_STATUS:
if (argc > optind) {
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c
index bf822d3aaa..c6f14eb416 100644
--- a/src/resolve/resolved-bus.c
+++ b/src/resolve/resolved-bus.c
@@ -1569,6 +1569,17 @@ static int bus_method_flush_caches(sd_bus_message *message, void *userdata, sd_b
return sd_bus_reply_method_return(message, NULL);
}
+static int bus_method_reset_server_features(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+
+ assert(message);
+ assert(m);
+
+ manager_reset_server_features(m);
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0),
@@ -1587,6 +1598,7 @@ static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResetStatistics", NULL, NULL, bus_method_reset_statistics, 0),
SD_BUS_METHOD("FlushCaches", NULL, NULL, bus_method_flush_caches, 0),
+ SD_BUS_METHOD("ResetServerFeatures", NULL, NULL, bus_method_reset_server_features, 0),
SD_BUS_METHOD("GetLink", "i", "o", bus_method_get_link, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetLinkDNS", "ia(iay)", NULL, bus_method_set_link_dns_servers, 0),
SD_BUS_METHOD("SetLinkDomains", "ia(sb)", NULL, bus_method_set_link_domains, 0),
diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c
index b3d37525f4..1b61dea626 100644
--- a/src/resolve/resolved-dns-server.c
+++ b/src/resolve/resolved-dns-server.c
@@ -70,15 +70,12 @@ int dns_server_new(
s->n_ref = 1;
s->manager = m;
- s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
- s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST;
- s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC;
- s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX;
s->type = type;
s->family = family;
s->address = *in_addr;
s->ifindex = ifindex;
- s->resend_timeout = DNS_TIMEOUT_MIN_USEC;
+
+ dns_server_reset_features(s);
switch (type) {
@@ -828,6 +825,85 @@ void dns_server_flush_cache(DnsServer *s) {
dns_cache_flush(&scope->cache);
}
+void dns_server_reset_features(DnsServer *s) {
+ assert(s);
+
+ s->max_rtt = 0;
+ s->resend_timeout = DNS_TIMEOUT_MIN_USEC;
+
+ s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
+ s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST;
+
+ s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX;
+
+ s->packet_bad_opt = false;
+ s->packet_rrsig_missing = false;
+
+ s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC;
+
+ s->warned_downgrade = false;
+
+ dns_server_reset_counters(s);
+}
+
+void dns_server_reset_features_all(DnsServer *s) {
+ DnsServer *i;
+
+ LIST_FOREACH(servers, i, s)
+ dns_server_reset_features(i);
+}
+
+void dns_server_dump(DnsServer *s, FILE *f) {
+ assert(s);
+
+ if (!f)
+ f = stdout;
+
+ fputs("[Server ", f);
+ fputs(dns_server_string(s), f);
+ fputs(" type=", f);
+ fputs(dns_server_type_to_string(s->type), f);
+
+ if (s->type == DNS_SERVER_LINK) {
+ assert(s->link);
+
+ fputs(" interface=", f);
+ fputs(s->link->name, f);
+ }
+
+ fputs("]\n", f);
+
+ fputs("\tVerified feature level: ", f);
+ fputs(strna(dns_server_feature_level_to_string(s->verified_feature_level)), f);
+ fputc('\n', f);
+
+ fputs("\tPossible feature level: ", f);
+ fputs(strna(dns_server_feature_level_to_string(s->possible_feature_level)), f);
+ fputc('\n', f);
+
+ fputs("\tDNSSEC Mode: ", f);
+ fputs(strna(dnssec_mode_to_string(dns_server_get_dnssec_mode(s))), f);
+ fputc('\n', f);
+
+ fputs("\tCan do DNSSEC: ", f);
+ fputs(yes_no(dns_server_dnssec_supported(s)), f);
+ fputc('\n', f);
+
+ fprintf(f,
+ "\tMaximum UDP packet size received: %zu\n"
+ "\tFailed UDP attempts: %u\n"
+ "\tFailed TCP attempts: %u\n"
+ "\tSeen truncated packet: %s\n"
+ "\tSeen OPT RR getting lost: %s\n"
+ "\tSeen RRSIG RR missing: %s\n",
+ s->received_udp_packet_max,
+ s->n_failed_udp,
+ s->n_failed_tcp,
+ yes_no(s->packet_truncated),
+ yes_no(s->packet_bad_opt),
+ yes_no(s->packet_rrsig_missing));
+}
+
static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = {
[DNS_SERVER_SYSTEM] = "system",
[DNS_SERVER_FALLBACK] = "fallback",
diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h
index bc95d53c6a..00edd47d9a 100644
--- a/src/resolve/resolved-dns-server.h
+++ b/src/resolve/resolved-dns-server.h
@@ -151,3 +151,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref);
extern const struct hash_ops dns_server_hash_ops;
void dns_server_flush_cache(DnsServer *s);
+
+void dns_server_reset_features(DnsServer *s);
+void dns_server_reset_features_all(DnsServer *s);
+
+void dns_server_dump(DnsServer *s, FILE *f);
diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c
index 21fd8465e6..3d26831b06 100644
--- a/src/resolve/resolved-link.c
+++ b/src/resolve/resolved-link.c
@@ -111,13 +111,30 @@ Link *link_free(Link *l) {
}
void link_allocate_scopes(Link *l) {
+ bool unicast_relevant;
int r;
assert(l);
- if (link_relevant(l, AF_UNSPEC, false) &&
- l->dns_servers) {
+ /* If a link that used to be relevant is no longer, or a link that did not use to be relevant now becomes
+ * relevant, let's reinit the learnt global DNS server information, since we might talk to different servers
+ * now, even if they have the same addresses as before. */
+
+ unicast_relevant = link_relevant(l, AF_UNSPEC, false);
+ if (unicast_relevant != l->unicast_relevant) {
+ l->unicast_relevant = unicast_relevant;
+
+ dns_server_reset_features_all(l->manager->fallback_dns_servers);
+ dns_server_reset_features_all(l->manager->dns_servers);
+ }
+
+ /* And now, allocate all scopes that makes sense now if we didn't have them yet, and drop those which we don't
+ * need anymore */
+
+ if (unicast_relevant && l->dns_servers) {
if (!l->unicast_scope) {
+ dns_server_reset_features_all(l->dns_servers);
+
r = dns_scope_new(l->manager, &l->unicast_scope, l, DNS_PROTOCOL_DNS, AF_UNSPEC);
if (r < 0)
log_warning_errno(r, "Failed to allocate DNS scope: %m");
diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h
index 55a56b7906..c20b8b6d29 100644
--- a/src/resolve/resolved-link.h
+++ b/src/resolve/resolved-link.h
@@ -88,6 +88,8 @@ struct Link {
bool loaded;
char *state_file;
+
+ bool unicast_relevant;
};
int link_new(Manager *m, Link **ret, int ifindex);
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index 58fe572d3b..23c6731954 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -519,8 +519,11 @@ static int manager_sigusr1(sd_event_source *s, const struct signalfd_siginfo *si
_cleanup_free_ char *buffer = NULL;
_cleanup_fclose_ FILE *f = NULL;
Manager *m = userdata;
+ DnsServer *server;
size_t size = 0;
DnsScope *scope;
+ Iterator i;
+ Link *l;
assert(s);
assert(si);
@@ -533,6 +536,14 @@ static int manager_sigusr1(sd_event_source *s, const struct signalfd_siginfo *si
LIST_FOREACH(scopes, scope, m->dns_scopes)
dns_scope_dump(scope, f);
+ LIST_FOREACH(servers, server, m->dns_servers)
+ dns_server_dump(server, f);
+ LIST_FOREACH(servers, server, m->fallback_dns_servers)
+ dns_server_dump(server, f);
+ HASHMAP_FOREACH(l, m->links, i)
+ LIST_FOREACH(servers, server, l->dns_servers)
+ dns_server_dump(server, f);
+
if (fflush_and_check(f) < 0)
return log_oom();
@@ -552,6 +563,17 @@ static int manager_sigusr2(sd_event_source *s, const struct signalfd_siginfo *si
return 0;
}
+static int manager_sigrtmin1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
+ Manager *m = userdata;
+
+ assert(s);
+ assert(si);
+ assert(m);
+
+ manager_reset_server_features(m);
+ return 0;
+}
+
int manager_new(Manager **ret) {
_cleanup_(manager_freep) Manager *m = NULL;
int r;
@@ -616,6 +638,7 @@ int manager_new(Manager **ret) {
(void) sd_event_add_signal(m->event, &m->sigusr1_event_source, SIGUSR1, manager_sigusr1, m);
(void) sd_event_add_signal(m->event, &m->sigusr2_event_source, SIGUSR2, manager_sigusr2, m);
+ (void) sd_event_add_signal(m->event, &m->sigrtmin1_event_source, SIGRTMIN+1, manager_sigrtmin1, m);
manager_cleanup_saved_user(m);
@@ -679,6 +702,7 @@ Manager *manager_free(Manager *m) {
sd_event_source_unref(m->sigusr1_event_source);
sd_event_source_unref(m->sigusr2_event_source);
+ sd_event_source_unref(m->sigrtmin1_event_source);
sd_event_unref(m->event);
@@ -1396,6 +1420,19 @@ void manager_flush_caches(Manager *m) {
log_info("Flushed all caches.");
}
+void manager_reset_server_features(Manager *m) {
+ Iterator i;
+ Link *l;
+
+ dns_server_reset_features_all(m->dns_servers);
+ dns_server_reset_features_all(m->fallback_dns_servers);
+
+ HASHMAP_FOREACH(l, m->links, i)
+ dns_server_reset_features_all(l->dns_servers);
+
+ log_info("Resetting learnt feature levels on all servers.");
+}
+
void manager_cleanup_saved_user(Manager *m) {
_cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h
index 97c52b7729..32a0e5fe0f 100644
--- a/src/resolve/resolved-manager.h
+++ b/src/resolve/resolved-manager.h
@@ -126,6 +126,7 @@ struct Manager {
sd_event_source *sigusr1_event_source;
sd_event_source *sigusr2_event_source;
+ sd_event_source *sigrtmin1_event_source;
unsigned n_transactions_total;
unsigned n_dnssec_verdict[_DNSSEC_VERDICT_MAX];
@@ -184,5 +185,6 @@ void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResource
bool manager_routable(Manager *m, int family);
void manager_flush_caches(Manager *m);
+void manager_reset_server_features(Manager *m);
void manager_cleanup_saved_user(Manager *m);
diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c
index 2af77b3407..e3d6a33409 100644
--- a/src/resolve/resolved-resolv-conf.c
+++ b/src/resolve/resolved-resolv-conf.c
@@ -26,6 +26,7 @@
#include "fileio.h"
#include "ordered-set.h"
#include "resolved-conf.h"
+#include "resolved-dns-server.h"
#include "resolved-resolv-conf.h"
#include "string-util.h"
#include "strv.h"
@@ -136,6 +137,11 @@ int manager_read_resolv_conf(Manager *m) {
if (m->unicast_scope)
dns_cache_flush(&m->unicast_scope->cache);
+ /* If /etc/resolv.conf changed, make sure to forget everything we learned about the DNS servers. After all we
+ * might now talk to a very different DNS server that just happens to have the same IP address as an old one
+ * (think 192.168.1.1). */
+ dns_server_reset_features_all(m->dns_servers);
+
return 0;
clear:
diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c
index 8fd81956b8..2eb7bfd030 100644
--- a/src/resolve/resolved.c
+++ b/src/resolve/resolved.c
@@ -80,7 +80,7 @@ int main(int argc, char *argv[]) {
goto finish;
}
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, SIGUSR2, -1) >= 0);
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, SIGUSR2, SIGRTMIN+1, -1) >= 0);
r = manager_new(&m);
if (r < 0) {