diff options
-rw-r--r-- | src/resolve/resolve-tool.c | 2 | ||||
-rw-r--r-- | src/resolve/resolved-dns-cache.c | 127 | ||||
-rw-r--r-- | src/resolve/resolved-dns-dnssec.c | 8 | ||||
-rw-r--r-- | src/resolve/resolved-dns-query.c | 12 | ||||
-rw-r--r-- | src/resolve/resolved-dns-server.c | 41 | ||||
-rw-r--r-- | src/resolve/resolved-dns-server.h | 2 | ||||
-rw-r--r-- | src/resolve/resolved-dns-stub.c | 47 | ||||
-rw-r--r-- | src/resolve/resolved-dns-transaction.c | 39 | ||||
-rw-r--r-- | src/resolve/resolved-dns-transaction.h | 2 | ||||
-rw-r--r-- | src/resolve/resolved-dns-trust-anchor.c | 25 |
10 files changed, 235 insertions, 70 deletions
diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c index 3cec36817a..32537ce6e8 100644 --- a/src/resolve/resolve-tool.c +++ b/src/resolve/resolve-tool.c @@ -38,7 +38,7 @@ #include "strv.h" #include "terminal-util.h" -#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) +#define DNS_CALL_TIMEOUT_USEC (90*USEC_PER_SEC) static int arg_family = AF_UNSPEC; static int arg_ifindex = 0; diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index c43a7865dc..46a25c2bee 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -34,6 +34,10 @@ /* We never keep any item longer than 2h in our cache */ #define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) +/* How long to cache strange rcodes, i.e. rcodes != SUCCESS and != NXDOMAIN (specifically: that's only SERVFAIL for + * now) */ +#define CACHE_TTL_STRANGE_RCODE_USEC (30 * USEC_PER_SEC) + typedef enum DnsCacheItemType DnsCacheItemType; typedef struct DnsCacheItem DnsCacheItem; @@ -41,12 +45,14 @@ enum DnsCacheItemType { DNS_CACHE_POSITIVE, DNS_CACHE_NODATA, DNS_CACHE_NXDOMAIN, + DNS_CACHE_RCODE, /* "strange" RCODE (effective only SERVFAIL for now) */ }; struct DnsCacheItem { DnsCacheItemType type; DnsResourceKey *key; DnsResourceRecord *rr; + int rcode; usec_t until; bool authenticated:1; @@ -60,6 +66,27 @@ struct DnsCacheItem { LIST_FIELDS(DnsCacheItem, by_key); }; +static const char *dns_cache_item_type_to_string(DnsCacheItem *item) { + assert(item); + + switch (item->type) { + + case DNS_CACHE_POSITIVE: + return "POSITIVE"; + + case DNS_CACHE_NODATA: + return "NODATA"; + + case DNS_CACHE_NXDOMAIN: + return "NXDOMAIN"; + + case DNS_CACHE_RCODE: + return dns_rcode_to_string(item->rcode); + } + + return NULL; +} + static void dns_cache_item_free(DnsCacheItem *i) { if (!i) return; @@ -484,7 +511,6 @@ static int dns_cache_put_negative( assert(c); assert(key); - assert(soa); assert(owner_address); /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly @@ -495,13 +521,17 @@ static int dns_cache_put_negative( if (dns_type_is_pseudo(key->type)) return 0; - if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { - log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - return 0; - } + if (IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) { + if (!soa) + return 0; - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) + /* For negative replies, check if we have a TTL of a SOA */ + if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { + log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + return 0; + } + } else if (rcode != DNS_RCODE_SERVFAIL) return 0; r = dns_cache_init(c); @@ -514,12 +544,17 @@ static int dns_cache_put_negative( if (!i) return -ENOMEM; - i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; - i->until = calculate_until(soa, nsec_ttl, timestamp, true); + i->type = + rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : + rcode == DNS_RCODE_NXDOMAIN ? DNS_CACHE_NXDOMAIN : DNS_CACHE_RCODE; + i->until = + i->type == DNS_CACHE_RCODE ? timestamp + CACHE_TTL_STRANGE_RCODE_USEC : + calculate_until(soa, nsec_ttl, timestamp, true); i->authenticated = authenticated; i->owner_family = owner_family; i->owner_address = *owner_address; i->prioq_idx = PRIOQ_IDX_NULL; + i->rcode = rcode; if (i->type == DNS_CACHE_NXDOMAIN) { /* NXDOMAIN entries should apply equally to all types, so we use ANY as @@ -543,7 +578,7 @@ static int dns_cache_put_negative( return r; log_debug("Added %s cache entry for %s "USEC_FMT"s", - i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", + dns_cache_item_type_to_string(i), dns_resource_key_to_string(i->key, key_str, sizeof key_str), (i->until - timestamp) / USEC_PER_SEC); @@ -615,6 +650,7 @@ int dns_cache_put( const union in_addr_union *owner_address) { DnsResourceRecord *soa = NULL, *rr; + bool weird_rcode = false; DnsAnswerFlags flags; unsigned cache_keys; int r, ifindex; @@ -624,18 +660,28 @@ int dns_cache_put( dns_cache_remove_previous(c, key, answer); - /* We only care for positive replies and NXDOMAINs, on all - * other replies we will simply flush the respective entries, - * and that's it */ - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) - return 0; + /* We only care for positive replies and NXDOMAINs, on all other replies we will simply flush the respective + * entries, and that's it. (Well, with one further exception: since some DNS zones (akamai!) return SERVFAIL + * consistently for some lookups, and forwarders tend to propagate that we'll cache that too, but only for a + * short time.) */ - if (dns_answer_size(answer) <= 0) { - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + if (IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) { - log_debug("Not caching negative entry without a SOA record: %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - return 0; + if (dns_answer_size(answer) <= 0) { + char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + + log_debug("Not caching negative entry without a SOA record: %s", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + return 0; + } + + } else { + /* Only cache SERVFAIL as "weird" rcode for now. We can add more later, should that turn out to be + * beneficial. */ + if (rcode != DNS_RCODE_SERVFAIL) + return 0; + + weird_rcode = true; } cache_keys = dns_answer_size(answer); @@ -690,19 +736,20 @@ int dns_cache_put( if (r > 0) return 0; - /* See https://tools.ietf.org/html/rfc2308, which say that a - * matching SOA record in the packet is used to enable - * negative caching. */ + /* See https://tools.ietf.org/html/rfc2308, which say that a matching SOA record in the packet is used to + * enable negative caching. We apply one exception though: if we are about to cache a weird rcode we do so + * regardless of a SOA. */ r = dns_answer_find_soa(answer, key, &soa, &flags); if (r < 0) goto fail; - if (r == 0) - return 0; - - /* Refuse using the SOA data if it is unsigned, but the key is - * signed */ - if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0) + if (r == 0 && !weird_rcode) return 0; + if (r > 0) { + /* Refuse using the SOA data if it is unsigned, but the key is + * signed */ + if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0) + return 0; + } r = dns_cache_put_negative( c, @@ -799,6 +846,7 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod DnsCacheItem *j, *first, *nsec = NULL; bool have_authenticated = false, have_non_authenticated = false; usec_t current; + int found_rcode = -1; assert(c); assert(key); @@ -817,6 +865,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod *ret = NULL; *rcode = DNS_RCODE_SUCCESS; + *authenticated = false; + return 0; } @@ -831,6 +881,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod *ret = NULL; *rcode = DNS_RCODE_SUCCESS; + *authenticated = false; + return 0; } @@ -842,6 +894,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod n++; } else if (j->type == DNS_CACHE_NXDOMAIN) nxdomain = true; + else if (j->type == DNS_CACHE_RCODE) + found_rcode = j->rcode; if (j->authenticated) have_authenticated = true; @@ -849,6 +903,19 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod have_non_authenticated = true; } + if (found_rcode >= 0) { + log_debug("RCODE %s cache hit for %s", + dns_rcode_to_string(found_rcode), + dns_resource_key_to_string(key, key_str, sizeof(key_str))); + + *ret = NULL; + *rcode = found_rcode; + *authenticated = false; + + c->n_hit++; + return 1; + } + if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) { /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from * the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ @@ -1042,7 +1109,7 @@ void dns_cache_dump(DnsCache *cache, FILE *f) { fputs(dns_resource_key_to_string(j->key, key_str, sizeof key_str), f); fputs(" -- ", f); - fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); + fputs(dns_cache_item_type_to_string(j), f); fputc('\n', f); } } diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 51327105d0..eddab58a81 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -1710,7 +1710,8 @@ static int dnssec_nsec_covers(DnsResourceRecord *rr, const char *name) { } static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) { - const char *common_suffix, *wc; + _cleanup_free_ char *wc = NULL; + const char *common_suffix; int r; assert(rr); @@ -1734,7 +1735,10 @@ static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) if (r <= 0) return r; - wc = strjoina("*.", common_suffix); + r = dns_name_concat("*", common_suffix, &wc); + if (r < 0) + return r; + return dns_name_between(dns_resource_key_name(rr->key), wc, rr->nsec.next_domain_name); } diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index c58845c3b6..2b091e6c45 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -28,7 +28,7 @@ #include "string-util.h" /* How long to wait for the query in total */ -#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) +#define QUERY_TIMEOUT_USEC (60 * USEC_PER_SEC) #define CNAME_MAX 8 #define QUERIES_MAX 2048 @@ -811,6 +811,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { q->answer = dns_answer_unref(q->answer); q->answer_rcode = 0; q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_authenticated = false; q->answer_errno = c->error_code; } @@ -847,15 +848,18 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { continue; default: - /* Any kind of failure? Store the data away, - * if there's nothing stored yet. */ - + /* Any kind of failure? Store the data away, if there's nothing stored yet. */ if (state == DNS_TRANSACTION_SUCCESS) continue; + /* If there's already an authenticated negative reply stored, then prefer that over any unauthenticated one */ + if (q->answer_authenticated && !t->answer_authenticated) + continue; + q->answer = dns_answer_unref(q->answer); q->answer_rcode = t->answer_rcode; q->answer_dnssec_result = t->answer_dnssec_result; + q->answer_authenticated = t->answer_authenticated; q->answer_errno = t->answer_errno; state = t->state; diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 072cbfca1a..5498f7b9cb 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -28,7 +28,7 @@ #include "string-util.h" /* After how much time to repeat classic DNS requests */ -#define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) +#define DNS_TIMEOUT_MIN_USEC (750 * USEC_PER_MSEC) #define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) /* The amount of time to wait before retrying with a full feature set */ @@ -399,12 +399,24 @@ static bool dns_server_grace_period_expired(DnsServer *s) { } DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { + DnsServerFeatureLevel best; + assert(s); - if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && - dns_server_grace_period_expired(s)) { + /* Determine the best feature level we care about. If DNSSEC mode is off there's no point in using anything + * better than EDNS0, hence don't even try. */ + best = dns_server_get_dnssec_mode(s) == DNSSEC_NO ? + DNS_SERVER_FEATURE_LEVEL_EDNS0 : + DNS_SERVER_FEATURE_LEVEL_BEST; + + /* Clamp the feature level the highest level we care about. The DNSSEC mode might have changed since the last + * time, hence let's downgrade if we are still at a higher level. */ + if (s->possible_feature_level > best) + s->possible_feature_level = best; - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; + if (s->possible_feature_level < best && dns_server_grace_period_expired(s)) { + + s->possible_feature_level = best; dns_server_reset_counters(s); @@ -415,6 +427,8 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { dns_server_feature_level_to_string(s->possible_feature_level), dns_server_string(s)); + dns_server_flush_cache(s); + } else if (s->possible_feature_level <= s->verified_feature_level) s->possible_feature_level = s->verified_feature_level; else { @@ -792,6 +806,25 @@ DnssecMode dns_server_get_dnssec_mode(DnsServer *s) { return manager_get_dnssec_mode(s->manager); } +void dns_server_flush_cache(DnsServer *s) { + DnsServer *current; + DnsScope *scope; + + assert(s); + + /* Flush the cache of the scope this server belongs to */ + + current = s->link ? s->link->current_dns_server : s->manager->current_dns_server; + if (current != s) + return; + + scope = s->link ? s->link->unicast_scope : s->manager->unicast_scope; + if (!scope) + return; + + dns_cache_flush(&scope->cache); +} + 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 406282d864..bc95d53c6a 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -149,3 +149,5 @@ DnssecMode dns_server_get_dnssec_mode(DnsServer *s); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); extern const struct hash_ops dns_server_hash_ops; + +void dns_server_flush_cache(DnsServer *s); diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 7d43825960..7afbfedfb0 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -94,9 +94,18 @@ static int dns_stub_finish_reply_packet( assert(p); - /* If the client didn't do EDNS, clamp the rcode to 4 bit */ - if (!add_opt && rcode > 0xF) - rcode = DNS_RCODE_SERVFAIL; + if (!add_opt) { + /* If the client can't to EDNS0, don't do DO either */ + edns0_do = false; + + /* If the client didn't do EDNS, clamp the rcode to 4 bit */ + if (rcode > 0xF) + rcode = DNS_RCODE_SERVFAIL; + } + + /* Don't set the AD bit unless DO is on, too */ + if (!edns0_do) + ad = false; DNS_PACKET_HEADER(p)->id = id; @@ -162,7 +171,7 @@ static int dns_stub_send(Manager *m, DnsStream *s, DnsPacket *p, DnsPacket *repl return 0; } -static int dns_stub_send_failure(Manager *m, DnsStream *s, DnsPacket *p, int rcode) { +static int dns_stub_send_failure(Manager *m, DnsStream *s, DnsPacket *p, int rcode, bool authenticated) { _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; int r; @@ -173,7 +182,7 @@ static int dns_stub_send_failure(Manager *m, DnsStream *s, DnsPacket *p, int rco if (r < 0) return log_debug_errno(r, "Failed to make failure packet: %m"); - r = dns_stub_finish_reply_packet(reply, DNS_PACKET_ID(p), rcode, !!p->opt, DNS_PACKET_DO(p), false); + r = dns_stub_finish_reply_packet(reply, DNS_PACKET_ID(p), rcode, !!p->opt, DNS_PACKET_DO(p), authenticated); if (r < 0) return log_debug_errno(r, "Failed to build failure packet: %m"); @@ -198,7 +207,7 @@ static void dns_stub_query_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL); + (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false); break; } if (r < 0) { @@ -214,7 +223,7 @@ static void dns_stub_query_complete(DnsQuery *q) { q->answer_rcode, !!q->request_dns_packet->opt, DNS_PACKET_DO(q->request_dns_packet), - DNS_PACKET_DO(q->request_dns_packet) && dns_query_fully_authenticated(q)); + dns_query_fully_authenticated(q)); if (r < 0) { log_debug_errno(r, "Failed to finish reply packet: %m"); break; @@ -224,11 +233,11 @@ static void dns_stub_query_complete(DnsQuery *q) { break; case DNS_TRANSACTION_RCODE_FAILURE: - (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, q->answer_rcode); + (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, q->answer_rcode, dns_query_fully_authenticated(q)); break; case DNS_TRANSACTION_NOT_FOUND: - (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN); + (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN, dns_query_fully_authenticated(q)); break; case DNS_TRANSACTION_TIMEOUT: @@ -244,7 +253,7 @@ static void dns_stub_query_complete(DnsQuery *q) { case DNS_TRANSACTION_NO_TRUST_ANCHOR: case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: case DNS_TRANSACTION_NETWORK_DOWN: - (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL); + (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false); break; case DNS_TRANSACTION_NULL: @@ -291,52 +300,52 @@ static void dns_stub_process_query(Manager *m, DnsStream *s, DnsPacket *p) { if (in_addr_is_localhost(p->family, &p->sender) <= 0 || in_addr_is_localhost(p->family, &p->destination) <= 0) { log_error("Got packet on unexpected IP range, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); + dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL, false); goto fail; } r = dns_packet_extract(p); if (r < 0) { log_debug_errno(r, "Failed to extract resources from incoming packet, ignoring packet: %m"); - dns_stub_send_failure(m, s, p, DNS_RCODE_FORMERR); + dns_stub_send_failure(m, s, p, DNS_RCODE_FORMERR, false); goto fail; } if (!DNS_PACKET_VERSION_SUPPORTED(p)) { log_debug("Got EDNS OPT field with unsupported version number."); - dns_stub_send_failure(m, s, p, DNS_RCODE_BADVERS); + dns_stub_send_failure(m, s, p, DNS_RCODE_BADVERS, false); goto fail; } if (dns_type_is_obsolete(p->question->keys[0]->type)) { log_debug("Got message with obsolete key type, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); + dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP, false); goto fail; } if (dns_type_is_zone_transer(p->question->keys[0]->type)) { log_debug("Got request for zone transfer, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); + dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP, false); goto fail; } if (!DNS_PACKET_RD(p)) { /* If the "rd" bit is off (i.e. recursion was not requested), then refuse operation */ log_debug("Got request with recursion disabled, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_REFUSED); + dns_stub_send_failure(m, s, p, DNS_RCODE_REFUSED, false); goto fail; } if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) { log_debug("Got request with DNSSEC CD bit set, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); + dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP, false); goto fail; } r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH); if (r < 0) { log_error_errno(r, "Failed to generate query object: %m"); - dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); + dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL, false); goto fail; } @@ -356,7 +365,7 @@ static void dns_stub_process_query(Manager *m, DnsStream *s, DnsPacket *p) { r = dns_query_go(q); if (r < 0) { log_error_errno(r, "Failed to start query: %m"); - dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); + dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL, false); goto fail; } diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 9687409663..ecd7068683 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -31,6 +31,7 @@ #include "string-table.h" #define TRANSACTIONS_MAX 4096 +#define TRANSACTION_TCP_TIMEOUT_USEC (10U*USEC_PER_SEC) static void dns_transaction_reset_answer(DnsTransaction *t) { assert(t); @@ -832,7 +833,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { * should hence not attempt to access the query or transaction * after calling this function. */ - log_debug("Processing incoming packet on transaction %" PRIu16".", t->id); + log_debug("Processing incoming packet on transaction %" PRIu16". (rcode=%s)", t->id, dns_rcode_to_string(DNS_PACKET_RCODE(p))); switch (t->scope->protocol) { @@ -910,9 +911,13 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { /* Request failed, immediately try again with reduced features */ - if (t->current_feature_level <= DNS_SERVER_FEATURE_LEVEL_WORST) { - /* This was already at the lowest possible feature level? If so, we can't downgrade - * this transaction anymore, hence let's process the response, and accept the rcode. */ + if (t->current_feature_level <= DNS_SERVER_FEATURE_LEVEL_UDP) { + /* This was already at UDP feature level? If so, it doesn't make sense to downgrade + * this transaction anymore, hence let's process the response, and accept the + * rcode. Note that we don't retry on TCP, since that's a suitable way to mitigate + * packet loss, but is not going to give us better rcodes should we actually have + * managed to get them already at UDP level. */ + log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p))); break; } @@ -1135,7 +1140,7 @@ static int dns_transaction_emit_udp(DnsTransaction *t) { return r; if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP) - return -EAGAIN; + return -EAGAIN; /* Sorry, can't do UDP, try TCP! */ if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) return -EOPNOTSUPP; @@ -1212,9 +1217,17 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) { assert(t); assert(t->scope); + switch (t->scope->protocol) { case DNS_PROTOCOL_DNS: + + /* When we do TCP, grant a much longer timeout, as in this case there's no need for us to quickly + * resend, as the kernel does that anyway for us, and we really don't want to interrupt it in that + * needlessly. */ + if (t->stream) + return TRANSACTION_TCP_TIMEOUT_USEC; + assert(t->server); return t->server->resend_timeout; @@ -1579,7 +1592,7 @@ int dns_transaction_go(DnsTransaction *t) { r = dns_transaction_emit_udp(t); if (r == -EMSGSIZE) log_debug("Sending query via TCP since it is too large."); - if (r == -EAGAIN) + else if (r == -EAGAIN) log_debug("Sending query via TCP since server doesn't support UDP."); if (r == -EMSGSIZE || r == -EAGAIN) r = dns_transaction_open_tcp(t); @@ -1996,8 +2009,18 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { r = dns_resource_key_match_rr(t->key, rr, NULL); if (r < 0) return r; - if (r == 0) - continue; + if (r == 0) { + /* Hmm, so this SOA RR doesn't match our original question. In this case, maybe this is + * a negative reply, and we need the a SOA RR's TTL in order to cache a negative entry? + * If so, we need to validate it, too. */ + + r = dns_answer_match_key(t->answer, t->key, NULL); + if (r < 0) + return r; + if (r > 0) /* positive reply, we won't need the SOA and hence don't need to validate + * it. */ + continue; + } r = dnssec_has_rrsig(t->answer, rr->key); if (r < 0) diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index c4fb51958d..a8d97738ef 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -178,7 +178,7 @@ DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; #define MDNS_PROBING_INTERVAL_USEC (250 * USEC_PER_MSEC) /* Maximum attempts to send DNS requests, across all DNS servers */ -#define DNS_TRANSACTION_ATTEMPTS_MAX 16 +#define DNS_TRANSACTION_ATTEMPTS_MAX 24 /* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ #define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c index d8529f8317..7e08cba4e1 100644 --- a/src/resolve/resolved-dns-trust-anchor.c +++ b/src/resolve/resolved-dns-trust-anchor.c @@ -547,10 +547,33 @@ int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *ke } int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) { + int r; + assert(d); assert(name); - return set_contains(d->negative_by_name, name); + for (;;) { + /* If the domain is listed as-is in the NTA database, then that counts */ + if (set_contains(d->negative_by_name, name)) + return true; + + /* If the domain isn't listed as NTA, but is listed as positive trust anchor, then that counts. See RFC + * 7646, section 1.1 */ + if (hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name))) + return false; + + if (hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_KEY, name))) + return false; + + /* And now, let's look at the parent, and check that too */ + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + break; + } + + return false; } static int dns_trust_anchor_revoked_put(DnsTrustAnchor *d, DnsResourceRecord *rr) { |