diff options
Diffstat (limited to 'src/resolve')
31 files changed, 2703 insertions, 451 deletions
diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c index 63b4b36e88..8281da3b7c 100644 --- a/src/resolve/dns-type.c +++ b/src/resolve/dns-type.c @@ -44,7 +44,44 @@ int dns_type_from_string(const char *s) { return sc->id; } -/* XXX: find an authoritative list of all pseudo types? */ -bool dns_type_is_pseudo(int n) { - return IN_SET(n, DNS_TYPE_ANY, DNS_TYPE_AXFR, DNS_TYPE_IXFR, DNS_TYPE_OPT); +bool dns_type_is_pseudo(uint16_t type) { + + /* Checks whether the specified type is a "pseudo-type". What + * a "pseudo-type" precisely is, is defined only very weakly, + * but apparently entails all RR types that are not actually + * stored as RRs on the server and should hence also not be + * cached. We use this list primarily to validate NSEC type + * bitfields, and to verify what to cache. */ + + return IN_SET(type, + 0, /* A Pseudo RR type, according to RFC 2931 */ + DNS_TYPE_ANY, + DNS_TYPE_AXFR, + DNS_TYPE_IXFR, + DNS_TYPE_OPT, + DNS_TYPE_TSIG, + DNS_TYPE_TKEY + ); +} + +bool dns_type_is_valid_query(uint16_t type) { + + /* The types valid as questions in packets */ + + return !IN_SET(type, + 0, + DNS_TYPE_OPT, + DNS_TYPE_TSIG, + DNS_TYPE_TKEY); +} + +bool dns_type_is_valid_rr(uint16_t type) { + + /* The types valid as RR in packets (but not necessarily + * stored on servers). */ + + return !IN_SET(type, + DNS_TYPE_ANY, + DNS_TYPE_AXFR, + DNS_TYPE_IXFR); } diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h index 950af36ee3..038a0d0e54 100644 --- a/src/resolve/dns-type.h +++ b/src/resolve/dns-type.h @@ -25,7 +25,10 @@ const char *dns_type_to_string(int type); int dns_type_from_string(const char *s); -bool dns_type_is_pseudo(int n); + +bool dns_type_is_pseudo(uint16_t type); +bool dns_type_is_valid_query(uint16_t type); +bool dns_type_is_valid_rr(uint16_t type); /* DNS record types, taken from * http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 0ceca56371..c8c0d3d9b8 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -60,6 +60,9 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_ABORTED: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted"); + case DNS_TRANSACTION_DNSSEC_FAILED: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "DNSSEC validation failed"); + case DNS_TRANSACTION_FAILURE: { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -550,6 +553,9 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd if (r == 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name); + if (!dns_type_is_valid_query(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid RR type for query %" PRIu16, type); + r = check_ifindex_flags(ifindex, &flags, 0, error); if (r < 0) return r; diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h index db5ee57b51..6014d345f3 100644 --- a/src/resolve/resolved-def.h +++ b/src/resolve/resolved-def.h @@ -24,6 +24,8 @@ #define SD_RESOLVED_DNS (UINT64_C(1) << 0) #define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1) #define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2) +#define SD_RESOLVED_MDNS_IPV4 (UINT64_C(1) << 3) +#define SD_RESOLVED_MDNS_IPV6 (UINT64_C(1) << 4) #define SD_RESOLVED_NO_CNAME (UINT64_C(1) << 5) #define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6) #define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7) @@ -31,4 +33,6 @@ #define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9) #define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) -#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) +#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) + +#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index 4db67f7278..5355303bd3 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -22,6 +22,7 @@ #include "alloc-util.h" #include "dns-domain.h" #include "resolved-dns-answer.h" +#include "resolved-dns-dnssec.h" #include "string-util.h" DnsAnswer *dns_answer_new(unsigned n) { @@ -46,6 +47,18 @@ DnsAnswer *dns_answer_ref(DnsAnswer *a) { return a; } +static void dns_answer_flush(DnsAnswer *a) { + DnsResourceRecord *rr; + + if (!a) + return; + + DNS_ANSWER_FOREACH(rr, a) + dns_resource_record_unref(rr); + + a->n_rrs = 0; +} + DnsAnswer *dns_answer_unref(DnsAnswer *a) { if (!a) return NULL; @@ -53,11 +66,7 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a) { assert(a->n_ref > 0); if (a->n_ref == 1) { - unsigned i; - - for (i = 0; i < a->n_rrs; i++) - dns_resource_record_unref(a->items[i].rr); - + dns_answer_flush(a); free(a); } else a->n_ref--; @@ -65,6 +74,35 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a) { return NULL; } +static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) { + assert(rr); + + if (!a) + return -ENOSPC; + + if (a->n_rrs >= a->n_allocated) + return -ENOSPC; + + a->items[a->n_rrs].rr = dns_resource_record_ref(rr); + a->items[a->n_rrs].ifindex = ifindex; + a->n_rrs++; + + return 1; +} + +static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { + DnsResourceRecord *rr; + int ifindex, r; + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, source) { + r = dns_answer_add_raw(a, rr, ifindex); + if (r < 0) + return r; + } + + return 0; +} + int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) { unsigned i; int r; @@ -73,6 +111,8 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) { if (!a) return -ENOSPC; + if (a->n_ref > 1) + return -EBUSY; for (i = 0; i < a->n_rrs; i++) { if (a->items[i].ifindex != ifindex) @@ -95,14 +135,33 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) { } } - if (a->n_rrs >= a->n_allocated) - return -ENOSPC; + return dns_answer_add_raw(a, rr, ifindex); +} - a->items[a->n_rrs].rr = dns_resource_record_ref(rr); - a->items[a->n_rrs].ifindex = ifindex; - a->n_rrs++; +static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { + DnsResourceRecord *rr; + int ifindex, r; - return 1; + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, b) { + r = dns_answer_add(a, rr, ifindex); + if (r < 0) + return r; + } + + return 0; +} + +int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex) { + int r; + + assert(a); + assert(rr); + + r = dns_answer_reserve_or_clone(a, 1); + if (r < 0) + return r; + + return dns_answer_add(*a, rr, ifindex); } int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { @@ -131,8 +190,8 @@ int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { return dns_answer_add(a, soa, 0); } -int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key) { - unsigned i; +int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key) { + DnsResourceRecord *i; int r; assert(key); @@ -140,8 +199,8 @@ int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key) { if (!a) return 0; - for (i = 0; i < a->n_rrs; i++) { - r = dns_resource_key_match_rr(key, a->items[i].rr, NULL); + DNS_ANSWER_FOREACH(i, a) { + r = dns_resource_key_match_rr(key, i, NULL); if (r < 0) return r; if (r > 0) @@ -151,24 +210,28 @@ int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key) { return 0; } -int dns_answer_match_soa(DnsResourceKey *key, DnsResourceKey *soa) { - if (soa->class != DNS_CLASS_IN) - return 0; +int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr) { + DnsResourceRecord *i; + int r; - if (soa->type != DNS_TYPE_SOA) - return 0; + assert(rr); - if (!dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa))) - return 0; + DNS_ANSWER_FOREACH(i, a) { + r = dns_resource_record_equal(i, rr); + if (r < 0) + return r; + if (r > 0) + return 1; + } - return 1; + return 0; } -int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **ret) { - unsigned i; +int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret) { + DnsResourceRecord *rr; + int r; assert(key); - assert(ret); if (!a) return 0; @@ -177,10 +240,40 @@ int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **r if (key->type == DNS_TYPE_SOA) return 0; - for (i = 0; i < a->n_rrs; i++) { + DNS_ANSWER_FOREACH(rr, a) { + r = dns_resource_key_match_soa(key, rr->key); + if (r < 0) + return r; + if (r > 0) { + if (ret) + *ret = rr; + return 1; + } + } + + return 0; +} + +int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret) { + DnsResourceRecord *rr; + int r; - if (dns_answer_match_soa(key, a->items[i].rr->key)) { - *ret = a->items[i].rr; + assert(key); + + if (!a) + return 0; + + /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ + if (key->type == DNS_TYPE_CNAME || key->type == DNS_TYPE_DNAME) + return 0; + + DNS_ANSWER_FOREACH(rr, a) { + r = dns_resource_key_match_cname_or_dname(key, rr->key, NULL); + if (r < 0) + return r; + if (r > 0) { + if (ret) + *ret = rr; return 1; } } @@ -188,41 +281,169 @@ int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **r return 0; } -DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b) { - _cleanup_(dns_answer_unrefp) DnsAnswer *ret = NULL; - DnsAnswer *k; +int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret) { + _cleanup_(dns_answer_unrefp) DnsAnswer *k = NULL; + int r; + + assert(ret); + + if (dns_answer_size(a) <= 0) { + *ret = dns_answer_ref(b); + return 0; + } + + if (dns_answer_size(b) <= 0) { + *ret = dns_answer_ref(a); + return 0; + } + + k = dns_answer_new(a->n_rrs + b->n_rrs); + if (!k) + return -ENOMEM; + + r = dns_answer_add_raw_all(k, a); + if (r < 0) + return r; + + r = dns_answer_add_all(k, b); + if (r < 0) + return r; + + *ret = k; + k = NULL; + + return 0; +} + +int dns_answer_extend(DnsAnswer **a, DnsAnswer *b) { + DnsAnswer *merged; + int r; + + assert(a); + + r = dns_answer_merge(*a, b, &merged); + if (r < 0) + return r; + + dns_answer_unref(*a); + *a = merged; + + return 0; +} + +int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { + bool found = false, other = false; + DnsResourceRecord *rr; unsigned i; int r; - if (a && (!b || b->n_rrs <= 0)) - return dns_answer_ref(a); - if ((!a || a->n_rrs <= 0) && b) - return dns_answer_ref(b); + assert(a); + assert(key); - ret = dns_answer_new((a ? a->n_rrs : 0) + (b ? b->n_rrs : 0)); - if (!ret) - return NULL; + /* Remove all entries matching the specified key from *a */ - if (a) { - for (i = 0; i < a->n_rrs; i++) { - r = dns_answer_add(ret, a->items[i].rr, a->items[i].ifindex); - if (r < 0) - return NULL; - } + DNS_ANSWER_FOREACH(rr, *a) { + r = dns_resource_key_equal(rr->key, key); + if (r < 0) + return r; + if (r > 0) + found = true; + else + other = true; + + if (found && other) + break; } - if (b) { - for (i = 0; i < b->n_rrs; i++) { - r = dns_answer_add(ret, b->items[i].rr, b->items[i].ifindex); + if (!found) + return 0; + + if (!other) { + *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ + return 1; + } + + if ((*a)->n_ref > 1) { + _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; + int ifindex; + + copy = dns_answer_new((*a)->n_rrs); + if (!copy) + return -ENOMEM; + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, *a) { + r = dns_resource_key_equal(rr->key, key); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_answer_add_raw(copy, rr, ifindex); if (r < 0) - return NULL; + return r; } + + dns_answer_unref(*a); + *a = copy; + copy = NULL; + + return 1; } - k = ret; - ret = NULL; + /* Only a single reference, edit in-place */ - return k; + i = 0; + for (;;) { + if (i >= (*a)->n_rrs) + break; + + r = dns_resource_key_equal((*a)->items[i].rr->key, key); + if (r < 0) + return r; + if (r > 0) { + /* Kill this entry */ + + dns_resource_record_unref((*a)->items[i].rr); + memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); + (*a)->n_rrs --; + continue; + + } else + /* Keep this entry */ + i++; + } + + return 1; +} + +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key) { + DnsResourceRecord *rr_source; + int ifindex_source, r; + + assert(a); + assert(key); + + /* Copy all RRs matching the specified key from source into *a */ + + DNS_ANSWER_FOREACH_IFINDEX(rr_source, ifindex_source, source) { + + r = dns_resource_key_equal(rr_source->key, key); + if (r < 0) + return r; + if (r == 0) + continue; + + /* Make space for at least one entry */ + r = dns_answer_reserve_or_clone(a, 1); + if (r < 0) + return r; + + r = dns_answer_add(*a, rr_source, ifindex_source); + if (r < 0) + return r; + } + + return 0; } void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { @@ -261,6 +482,8 @@ void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { int dns_answer_reserve(DnsAnswer **a, unsigned n_free) { DnsAnswer *n; + assert(a); + if (n_free <= 0) return 0; @@ -275,6 +498,9 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free) { if ((*a)->n_allocated >= ns) return 0; + /* Allocate more than we need */ + ns *= 2; + n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns); if (!n) return -ENOMEM; @@ -289,3 +515,36 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free) { *a = n; return 0; } + +int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) { + _cleanup_(dns_answer_unrefp) DnsAnswer *n = NULL; + int r; + + assert(a); + + /* Tries to extend the DnsAnswer object. And if that's not + * possibly, since we are not the sole owner, then allocate a + * new, appropriately sized one. Either way, after this call + * the object will only have a single reference, and has room + * for at least the specified number of RRs. */ + + r = dns_answer_reserve(a, n_free); + if (r != -EBUSY) + return r; + + assert(*a); + + n = dns_answer_new(((*a)->n_rrs + n_free) * 2); + if (!n) + return -ENOMEM; + + r = dns_answer_add_raw_all(n, *a); + if (r < 0) + return r; + + dns_answer_unref(*a); + *a = n; + n = NULL; + + return 0; +} diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index 89c254b02e..56b462ed7e 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -30,7 +30,9 @@ typedef struct DnsAnswerItem DnsAnswerItem; /* A simple array of resource records. We keep track of the * originating ifindex for each RR where that makes sense, so that we * can qualify A and AAAA RRs referring to a local link with the - * right ifindex. */ + * right ifindex. + * + * Note that we usually encode the empty answer as a simple NULL. */ struct DnsAnswerItem { DnsResourceRecord *rr; @@ -48,15 +50,29 @@ DnsAnswer *dns_answer_ref(DnsAnswer *a); DnsAnswer *dns_answer_unref(DnsAnswer *a); int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex); +int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex); int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl); -int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key); -int dns_answer_match_soa(DnsResourceKey *key, DnsResourceKey *soa); -int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **ret); -DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b); +int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key); +int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr); + +int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret); +int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret); + +int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret); +int dns_answer_extend(DnsAnswer **a, DnsAnswer *b); + void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local); int dns_answer_reserve(DnsAnswer **a, unsigned n_free); +int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free); + +int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key); + +static inline unsigned dns_answer_size(DnsAnswer *a) { + return a ? a->n_rrs : 0; +} DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); @@ -70,13 +86,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); #define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a) -#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifindex, a) \ +#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifi, a) \ for (unsigned UNIQ_T(i, q) = ({ \ (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - (ifindex) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ + (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ 0; \ }); \ (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), (ifindex) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) + UNIQ_T(i, q)++, (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) #define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) _DNS_ANSWER_FOREACH_IFINDEX(UNIQ, kk, ifindex, a) diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index bcb9994a8c..676fa08ffb 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -21,6 +21,7 @@ #include "alloc-util.h" #include "dns-domain.h" +#include "resolved-dns-answer.h" #include "resolved-dns-cache.h" #include "resolved-dns-packet.h" #include "string-util.h" @@ -275,7 +276,7 @@ static int dns_cache_put_positive( _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; _cleanup_free_ char *key_str = NULL; DnsCacheItem *existing; - int r; + int r, k; assert(c); assert(rr); @@ -283,21 +284,25 @@ static int dns_cache_put_positive( /* New TTL is 0? Delete the entry... */ if (rr->ttl <= 0) { - r = dns_resource_key_to_string(rr->key, &key_str); - if (r < 0) - return r; + k = dns_cache_remove(c, rr->key); - if (dns_cache_remove(c, rr->key)) - log_debug("Removed zero TTL entry from cache: %s", key_str); - else - log_debug("Not caching zero TTL cache entry: %s", key_str); + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(rr->key, &key_str); + if (r < 0) + return r; + + if (k > 0) + log_debug("Removed zero TTL entry from cache: %s", key_str); + else + log_debug("Not caching zero TTL cache entry: %s", key_str); + } return 0; } if (rr->key->class == DNS_CLASS_ANY) return 0; - if (rr->key->type == DNS_TYPE_ANY) + if (dns_type_is_pseudo(rr->key->type)) return 0; /* Entry exists already? Update TTL and timestamp */ @@ -331,11 +336,13 @@ static int dns_cache_put_positive( if (r < 0) return r; - r = dns_resource_key_to_string(i->key, &key_str); - if (r < 0) - return r; + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(i->key, &key_str); + if (r < 0) + return r; - log_debug("Added cache entry for %s", key_str); + log_debug("Added cache entry for %s", key_str); + } i = NULL; return 0; @@ -363,14 +370,18 @@ static int dns_cache_put_negative( if (key->class == DNS_CLASS_ANY) return 0; - if (key->type == DNS_TYPE_ANY) + if (dns_type_is_pseudo(key->type)) + /* ANY is particularly important to filter out as we + * use this as a pseudo-type for NXDOMAIN entries */ return 0; if (soa_ttl <= 0) { - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; - log_debug("Not caching negative entry with zero SOA TTL: %s", key_str); + log_debug("Not caching negative entry with zero SOA TTL: %s", key_str); + } return 0; } @@ -389,22 +400,32 @@ static int dns_cache_put_negative( return -ENOMEM; i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; - i->key = dns_resource_key_ref(key); i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); i->prioq_idx = PRIOQ_IDX_NULL; i->owner_family = owner_family; i->owner_address = *owner_address; i->authenticated = authenticated; + if (i->type == DNS_CACHE_NXDOMAIN) { + /* NXDOMAIN entries should apply equally to all types, so we use ANY as + * a pseudo type for this purpose here. */ + i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(key)); + if (!i->key) + return -ENOMEM; + } else + i->key = dns_resource_key_ref(key); + r = dns_cache_link_item(c, i); if (r < 0) return r; - r = dns_resource_key_to_string(i->key, &key_str); - if (r < 0) - return r; + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(i->key, &key_str); + if (r < 0) + return r; - log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str); + log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str); + } i = NULL; return 0; @@ -421,7 +442,7 @@ int dns_cache_put( int owner_family, const union in_addr_union *owner_address) { - DnsResourceRecord *soa = NULL; + DnsResourceRecord *soa = NULL, *rr; unsigned cache_keys, i; int r; @@ -433,11 +454,23 @@ int dns_cache_put( dns_cache_remove(c, key); } - if (!answer) + if (!answer) { + if (log_get_max_level() >= LOG_DEBUG) { + _cleanup_free_ char *key_str = NULL; + + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("Not caching negative entry without a SOA record: %s", key_str); + } + return 0; + } - for (i = 0; i < answer->n_rrs; i++) - dns_cache_remove(c, answer->items[i].rr->key); + DNS_ANSWER_FOREACH(rr, answer) + if (rr->key->cache_flush) + dns_cache_remove(c, rr->key); /* We only care for positive replies and NXDOMAINs, on all * other replies we will simply flush the respective entries, @@ -459,7 +492,9 @@ int dns_cache_put( /* Second, add in positive entries for all contained RRs */ for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) { - r = dns_cache_put_positive(c, answer->items[i].rr, authenticated, timestamp, owner_family, owner_address); + rr = answer->items[i].rr; + + r = dns_cache_put_positive(c, rr, authenticated, timestamp, owner_family, owner_address); if (r < 0) goto fail; } @@ -468,15 +503,24 @@ int dns_cache_put( return 0; /* Third, add in negative entries if the key has no RR */ - r = dns_answer_contains(answer, key); + r = dns_answer_match_key(answer, key); + if (r < 0) + goto fail; + if (r > 0) + return 0; + + /* But not if it has a matching CNAME/DNAME (the negative + * caching will be done on the canonical name, not on the + * alias) */ + r = dns_answer_find_cname_or_dname(answer, key, NULL); if (r < 0) goto fail; 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 to enable negative caching. */ + /* See https://tools.ietf.org/html/rfc2308, which say that a + * matching SOA record in the packet is used to to enable + * negative caching. */ r = dns_answer_find_soa(answer, key, &soa); if (r < 0) @@ -484,31 +528,6 @@ int dns_cache_put( if (r == 0) return 0; - /* Also, if the requested key is an alias, the negative response should - be cached for each name in the redirect chain. Any CNAME record in - the response is from the redirection chain, though only the final one - is guaranteed to be included. This means that we cannot verify the - chain and that we need to cache them all as it may be incomplete. */ - for (i = 0; i < answer->n_rrs; i++) { - DnsResourceRecord *answer_rr = answer->items[i].rr; - - if (answer_rr->key->type == DNS_TYPE_CNAME) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *canonical_key = NULL; - - canonical_key = dns_resource_key_new_redirect(key, answer_rr); - if (!canonical_key) - goto fail; - - /* Let's not add negative cache entries for records outside the current zone. */ - if (!dns_answer_match_soa(canonical_key, soa->key)) - continue; - - r = dns_cache_put_negative(c, canonical_key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address); - if (r < 0) - goto fail; - } - } - r = dns_cache_put_negative(c, key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address); if (r < 0) goto fail; @@ -540,36 +559,50 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D * much, after all this is just a cache */ i = hashmap_get(c->by_key, k); - if (i || IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME, DNS_TYPE_NSEC)) + if (i) return i; n = DNS_RESOURCE_KEY_NAME(k); - /* Check if we have an NSEC record instead for the name. */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n)); - if (i) + /* Check if we have an NXDOMAIN cache item for the name, notice that we use + * the pseudo-type ANY for NXDOMAIN cache items. */ + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n)); + if (i && i->type == DNS_CACHE_NXDOMAIN) return i; - /* Check if we have a CNAME record instead */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n)); - if (i) - return i; + /* The following record types should never be redirected. See + * <https://tools.ietf.org/html/rfc4035#section-2.5>. */ + if (!IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME, + DNS_TYPE_NSEC3, DNS_TYPE_NSEC, DNS_TYPE_RRSIG, + DNS_TYPE_NXT, DNS_TYPE_SIG, DNS_TYPE_KEY)) { + /* Check if we have a CNAME record instead */ + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n)); + if (i) + return i; - /* OK, let's look for cached DNAME records. */ - for (;;) { - char label[DNS_LABEL_MAX]; + /* OK, let's look for cached DNAME records. */ + for (;;) { + char label[DNS_LABEL_MAX]; + + if (isempty(n)) + return NULL; - if (isempty(n)) - return NULL; + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n)); + if (i) + return i; - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n)); + /* Jump one label ahead */ + r = dns_label_unescape(&n, label, sizeof(label)); + if (r <= 0) + return NULL; + } + } + + if (k-> type != DNS_TYPE_NSEC) { + /* Check if we have an NSEC record instead for the name. */ + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n)); if (i) return i; - - /* Jump one label ahead */ - r = dns_label_unescape(&n, label, sizeof(label)); - if (r <= 0) - return NULL; } return NULL; @@ -596,11 +629,13 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r /* If we have ANY lookups we don't use the cache, so * that the caller refreshes via the network. */ - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; - log_debug("Ignoring cache for ANY lookup: %s", key_str); + log_debug("Ignoring cache for ANY lookup: %s", key_str); + } *ret = NULL; *rcode = DNS_RCODE_SUCCESS; @@ -611,11 +646,13 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r if (!first) { /* If one question cannot be answered we need to refresh */ - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; - log_debug("Cache miss for %s", key_str); + log_debug("Cache miss for %s", key_str); + } *ret = NULL; *rcode = DNS_RCODE_SUCCESS; @@ -637,15 +674,17 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r have_non_authenticated = true; } - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; - if (nsec && key->type != DNS_TYPE_NSEC) { - log_debug("NSEC NODATA cache hit for %s", key_str); + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("NSEC NODATA cache hit for %s", key_str); + } /* We only found an NSEC record that matches our name. - * If it says the type doesn't exit report + * If it says the type doesn't exist report * NODATA. Otherwise report a cache miss. */ *ret = NULL; @@ -657,10 +696,16 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME); } - log_debug("%s cache hit for %s", - n > 0 ? "Positive" : - nxdomain ? "NXDOMAIN" : "NODATA", - key_str); + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("%s cache hit for %s", + n > 0 ? "Positive" : + nxdomain ? "NXDOMAIN" : "NODATA", + key_str); + } if (n <= 0) { *ret = NULL; @@ -726,6 +771,57 @@ int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_ return 1; } +int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { + unsigned ancount = 0; + Iterator iterator; + DnsCacheItem *i; + int r; + + assert(cache); + assert(p); + + HASHMAP_FOREACH(i, cache->by_key, iterator) { + DnsCacheItem *j; + + LIST_FOREACH(by_key, j, i) { + _cleanup_free_ char *t = NULL; + + if (!j->rr) + continue; + + if (!dns_key_is_shared(j->rr->key)) + continue; + + r = dns_packet_append_rr(p, j->rr, NULL, NULL); + if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) { + /* For mDNS, if we're unable to stuff all known answers into the given packet, + * allocate a new one, push the RR into that one and link it to the current one. + */ + + DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); + ancount = 0; + + r = dns_packet_new_query(&p->more, p->protocol, 0, true); + if (r < 0) + return r; + + /* continue with new packet */ + p = p->more; + r = dns_packet_append_rr(p, j->rr, NULL, NULL); + } + + if (r < 0) + return r; + + ancount ++; + } + } + + DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); + + return 0; +} + void dns_cache_dump(DnsCache *cache, FILE *f) { Iterator iterator; DnsCacheItem *i; diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h index 5f91164785..0f28bbe543 100644 --- a/src/resolve/resolved-dns-cache.h +++ b/src/resolve/resolved-dns-cache.h @@ -32,6 +32,7 @@ typedef struct DnsCache { } DnsCache; #include "resolved-dns-answer.h" +#include "resolved-dns-packet.h" #include "resolved-dns-question.h" #include "resolved-dns-rr.h" @@ -45,3 +46,5 @@ int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_ void dns_cache_dump(DnsCache *cache, FILE *f); bool dns_cache_is_empty(DnsCache *cache); + +int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p); diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 2d06775dca..ed126505ad 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -23,6 +23,7 @@ #include "alloc-util.h" #include "dns-domain.h" +#include "hexdecoct.h" #include "resolved-dns-dnssec.h" #include "resolved-dns-packet.h" #include "string-table.h" @@ -40,8 +41,10 @@ * - Make trust anchor store read additional DS+DNSKEY data from disk * - wildcard zones compatibility * - multi-label zone compatibility - * - DMSSEC cname/dname compatibility + * - DNSSEC cname/dname compatibility * - per-interface DNSSEC setting + * - retry on failed validation + * - fix TTL for cache entries to match RRSIG TTL * - DSA support * - EC support? * @@ -64,6 +67,19 @@ * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS */ +static void initialize_libgcrypt(void) { + const char *p; + + if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) + return; + + p = gcry_check_version("1.4.5"); + assert(p); + + gcry_control(GCRYCTL_DISABLE_SECMEM); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); +} + static bool dnssec_algorithm_supported(int algorithm) { return IN_SET(algorithm, DNSSEC_ALGORITHM_RSASHA1, @@ -72,12 +88,6 @@ static bool dnssec_algorithm_supported(int algorithm) { DNSSEC_ALGORITHM_RSASHA512); } -static bool dnssec_digest_supported(int digest) { - return IN_SET(digest, - DNSSEC_DIGEST_SHA1, - DNSSEC_DIGEST_SHA256); -} - uint16_t dnssec_keytag(DnsResourceRecord *dnskey) { const uint8_t *p; uint32_t sum; @@ -193,11 +203,12 @@ static int dnssec_rsa_verify( } ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); - if (ge == GPG_ERR_BAD_SIGNATURE) + if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) r = 0; - else if (ge != 0) + else if (ge != 0) { + log_debug("RSA signature check failed: %s", gpg_strerror(ge)); r = -EIO; - else + } else r = 1; finish: @@ -272,7 +283,8 @@ int dnssec_verify_rrset( DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, - usec_t realtime) { + usec_t realtime, + DnssecResult *result) { uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; size_t exponent_size, modulus_size, hash_size; @@ -285,6 +297,7 @@ int dnssec_verify_rrset( assert(key); assert(rrsig); assert(dnskey); + assert(result); assert(rrsig->key->type == DNS_TYPE_RRSIG); assert(dnskey->key->type == DNS_TYPE_DNSKEY); @@ -292,8 +305,10 @@ int dnssec_verify_rrset( * using the signature "rrsig" and the key "dnskey". It's * assumed the RRSIG and DNSKEY match. */ - if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm)) - return -EOPNOTSUPP; + if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm)) { + *result = DNSSEC_UNSUPPORTED_ALGORITHM; + return 0; + } if (a->n_rrs > VERIFY_RRS_MAX) return -E2BIG; @@ -301,8 +316,10 @@ int dnssec_verify_rrset( r = dnssec_rrsig_expired(rrsig, realtime); if (r < 0) return r; - if (r > 0) - return DNSSEC_SIGNATURE_EXPIRED; + if (r > 0) { + *result = DNSSEC_SIGNATURE_EXPIRED; + return 0; + } /* Collect all relevant RRs in a single array, so that we can look at the RRset */ list = newa(DnsResourceRecord *, a->n_rrs); @@ -326,7 +343,9 @@ int dnssec_verify_rrset( return -ENODATA; /* Bring the RRs into canonical order */ - qsort_safe(list, n, sizeof(DnsResourceRecord), rr_compare); + qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare); + + initialize_libgcrypt(); /* OK, the RRs are now in canonical order. Let's calculate the digest */ switch (rrsig->rrsig.algorithm) { @@ -444,7 +463,8 @@ int dnssec_verify_rrset( if (r < 0) goto finish; - r = r ? DNSSEC_VERIFIED : DNSSEC_INVALID; + *result = r ? DNSSEC_VALIDATED : DNSSEC_INVALID; + r = 0; finish: gcry_md_close(md); @@ -476,7 +496,7 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag) return 0; - return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(rrsig->key)); + return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer); } int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) { @@ -499,15 +519,17 @@ int dnssec_verify_rrset_search( DnsAnswer *a, DnsResourceKey *key, DnsAnswer *validated_dnskeys, - usec_t realtime) { + usec_t realtime, + DnssecResult *result) { - bool found_rrsig = false, found_dnskey = false; + bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false; DnsResourceRecord *rrsig; int r; assert(key); + assert(result); - /* Verifies all RRs from "a" that match the key "key", against DNSKEY RRs in "validated_dnskeys" */ + /* Verifies all RRs from "a" that match the key "key", against DNSKEY and DS RRs in "validated_dnskeys" */ if (!a || a->n_rrs <= 0) return -ENODATA; @@ -516,6 +538,7 @@ int dnssec_verify_rrset_search( DNS_ANSWER_FOREACH(rrsig, a) { DnsResourceRecord *dnskey; + /* Is this an RRSIG RR that applies to RRs matching our key? */ r = dnssec_key_match_rrsig(key, rrsig); if (r < 0) return r; @@ -524,16 +547,17 @@ int dnssec_verify_rrset_search( found_rrsig = true; + /* Look for a matching key */ DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) { + DnssecResult one_result; + /* Is this a DNSKEY RR that matches they key of our RRSIG? */ r = dnssec_rrsig_match_dnskey(rrsig, dnskey); if (r < 0) return r; if (r == 0) continue; - found_dnskey = true; - /* Take the time here, if it isn't set yet, so * that we do all validations with the same * time. */ @@ -545,27 +569,61 @@ int dnssec_verify_rrset_search( * the RRSet against the RRSIG and DNSKEY * combination. */ - r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime); - if (r < 0 && r != EOPNOTSUPP) + r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result); + if (r < 0) return r; - if (r == DNSSEC_VERIFIED) - return DNSSEC_VERIFIED; - - /* If the signature is invalid, or done using - an unsupported algorithm, let's try another - key and/or signature. After all they - key_tags and stuff are not unique, and - might be shared by multiple keys. */ + + switch (one_result) { + + case DNSSEC_VALIDATED: + /* Yay, the RR has been validated, + * return immediately. */ + *result = DNSSEC_VALIDATED; + return 0; + + case DNSSEC_INVALID: + /* If the signature is invalid, let's try another + key and/or signature. After all they + key_tags and stuff are not unique, and + might be shared by multiple keys. */ + found_invalid = true; + continue; + + case DNSSEC_UNSUPPORTED_ALGORITHM: + /* If the key algorithm is + unsupported, try another + RRSIG/DNSKEY pair, but remember we + encountered this, so that we can + return a proper error when we + encounter nothing better. */ + found_unsupported_algorithm = true; + continue; + + case DNSSEC_SIGNATURE_EXPIRED: + /* If the signature is expired, try + another one, but remember it, so + that we can return this */ + found_expired_rrsig = true; + continue; + + default: + assert_not_reached("Unexpected DNSSEC validation result"); + } } } - if (found_dnskey) - return DNSSEC_INVALID; - - if (found_rrsig) - return DNSSEC_MISSING_KEY; + if (found_expired_rrsig) + *result = DNSSEC_SIGNATURE_EXPIRED; + else if (found_unsupported_algorithm) + *result = DNSSEC_UNSUPPORTED_ALGORITHM; + else if (found_invalid) + *result = DNSSEC_INVALID; + else if (found_rrsig) + *result = DNSSEC_MISSING_KEY; + else + *result = DNSSEC_NO_SIGNATURE; - return DNSSEC_NO_SIGNATURE; + return 0; } int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { @@ -633,9 +691,28 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { return (int) c; } +static int digest_to_gcrypt(uint8_t algorithm) { + + /* Translates a DNSSEC digest algorithm into a gcrypt digest iedntifier */ + + switch (algorithm) { + + case DNSSEC_DIGEST_SHA1: + return GCRY_MD_SHA1; + + case DNSSEC_DIGEST_SHA256: + return GCRY_MD_SHA256; + + default: + return -EOPNOTSUPP; + } +} + int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) { - gcry_md_hd_t md = NULL; char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX]; + gcry_md_hd_t md = NULL; + size_t hash_size; + int algorithm; void *result; int r; @@ -653,45 +730,31 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) { if (dnskey->dnskey.protocol != 3) return -EKEYREJECTED; - if (!dnssec_algorithm_supported(dnskey->dnskey.algorithm)) - return -EOPNOTSUPP; - if (!dnssec_digest_supported(ds->ds.digest_type)) - return -EOPNOTSUPP; - if (dnskey->dnskey.algorithm != ds->ds.algorithm) return 0; if (dnssec_keytag(dnskey) != ds->ds.key_tag) return 0; - switch (ds->ds.digest_type) { - - case DNSSEC_DIGEST_SHA1: + initialize_libgcrypt(); - if (ds->ds.digest_size != 20) - return 0; + algorithm = digest_to_gcrypt(ds->ds.digest_type); + if (algorithm < 0) + return algorithm; - gcry_md_open(&md, GCRY_MD_SHA1, 0); - break; + hash_size = gcry_md_get_algo_dlen(algorithm); + assert(hash_size > 0); - case DNSSEC_DIGEST_SHA256: - - if (ds->ds.digest_size != 32) - return 0; - - gcry_md_open(&md, GCRY_MD_SHA256, 0); - break; + if (ds->ds.digest_size != hash_size) + return 0; - default: - assert_not_reached("Unknown digest"); - } + r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name)); + if (r < 0) + return r; + gcry_md_open(&md, algorithm, 0); if (!md) return -EIO; - r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name)); - if (r < 0) - goto finish; - gcry_md_write(md, owner_name, r); md_add_uint16(md, dnskey->dnskey.flags); md_add_uint8(md, dnskey->dnskey.protocol); @@ -711,9 +774,227 @@ finish: return r; } +int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { + DnsResourceRecord *ds; + int r; + + assert(dnskey); + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + + DNS_ANSWER_FOREACH(ds, validated_ds) { + + if (ds->key->type != DNS_TYPE_DS) + continue; + + r = dnssec_verify_dnskey(dnskey, ds); + if (r < 0) + return r; + if (r > 0) + return 1; + } + + return 0; +} + +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { + uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX]; + gcry_md_hd_t md = NULL; + size_t hash_size; + int algorithm; + void *result; + unsigned k; + int r; + + assert(nsec3); + assert(name); + assert(ret); + + if (nsec3->key->type != DNS_TYPE_NSEC3) + return -EINVAL; + + algorithm = digest_to_gcrypt(nsec3->nsec3.algorithm); + if (algorithm < 0) + return algorithm; + + initialize_libgcrypt(); + + hash_size = gcry_md_get_algo_dlen(algorithm); + assert(hash_size > 0); + + if (nsec3->nsec3.next_hashed_name_size != hash_size) + return -EINVAL; + + r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); + if (r < 0) + return r; + + gcry_md_open(&md, algorithm, 0); + if (!md) + return -EIO; + + gcry_md_write(md, wire_format, r); + gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + + for (k = 0; k < nsec3->nsec3.iterations; k++) { + uint8_t tmp[hash_size]; + memcpy(tmp, result, hash_size); + + gcry_md_reset(md); + gcry_md_write(md, tmp, hash_size); + gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + } + + memcpy(ret, result, hash_size); + r = (int) hash_size; + +finish: + gcry_md_close(md); + return r; +} + +int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result) { + DnsResourceRecord *rr; + int r; + + assert(key); + assert(result); + + /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */ + + DNS_ANSWER_FOREACH(rr, answer) { + + if (rr->key->class != key->class) + continue; + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); + if (r < 0) + return r; + if (r > 0) { + *result = bitmap_isset(rr->nsec.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA; + return 0; + } + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_NSEC_NXDOMAIN; + return 0; + } + break; + + case DNS_TYPE_NSEC3: { + _cleanup_free_ void *decoded = NULL; + size_t decoded_size; + char label[DNS_LABEL_MAX]; + uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; + int label_length, c, q; + const char *p; + bool covered; + + /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */ + if (!IN_SET(rr->nsec3.flags, 0, 1)) + continue; + + p = DNS_RESOURCE_KEY_NAME(rr->key); + label_length = dns_label_unescape(&p, label, sizeof(label)); + if (label_length < 0) + return label_length; + if (label_length == 0) + continue; + + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), p); + if (r < 0) + return r; + if (r == 0) + continue; + + r = unbase32hexmem(label, label_length, false, &decoded, &decoded_size); + if (r == -EINVAL) + continue; + if (r < 0) + return r; + + if (decoded_size != rr->nsec3.next_hashed_name_size) + continue; + + c = memcmp(decoded, rr->nsec3.next_hashed_name, decoded_size); + if (c == 0) + continue; + + r = dnssec_nsec3_hash(rr, DNS_RESOURCE_KEY_NAME(key), hashed); + /* RFC 5155, Section 8.1 says we MUST ignore NSEC3 RRs with unknown algorithms */ + if (r == -EOPNOTSUPP) + continue; + if (r < 0) + return r; + if ((size_t) r != decoded_size) + continue; + + r = memcmp(decoded, hashed, decoded_size); + if (r == 0) { + *result = bitmap_isset(rr->nsec3.types, key->type) ? DNSSEC_NSEC_FOUND : DNSSEC_NSEC_NODATA; + return 0; + } + + q = memcmp(hashed, rr->nsec3.next_hashed_name, decoded_size); + + covered = c < 0 ? + r < 0 && q < 0 : + q < 0 || r < 0; + + if (covered) { + *result = DNSSEC_NSEC_NXDOMAIN; + return 0; + } + + break; + } + + default: + break; + } + } + + /* No approproate NSEC RR found, report this. */ + *result = DNSSEC_NSEC_NO_RR; + return 0; +} + static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = { [DNSSEC_NO] = "no", [DNSSEC_TRUST] = "trust", [DNSSEC_YES] = "yes", }; DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode); + +static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { + [DNSSEC_VALIDATED] = "validated", + [DNSSEC_INVALID] = "invalid", + [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", + [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", + [DNSSEC_NO_SIGNATURE] = "no-signature", + [DNSSEC_MISSING_KEY] = "missing-key", + [DNSSEC_UNSIGNED] = "unsigned", + [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary", + [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult); diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h index f4cb58988a..442e301302 100644 --- a/src/resolve/resolved-dns-dnssec.h +++ b/src/resolve/resolved-dns-dnssec.h @@ -22,6 +22,7 @@ ***/ typedef enum DnssecMode DnssecMode; +typedef enum DnssecResult DnssecResult; #include "dns-domain.h" #include "resolved-dns-answer.h" @@ -41,27 +42,56 @@ enum DnssecMode { _DNSSEC_MODE_INVALID = -1 }; -enum { - DNSSEC_VERIFIED, +enum DnssecResult { + /* These four are returned by dnssec_verify_rrset() */ + DNSSEC_VALIDATED, DNSSEC_INVALID, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM, + + /* These two are added by dnssec_verify_rrset_search() */ DNSSEC_NO_SIGNATURE, DNSSEC_MISSING_KEY, - DNSSEC_SIGNATURE_EXPIRED, + + /* These two are added by the DnsTransaction logic */ + DNSSEC_UNSIGNED, + DNSSEC_FAILED_AUXILIARY, + DNSSEC_NSEC_MISMATCH, + _DNSSEC_RESULT_MAX, + _DNSSEC_RESULT_INVALID = -1 }; #define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2) +/* The longest digest we'll ever generate, of all digest algorithms we support */ +#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32)) + int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey); int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig); -int dnssec_verify_rrset(DnsAnswer *answer, DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime); -int dnssec_verify_rrset_search(DnsAnswer *a, DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime); +int dnssec_verify_rrset(DnsAnswer *answer, DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result); +int dnssec_verify_rrset_search(DnsAnswer *answer, DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result); int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds); +int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); uint16_t dnssec_keytag(DnsResourceRecord *dnskey); int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); + +typedef enum DnssecNsecResult { + DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ + DNSSEC_NSEC_NXDOMAIN, + DNSSEC_NSEC_NODATA, + DNSSEC_NSEC_FOUND, +} DnssecNsecResult; + +int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result); + const char* dnssec_mode_to_string(DnssecMode m) _const_; DnssecMode dnssec_mode_from_string(const char *s) _pure_; + +const char* dnssec_result_to_string(DnssecResult m) _const_; +DnssecResult dnssec_result_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index ea776f7916..c34ecc44f8 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -65,20 +65,18 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { return 0; } -int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) { - DnsPacket *p; - DnsPacketHeader *h; - int r; +void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated) { - assert(ret); + DnsPacketHeader *h; - r = dns_packet_new(&p, protocol, mtu); - if (r < 0) - return r; + assert(p); h = DNS_PACKET_HEADER(p); - if (protocol == DNS_PROTOCOL_LLMNR) + switch(p->protocol) { + case DNS_PROTOCOL_LLMNR: + assert(!truncated); + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, 0 /* opcode */, 0 /* c */, @@ -88,7 +86,23 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool 0 /* ad */, 0 /* cd */, 0 /* rcode */)); - else + break; + + case DNS_PROTOCOL_MDNS: + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* aa */, + truncated /* tc */, + 0 /* rd (ask for recursion) */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); + break; + + default: + assert(!truncated); + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, 0 /* opcode */, 0 /* aa */, @@ -98,6 +112,23 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool 0 /* ad */, dnssec_checking_disabled /* cd */, 0 /* rcode */)); + } +} + +int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) { + DnsPacket *p; + int r; + + assert(ret); + + r = dns_packet_new(&p, protocol, mtu); + if (r < 0) + return r; + + /* Always set the TC bit to 0 initially. + * If there are multiple packets later, we'll update the bit shortly before sending. + */ + dns_packet_set_flags(p, dnssec_checking_disabled, false); *ret = p; return 0; @@ -122,6 +153,7 @@ static void dns_packet_free(DnsPacket *p) { dns_question_unref(p->question); dns_answer_unref(p->answer); + dns_resource_record_unref(p->opt); while ((s = hashmap_steal_first_key(p->names))) free(s); @@ -139,6 +171,8 @@ DnsPacket *dns_packet_unref(DnsPacket *p) { assert(p->n_ref > 0); + dns_packet_unref(p->more); + if (p->n_ref == 1) dns_packet_free(p); else @@ -175,6 +209,7 @@ int dns_packet_validate_reply(DnsPacket *p) { return -EBADMSG; switch (p->protocol) { + case DNS_PROTOCOL_LLMNR: /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ if (DNS_PACKET_QDCOUNT(p) != 1) @@ -182,6 +217,13 @@ int dns_packet_validate_reply(DnsPacket *p) { break; + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + default: break; } @@ -208,6 +250,7 @@ int dns_packet_validate_query(DnsPacket *p) { return -EBADMSG; switch (p->protocol) { + case DNS_PROTOCOL_LLMNR: /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ if (DNS_PACKET_QDCOUNT(p) != 1) @@ -223,6 +266,18 @@ int dns_packet_validate_query(DnsPacket *p) { break; + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_AA(p) != 0 || + DNS_PACKET_RD(p) != 0 || + DNS_PACKET_RA(p) != 0 || + DNS_PACKET_AD(p) != 0 || + DNS_PACKET_CD(p) != 0 || + DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + default: break; } @@ -383,10 +438,15 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_ return 0; } -int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) { +int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) { uint8_t *w; int r; + /* Append a label to a packet. Optionally, does this in DNSSEC + * canonical form, if this label is marked as a candidate for + * it, and the canonical form logic is enabled for the + * packet */ + assert(p); assert(d); @@ -399,7 +459,7 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start *(w++) = (uint8_t) l; - if (p->canonical_form) { + if (p->canonical_form && canonical_candidate) { size_t i; /* Generate in canonical form, as defined by DNSSEC @@ -424,6 +484,7 @@ int dns_packet_append_name( DnsPacket *p, const char *name, bool allow_compression, + bool canonical_candidate, size_t *start) { size_t saved_size; @@ -478,7 +539,7 @@ int dns_packet_append_name( if (k > 0) r = k; - r = dns_packet_append_label(p, label, r, &n); + r = dns_packet_append_label(p, label, r, canonical_candidate, &n); if (r < 0) goto fail; @@ -519,7 +580,7 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) saved_size = p->size; - r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, NULL); + r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, true, NULL); if (r < 0) goto fail; @@ -541,7 +602,7 @@ fail: return r; } -static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, uint8_t *types, size_t *start) { +static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) { size_t saved_size; int r; @@ -598,15 +659,16 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { } window = n >> 8; - entry = n & 255; bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); } - r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); - if (r < 0) - goto fail; + if (bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + } if (start) *start = saved_size; @@ -707,14 +769,14 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->srv.name, true, NULL); + r = dns_packet_append_name(p, rr->srv.name, true, false, NULL); break; case DNS_TYPE_PTR: case DNS_TYPE_NS: case DNS_TYPE_CNAME: case DNS_TYPE_DNAME: - r = dns_packet_append_name(p, rr->ptr.name, true, NULL); + r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL); break; case DNS_TYPE_HINFO: @@ -757,11 +819,11 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_SOA: - r = dns_packet_append_name(p, rr->soa.mname, true, NULL); + r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL); if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->soa.rname, true, NULL); + r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL); if (r < 0) goto fail; @@ -789,7 +851,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->mx.exchange, true, NULL); + r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); break; case DNS_TYPE_LOC: @@ -893,7 +955,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->rrsig.signer, false, NULL); + r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); if (r < 0) goto fail; @@ -901,7 +963,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_NSEC: - r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, NULL); + r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); if (r < 0) goto fail; @@ -910,6 +972,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star goto fail; break; + case DNS_TYPE_NSEC3: r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL); if (r < 0) @@ -944,6 +1007,8 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star goto fail; break; + + case DNS_TYPE_OPT: case _DNS_TYPE_INVALID: /* unparseable */ default: @@ -1392,6 +1457,7 @@ fail: int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { _cleanup_free_ char *name = NULL; + bool cache_flush = true; uint16_t class, type; DnsResourceKey *key; size_t saved_rindex; @@ -1414,12 +1480,23 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { if (r < 0) goto fail; + if (p->protocol == DNS_PROTOCOL_MDNS) { + /* See RFC6762, Section 10.2 */ + + if (class & MDNS_RR_CACHE_FLUSH) + class &= ~MDNS_RR_CACHE_FLUSH; + else + cache_flush = false; + } + key = dns_resource_key_new_consume(class, type, name); if (!key) { r = -ENOMEM; goto fail; } + key->cache_flush = cache_flush; + name = NULL; *ret = key; @@ -1455,7 +1532,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { goto fail; if (key->class == DNS_CLASS_ANY || - key->type == DNS_TYPE_ANY) { + !dns_type_is_valid_rr(key->type)) { r = -EBADMSG; goto fail; } @@ -1503,10 +1580,6 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { r = dns_packet_read_name(p, &rr->ptr.name, true, NULL); break; - case DNS_TYPE_OPT: /* we only care about the header */ - r = 0; - break; - case DNS_TYPE_HINFO: r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL); if (r < 0) @@ -1684,6 +1757,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { } break; + case DNS_TYPE_SSHFP: r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); if (r < 0) @@ -1778,8 +1852,16 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { break; - case DNS_TYPE_NSEC: - r = dns_packet_read_name(p, &rr->nsec.next_domain_name, false, NULL); + case DNS_TYPE_NSEC: { + + /* + * RFC6762, section 18.14 explictly states mDNS should use name compression. + * This contradicts RFC3845, section 2.1.1 + */ + + bool allow_compressed = p->protocol == DNS_PROTOCOL_MDNS; + + r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL); if (r < 0) goto fail; @@ -1792,7 +1874,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { * without the NSEC bit set. */ break; - + } case DNS_TYPE_NSEC3: { uint8_t size; @@ -1838,6 +1920,8 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { break; } + + case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */ default: unparseable: r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.size, NULL); @@ -1892,6 +1976,11 @@ int dns_packet_extract(DnsPacket *p) { if (r < 0) goto finish; + if (!dns_type_is_valid_query(key->type)) { + r = -EBADMSG; + goto finish; + } + r = dns_question_add(question, key); if (r < 0) goto finish; @@ -1913,9 +2002,26 @@ int dns_packet_extract(DnsPacket *p) { if (r < 0) goto finish; - r = dns_answer_add(answer, rr, p->ifindex); - if (r < 0) - goto finish; + if (rr->key->type == DNS_TYPE_OPT) { + + /* The OPT RR is only valid in the Additional section */ + if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) { + r = -EBADMSG; + goto finish; + } + + /* Two OPT RRs? */ + if (p->opt) { + r = -EBADMSG; + goto finish; + } + + p->opt = dns_resource_record_ref(rr); + } else { + r = dns_answer_add(answer, rr, p->ifindex); + if (r < 0) + goto finish; + } } } @@ -1934,6 +2040,30 @@ finish: return r; } +int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) { + int r; + + assert(p); + assert(key); + + /* Checks if the specified packet is a reply for the specified + * key and the specified key is the only one in the question + * section. */ + + if (DNS_PACKET_QR(p) != 1) + return 0; + + /* Let's unpack the packet, if that hasn't happened yet. */ + r = dns_packet_extract(p); + if (r < 0) + return r; + + if (p->question->n_keys != 1) + return 0; + + return dns_resource_key_equal(p->question->keys[0], key); +} + static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { [DNS_RCODE_SUCCESS] = "SUCCESS", [DNS_RCODE_FORMERR] = "FORMERR", diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index aa2823cfb9..082e92833c 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -80,6 +80,7 @@ struct DnsPacket { /* Parsed data */ DnsQuestion *question; DnsAnswer *answer; + DnsResourceRecord *opt; /* Packet reception metadata */ int ifindex; @@ -88,6 +89,9 @@ struct DnsPacket { uint16_t sender_port, destination_port; uint32_t ttl; + /* For support of truncated packets */ + DnsPacket *more; + bool on_stack:1; bool extracted:1; bool refuse_compression:1; @@ -146,6 +150,8 @@ static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) { int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t mtu); int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled); +void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated); + DnsPacket *dns_packet_ref(DnsPacket *p); DnsPacket *dns_packet_unref(DnsPacket *p); @@ -155,14 +161,16 @@ int dns_packet_validate(DnsPacket *p); int dns_packet_validate_reply(DnsPacket *p); int dns_packet_validate_query(DnsPacket *p); +int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key); + int dns_packet_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start); int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start); int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start); int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start); int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start); int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start); -int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, size_t *start); -int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, size_t *start); +int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start); +int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start); int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start); int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start); int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start); @@ -225,6 +233,9 @@ DnsProtocol dns_protocol_from_string(const char *s) _pure_; #define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) }) #define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } }) +#define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) }) +#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xfb } }) + static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) { uint64_t f; @@ -239,6 +250,9 @@ static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, case DNS_PROTOCOL_LLMNR: return f|(family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4); + case DNS_PROTOCOL_MDNS: + return family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4; + default: break; } diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 089d9fb70d..405882a6ea 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -59,7 +59,7 @@ static void dns_query_candidate_stop(DnsQueryCandidate *c) { assert(c); while ((t = set_steal_first(c->transactions))) { - set_remove(t->query_candidates, c); + set_remove(t->notify_query_candidates, c); dns_transaction_gc(t); } } @@ -116,32 +116,35 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource assert(c); assert(key); - r = set_ensure_allocated(&c->transactions, NULL); - if (r < 0) - return r; - t = dns_scope_find_transaction(c->scope, key, true); if (!t) { r = dns_transaction_new(&t, c->scope, key); if (r < 0) return r; + } else { + if (set_contains(c->transactions, t)) + return 0; } - r = set_ensure_allocated(&t->query_candidates, NULL); + r = set_ensure_allocated(&c->transactions, NULL); if (r < 0) goto gc; - r = set_put(t->query_candidates, c); + r = set_ensure_allocated(&t->notify_query_candidates, NULL); + if (r < 0) + goto gc; + + r = set_put(t->notify_query_candidates, c); if (r < 0) goto gc; r = set_put(c->transactions, t); if (r < 0) { - set_remove(t->query_candidates, c); + (void) set_remove(t->notify_query_candidates, c); goto gc; } - return 0; + return 1; gc: dns_transaction_gc(t); @@ -182,9 +185,21 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { switch (t->state) { - case DNS_TRANSACTION_PENDING: case DNS_TRANSACTION_NULL: - return t->state; + /* If there's a NULL transaction pending, then + * this means not all transactions where + * started yet, and we were called from within + * the stackframe that is supposed to start + * remaining transactions. In this case, + * simply claim the candidate is pending. */ + + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + /* If there's one transaction currently in + * VALIDATING state, then this means there's + * also one in PENDING state, hence we can + * return PENDING immediately. */ + return DNS_TRANSACTION_PENDING; case DNS_TRANSACTION_SUCCESS: state = t->state; @@ -233,7 +248,7 @@ fail: return r; } -void dns_query_candidate_ready(DnsQueryCandidate *c) { +void dns_query_candidate_notify(DnsQueryCandidate *c) { DnsTransactionState state; int r; @@ -241,7 +256,7 @@ void dns_query_candidate_ready(DnsQueryCandidate *c) { state = dns_query_candidate_state(c); - if (IN_SET(state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL)) + if (DNS_TRANSACTION_IS_LIVE(state)) return; if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) { @@ -394,8 +409,8 @@ int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) { static void dns_query_complete(DnsQuery *q, DnsTransactionState state) { assert(q); - assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); - assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); + assert(!DNS_TRANSACTION_IS_LIVE(state)); + assert(DNS_TRANSACTION_IS_LIVE(q->state)); /* Note that this call might invalidate the query. Callers * should hence not attempt to access the query or transaction @@ -970,9 +985,10 @@ fail: static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + bool has_authenticated = false, has_non_authenticated = false; DnsTransaction *t; Iterator i; - bool has_authenticated = false, has_non_authenticated = false; + int r; assert(q); @@ -988,16 +1004,11 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { case DNS_TRANSACTION_SUCCESS: { /* We found a successfuly reply, merge it into the answer */ - DnsAnswer *merged; - - merged = dns_answer_merge(q->answer, t->answer); - if (!merged) { + r = dns_answer_extend(&q->answer, t->answer); + if (r < 0) { dns_query_complete(q, DNS_TRANSACTION_RESOURCES); return; } - - dns_answer_unref(q->answer); - q->answer = merged; q->answer_rcode = t->answer_rcode; if (t->answer_authenticated) @@ -1009,8 +1020,9 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { break; } - case DNS_TRANSACTION_PENDING: case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: case DNS_TRANSACTION_ABORTED: /* Ignore transactions that didn't complete */ continue; @@ -1049,7 +1061,7 @@ void dns_query_ready(DnsQuery *q) { bool pending = false; assert(q); - assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); + assert(DNS_TRANSACTION_IS_LIVE(q->state)); /* Note that this call might invalidate the query. Callers * should hence not attempt to access the query or transaction @@ -1066,14 +1078,16 @@ void dns_query_ready(DnsQuery *q) { switch (state) { case DNS_TRANSACTION_SUCCESS: - /* One of the transactions is successful, + /* One of the candidates is successful, * let's use it, and copy its data out */ dns_query_accept(q, c); return; - case DNS_TRANSACTION_PENDING: case DNS_TRANSACTION_NULL: - /* One of the transactions is still going on, let's maybe wait for it */ + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + /* One of the candidates is still going on, + * let's maybe wait for it */ pending = true; break; @@ -1096,6 +1110,8 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) assert(q); + log_debug("Following CNAME %s → %s", dns_question_first_name(q->question), cname->cname.name); + q->n_cname_redirects ++; if (q->n_cname_redirects > CNAME_MAX) return -ELOOP; diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index b71bb2352b..d7f96c3ca4 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -95,7 +95,7 @@ struct DnsQuery { }; DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); -void dns_query_candidate_ready(DnsQueryCandidate *c); +void dns_query_candidate_notify(DnsQueryCandidate *c); int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question, int family, uint64_t flags); DnsQuery *dns_query_free(DnsQuery *q); diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index 3249448d3b..4ed7434d3c 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -117,7 +117,7 @@ int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char return 0; for (i = 0; i < q->n_keys; i++) { - r = dns_resource_key_match_cname(q->keys[i], rr, search_domain); + r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain); if (r != 0) return r; } diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 934a18334c..74c9d87319 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -168,6 +168,9 @@ bool dns_resource_key_is_address(const DnsResourceKey *key) { int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { int r; + if (a == b) + return 1; + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(a), DNS_RESOURCE_KEY_NAME(b)); if (r <= 0) return r; @@ -187,6 +190,9 @@ int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord assert(key); assert(rr); + if (key == rr->key) + return 1; + /* Checks if an rr matches the specified key. If a search * domain is specified, it will also be checked if the key * with the search domain suffixed might match the RR. */ @@ -214,19 +220,19 @@ int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord return 0; } -int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain) { +int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain) { int r; assert(key); - assert(rr); + assert(cname); - if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) + if (cname->class != key->class && key->class != DNS_CLASS_ANY) return 0; - if (rr->key->type == DNS_TYPE_CNAME) - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key)); - else if (rr->key->type == DNS_TYPE_DNAME) - r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key)); + if (cname->type == DNS_TYPE_CNAME) + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname)); + else if (cname->type == DNS_TYPE_DNAME) + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname)); else return 0; @@ -240,14 +246,31 @@ int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRec if (r < 0) return r; - if (rr->key->type == DNS_TYPE_CNAME) - return dns_name_equal(joined, DNS_RESOURCE_KEY_NAME(rr->key)); - else if (rr->key->type == DNS_TYPE_DNAME) - return dns_name_endswith(joined, DNS_RESOURCE_KEY_NAME(rr->key)); + if (cname->type == DNS_TYPE_CNAME) + return dns_name_equal(joined, DNS_RESOURCE_KEY_NAME(cname)); + else if (cname->type == DNS_TYPE_DNAME) + return dns_name_endswith(joined, DNS_RESOURCE_KEY_NAME(cname)); } return 0; +} + +int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa) { + assert(soa); + assert(key); + + /* Checks whether 'soa' is a SOA record for the specified key. */ + if (soa->class != DNS_CLASS_IN) + return 0; + + if (soa->type != DNS_TYPE_SOA) + return 0; + + if (!dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa))) + return 0; + + return 1; } static void dns_resource_key_hash_func(const void *i, struct siphash *state) { @@ -303,7 +326,7 @@ int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) { t = tbuf; } - if (asprintf(&s, "%s %s %-5s", DNS_RESOURCE_KEY_NAME(key), c, t) < 0) + if (asprintf(&s, "%s. %s %-5s", DNS_RESOURCE_KEY_NAME(key), c, t) < 0) return -ENOMEM; *ret = s; @@ -503,6 +526,9 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor assert(a); assert(b); + if (a == b) + return 1; + r = dns_resource_key_equal(a->key, b->key); if (r <= 0) return r; @@ -1090,6 +1116,9 @@ DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) { bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { + if (a == b) + return true; + if (!a != !b) return false; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index b82fa77562..632ee59994 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -45,6 +45,9 @@ enum { #define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) #define DNSKEY_FLAG_SEP (UINT16_C(1) << 0) +/* mDNS RR flags */ +#define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15) + /* DNSSEC algorithm identifiers, see * http://tools.ietf.org/html/rfc4034#appendix-A.1 and * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ @@ -76,6 +79,7 @@ struct DnsResourceKey { unsigned n_ref; uint16_t class, type; char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */ + bool cache_flush:1; }; /* Creates a temporary resource key. This is only useful to quickly @@ -110,7 +114,7 @@ struct DnsResourceRecord { struct { void *data; size_t size; - } generic; + } generic, opt; struct { uint16_t priority; @@ -242,10 +246,15 @@ DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key); bool dns_resource_key_is_address(const DnsResourceKey *key); int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain); -int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain); +int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain); +int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa); int dns_resource_key_to_string(const DnsResourceKey *key, char **ret); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref); +static inline bool dns_key_is_shared(const DnsResourceKey *key) { + return IN_SET(key->type, DNS_TYPE_PTR); +} + DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key); DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name); DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index a90692cdf4..5bd733a8ff 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -30,6 +30,7 @@ #include "random-util.h" #include "resolved-dns-scope.h" #include "resolved-llmnr.h" +#include "resolved-mdns.h" #include "socket-util.h" #include "strv.h" @@ -59,6 +60,7 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int LIST_PREPEND(scopes, m->dns_scopes, s); dns_scope_llmnr_membership(s, true); + dns_scope_mdns_membership(s, true); log_debug("New scope on link %s, protocol %s, family %s", l ? l->name : "*", dns_protocol_to_string(protocol), family == AF_UNSPEC ? "*" : af_to_name(family)); @@ -95,6 +97,7 @@ DnsScope* dns_scope_free(DnsScope *s) { log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family)); dns_scope_llmnr_membership(s, false); + dns_scope_mdns_membership(s, false); dns_scope_abort_transactions(s); while (s->query_candidates) @@ -158,11 +161,10 @@ void dns_scope_packet_lost(DnsScope *s, usec_t usec) { s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); } -int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { +static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { union in_addr_union addr; int ifindex = 0, r; int family; - uint16_t port; uint32_t mtu; size_t saved_size = 0; @@ -228,7 +230,6 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { return -EBUSY; family = s->family; - port = LLMNR_PORT; if (family == AF_INET) { addr.in = LLMNR_MULTICAST_IPV4_ADDRESS; @@ -241,7 +242,30 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { if (fd < 0) return fd; - r = manager_send(s->manager, fd, ifindex, family, &addr, port, p); + r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p); + if (r < 0) + return r; + + break; + + case DNS_PROTOCOL_MDNS: + if (!ratelimit_test(&s->ratelimit)) + return -EBUSY; + + family = s->family; + + if (family == AF_INET) { + addr.in = MDNS_MULTICAST_IPV4_ADDRESS; + fd = manager_mdns_ipv4_fd(s->manager); + } else if (family == AF_INET6) { + addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS; + fd = manager_mdns_ipv6_fd(s->manager); + } else + return -EAFNOSUPPORT; + if (fd < 0) + return fd; + + r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p); if (r < 0) return r; @@ -254,6 +278,31 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { return 1; } +int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { + int r; + + assert(s); + assert(p); + assert(p->protocol == s->protocol); + assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0)); + + do { + /* If there are multiple linked packets, set the TC bit in all but the last of them */ + if (p->more) { + assert(p->protocol == DNS_PROTOCOL_MDNS); + dns_packet_set_flags(p, true, true); + } + + r = dns_scope_emit_one(s, fd, server, p); + if (r < 0) + return r; + + p = p->more; + } while(p); + + return 0; +} + static int dns_scope_socket(DnsScope *s, int type, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) { DnsServer *srv = NULL; _cleanup_close_ int fd = -1; @@ -392,6 +441,10 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co dns_name_equal(domain, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) return DNS_SCOPE_NO; + /* Never respond to some of the domains listed in RFC6761 */ + if (dns_name_endswith(domain, "invalid") > 0) + return DNS_SCOPE_NO; + /* Always honour search domains for routing queries. Note that * we return DNS_SCOPE_YES here, rather than just * DNS_SCOPE_MAYBE, which means wildcard scopes won't be @@ -409,7 +462,11 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co dns_name_endswith(domain, "8.e.f.ip6.arpa") == 0 && dns_name_endswith(domain, "9.e.f.ip6.arpa") == 0 && dns_name_endswith(domain, "a.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0) + dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0 && + /* If networks use .local in their private setups, they are supposed to also add .local to their search + * domains, which we already checked above. Otherwise, we consider .local specific to mDNS and won't + * send such queries ordinary DNS servers. */ + dns_name_endswith(domain, "local") == 0) return DNS_SCOPE_MAYBE; return DNS_SCOPE_NO; @@ -449,7 +506,7 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) { if (s->protocol == DNS_PROTOCOL_DNS) { - /* On classic DNS, lookin up non-address RRs is always + /* On classic DNS, looking up non-address RRs is always * fine. (Specifically, we want to permit looking up * DNSKEY and DS records on the root and top-level * domains.) */ @@ -477,19 +534,15 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) { return true; } -int dns_scope_llmnr_membership(DnsScope *s, bool b) { +static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in, struct in6_addr in6) { int fd; assert(s); - - if (s->protocol != DNS_PROTOCOL_LLMNR) - return 0; - assert(s->link); if (s->family == AF_INET) { struct ip_mreqn mreqn = { - .imr_multiaddr = LLMNR_MULTICAST_IPV4_ADDRESS, + .imr_multiaddr = in, .imr_ifindex = s->link->ifindex, }; @@ -508,7 +561,7 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b) { } else if (s->family == AF_INET6) { struct ipv6_mreq mreq = { - .ipv6mr_multiaddr = LLMNR_MULTICAST_IPV6_ADDRESS, + .ipv6mr_multiaddr = in6, .ipv6mr_interface = s->link->ifindex, }; @@ -527,6 +580,22 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b) { return 0; } +int dns_scope_llmnr_membership(DnsScope *s, bool b) { + + if (s->protocol != DNS_PROTOCOL_LLMNR) + return 0; + + return dns_scope_multicast_membership(s, b, LLMNR_MULTICAST_IPV4_ADDRESS, LLMNR_MULTICAST_IPV6_ADDRESS); +} + +int dns_scope_mdns_membership(DnsScope *s, bool b) { + + if (s->protocol != DNS_PROTOCOL_MDNS) + return 0; + + return dns_scope_multicast_membership(s, b, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS); +} + static int dns_scope_make_reply_packet( DnsScope *s, uint16_t id, @@ -753,7 +822,11 @@ static int dns_scope_make_conflict_packet( 0 /* (ad) */, 0 /* (cd) */, 0)); - random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t)); + + /* For mDNS, the transaction ID should always be 0 */ + if (s->protocol != DNS_PROTOCOL_MDNS) + random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t)); + DNS_PACKET_HEADER(p)->qdcount = htobe16(1); DNS_PACKET_HEADER(p)->arcount = htobe16(1); diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 15d9a1fd6f..2fc2e07deb 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -93,6 +93,7 @@ DnsServer *dns_scope_get_dns_server(DnsScope *s); void dns_scope_next_dns_server(DnsScope *s); int dns_scope_llmnr_membership(DnsScope *s, bool b); +int dns_scope_mdns_membership(DnsScope *s, bool b); void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 1103a34c6f..f09788b0c6 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -24,6 +24,7 @@ #include "dns-domain.h" #include "fd-util.h" #include "random-util.h" +#include "resolved-dns-cache.h" #include "resolved-dns-transaction.h" #include "resolved-llmnr.h" #include "string-table.h" @@ -31,6 +32,7 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { DnsQueryCandidate *c; DnsZoneItem *i; + DnsTransaction *z; if (!t) return NULL; @@ -58,14 +60,25 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { dns_resource_key_unref(t->key); - while ((c = set_steal_first(t->query_candidates))) + while ((c = set_steal_first(t->notify_query_candidates))) set_remove(c->transactions, t); + set_free(t->notify_query_candidates); - set_free(t->query_candidates); - - while ((i = set_steal_first(t->zone_items))) + while ((i = set_steal_first(t->notify_zone_items))) i->probe_transaction = NULL; - set_free(t->zone_items); + set_free(t->notify_zone_items); + + while ((z = set_steal_first(t->notify_transactions))) + set_remove(z->dnssec_transactions, t); + set_free(t->notify_transactions); + + while ((z = set_steal_first(t->dnssec_transactions))) { + set_remove(z->notify_transactions, t); + dns_transaction_gc(z); + } + set_free(t->dnssec_transactions); + + dns_answer_unref(t->validated_keys); free(t); return NULL; @@ -79,7 +92,9 @@ void dns_transaction_gc(DnsTransaction *t) { if (t->block_gc > 0) return; - if (set_isempty(t->query_candidates) && set_isempty(t->zone_items)) + if (set_isempty(t->notify_query_candidates) && + set_isempty(t->notify_zone_items) && + set_isempty(t->notify_transactions)) dns_transaction_free(t); } @@ -91,6 +106,14 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) assert(s); assert(key); + /* Don't allow looking up invalid or pseudo RRs */ + if (!dns_type_is_valid_query(key->type)) + return -EINVAL; + + /* We only support the IN class */ + if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY) + return -EOPNOTSUPP; + r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); if (r < 0) return r; @@ -105,6 +128,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) t->dns_udp_fd = -1; t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; + t->dnssec_result = _DNSSEC_RESULT_INVALID; t->key = dns_resource_key_ref(key); /* Find a fresh, unused transaction id */ @@ -174,7 +198,7 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { log_debug("We have the lexicographically larger IP address and thus lost in the conflict."); t->block_gc++; - while ((z = set_first(t->zone_items))) { + while ((z = set_first(t->notify_zone_items))) { /* First, make sure the zone item drops the reference * to us */ dns_zone_item_probe_stop(z); @@ -191,10 +215,11 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { DnsQueryCandidate *c; DnsZoneItem *z; + DnsTransaction *d; Iterator i; assert(t); - assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); + assert(!DNS_TRANSACTION_IS_LIVE(state)); /* Note that this call might invalidate the query. Callers * should hence not attempt to access the query or transaction @@ -214,10 +239,12 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { /* Notify all queries that are interested, but make sure the * transaction isn't freed while we are still looking at it */ t->block_gc++; - SET_FOREACH(c, t->query_candidates, i) - dns_query_candidate_ready(c); - SET_FOREACH(z, t->zone_items, i) - dns_zone_item_ready(z); + SET_FOREACH(c, t->notify_query_candidates, i) + dns_query_candidate_notify(c); + SET_FOREACH(z, t->notify_zone_items, i) + dns_zone_item_notify(z); + SET_FOREACH(d, t->notify_transactions, i) + dns_transaction_notify(d, t); t->block_gc--; dns_transaction_gc(t); @@ -243,7 +270,7 @@ static int on_stream_complete(DnsStream *s, int error) { } if (dns_packet_validate_reply(p) <= 0) { - log_debug("Invalid LLMNR TCP packet."); + log_debug("Invalid TCP reply packet."); dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); return 0; } @@ -324,6 +351,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { t->server = dns_server_ref(server); t->received = dns_packet_unref(t->received); t->answer = dns_answer_unref(t->answer); + t->n_answer_cacheable = 0; t->answer_rcode = 0; t->stream->complete = on_stream_complete; t->stream->transaction = t; @@ -347,6 +375,62 @@ static void dns_transaction_next_dns_server(DnsTransaction *t) { dns_scope_next_dns_server(t->scope); } +static void dns_transaction_cache_answer(DnsTransaction *t) { + assert(t); + + /* For mDNS we cache whenever we get the packet, rather than + * in each transaction. */ + if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) + return; + + /* We never cache if this packet is from the local host, under + * the assumption that a locally running DNS server would + * cache this anyway, and probably knows better when to flush + * the cache then we could. */ + if (!DNS_PACKET_SHALL_CACHE(t->received)) + return; + + dns_cache_put(&t->scope->cache, + t->key, + t->answer_rcode, + t->answer, + t->n_answer_cacheable, + t->answer_authenticated, + 0, + t->received->family, + &t->received->sender); +} + +static void dns_transaction_process_dnssec(DnsTransaction *t) { + int r; + + assert(t); + + /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */ + if (!set_isempty(t->dnssec_transactions)) + return; + + /* All our auxiliary DNSSEC transactions are complete now. Try + * to validate our RRset now. */ + r = dns_transaction_validate_dnssec(t); + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + return; + } + + if (!IN_SET(t->dnssec_result, _DNSSEC_RESULT_INVALID, DNSSEC_VALIDATED, DNSSEC_NO_SIGNATURE /* FOR NOW! */)) { + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return; + } + + dns_transaction_cache_answer(t); + + if (t->answer_rcode == DNS_RCODE_SUCCESS) + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + else + dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); +} + void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { usec_t ts; int r; @@ -361,7 +445,10 @@ 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); + switch (t->scope->protocol) { + case DNS_PROTOCOL_LLMNR: assert(t->scope->link); @@ -384,6 +471,18 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { break; + case DNS_PROTOCOL_MDNS: + assert(t->scope->link); + + /* For mDNS we will not accept any packets from other interfaces */ + if (p->ifindex != t->scope->link->ifindex) + return; + + if (p->family != t->scope->family) + return; + + break; + case DNS_PROTOCOL_DNS: break; @@ -415,12 +514,13 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: assert(t->server); if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) { - /* request failed, immediately try again with reduced features */ + /* Request failed, immediately try again with reduced features */ log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p))); dns_server_packet_failed(t->server, t->current_features); @@ -436,16 +536,24 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { dns_server_packet_received(t->server, t->current_features, ts - t->start_usec, p->size); break; + case DNS_PROTOCOL_LLMNR: case DNS_PROTOCOL_MDNS: dns_scope_packet_received(t->scope, ts - t->start_usec); - break; + default: - break; + assert_not_reached("Invalid DNS protocol."); } if (DNS_PACKET_TC(p)) { + + /* Truncated packets for mDNS are not allowed. Give up immediately. */ + if (t->scope->protocol == DNS_PROTOCOL_MDNS) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + /* Response was truncated, let's try again with good old TCP */ r = dns_transaction_open_tcp(t); if (r == -ESRCH) { @@ -474,41 +582,54 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { } } - /* Parse and update the cache */ + /* Parse message, if it isn't parsed yet. */ r = dns_packet_extract(p); if (r < 0) { dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); return; } - /* Only consider responses with equivalent query section to the request */ - if (p->question->n_keys != 1 || dns_resource_key_equal(p->question->keys[0], t->key) <= 0) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; + if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) { + + /* Only consider responses with equivalent query section to the request */ + r = dns_packet_is_reply_for(p, t->key); + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + return; + } + if (r == 0) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + /* Install the answer as answer to the transaction */ + dns_answer_unref(t->answer); + t->answer = dns_answer_ref(p->answer); + t->answer_rcode = DNS_PACKET_RCODE(p); + t->answer_authenticated = t->scope->dnssec_mode == DNSSEC_TRUST && DNS_PACKET_AD(p); + + /* According to RFC 4795, section 2.9. only the RRs + * from the answer section shall be cached. However, + * if we know the message is authenticated, we might + * as well cache everything. */ + if (t->answer_authenticated) + t->n_answer_cacheable = (unsigned) -1; /* everything! */ + else + t->n_answer_cacheable = DNS_PACKET_ANCOUNT(t->received); /* only the answer section */ + + r = dns_transaction_request_dnssec_keys(t); + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + return; + } + if (r > 0) { + /* There are DNSSEC transactions pending now. Update the state accordingly. */ + t->state = DNS_TRANSACTION_VALIDATING; + return; + } } - /* Install the answer as answer to the transaction */ - dns_answer_unref(t->answer); - t->answer = dns_answer_ref(p->answer); - t->answer_rcode = DNS_PACKET_RCODE(p); - t->answer_authenticated = t->scope->dnssec_mode == DNSSEC_TRUST && DNS_PACKET_AD(p); - - /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */ - if (DNS_PACKET_SHALL_CACHE(p)) - dns_cache_put(&t->scope->cache, - t->key, - DNS_PACKET_RCODE(p), - p->answer, - DNS_PACKET_ANCOUNT(p), - t->answer_authenticated, - 0, - p->family, - &p->sender); - - if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS) - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - else - dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); + dns_transaction_process_dnssec(t); } static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { @@ -527,7 +648,7 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use DNS_PACKET_ID(p) == t->id) dns_transaction_process_reply(t, p); else - log_debug("Invalid DNS packet."); + log_debug("Invalid DNS packet, ignoring."); return 0; } @@ -571,21 +692,26 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat assert(s); assert(t); - /* Timeout reached? Increase the timeout for the server used */ - switch (t->scope->protocol) { - case DNS_PROTOCOL_DNS: - assert(t->server); + if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) { + /* Timeout reached? Increase the timeout for the server used */ + switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: + assert(t->server); - dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec); + dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec); - break; - case DNS_PROTOCOL_LLMNR: - case DNS_PROTOCOL_MDNS: - dns_scope_packet_lost(t->scope, usec - t->start_usec); + break; + case DNS_PROTOCOL_LLMNR: + case DNS_PROTOCOL_MDNS: + dns_scope_packet_lost(t->scope, usec - t->start_usec); - break; - default: - assert_not_reached("Invalid DNS protocol."); + break; + default: + assert_not_reached("Invalid DNS protocol."); + } + + if (t->initial_jitter_scheduled) + t->initial_jitter_elapsed = true; } /* ...and try again with a new server */ @@ -598,38 +724,6 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat return 0; } -static int dns_transaction_make_packet(DnsTransaction *t) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - - assert(t); - - if (t->sent) - return 0; - - r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES); - if (r < 0) - return r; - - r = dns_scope_good_key(t->scope, t->key); - if (r < 0) - return r; - if (r == 0) - return -EDOM; - - r = dns_packet_append_key(p, t->key, NULL); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->qdcount = htobe16(1); - DNS_PACKET_HEADER(p)->id = t->id; - - t->sent = p; - p = NULL; - - return 0; -} - static usec_t transaction_get_resend_timeout(DnsTransaction *t) { assert(t); assert(t->scope); @@ -639,17 +733,18 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) { assert(t->server); return t->server->resend_timeout; - case DNS_PROTOCOL_LLMNR: case DNS_PROTOCOL_MDNS: + assert(t->n_attempts > 0); + return (1 << (t->n_attempts - 1)) * USEC_PER_SEC; + case DNS_PROTOCOL_LLMNR: return t->scope->resend_timeout; default: assert_not_reached("Invalid DNS protocol."); } } -int dns_transaction_go(DnsTransaction *t) { +static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { bool had_stream; - usec_t ts; int r; assert(t); @@ -658,11 +753,6 @@ int dns_transaction_go(DnsTransaction *t) { dns_transaction_stop(t); - log_debug("Excercising transaction on scope %s on %s/%s", - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); - if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); return 0; @@ -675,12 +765,11 @@ int dns_transaction_go(DnsTransaction *t) { return 0; } - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - t->n_attempts++; t->start_usec = ts; t->received = dns_packet_unref(t->received); t->answer = dns_answer_unref(t->answer); + t->n_answer_cacheable = 0; t->answer_rcode = 0; t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; @@ -700,7 +789,7 @@ int dns_transaction_go(DnsTransaction *t) { /* Check the zone, but only if this transaction is not used * for probing or verifying a zone item. */ - if (set_isempty(t->zone_items)) { + if (set_isempty(t->notify_zone_items)) { r = dns_zone_lookup(&t->scope->zone, t->key, &t->answer, NULL, NULL); if (r < 0) @@ -716,7 +805,7 @@ int dns_transaction_go(DnsTransaction *t) { /* Check the cache, but only if this transaction is not used * for probing or verifying a zone item. */ - if (set_isempty(t->zone_items)) { + if (set_isempty(t->notify_zone_items)) { /* Before trying the cache, let's make sure we figured out a * server to use. Should this cause a change of server this @@ -739,31 +828,210 @@ int dns_transaction_go(DnsTransaction *t) { } } - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && !t->initial_jitter) { - usec_t jitter; + return 1; +} + +static int dns_transaction_make_packet_mdns(DnsTransaction *t) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + bool add_known_answers = false; + DnsTransaction *other; + unsigned qdcount; + usec_t ts; + int r; + + assert(t); + assert(t->scope->protocol == DNS_PROTOCOL_MDNS); + + /* Discard any previously prepared packet, so we can start over and coalesce again */ + t->sent = dns_packet_unref(t->sent); + + r = dns_packet_new_query(&p, t->scope->protocol, 0, false); + if (r < 0) + return r; + + r = dns_packet_append_key(p, t->key, NULL); + if (r < 0) + return r; + + qdcount = 1; + + if (dns_key_is_shared(t->key)) + add_known_answers = true; + + /* + * For mDNS, we want to coalesce as many open queries in pending transactions into one single + * query packet on the wire as possible. To achieve that, we iterate through all pending transactions + * in our current scope, and see whether their timing contraints allow them to be sent. + */ + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) { + + /* Skip ourselves */ + if (other == t) + continue; + + if (other->state != DNS_TRANSACTION_PENDING) + continue; + + if (other->next_attempt_after > ts) + continue; + + if (qdcount >= UINT16_MAX) + break; + + r = dns_packet_append_key(p, other->key, NULL); + + /* + * If we can't stuff more questions into the packet, just give up. + * One of the 'other' transactions will fire later and take care of the rest. + */ + if (r == -EMSGSIZE) + break; + + if (r < 0) + return r; + + r = dns_transaction_prepare(other, ts); + if (r <= 0) + continue; + + ts += transaction_get_resend_timeout(other); + + r = sd_event_add_time( + other->scope->manager->event, + &other->timeout_event_source, + clock_boottime_or_monotonic(), + ts, 0, + on_transaction_timeout, other); + if (r < 0) + return r; + + other->state = DNS_TRANSACTION_PENDING; + other->next_attempt_after = ts; + + qdcount ++; + + if (dns_key_is_shared(other->key)) + add_known_answers = true; + } + + DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount); + + /* Append known answer section if we're asking for any shared record */ + if (add_known_answers) { + r = dns_cache_export_shared_to_packet(&t->scope->cache, p); + if (r < 0) + return r; + } + + t->sent = p; + p = NULL; + + return 0; +} + +static int dns_transaction_make_packet(DnsTransaction *t) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + assert(t); + + if (t->scope->protocol == DNS_PROTOCOL_MDNS) + return dns_transaction_make_packet_mdns(t); + + if (t->sent) + return 0; + + r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES); + if (r < 0) + return r; + + r = dns_scope_good_key(t->scope, t->key); + if (r < 0) + return r; + if (r == 0) + return -EDOM; + + r = dns_packet_append_key(p, t->key, NULL); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->qdcount = htobe16(1); + DNS_PACKET_HEADER(p)->id = t->id; + + t->sent = p; + p = NULL; + + return 0; +} + +int dns_transaction_go(DnsTransaction *t) { + usec_t ts; + int r; + + assert(t); + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + r = dns_transaction_prepare(t, ts); + if (r <= 0) + return r; + + if (log_get_max_level() >= LOG_DEBUG) { + _cleanup_free_ char *ks = NULL; + + (void) dns_resource_key_to_string(t->key, &ks); + + log_debug("Excercising transaction for <%s> on scope %s on %s/%s", + ks ? strstrip(ks) : "???", + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); + } + + if (!t->initial_jitter_scheduled && + (t->scope->protocol == DNS_PROTOCOL_LLMNR || + t->scope->protocol == DNS_PROTOCOL_MDNS)) { + usec_t jitter, accuracy; /* RFC 4795 Section 2.7 suggests all queries should be * delayed by a random time from 0 to JITTER_INTERVAL. */ - t->initial_jitter = true; + t->initial_jitter_scheduled = true; random_bytes(&jitter, sizeof(jitter)); - jitter %= LLMNR_JITTER_INTERVAL_USEC; + + switch (t->scope->protocol) { + case DNS_PROTOCOL_LLMNR: + jitter %= LLMNR_JITTER_INTERVAL_USEC; + accuracy = LLMNR_JITTER_INTERVAL_USEC; + break; + case DNS_PROTOCOL_MDNS: + jitter %= MDNS_JITTER_RANGE_USEC; + jitter += MDNS_JITTER_MIN_USEC; + accuracy = MDNS_JITTER_RANGE_USEC; + break; + default: + assert_not_reached("bad protocol"); + } r = sd_event_add_time( t->scope->manager->event, &t->timeout_event_source, clock_boottime_or_monotonic(), - ts + jitter, - LLMNR_JITTER_INTERVAL_USEC, + ts + jitter, accuracy, on_transaction_timeout, t); if (r < 0) return r; t->n_attempts = 0; + t->next_attempt_after = ts; t->state = DNS_TRANSACTION_PENDING; - log_debug("Delaying LLMNR transaction for " USEC_FMT "us.", jitter); + log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter); return 0; } @@ -810,22 +1078,458 @@ int dns_transaction_go(DnsTransaction *t) { return dns_transaction_go(t); } + ts += transaction_get_resend_timeout(t); + r = sd_event_add_time( t->scope->manager->event, &t->timeout_event_source, clock_boottime_or_monotonic(), - ts + transaction_get_resend_timeout(t), 0, + ts, 0, on_transaction_timeout, t); if (r < 0) return r; t->state = DNS_TRANSACTION_PENDING; + t->next_attempt_after = ts; + + return 1; +} + +static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) { + DnsTransaction *aux; + int r; + + assert(t); + assert(ret); + assert(key); + + aux = dns_scope_find_transaction(t->scope, key, true); + if (!aux) { + r = dns_transaction_new(&aux, t->scope, key); + if (r < 0) + return r; + } else { + if (set_contains(t->dnssec_transactions, aux)) { + *ret = aux; + return 0; + } + } + + r = set_ensure_allocated(&t->dnssec_transactions, NULL); + if (r < 0) + goto gc; + + r = set_ensure_allocated(&aux->notify_transactions, NULL); + if (r < 0) + goto gc; + + r = set_put(t->dnssec_transactions, aux); + if (r < 0) + goto gc; + + r = set_put(aux->notify_transactions, t); + if (r < 0) { + (void) set_remove(t->dnssec_transactions, aux); + goto gc; + } + + *ret = aux; + return 1; + +gc: + dns_transaction_gc(aux); + return r; +} + +static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { + _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL; + DnsTransaction *aux; + int r; + + assert(t); + assert(key); + + /* Try to get the data from the trust anchor */ + r = dns_trust_anchor_lookup(&t->scope->manager->trust_anchor, key, &a); + if (r < 0) + return r; + if (r > 0) { + r = dns_answer_extend(&t->validated_keys, a); + if (r < 0) + return r; + + return 0; + } + + /* This didn't work, ask for it via the network/cache then. */ + r = dns_transaction_add_dnssec_transaction(t, key, &aux); + if (r < 0) + return r; + + if (aux->state == DNS_TRANSACTION_NULL) { + r = dns_transaction_go(aux); + if (r < 0) + return r; + } + + return 0; +} + +int dns_transaction_request_dnssec_keys(DnsTransaction *t) { + DnsResourceRecord *rr; + int r; + + assert(t); + + if (t->scope->dnssec_mode != DNSSEC_YES) + return 0; + + DNS_ANSWER_FOREACH(rr, t->answer) { + + switch (rr->key->type) { + + case DNS_TYPE_RRSIG: { + /* For each RRSIG we request the matching DNSKEY */ + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL; + + /* If this RRSIG is about a DNSKEY RR and the + * signer is the same as the owner, then we + * already have the DNSKEY, and we don't have + * to look for more. */ + if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) { + r = dns_name_equal(rr->rrsig.signer, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + continue; + } + + /* If the signer is not a parent of the owner, + * then the signature is bogus, let's ignore + * it. */ + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), rr->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + continue; + + dnskey = dns_resource_key_new(rr->key->class, DNS_TYPE_DNSKEY, rr->rrsig.signer); + if (!dnskey) + return -ENOMEM; + + log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (key tag: %" PRIu16 ").", t->id, rr->rrsig.key_tag); + + r = dns_transaction_request_dnssec_rr(t, dnskey); + if (r < 0) + return r; + break; + } + + case DNS_TYPE_DNSKEY: { + /* For each DNSKEY we request the matching DS */ + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; + + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key)); + if (!ds) + return -ENOMEM; + + log_debug("Requesting DS to validate transaction %" PRIu16" (key tag: %" PRIu16 ").", t->id, dnssec_keytag(rr)); + + r = dns_transaction_request_dnssec_rr(t, ds); + if (r < 0) + return r; + + break; + }} + } + + return !set_isempty(t->dnssec_transactions); +} + +void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) { + int r; + + assert(t); + assert(IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)); + assert(source); + + /* Invoked whenever any of our auxiliary DNSSEC transactions + completed its work. We simply copy the answer from that + transaction over. */ + + if (source->state != DNS_TRANSACTION_SUCCESS) { + log_debug("Auxiliary DNSSEC RR query failed."); + t->dnssec_result = DNSSEC_FAILED_AUXILIARY; + } else { + r = dns_answer_extend(&t->validated_keys, source->answer); + if (r < 0) { + log_error_errno(r, "Failed to merge validated DNSSEC key data: %m"); + t->dnssec_result = DNSSEC_FAILED_AUXILIARY; + } + } + + /* Detach us from the DNSSEC transaction. */ + (void) set_remove(t->dnssec_transactions, source); + (void) set_remove(source->notify_transactions, t); + + /* If the state is still PENDING, we are still in the loop + * that adds further DNSSEC transactions, hence don't check if + * we are ready yet. If the state is VALIDATING however, we + * should check if we are complete now. */ + if (t->state == DNS_TRANSACTION_VALIDATING) + dns_transaction_process_dnssec(t); +} + +static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) { + int r; + + assert(t); + assert(rr); + + /* Check if the specified RR is the "primary" response, + * i.e. either matches the question precisely or is a + * CNAME/DNAME for it, or is any kind of NSEC/NSEC3 RR */ + + if (IN_SET(rr->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3)) + return 1; + + r = dns_resource_key_match_rr(t->key, rr, NULL); + if (r != 0) + return r; + + r = dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); + if (r != 0) + return r; + + return 0; +} + +static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { + DnsResourceRecord *rr; + int ifindex, r; + + assert(t); + + /* Add all DNSKEY RRs from the answer that are validated by DS + * RRs from the list of validated keys to the lis of validated + * keys. */ + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { + + r = dnssec_verify_dnskey_search(rr, t->validated_keys); + if (r < 0) + return r; + if (r == 0) + continue; + + /* If so, the DNSKEY is validated too. */ + r = dns_answer_add_extend(&t->validated_keys, rr, ifindex); + if (r < 0) + return r; + } + + return 0; +} + +int dns_transaction_validate_dnssec(DnsTransaction *t) { + _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL; + bool dnskeys_finalized = false; + DnsResourceRecord *rr; + int r; + + assert(t); + + /* We have now collected all DS and DNSKEY RRs in + * t->validated_keys, let's see which RRs we can now + * authenticate with that. */ + + if (t->scope->dnssec_mode != DNSSEC_YES) + return 0; + + /* Already validated */ + if (t->dnssec_result != _DNSSEC_RESULT_INVALID) + return 0; + + if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) { + t->dnssec_result = DNSSEC_VALIDATED; + t->answer_authenticated = true; + return 0; + } + + if (log_get_max_level() >= LOG_DEBUG) { + _cleanup_free_ char *ks = NULL; + + (void) dns_resource_key_to_string(t->key, &ks); + log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, ks ? strstrip(ks) : "???"); + } + + /* First see if there are DNSKEYs we already known a validated DS for. */ + r = dns_transaction_validate_dnskey_by_ds(t); + if (r < 0) + return r; + + for (;;) { + bool changed = false; + + DNS_ANSWER_FOREACH(rr, t->answer) { + DnssecResult result; + + if (rr->key->type == DNS_TYPE_RRSIG) + continue; + + r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result); + if (r < 0) + return r; + + if (log_get_max_level() >= LOG_DEBUG) { + _cleanup_free_ char *rrs = NULL; + + (void) dns_resource_record_to_string(rr, &rrs); + log_debug("Looking at %s: %s", rrs ? strstrip(rrs) : "???", dnssec_result_to_string(result)); + } + + if (result == DNSSEC_VALIDATED) { + + /* Add the validated RRset to the new list of validated RRsets */ + r = dns_answer_copy_by_key(&validated, t->answer, rr->key); + if (r < 0) + return r; + + if (rr->key->type == DNS_TYPE_DNSKEY) { + /* If we just validated a + * DNSKEY RRset, then let's + * add these keys to the set + * of validated keys for this + * transaction. */ + + r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key); + if (r < 0) + return r; + } + + /* Now, remove this RRset from the RRs still to process */ + r = dns_answer_remove_by_key(&t->answer, rr->key); + if (r < 0) + return r; + + /* Exit the loop, we dropped something from the answer, start from the beginning */ + changed = true; + break; + + } else if (dnskeys_finalized) { + /* If we haven't read all DNSKEYs yet + * a negative result of the validation + * is irrelevant, as there might be + * more DNSKEYs coming. */ + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r > 0) { + /* This is a primary response + * to our question, and it + * failed validation. That's + * fatal. */ + t->dnssec_result = result; + return 0; + } + + /* This is just some auxiliary + * data. Just remove the RRset and + * continue. */ + r = dns_answer_remove_by_key(&t->answer, rr->key); + if (r < 0) + return r; + + /* Exit the loop, we dropped something from the answer, start from the beginning */ + changed = true; + break; + } + } + + if (changed) + continue; + + if (!dnskeys_finalized) { + /* OK, now we know we have added all DNSKEYs + * we possibly could to our validated + * list. Now run the whole thing once more, + * and strip everything we still cannot + * validate. + */ + dnskeys_finalized = true; + continue; + } + + /* We're done */ + break; + } + + dns_answer_unref(t->answer); + t->answer = validated; + validated = NULL; + + /* Everything that's now in t->answer is known to be good, hence cacheable. */ + t->n_answer_cacheable = (unsigned) -1; /* everything! */ + + /* At this point the answer only contains validated + * RRsets. Now, let's see if it actually answers the question + * we asked. If so, great! If it doesn't, then see if + * NSEC/NSEC3 can prove this. */ + r = dns_answer_match_key(t->answer, t->key); + if (r < 0) + return r; + if (r > 0) { + /* Yes, it answer the question, everything is authenticated. */ + t->dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = true; + } else if (r == 0) { + DnssecNsecResult nr; + + /* Bummer! Let's check NSEC/NSEC3 */ + r = dnssec_test_nsec(t->answer, t->key, &nr); + if (r < 0) + return r; + + switch (nr) { + + case DNSSEC_NSEC_NXDOMAIN: + /* NSEC proves the domain doesn't exist. Very good. */ + t->dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_NXDOMAIN; + t->answer_authenticated = true; + break; + + case DNSSEC_NSEC_NODATA: + /* NSEC proves that there's no data here, very good. */ + t->dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = true; + break; + + case DNSSEC_NSEC_NO_RR: + /* No NSEC data? Bummer! */ + t->dnssec_result = DNSSEC_UNSIGNED; + break; + + case DNSSEC_NSEC_FOUND: + /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ + t->dnssec_result = DNSSEC_NSEC_MISMATCH; + break; + + default: + assert_not_reached("Unexpected NSEC result."); + } + } + return 1; } static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = { [DNS_TRANSACTION_NULL] = "null", [DNS_TRANSACTION_PENDING] = "pending", + [DNS_TRANSACTION_VALIDATING] = "validating", [DNS_TRANSACTION_FAILURE] = "failure", [DNS_TRANSACTION_SUCCESS] = "success", [DNS_TRANSACTION_NO_SERVERS] = "no-servers", @@ -834,6 +1538,7 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", [DNS_TRANSACTION_RESOURCES] = "resources", [DNS_TRANSACTION_ABORTED] = "aborted", + [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed", }; DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index a3058ce6e8..1f35a4dd8f 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -28,6 +28,7 @@ typedef enum DnsTransactionSource DnsTransactionSource; enum DnsTransactionState { DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, + DNS_TRANSACTION_VALIDATING, DNS_TRANSACTION_FAILURE, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NO_SERVERS, @@ -36,10 +37,13 @@ enum DnsTransactionState { DNS_TRANSACTION_INVALID_REPLY, DNS_TRANSACTION_RESOURCES, DNS_TRANSACTION_ABORTED, + DNS_TRANSACTION_DNSSEC_FAILED, _DNS_TRANSACTION_STATE_MAX, _DNS_TRANSACTION_STATE_INVALID = -1 }; +#define DNS_TRANSACTION_IS_LIVE(state) IN_SET((state), DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING) + enum DnsTransactionSource { DNS_TRANSACTION_NETWORK, DNS_TRANSACTION_CACHE, @@ -60,18 +64,26 @@ struct DnsTransaction { DnsResourceKey *key; DnsTransactionState state; + DnssecResult dnssec_result; + uint16_t id; - bool initial_jitter; + bool initial_jitter_scheduled; + bool initial_jitter_elapsed; DnsPacket *sent, *received; DnsAnswer *answer; + unsigned n_answer_cacheable; /* Specifies how many RRs of the answer shall be cached, from the beginning */ int answer_rcode; DnsTransactionSource answer_source; bool answer_authenticated; + /* Contains DS and DNSKEY RRs we already verified and need to authenticate this reply */ + DnsAnswer *validated_keys; + usec_t start_usec; + usec_t next_attempt_after; sd_event_source *timeout_event_source; unsigned n_attempts; @@ -81,7 +93,7 @@ struct DnsTransaction { /* The active server */ DnsServer *server; - /* the features of the DNS server at time of transaction start */ + /* The features of the DNS server at time of transaction start */ DnsServerFeatureLevel current_features; /* TCP connection logic, if we need it */ @@ -90,11 +102,21 @@ struct DnsTransaction { /* Query candidates this transaction is referenced by and that * shall be notified about this specific transaction * completing. */ - Set *query_candidates; + Set *notify_query_candidates; /* Zone items this transaction is referenced by and that shall * be notified about completion. */ - Set *zone_items; + Set *notify_zone_items; + + /* Other transactions that this transactions is referenced by + * and that shall be notified about completion. This is used + * when transactions want to validate their RRsets, but need + * another DNSKEY or DS RR to do so. */ + Set *notify_transactions; + + /* The opposite direction: the transactions this transaction + * created in order to request DNSKEY or DS RRs. */ + Set *dnssec_transactions; unsigned block_gc; @@ -110,6 +132,10 @@ int dns_transaction_go(DnsTransaction *t); void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p); void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state); +void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source); +int dns_transaction_validate_dnssec(DnsTransaction *t); +int dns_transaction_request_dnssec_keys(DnsTransaction *t); + const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; @@ -119,6 +145,10 @@ DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; /* LLMNR Jitter interval, see RFC 4795 Section 7 */ #define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) +/* mDNS Jitter interval, see RFC 6762 Section 5.2 */ +#define MDNS_JITTER_MIN_USEC (20 * USEC_PER_MSEC) +#define MDNS_JITTER_RANGE_USEC (100 * USEC_PER_MSEC) + /* Maximum attempts to send DNS requests, across all DNS servers */ #define DNS_TRANSACTION_ATTEMPTS_MAX 16 diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 78f44d51a2..8046e2ed34 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -39,7 +39,7 @@ void dns_zone_item_probe_stop(DnsZoneItem *i) { t = i->probe_transaction; i->probe_transaction = NULL; - set_remove(t->zone_items, i); + set_remove(t->notify_zone_items, i); dns_transaction_gc(t); } @@ -184,11 +184,11 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) { return r; } - r = set_ensure_allocated(&t->zone_items, NULL); + r = set_ensure_allocated(&t->notify_zone_items, NULL); if (r < 0) goto gc; - r = set_put(t->zone_items, i); + r = set_put(t->notify_zone_items, i); if (r < 0) goto gc; @@ -206,7 +206,7 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) { } } - dns_zone_item_ready(i); + dns_zone_item_notify(i); return 0; gc: @@ -491,7 +491,7 @@ void dns_zone_item_conflict(DnsZoneItem *i) { manager_next_hostname(i->scope->manager); } -void dns_zone_item_ready(DnsZoneItem *i) { +void dns_zone_item_notify(DnsZoneItem *i) { _cleanup_free_ char *pretty = NULL; assert(i); @@ -500,7 +500,7 @@ void dns_zone_item_ready(DnsZoneItem *i) { if (i->block_ready > 0) return; - if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)) + if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) return; if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) { diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h index 44a8624c30..dbd6a2a368 100644 --- a/src/resolve/resolved-dns-zone.h +++ b/src/resolve/resolved-dns-zone.h @@ -70,7 +70,7 @@ void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr); int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **answer, DnsAnswer **soa, bool *tentative); void dns_zone_item_conflict(DnsZoneItem *i); -void dns_zone_item_ready(DnsZoneItem *i); +void dns_zone_item_notify(DnsZoneItem *i); int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr); int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index ddd9427dab..84100bd988 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -77,6 +77,8 @@ Link *link_free(Link *l) { dns_scope_free(l->unicast_scope); dns_scope_free(l->llmnr_ipv4_scope); dns_scope_free(l->llmnr_ipv6_scope); + dns_scope_free(l->mdns_ipv4_scope); + dns_scope_free(l->mdns_ipv6_scope); free(l); return NULL; @@ -118,6 +120,28 @@ static void link_allocate_scopes(Link *l) { } } else l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope); + + if (link_relevant(l, AF_INET) && + l->mdns_support != SUPPORT_NO && + l->manager->mdns_support != SUPPORT_NO) { + if (!l->mdns_ipv4_scope) { + r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET); + if (r < 0) + log_warning_errno(r, "Failed to allocate mDNS IPv4 scope: %m"); + } + } else + l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); + + if (link_relevant(l, AF_INET6) && + l->mdns_support != SUPPORT_NO && + l->manager->mdns_support != SUPPORT_NO) { + if (!l->mdns_ipv6_scope) { + r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6); + if (r < 0) + log_warning_errno(r, "Failed to allocate mDNS IPv6 scope: %m"); + } + } else + l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope); } void link_add_rrs(Link *l, bool force_remove) { diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index eb00015bd5..a3b406bbc2 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -67,10 +67,13 @@ struct Link { unsigned n_search_domains; Support llmnr_support; + Support mdns_support; DnsScope *unicast_scope; DnsScope *llmnr_ipv4_scope; DnsScope *llmnr_ipv6_scope; + DnsScope *mdns_ipv4_scope; + DnsScope *mdns_ipv6_scope; char name[IF_NAMESIZE]; uint32_t mtu; diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c index 6a7ff9d245..ed754c3899 100644 --- a/src/resolve/resolved-llmnr.c +++ b/src/resolve/resolved-llmnr.c @@ -461,10 +461,8 @@ int manager_llmnr_ipv6_tcp_fd(Manager *m) { } r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, m); - if (r < 0) { - r = -errno; + if (r < 0) goto fail; - } return m->llmnr_ipv6_tcp_fd; diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 5a3696ccb0..a2677f442a 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -40,6 +40,7 @@ #include "resolved-llmnr.h" #include "resolved-manager.h" #include "resolved-resolv-conf.h" +#include "resolved-mdns.h" #include "socket-util.h" #include "string-table.h" #include "string-util.h" @@ -472,6 +473,7 @@ int manager_new(Manager **ret) { m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1; m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1; + m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1; m->hostname_fd = -1; m->llmnr_support = SUPPORT_YES; @@ -528,6 +530,10 @@ int manager_start(Manager *m) { if (r < 0) return r; + r = manager_mdns_start(m); + if (r < 0) + return r; + return 0; } @@ -559,6 +565,7 @@ Manager *manager_free(Manager *m) { sd_event_source_unref(m->rtnl_event_source); manager_llmnr_stop(m); + manager_mdns_stop(m); sd_bus_slot_unref(m->prepare_for_sleep_slot); sd_event_source_unref(m->bus_retry_event_source); @@ -1024,11 +1031,25 @@ DnsScope* manager_find_scope(Manager *m, DnsPacket *p) { if (!l) return NULL; - if (p->protocol == DNS_PROTOCOL_LLMNR) { + switch (p->protocol) { + case DNS_PROTOCOL_LLMNR: if (p->family == AF_INET) return l->llmnr_ipv4_scope; else if (p->family == AF_INET6) return l->llmnr_ipv6_scope; + + break; + + case DNS_PROTOCOL_MDNS: + if (p->family == AF_INET) + return l->mdns_ipv4_scope; + else if (p->family == AF_INET6) + return l->mdns_ipv6_scope; + + break; + + default: + break; } return NULL; diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 1056f23ab7..b52273403a 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -54,6 +54,7 @@ struct Manager { sd_event *event; Support llmnr_support; + Support mdns_support; /* Network */ Hashmap *links; @@ -102,6 +103,13 @@ struct Manager { sd_event_source *llmnr_ipv4_tcp_event_source; sd_event_source *llmnr_ipv6_tcp_event_source; + /* mDNS */ + int mdns_ipv4_fd; + int mdns_ipv6_fd; + + sd_event_source *mdns_ipv4_event_source; + sd_event_source *mdns_ipv6_event_source; + /* dbus */ sd_bus *bus; sd_event_source *bus_retry_event_source; diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c new file mode 100644 index 0000000000..d6973a6999 --- /dev/null +++ b/src/resolve/resolved-mdns.c @@ -0,0 +1,290 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Daniel Mack + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. + ***/ + +#include <resolv.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "fd-util.h" +#include "resolved-manager.h" +#include "resolved-mdns.h" + +void manager_mdns_stop(Manager *m) { + assert(m); + + m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source); + m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); + + m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source); + m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); +} + +int manager_mdns_start(Manager *m) { + int r; + + assert(m); + + if (m->mdns_support == SUPPORT_NO) + return 0; + + r = manager_mdns_ipv4_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + if (socket_ipv6_is_supported()) { + r = manager_mdns_ipv6_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + } + + return 0; + +eaddrinuse: + log_warning("There appears to be another mDNS responder running. Turning off mDNS support."); + m->mdns_support = SUPPORT_NO; + manager_mdns_stop(m); + + return 0; +} + +static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + Manager *m = userdata; + DnsScope *scope; + int r; + + r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); + if (r <= 0) + return r; + + scope = manager_find_scope(m, p); + if (!scope) { + log_warning("Got mDNS UDP packet on unknown scope. Ignoring."); + return 0; + } + + if (dns_packet_validate_reply(p) > 0) { + DnsResourceRecord *rr; + + log_debug("Got mDNS reply packet"); + + /* + * mDNS is different from regular DNS and LLMNR with regard to handling responses. + * While on other protocols, we can ignore every answer that doesn't match a question + * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all + * incoming information, regardless of the DNS packet ID. + * + * Hence, extract the packet here, and try to find a transaction for answer the we got + * and complete it. Also store the new information in scope's cache. + */ + r = dns_packet_extract(p); + if (r < 0) { + log_debug("mDNS packet extraction failed."); + return 0; + } + + dns_scope_check_conflicts(scope, p); + + DNS_ANSWER_FOREACH(rr, p->answer) { + const char *name = DNS_RESOURCE_KEY_NAME(rr->key); + DnsTransaction *t; + + /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa, + * we assume someone's playing tricks on us and discard the packet completely. */ + if (!(dns_name_endswith(name, "in-addr.arpa") > 0 || + dns_name_endswith(name, "local") > 0)) + return 0; + + t = dns_scope_find_transaction(scope, rr->key, false); + if (t) + dns_transaction_process_reply(t, p); + } + + dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, + p->answer->n_rrs, false, 0, p->family, &p->sender); + + } else if (dns_packet_validate_query(p) > 0) { + log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); + + dns_scope_process_query(scope, NULL, p); + } else + log_debug("Invalid mDNS UDP packet."); + + return 0; +} + +int manager_mdns_ipv4_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(MDNS_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; + int r; + + assert(m); + + if (m->mdns_ipv4_fd >= 0) + return m->mdns_ipv4_fd; + + m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->mdns_ipv4_fd < 0) + return -errno; + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m); + if (r < 0) + goto fail; + + return m->mdns_ipv4_fd; + +fail: + m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); + return r; +} + +int manager_mdns_ipv6_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(MDNS_PORT), + }; + static const int one = 1, ttl = 255; + int r; + + assert(m); + + if (m->mdns_ipv6_fd >= 0) + return m->mdns_ipv6_fd; + + m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->mdns_ipv6_fd < 0) + return -errno; + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m); + if (r < 0) + goto fail; + + return m->mdns_ipv6_fd; + +fail: + m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); + return r; +} diff --git a/src/resolve/resolved-mdns.h b/src/resolve/resolved-mdns.h new file mode 100644 index 0000000000..8a84010615 --- /dev/null +++ b/src/resolve/resolved-mdns.h @@ -0,0 +1,32 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Daniel Mack + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "resolved-manager.h" + +#define MDNS_PORT 5353 + +int manager_mdns_ipv4_fd(Manager *m); +int manager_mdns_ipv6_fd(Manager *m); + +void manager_mdns_stop(Manager *m); +int manager_mdns_start(Manager *m); diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c index 0b2ffeeddd..807eeb3d9a 100644 --- a/src/resolve/test-dnssec.c +++ b/src/resolve/test-dnssec.c @@ -27,6 +27,103 @@ #include "resolved-dns-dnssec.h" #include "resolved-dns-rr.h" #include "string-util.h" +#include "hexdecoct.h" + +static void test_dnssec_verify_rrset2(void) { + + static const uint8_t signature_blob[] = { + 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11, + 0x7d, 0xf1, 0xe6, 0x87, 0xb9, 0x42, 0xd3, 0x8b, 0x9e, 0xaf, 0x92, 0x31, 0x0a, 0x53, 0xad, 0x8b, + 0xa7, 0x5c, 0x83, 0x39, 0x8c, 0x28, 0xac, 0xce, 0x6e, 0x9c, 0x18, 0xe3, 0x31, 0x16, 0x6e, 0xca, + 0x38, 0x31, 0xaf, 0xd9, 0x94, 0xf1, 0x84, 0xb1, 0xdf, 0x5a, 0xc2, 0x73, 0x22, 0xf6, 0xcb, 0xa2, + 0xe7, 0x8c, 0x77, 0x0c, 0x74, 0x2f, 0xc2, 0x13, 0xb0, 0x93, 0x51, 0xa9, 0x4f, 0xae, 0x0a, 0xda, + 0x45, 0xcc, 0xfd, 0x43, 0x99, 0x36, 0x9a, 0x0d, 0x21, 0xe0, 0xeb, 0x30, 0x65, 0xd4, 0xa0, 0x27, + 0x37, 0x3b, 0xe4, 0xc1, 0xc5, 0xa1, 0x2a, 0xd1, 0x76, 0xc4, 0x7e, 0x64, 0x0e, 0x5a, 0xa6, 0x50, + 0x24, 0xd5, 0x2c, 0xcc, 0x6d, 0xe5, 0x37, 0xea, 0xbd, 0x09, 0x34, 0xed, 0x24, 0x06, 0xa1, 0x22, + }; + + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0xc3, 0x7f, 0x1d, 0xd1, 0x1c, 0x97, 0xb1, 0x13, 0x34, 0x3a, 0x9a, 0xea, + 0xee, 0xd9, 0x5a, 0x11, 0x1b, 0x17, 0xc7, 0xe3, 0xd4, 0xda, 0x20, 0xbc, 0x5d, 0xba, 0x74, 0xe3, + 0x37, 0x99, 0xec, 0x25, 0xce, 0x93, 0x7f, 0xbd, 0x22, 0x73, 0x7e, 0x14, 0x71, 0xe0, 0x60, 0x07, + 0xd4, 0x39, 0x8b, 0x5e, 0xe9, 0xba, 0x25, 0xe8, 0x49, 0xe9, 0x34, 0xef, 0xfe, 0x04, 0x5c, 0xa5, + 0x27, 0xcd, 0xa9, 0xda, 0x70, 0x05, 0x21, 0xab, 0x15, 0x82, 0x24, 0xc3, 0x94, 0xf5, 0xd7, 0xb7, + 0xc4, 0x66, 0xcb, 0x32, 0x6e, 0x60, 0x2b, 0x55, 0x59, 0x28, 0x89, 0x8a, 0x72, 0xde, 0x88, 0x56, + 0x27, 0x95, 0xd9, 0xac, 0x88, 0x4f, 0x65, 0x2b, 0x68, 0xfc, 0xe6, 0x41, 0xc1, 0x1b, 0xef, 0x4e, + 0xd6, 0xc2, 0x0f, 0x64, 0x88, 0x95, 0x5e, 0xdd, 0x3a, 0x02, 0x07, 0x50, 0xa9, 0xda, 0xa4, 0x49, + 0x74, 0x62, 0xfe, 0xd7, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL; + DnssecResult result; + + nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov"); + assert_se(nsec); + + nsec->nsec.next_domain_name = strdup("3D-Printing.nasa.gov"); + assert_se(nsec->nsec.next_domain_name); + + nsec->nsec.types = bitmap_new(); + assert_se(nsec->nsec.types); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_A) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NS) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_SOA) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_MX) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_TXT) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_RRSIG) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NSEC) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0); + assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0); + + assert_se(dns_resource_record_to_string(nsec, &x) >= 0); + log_info("NSEC: %s", x); + + rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); + assert_se(rrsig); + + rrsig->rrsig.type_covered = DNS_TYPE_NSEC; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rrsig->rrsig.labels = 2; + rrsig->rrsig.original_ttl = 300; + rrsig->rrsig.expiration = 0x5689002f; + rrsig->rrsig.inception = 0x56617230; + rrsig->rrsig.key_tag = 30390; + rrsig->rrsig.signer = strdup("Nasa.Gov."); + assert_se(rrsig->rrsig.signer); + rrsig->rrsig.signature_size = sizeof(signature_blob); + rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); + assert_se(rrsig->rrsig.signature); + + assert_se(dns_resource_record_to_string(rrsig, &y) >= 0); + log_info("RRSIG: %s", y); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 256; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + assert_se(dns_resource_record_to_string(dnskey, &z) >= 0); + log_info("DNSKEY: %s", z); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey)); + + assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey) > 0); + + answer = dns_answer_new(1); + assert_se(answer); + assert_se(dns_answer_add(answer, nsec, 0) >= 0); + + /* Validate the RR as it if was 2015-12-11 today */ + assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0); + assert_se(result == DNSSEC_VALIDATED); +} static void test_dnssec_verify_rrset(void) { @@ -56,6 +153,7 @@ static void test_dnssec_verify_rrset(void) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL; + DnssecResult result; a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov"); assert_se(a); @@ -106,7 +204,8 @@ static void test_dnssec_verify_rrset(void) { assert_se(dns_answer_add(answer, a, 0) >= 0); /* Validate the RR as it if was 2015-12-2 today */ - assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC) == DNSSEC_VERIFIED); + assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0); + assert_se(result == DNSSEC_VALIDATED); } static void test_dnssec_verify_dns_key(void) { @@ -207,11 +306,46 @@ static void test_dnssec_canonicalize(void) { test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL); } +static void test_dnssec_nsec3_hash(void) { + static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE }; + static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 }; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ char *a = NULL, *b = NULL; + uint8_t h[DNSSEC_HASH_SIZE_MAX]; + int k; + + /* The NSEC3 RR for eurid.eu on 2015-12-14. */ + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC3, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM.eurid.eu."); + assert_se(rr); + + rr->nsec3.algorithm = DNSSEC_DIGEST_SHA1; + rr->nsec3.flags = 1; + rr->nsec3.iterations = 1; + rr->nsec3.salt = memdup(salt, sizeof(salt)); + assert_se(rr->nsec3.salt); + rr->nsec3.salt_size = sizeof(salt); + rr->nsec3.next_hashed_name = memdup(next_hashed_name, sizeof(next_hashed_name)); + assert_se(rr->nsec3.next_hashed_name); + rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name); + + assert_se(dns_resource_record_to_string(rr, &a) >= 0); + log_info("NSEC3: %s", a); + + k = dnssec_nsec3_hash(rr, "eurid.eu", &h); + assert_se(k >= 0); + + b = base32hexmem(h, k, false); + assert_se(b); + assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0); +} + int main(int argc, char*argv[]) { test_dnssec_canonicalize(); test_dnssec_verify_dns_key(); test_dnssec_verify_rrset(); + test_dnssec_verify_rrset2(); + test_dnssec_nsec3_hash(); return 0; } |