diff options
Diffstat (limited to 'src/resolve/resolved-dns-server.c')
-rw-r--r-- | src/resolve/resolved-dns-server.c | 175 |
1 files changed, 131 insertions, 44 deletions
diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 0969e31e8a..e1d2025863 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -19,6 +19,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sd-messages.h> + #include "alloc-util.h" #include "resolved-dns-server.h" #include "resolved-resolv-conf.h" @@ -232,7 +234,9 @@ static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) { return; if (s->verified_feature_level != level) { - log_debug("Verified feature level %s.", dns_server_feature_level_to_string(level)); + log_debug("Verified we get a response at feature level %s from DNS server %s.", + dns_server_feature_level_to_string(level), + dns_server_string(s)); s->verified_feature_level = level; } @@ -246,13 +250,18 @@ void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLeve if (s->possible_feature_level == level) s->n_failed_udp = 0; + /* If the RRSIG data is missing, then we can only validate EDNS0 at max */ + if (s->packet_rrsig_missing && level >= DNS_SERVER_FEATURE_LEVEL_DO) + level = DNS_SERVER_FEATURE_LEVEL_DO - 1; + + /* If the OPT RR got lost, then we can only validate UDP at max */ + if (s->packet_bad_opt && level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) + level = DNS_SERVER_FEATURE_LEVEL_EDNS0 - 1; + + /* Even if we successfully receive a reply to a request announcing support for large packets, + that does not mean we can necessarily receive large packets. */ if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) - /* Even if we successfully receive a reply to a request announcing support for large packets, - that does not mean we can necessarily receive large packets. */ - dns_server_verified(s, DNS_SERVER_FEATURE_LEVEL_LARGE - 1); - else - /* A successful UDP reply, verifies UDP, ENDS0 and DO levels */ - dns_server_verified(s, level); + level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; } else if (protocol == IPPROTO_TCP) { @@ -260,9 +269,11 @@ void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLeve s->n_failed_tcp = 0; /* Successful TCP connections are only useful to verify the TCP feature level. */ - dns_server_verified(s, DNS_SERVER_FEATURE_LEVEL_TCP); + level = DNS_SERVER_FEATURE_LEVEL_TCP; } + dns_server_verified(s, level); + /* Remember the size of the largest UDP packet we received from a server, we know that we can always announce support for packets with at least this size. */ @@ -294,7 +305,6 @@ void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel le void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) { assert(s); - assert(s->manager); /* Invoked whenever we get a FORMERR, SERVFAIL or NOTIMP rcode from a server. */ @@ -306,7 +316,6 @@ void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) { void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) { assert(s); - assert(s->manager); /* Invoked whenever we get a packet with TC bit set. */ @@ -316,13 +325,30 @@ void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) { s->packet_truncated = true; } -void dns_server_packet_rrsig_missing(DnsServer *s) { +void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level) { assert(s); - assert(s->manager); - log_warning("DNS server %s does not augment replies with RRSIG records, DNSSEC not available.", dns_server_string(s)); + if (level < DNS_SERVER_FEATURE_LEVEL_DO) + return; + + /* If the RRSIG RRs are missing, we have to downgrade what we previously verified */ + if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) + s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_DO-1; - s->rrsig_missing = true; + s->packet_rrsig_missing = true; +} + +void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) + return; + + /* If the OPT RR got lost, we have to downgrade what we previously verified */ + if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) + s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0-1; + + s->packet_bad_opt = true; } static bool dns_server_grace_period_expired(DnsServer *s) { @@ -352,6 +378,17 @@ static void dns_server_reset_counters(DnsServer *s) { s->packet_failed = false; s->packet_truncated = false; s->verified_usec = 0; + + /* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the + * grace period ends, but not when lowering the possible feature level, as a lower level feature level should + * not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's + * indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that + * either. + * + * This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A), + * but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not + * reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be + * incomplete. */ } DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { @@ -361,11 +398,13 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { dns_server_grace_period_expired(s)) { s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; - s->rrsig_missing = false; dns_server_reset_counters(s); - log_info("Grace period over, resuming full feature set (%s) for DNS server %s", + s->packet_bad_opt = false; + s->packet_rrsig_missing = false; + + log_info("Grace period over, resuming full feature set (%s) for DNS server %s.", dns_server_feature_level_to_string(s->possible_feature_level), dns_server_string(s)); @@ -375,46 +414,75 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { DnsServerFeatureLevel p = s->possible_feature_level; if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP) + s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP) { /* We are at the TCP (lowest) level, and we tried a couple of TCP connections, and it didn't * work. Upgrade back to UDP again. */ + log_debug("Reached maximum number of failed TCP connection attempts, trying UDP again..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; + + } else if (s->packet_bad_opt && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) { + + /* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to below + * EDNS0 levels. After all, some records generate different responses with and without OPT RR + * in the request. Example: + * https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ + + log_debug("Server doesn't support EDNS(0) properly, downgrading feature level..."); s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; - else if ((s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) || - (s->packet_failed && - s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) || - (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->packet_truncated && - s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP)) - - /* Downgrade the feature one level, maybe things will work better then. We do this under any of - * three conditions: - * - * 1. We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If - * the packets are lost, maybe the server cannot parse them, hence downgrading sounds like a - * good idea. We might downgrade all the way down to TCP this way. - * - * 2. We got a failure packet, and are at a feature level above UDP. Note that in this case we - * downgrade no further than UDP, under the assumption that a failure packet indicates an - * incompatible packet contents, but not a problem with the transport. - * - * 3. We got too many TCP connection failures in a row, we had at least one truncated packet, - * and are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or - * EDNS0 data we hope to make the packet smaller, so that it still works via UDP given that - * TCP appears not to be a fallback. Note that if we are already at the lowest UDP level, we - * don't go further down, since that's TCP, and TCP failed too often after all. - */ + } else if (s->packet_rrsig_missing && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) { + /* RRSIG data was missing on a EDNS0 packet with DO bit set. This means the server doesn't + * augment responses with DNSSEC RRs. If so, let's better not ask the server for it anymore, + * after all some servers generate different replies depending if an OPT RR is in the query or + * not. */ + + log_debug("Detected server responses lack RRSIG records, downgrading feature level..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0; + + } else if (s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) { + + /* We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If the + * packets are lost, maybe the server cannot parse them, hence downgrading sounds like a good + * idea. We might downgrade all the way down to TCP this way. */ + + log_debug("Lost too many UDP packets, downgrading feature level..."); + s->possible_feature_level--; + + } else if (s->packet_failed && + s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { + + /* We got a failure packet, and are at a feature level above UDP. Note that in this case we + * downgrade no further than UDP, under the assumption that a failure packet indicates an + * incompatible packet contents, but not a problem with the transport. */ + + log_debug("Got server failure, downgrading feature level..."); + s->possible_feature_level--; + + } else if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->packet_truncated && + s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { + + /* We got too many TCP connection failures in a row, we had at least one truncated packet, and + * are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or EDNS0 + * data we hope to make the packet smaller, so that it still works via UDP given that TCP + * appears not to be a fallback. Note that if we are already at the lowest UDP level, we don't + * go further down, since that's TCP, and TCP failed too often after all. */ + + log_debug("Got too many failed TCP connection failures and truncated UDP packets, downgrading feature level..."); s->possible_feature_level--; + } if (p != s->possible_feature_level) { /* We changed the feature level, reset the counting */ dns_server_reset_counters(s); - log_warning("Using degraded feature set (%s) for DNS server %s", + log_warning("Using degraded feature set (%s) for DNS server %s.", dns_server_feature_level_to_string(s->possible_feature_level), dns_server_string(s)); } @@ -468,7 +536,10 @@ bool dns_server_dnssec_supported(DnsServer *server) { if (server->possible_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) return false; - if (server->rrsig_missing) + if (server->packet_bad_opt) + return false; + + if (server->packet_rrsig_missing) return false; /* DNSSEC servers need to support TCP properly (see RFC5966), if they don't, we assume DNSSEC is borked too */ @@ -478,6 +549,22 @@ bool dns_server_dnssec_supported(DnsServer *server) { return true; } +void dns_server_warn_downgrade(DnsServer *server) { + assert(server); + + if (server->warned_downgrade) + return; + + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_DOWNGRADE), + LOG_MESSAGE("Server %s does not support DNSSEC, downgrading to non-DNSSEC mode.", dns_server_string(server)), + "DNS_SERVER=%s", dns_server_string(server), + "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(server->possible_feature_level), + NULL); + + server->warned_downgrade = true; +} + static void dns_server_hash_func(const void *p, struct siphash *state) { const DnsServer *s = p; |