diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 4966db07b35..cb0708d0987 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -75,6 +75,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY), SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SOURCE, ESRCH), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DNSSD_SERVICE, ENOENT), SD_BUS_ERROR_MAP(BUS_ERROR_DNSSD_SERVICE_EXISTS, EEXIST), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 56eb5551ec6..28332836b51 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -73,6 +73,7 @@ #define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink" #define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy" #define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown" +#define BUS_ERROR_NO_SOURCE "org.freedesktop.resolve1.NoSource" #define BUS_ERROR_NO_SUCH_DNSSD_SERVICE "org.freedesktop.resolve1.NoSuchDnssdService" #define BUS_ERROR_DNSSD_SERVICE_EXISTS "org.freedesktop.resolve1.DnssdServiceExists" #define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index dd27c50b498..6db8261ac02 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -101,6 +101,9 @@ static int reply_query_state(DnsQuery *q) { * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ return sd_bus_reply_method_errorf(q->bus_request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); + case DNS_TRANSACTION_NO_SOURCE: + return sd_bus_reply_method_errorf(q->bus_request, BUS_ERROR_NO_SOURCE, "All suitable resolution sources turned off"); + case DNS_TRANSACTION_RCODE_FAILURE: { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -274,8 +277,8 @@ static int validate_and_mangle_flags( * * 1. Checks that the interface index is either 0 (meaning *all* interfaces) or positive * - * 2. Only the protocols flags and the NO_CNAME flag are set, at most. Plus additional flags specific - * to our method, passed in the "ok" parameter. + * 2. Only the protocols flags and a bunch of NO_XYZ flags are set, at most. Plus additional flags + * specific to our method, passed in the "ok" parameter. * * 3. If zero protocol flags are specified it is automatically turned into *all* protocols. This way * clients can simply pass 0 as flags and all will work as it should. They can also use this so @@ -283,7 +286,15 @@ static int validate_and_mangle_flags( * to mean "all supported protocols". */ - if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) + if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL| + SD_RESOLVED_NO_CNAME| + SD_RESOLVED_NO_VALIDATE| + SD_RESOLVED_NO_SYNTHESIZE| + SD_RESOLVED_NO_CACHE| + SD_RESOLVED_NO_ZONE| + SD_RESOLVED_NO_TRUST_ANCHOR| + SD_RESOLVED_NO_NETWORK| + ok)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ @@ -406,7 +417,7 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, if (r < 0 && r != -EALREADY) return r; - r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, ifindex, flags); + r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, NULL, ifindex, flags); if (r < 0) return r; @@ -548,7 +559,7 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s if (r < 0) return r; - r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; @@ -724,14 +735,12 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd if (r < 0) return r; - r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); + /* Setting SD_RESOLVED_CLAMP_TTL: let's request that the TTL is fixed up for locally cached entries, + * after all we return it in the wire format blob. */ + r = dns_query_new(m, &q, question, question, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_CLAMP_TTL); if (r < 0) return r; - /* Let's request that the TTL is fixed up for locally cached entries, after all we return it in the wire format - * blob */ - q->clamp_ttl = true; - q->bus_request = sd_bus_message_ref(message); q->complete = bus_method_resolve_record_complete; @@ -1088,7 +1097,7 @@ static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifin if (r < 0) return r; - r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new(q->manager, &aux, question, question, NULL, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; @@ -1258,7 +1267,7 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s if (r < 0) return r; - r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new(m, &q, question_utf8, question_idna, NULL, ifindex, flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h index 21eb6994e62..133cf0556f6 100644 --- a/src/resolve/resolved-def.h +++ b/src/resolve/resolved-def.h @@ -27,6 +27,31 @@ /* Output: Result is authenticated */ #define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9) +/* Input: Don't DNSSEC validate request */ +#define SD_RESOLVED_NO_VALIDATE (UINT64_C(1) << 10) + +/* Input: Don't answer request from locally synthesized records (which includes /etc/hosts) */ +#define SD_RESOLVED_NO_SYNTHESIZE (UINT64_C(1) << 11) + +/* Input: Don't answer request from cache */ +#define SD_RESOLVED_NO_CACHE (UINT64_C(1) << 12) + +/* Input: Don't answer request from locally registered public LLMNR/mDNS RRs */ +#define SD_RESOLVED_NO_ZONE (UINT64_C(1) << 13) + +/* Input: Don't answer request from locally registered public LLMNR/mDNS RRs */ +#define SD_RESOLVED_NO_TRUST_ANCHOR (UINT64_C(1) << 14) + +/* Input: Don't go to network for this request */ +#define SD_RESOLVED_NO_NETWORK (UINT64_C(1) << 15) + +/* Input: Require that request is answered from a "primary" answer, i.e. not from RRs acquired as + * side-effect of a previous transaction */ +#define SD_RESOLVED_REQUIRE_PRIMARY (UINT64_C(1) << 16) + +/* Input: If reply is answered from cache, the TTLs will be adjusted by age of cache entry */ +#define SD_RESOLVED_CLAMP_TTL (UINT64_C(1) << 17) + #define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) #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 a2878ec2bb8..051a2a0f252 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -55,20 +55,21 @@ DnsAnswer *dns_answer_new(size_t n) { a->n_ref = 1; a->n_allocated = n; a->set_items = TAKE_PTR(s); - return a; } static void dns_answer_flush(DnsAnswer *a) { - DnsResourceRecord *rr; + DnsAnswerItem *item; if (!a) return; a->set_items = set_free(a->set_items); - DNS_ANSWER_FOREACH(rr, a) - dns_resource_record_unref(rr); + DNS_ANSWER_FOREACH_ITEM(item, a) { + dns_resource_record_unref(item->rr); + dns_resource_record_unref(item->rrsig); + } a->n_rrs = 0; } @@ -82,7 +83,13 @@ static DnsAnswer *dns_answer_free(DnsAnswer *a) { DEFINE_TRIVIAL_REF_UNREF_FUNC(DnsAnswer, dns_answer, dns_answer_free); -static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { +static int dns_answer_add_raw( + DnsAnswer *a, + DnsResourceRecord *rr, + int ifindex, + DnsAnswerFlags flags, + DnsResourceRecord *rrsig) { + int r; assert(rr); @@ -97,6 +104,7 @@ static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, .rr = rr, .ifindex = ifindex, .flags = flags, + .rrsig = dns_resource_record_ref(rrsig), }; r = set_put(a->set_items, &a->items[a->n_rrs]); @@ -112,12 +120,16 @@ static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, } static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex, r; + DnsAnswerItem *item; + int r; - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, source) { - r = dns_answer_add_raw(a, rr, ifindex, flags); + DNS_ANSWER_FOREACH_ITEM(item, source) { + r = dns_answer_add_raw( + a, + item->rr, + item->ifindex, + item->flags, + item->rrsig); if (r < 0) return r; } @@ -125,7 +137,13 @@ static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { return 0; } -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { +int dns_answer_add( + DnsAnswer *a, + DnsResourceRecord *rr, + int ifindex, + DnsAnswerFlags flags, + DnsResourceRecord *rrsig) { + DnsAnswerItem tmp, *exist; assert(rr); @@ -152,22 +170,28 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFl if (rr->ttl > exist->rr->ttl) { dns_resource_record_unref(exist->rr); exist->rr = dns_resource_record_ref(rr); + + /* Update RRSIG and RR at the same time */ + if (rrsig) { + dns_resource_record_ref(rrsig); + dns_resource_record_unref(exist->rrsig); + exist->rrsig = rrsig; + } } exist->flags |= flags; return 0; } - return dns_answer_add_raw(a, rr, ifindex, flags); + return dns_answer_add_raw(a, rr, ifindex, flags, rrsig); } static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex, r; + DnsAnswerItem *item; + int r; - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) { - r = dns_answer_add(a, rr, ifindex, flags); + DNS_ANSWER_FOREACH_ITEM(item, b) { + r = dns_answer_add(a, item->rr, item->ifindex, item->flags, item->rrsig); if (r < 0) return r; } @@ -175,7 +199,13 @@ static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { return 0; } -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { +int dns_answer_add_extend( + DnsAnswer **a, + DnsResourceRecord *rr, + int ifindex, + DnsAnswerFlags flags, + DnsResourceRecord *rrsig) { + int r; assert(a); @@ -185,7 +215,7 @@ int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, Dns if (r < 0) return r; - return dns_answer_add(*a, rr, ifindex, flags); + return dns_answer_add(*a, rr, ifindex, flags, rrsig); } int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex) { @@ -211,7 +241,7 @@ int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex soa->soa.expire = 1; soa->soa.minimum = ttl; - return dns_answer_add(a, soa, ifindex, DNS_ANSWER_AUTHENTICATED); + return dns_answer_add(a, soa, ifindex, DNS_ANSWER_AUTHENTICATED, NULL); } int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { @@ -249,10 +279,9 @@ int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) { DnsResourceRecord *i; - DNS_ANSWER_FOREACH(i, a) { + DNS_ANSWER_FOREACH(i, a) if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3)) return true; - } return false; } @@ -284,7 +313,22 @@ int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone) { return false; } -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { +int dns_answer_contains(DnsAnswer *answer, DnsResourceRecord *rr) { + DnsResourceRecord *i; + + DNS_ANSWER_FOREACH(i, answer) + if (dns_resource_record_equal(i, rr)) + return true; + + return false; +} + +int dns_answer_find_soa( + DnsAnswer *a, + const DnsResourceKey *key, + DnsResourceRecord **ret, + DnsAnswerFlags *ret_flags) { + DnsResourceRecord *rr, *soa = NULL; DnsAnswerFlags rr_flags, soa_flags = 0; int r; @@ -293,7 +337,7 @@ int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceReco /* For a SOA record we can never find a matching SOA record */ if (key->type == DNS_TYPE_SOA) - return 0; + goto not_found; DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { r = dns_resource_key_match_soa(key, rr->key); @@ -315,17 +359,30 @@ int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceReco } if (!soa) - return 0; + goto not_found; if (ret) *ret = soa; - if (flags) - *flags = soa_flags; + if (ret_flags) + *ret_flags = soa_flags; return 1; + +not_found: + if (ret) + *ret = NULL; + if (ret_flags) + *ret_flags = 0; + + return 0; } -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { +int dns_answer_find_cname_or_dname( + DnsAnswer *a, + const DnsResourceKey *key, + DnsResourceRecord **ret, + DnsAnswerFlags *ret_flags) { + DnsResourceRecord *rr; DnsAnswerFlags rr_flags; int r; @@ -343,12 +400,17 @@ int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsR if (r > 0) { if (ret) *ret = rr; - if (flags) - *flags = rr_flags; + if (ret_flags) + *ret_flags = rr_flags; return 1; } } + if (ret) + *ret = NULL; + if (ret_flags) + *ret_flags = 0; + return 0; } @@ -440,21 +502,20 @@ int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { if ((*a)->n_ref > 1) { _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; - DnsAnswerFlags flags; - int ifindex; + DnsAnswerItem *item; copy = dns_answer_new((*a)->n_rrs); if (!copy) return -ENOMEM; - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { - r = dns_resource_key_equal(rr->key, key); + DNS_ANSWER_FOREACH_ITEM(item, *a) { + r = dns_resource_key_equal(item->rr->key, key); if (r < 0) return r; if (r > 0) continue; - r = dns_answer_add_raw(copy, rr, ifindex, flags); + r = dns_answer_add_raw(copy, item->rr, item->ifindex, item->flags, item->rrsig); if (r < 0) return r; } @@ -479,6 +540,8 @@ int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { /* Kill this entry */ dns_resource_record_unref((*a)->items[i].rr); + dns_resource_record_unref((*a)->items[i].rrsig); + memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); (*a)->n_rrs--; continue; @@ -525,21 +588,20 @@ int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) { if ((*a)->n_ref > 1) { _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; - DnsAnswerFlags flags; - int ifindex; + DnsAnswerItem *item; copy = dns_answer_new((*a)->n_rrs); if (!copy) return -ENOMEM; - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { - r = dns_resource_record_equal(rr, rm); + DNS_ANSWER_FOREACH_ITEM(item, *a) { + r = dns_resource_record_equal(item->rr, rm); if (r < 0) return r; if (r > 0) continue; - r = dns_answer_add_raw(copy, rr, ifindex, flags); + r = dns_answer_add_raw(copy, item->rr, item->ifindex, item->flags, item->rrsig); if (r < 0) return r; } @@ -564,6 +626,7 @@ int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) { /* Kill this entry */ dns_resource_record_unref((*a)->items[i].rr); + dns_resource_record_unref((*a)->items[i].rrsig); memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); (*a)->n_rrs--; continue; @@ -576,19 +639,24 @@ int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) { return 1; } -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) { - DnsResourceRecord *rr_source; - int ifindex_source, r; - DnsAnswerFlags flags_source; +int dns_answer_copy_by_key( + DnsAnswer **a, + DnsAnswer *source, + const DnsResourceKey *key, + DnsAnswerFlags or_flags, + DnsResourceRecord *rrsig) { + + DnsAnswerItem *item; + int r; assert(a); assert(key); /* Copy all RRs matching the specified key from source into *a */ - DNS_ANSWER_FOREACH_FULL(rr_source, ifindex_source, flags_source, source) { + DNS_ANSWER_FOREACH_ITEM(item, source) { - r = dns_resource_key_equal(rr_source->key, key); + r = dns_resource_key_equal(item->rr->key, key); if (r < 0) return r; if (r == 0) @@ -599,7 +667,7 @@ int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKe if (r < 0) return r; - r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags); + r = dns_answer_add(*a, item->rr, item->ifindex, item->flags|or_flags, item->rrsig); if (r < 0) return r; } @@ -607,14 +675,19 @@ int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKe return 0; } -int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags) { +int dns_answer_move_by_key( + DnsAnswer **to, + DnsAnswer **from, + const DnsResourceKey *key, + DnsAnswerFlags or_flags, + DnsResourceRecord *rrsig) { int r; assert(to); assert(from); assert(key); - r = dns_answer_copy_by_key(to, *from, key, or_flags); + r = dns_answer_copy_by_key(to, *from, key, or_flags, rrsig); if (r < 0) return r; @@ -744,19 +817,17 @@ int dns_answer_reserve_or_clone(DnsAnswer **a, size_t n_free) { * This function is not used in the code base, but is useful when debugging. Do not delete. */ void dns_answer_dump(DnsAnswer *answer, FILE *f) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex; + DnsAnswerItem *item; if (!f) f = stdout; - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { + DNS_ANSWER_FOREACH_ITEM(item, answer) { const char *t; fputc('\t', f); - t = dns_resource_record_to_string(rr); + t = dns_resource_record_to_string(item->rr); if (!t) { log_oom(); continue; @@ -764,21 +835,29 @@ void dns_answer_dump(DnsAnswer *answer, FILE *f) { fputs(t, f); - if (ifindex != 0 || flags != 0) + if (item->ifindex != 0 || item->rrsig || item->flags != 0) fputs("\t;", f); - if (ifindex != 0) - fprintf(f, " ifindex=%i", ifindex); - if (flags & DNS_ANSWER_AUTHENTICATED) + if (item->ifindex != 0) + fprintf(f, " ifindex=%i", item->ifindex); + if (item->rrsig) + fputs(" rrsig", f); + if (item->flags & DNS_ANSWER_AUTHENTICATED) fputs(" authenticated", f); - if (flags & DNS_ANSWER_CACHEABLE) + if (item->flags & DNS_ANSWER_CACHEABLE) fputs(" cacheable", f); - if (flags & DNS_ANSWER_SHARED_OWNER) + if (item->flags & DNS_ANSWER_SHARED_OWNER) fputs(" shared-owner", f); - if (flags & DNS_ANSWER_CACHE_FLUSH) + if (item->flags & DNS_ANSWER_CACHE_FLUSH) fputs(" cache-flush", f); - if (flags & DNS_ANSWER_GOODBYE) + if (item->flags & DNS_ANSWER_GOODBYE) fputs(" goodbye", f); + if (item->flags & DNS_ANSWER_SECTION_ANSWER) + fputs(" section-answer", f); + if (item->flags & DNS_ANSWER_SECTION_AUTHORITY) + fputs(" section-authority", f); + if (item->flags & DNS_ANSWER_SECTION_ADDITIONAL) + fputs(" section-additional", f); fputc('\n', f); } diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index d73525cedd2..cc2cd93a333 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -8,23 +8,25 @@ typedef struct DnsAnswerItem DnsAnswerItem; #include "resolved-dns-rr.h" #include "set.h" -/* 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. +/* 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. * * Note that we usually encode the empty DnsAnswer object as a simple NULL. */ typedef enum DnsAnswerFlags { - DNS_ANSWER_AUTHENTICATED = 1 << 0, /* Item has been authenticated */ - DNS_ANSWER_CACHEABLE = 1 << 1, /* Item is subject to caching */ - DNS_ANSWER_SHARED_OWNER = 1 << 2, /* For mDNS: RRset may be owner by multiple peers */ - DNS_ANSWER_CACHE_FLUSH = 1 << 3, /* For mDNS: sets cache-flush bit in the rrclass of response records */ - DNS_ANSWER_GOODBYE = 1 << 4, /* For mDNS: item is subject to disappear */ + DNS_ANSWER_AUTHENTICATED = 1 << 0, /* Item has been authenticated */ + DNS_ANSWER_CACHEABLE = 1 << 1, /* Item is subject to caching */ + DNS_ANSWER_SHARED_OWNER = 1 << 2, /* For mDNS: RRset may be owner by multiple peers */ + DNS_ANSWER_CACHE_FLUSH = 1 << 3, /* For mDNS: sets cache-flush bit in the rrclass of response records */ + DNS_ANSWER_GOODBYE = 1 << 4, /* For mDNS: item is subject to disappear */ + DNS_ANSWER_SECTION_ANSWER = 1 << 5, /* When parsing: RR originates from answer section */ + DNS_ANSWER_SECTION_AUTHORITY = 1 << 6, /* When parsing: RR originates from authority section */ + DNS_ANSWER_SECTION_ADDITIONAL = 1 << 7, /* When parsing: RR originates from additional section */ } DnsAnswerFlags; struct DnsAnswerItem { DnsResourceRecord *rr; + DnsResourceRecord *rrsig; /* Optionally, also store RRSIG RR that successfully validates this item */ int ifindex; DnsAnswerFlags flags; }; @@ -40,16 +42,17 @@ DnsAnswer *dns_answer_new(size_t n); DnsAnswer *dns_answer_ref(DnsAnswer *a); DnsAnswer *dns_answer_unref(DnsAnswer *a); -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig); +int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags, DnsResourceRecord *rrsig); int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex); int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a); int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone); +int dns_answer_contains(DnsAnswer *answer, DnsResourceRecord *rr); -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); +int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *ret_flags); +int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *ret_flags); int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret); int dns_answer_extend(DnsAnswer **a, DnsAnswer *b); @@ -62,8 +65,8 @@ int dns_answer_reserve_or_clone(DnsAnswer **a, size_t n_free); int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); -int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags, DnsResourceRecord *rrsig); +int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags, DnsResourceRecord *rrsig); int dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); @@ -115,17 +118,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); #define DNS_ANSWER_FOREACH_FLAGS(kk, flags, a) _DNS_ANSWER_FOREACH_FLAGS(UNIQ, kk, flags, a) -#define _DNS_ANSWER_FOREACH_FULL(q, kk, ifi, fl, a) \ +#define _DNS_ANSWER_FOREACH_ITEM(q, item, a) \ for (size_t UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ - (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ + (item) = dns_answer_isempty(a) ? NULL : (a)->items; \ 0; \ }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q) < dns_answer_size(a); \ 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), \ - (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) + (item) = ((UNIQ_T(i, q) < dns_answer_size(a)) ? (a)->items + UNIQ_T(i, q) : NULL)) -#define DNS_ANSWER_FOREACH_FULL(kk, ifindex, flags, a) _DNS_ANSWER_FOREACH_FULL(UNIQ, kk, ifindex, flags, a) +#define DNS_ANSWER_FOREACH_ITEM(item, a) _DNS_ANSWER_FOREACH_ITEM(UNIQ, item, a) diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 75f1ccb6498..cf0e221621c 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -34,13 +34,16 @@ enum DnsCacheItemType { struct DnsCacheItem { DnsCacheItemType type; - DnsResourceKey *key; - DnsResourceRecord *rr; + DnsResourceKey *key; /* The key for this item, i.e. the lookup key */ + DnsResourceRecord *rr; /* The RR for this item, i.e. the lookup value for positive queries */ + DnsAnswer *answer; /* The full validated answer, if this is an RRset acquired via a "primary" lookup */ + DnsPacket *full_packet; /* The full packet this information was acquired with */ int rcode; usec_t until; bool authenticated:1; bool shared_owner:1; + DnssecResult dnssec_result; int ifindex; int owner_family; @@ -50,6 +53,12 @@ struct DnsCacheItem { LIST_FIELDS(DnsCacheItem, by_key); }; +/* Returns true if this is a cache item created as result of an explicit lookup, or created as "side-effect" + * of another request. "Primary" entries will carry the full answer data (with NSEC, …) that can aso prove + * wildcard expansion, non-existance and such, while entries that were created as "side-effect" just contain + * immediate RR data for the specified RR key, but nothing else. */ +#define DNS_CACHE_ITEM_IS_PRIMARY(item) (!!(item)->answer) + static const char *dns_cache_item_type_to_string(DnsCacheItem *item) { assert(item); @@ -77,6 +86,8 @@ static void dns_cache_item_free(DnsCacheItem *i) { dns_resource_record_unref(i->rr); dns_resource_key_unref(i->key); + dns_answer_unref(i->answer); + dns_packet_unref(i->full_packet); free(i); } @@ -340,8 +351,11 @@ static void dns_cache_item_update_positive( DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, + DnsAnswer *answer, + DnsPacket *full_packet, bool authenticated, bool shared_owner, + DnssecResult dnssec_result, usec_t timestamp, int ifindex, int owner_family, @@ -367,9 +381,18 @@ static void dns_cache_item_update_positive( dns_resource_key_unref(i->key); i->key = dns_resource_key_ref(rr->key); - i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); + dns_answer_ref(answer); + dns_answer_unref(i->answer); + i->answer = answer; + + dns_packet_ref(full_packet); + dns_packet_unref(i->full_packet); + i->full_packet = full_packet; + + i->until = calculate_until(rr, UINT32_MAX, timestamp, false); i->authenticated = authenticated; i->shared_owner = shared_owner; + i->dnssec_result = dnssec_result; i->ifindex = ifindex; @@ -382,8 +405,11 @@ static void dns_cache_item_update_positive( static int dns_cache_put_positive( DnsCache *c, DnsResourceRecord *rr, + DnsAnswer *answer, + DnsPacket *full_packet, bool authenticated, bool shared_owner, + DnssecResult dnssec_result, usec_t timestamp, int ifindex, int owner_family, @@ -420,8 +446,11 @@ static int dns_cache_put_positive( c, existing, rr, + answer, + full_packet, authenticated, shared_owner, + dnssec_result, timestamp, ifindex, owner_family, @@ -444,9 +473,12 @@ static int dns_cache_put_positive( .type = DNS_CACHE_POSITIVE, .key = dns_resource_key_ref(rr->key), .rr = dns_resource_record_ref(rr), + .answer = dns_answer_ref(answer), + .full_packet = dns_packet_ref(full_packet), .until = calculate_until(rr, (uint32_t) -1, timestamp, false), .authenticated = authenticated, .shared_owner = shared_owner, + .dnssec_result = dnssec_result, .ifindex = ifindex, .owner_family = owner_family, .owner_address = *owner_address, @@ -481,7 +513,10 @@ static int dns_cache_put_negative( DnsCache *c, DnsResourceKey *key, int rcode, + DnsAnswer *answer, + DnsPacket *full_packet, bool authenticated, + DnssecResult dnssec_result, uint32_t nsec_ttl, usec_t timestamp, DnsResourceRecord *soa, @@ -532,10 +567,13 @@ static int dns_cache_put_negative( rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : rcode == DNS_RCODE_NXDOMAIN ? DNS_CACHE_NXDOMAIN : DNS_CACHE_RCODE, .authenticated = authenticated, + .dnssec_result = dnssec_result, .owner_family = owner_family, .owner_address = *owner_address, .prioq_idx = PRIOQ_IDX_NULL, .rcode = rcode, + .answer = dns_answer_ref(answer), + .full_packet = dns_packet_ref(full_packet), }; i->until = @@ -630,17 +668,20 @@ int dns_cache_put( DnsResourceKey *key, int rcode, DnsAnswer *answer, + DnsPacket *full_packet, bool authenticated, + DnssecResult dnssec_result, uint32_t nsec_ttl, - usec_t timestamp, int owner_family, const union in_addr_union *owner_address) { - DnsResourceRecord *soa = NULL, *rr; + DnsResourceRecord *soa = NULL; bool weird_rcode = false; + DnsAnswerItem *item; DnsAnswerFlags flags; unsigned cache_keys; - int r, ifindex; + usec_t timestamp; + int r; assert(c); assert(owner_address); @@ -660,6 +701,7 @@ int dns_cache_put( log_debug("Not caching negative entry without a SOA record: %s", dns_resource_key_to_string(key, key_str, sizeof key_str)); } + return 0; } @@ -679,23 +721,53 @@ int dns_cache_put( /* Make some space for our new entries */ dns_cache_make_space(c, cache_keys); - if (timestamp <= 0) - timestamp = now(clock_boottime_or_monotonic()); + timestamp = now(clock_boottime_or_monotonic()); /* Second, add in positive entries for all contained RRs */ - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0 || - !rr_eligible(rr)) + DNS_ANSWER_FOREACH_ITEM(item, answer) { + int primary = false; + + if (!FLAGS_SET(item->flags, DNS_ANSWER_CACHEABLE) || + !rr_eligible(item->rr)) continue; + if (key) { + /* We store the auxiliary RRs and packet data in the cache only if they were in + * direct response to the original query. If we cache an RR we also received, and + * that is just auxiliary information we can't use the data, hence don't. */ + + primary = dns_resource_key_match_rr(key, item->rr, NULL); + if (primary < 0) + return primary; + if (primary == 0) { + primary = dns_resource_key_match_cname_or_dname(key, item->rr->key, NULL); + if (primary < 0) + return primary; + } + } + + if (!primary) { + DnsCacheItem *first; + + /* Do not replace existing cache items for primary lookups with non-primary + * data. After all the primary lookup data is a lot more useful. */ + first = hashmap_get(c->by_key, item->rr->key); + if (first && DNS_CACHE_ITEM_IS_PRIMARY(first)) + return 0; + } + r = dns_cache_put_positive( c, - rr, - flags & DNS_ANSWER_AUTHENTICATED, - flags & DNS_ANSWER_SHARED_OWNER, + item->rr, + primary ? answer : NULL, + primary ? full_packet : NULL, + item->flags & DNS_ANSWER_AUTHENTICATED, + item->flags & DNS_ANSWER_SHARED_OWNER, + dnssec_result, timestamp, - ifindex, - owner_family, owner_address); + item->ifindex, + owner_family, + owner_address); if (r < 0) goto fail; } @@ -745,7 +817,10 @@ int dns_cache_put( c, key, rcode, + answer, + full_packet, authenticated, + dnssec_result, nsec_ttl, timestamp, soa, @@ -762,11 +837,11 @@ fail: if (key) dns_cache_remove_by_key(c, key); - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0) + DNS_ANSWER_FOREACH_ITEM(item, answer) { + if ((item->flags & DNS_ANSWER_CACHEABLE) == 0) continue; - dns_cache_remove_by_key(c, rr->key); + dns_cache_remove_by_key(c, item->rr->key); } return r; @@ -827,7 +902,59 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D return NULL; } -int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcode, DnsAnswer **ret, bool *authenticated) { +static int answer_add_clamp_ttl( + DnsAnswer **answer, + DnsResourceRecord *rr, + int ifindex, + DnsAnswerFlags answer_flags, + DnsResourceRecord *rrsig, + uint64_t query_flags, + usec_t until, + usec_t current) { + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *patched = NULL, *patched_rrsig = NULL; + int r; + + assert(answer); + assert(rr); + + if (FLAGS_SET(query_flags, SD_RESOLVED_CLAMP_TTL)) { + patched = dns_resource_record_ref(rr); + + r = dns_resource_record_clamp_ttl(&patched, LESS_BY(until, current) / USEC_PER_SEC); + if (r < 0) + return r; + + rr = patched; + + if (rrsig) { + patched_rrsig = dns_resource_record_ref(rrsig); + r = dns_resource_record_clamp_ttl(&patched_rrsig, LESS_BY(until, current) / USEC_PER_SEC); + if (r < 0) + return r; + + rrsig = patched_rrsig; + } + } + + r = dns_answer_add_extend(answer, rr, ifindex, answer_flags, rrsig); + if (r < 0) + return r; + + return 0; +} + +int dns_cache_lookup( + DnsCache *c, + DnsResourceKey *key, + uint64_t query_flags, + int *ret_rcode, + DnsAnswer **ret_answer, + DnsPacket **ret_full_packet, + bool *ret_authenticated, + DnssecResult *ret_dnssec_result) { + + _cleanup_(dns_packet_unrefp) DnsPacket *full_packet = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; char key_str[DNS_RESOURCE_KEY_STRING_MAX]; unsigned n = 0; @@ -837,27 +964,19 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod bool have_authenticated = false, have_non_authenticated = false; usec_t current; int found_rcode = -1; + DnssecResult dnssec_result = -1; + int have_dnssec_result = -1; assert(c); assert(key); - assert(rcode); - assert(ret); - assert(authenticated); if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { - /* If we have ANY lookups we don't use the cache, so - * that the caller refreshes via the network. */ + /* If we have ANY lookups we don't use the cache, so that the caller refreshes via the + * network. */ log_debug("Ignoring cache for ANY lookup: %s", dns_resource_key_to_string(key, key_str, sizeof key_str)); - - c->n_miss++; - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - *authenticated = false; - - return 0; + goto miss; } first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key); @@ -866,31 +985,80 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod log_debug("Cache miss for %s", dns_resource_key_to_string(key, key_str, sizeof key_str)); - - c->n_miss++; - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - *authenticated = false; - - return 0; + goto miss; } + if (FLAGS_SET(query_flags, SD_RESOLVED_CLAMP_TTL)) + current = now(clock_boottime_or_monotonic()); + LIST_FOREACH(by_key, j, first) { - if (j->rr) { + /* If the caller doesn't allow us to answer questions from cache data learned from + * "side-effect", skip this entry. */ + if (FLAGS_SET(query_flags, SD_RESOLVED_REQUIRE_PRIMARY) && + !DNS_CACHE_ITEM_IS_PRIMARY(j)) { + log_debug("Primary answer was requested for cache lookup for %s, which we don't have.", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + + goto miss; + } + + if (j->type == DNS_CACHE_NXDOMAIN) + nxdomain = true; + else if (j->type == DNS_CACHE_RCODE) + found_rcode = j->rcode; + else if (j->rr) { if (j->rr->key->type == DNS_TYPE_NSEC) nsec = j; n++; - } else if (j->type == DNS_CACHE_NXDOMAIN) - nxdomain = true; - else if (j->type == DNS_CACHE_RCODE) - found_rcode = j->rcode; + } if (j->authenticated) have_authenticated = true; else have_non_authenticated = true; + + if (j->dnssec_result < 0) { + have_dnssec_result = false; /* an entry without dnssec result? then invalidate things for good */ + dnssec_result = _DNSSEC_RESULT_INVALID; + } else if (have_dnssec_result < 0) { + have_dnssec_result = true; /* So far no result seen, let's pick this one up */ + dnssec_result = j->dnssec_result; + } else if (have_dnssec_result > 0 && j->dnssec_result != dnssec_result) { + have_dnssec_result = false; /* conflicting result seen? then invalidate for good */ + dnssec_result = _DNSSEC_RESULT_INVALID; + } + + /* Append the answer RRs to our answer. Ideally we have the answer object, which we + * preferably use. But if the cached entry was generated as "side-effect" of a reply, + * i.e. from validated auxiliary records rather than from the main reply, then we use the + * individual RRs only instead. */ + if (j->answer) { + + /* Minor optimization, if the full answer object of this and the previous RR is the + * same, don't bother adding it again. Typically we store a full RRset here, hence + * that should be the case. */ + if (!j->by_key_prev || j->answer != j->by_key_prev->answer) { + DnsAnswerItem *item; + + DNS_ANSWER_FOREACH_ITEM(item, j->answer) { + r = answer_add_clamp_ttl(&answer, item->rr, item->ifindex, item->flags, item->rrsig, query_flags, j->until, current); + if (r < 0) + return r; + } + } + + } else if (j->rr) { + r = answer_add_clamp_ttl(&answer, j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0, NULL, query_flags, j->until, current); + if (r < 0) + return r; + } + + /* We'll return any packet we have for this. Typically all cache entries for the same key + * should come from the same packet anyway, hence it doesn't really matter which packet we + * return here, they should all resolve to the same anyway. */ + if (!full_packet && j->full_packet) + full_packet = dns_packet_ref(j->full_packet); } if (found_rcode >= 0) { @@ -898,28 +1066,41 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod dns_rcode_to_string(found_rcode), dns_resource_key_to_string(key, key_str, sizeof(key_str))); - *ret = NULL; - *rcode = found_rcode; - *authenticated = false; + if (ret_rcode) + *ret_rcode = found_rcode; + if (ret_answer) + *ret_answer = TAKE_PTR(answer); + if (ret_full_packet) + *ret_full_packet = TAKE_PTR(full_packet); + if (ret_authenticated) + *ret_authenticated = false; + if (ret_dnssec_result) + *ret_dnssec_result = dnssec_result; c->n_hit++; return 1; } if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) { - /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from - * the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ + /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC + * RRs from the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ log_debug("NSEC NODATA cache hit for %s", dns_resource_key_to_string(key, key_str, sizeof key_str)); - /* We only found an NSEC record that matches our name. - * If it says the type doesn't exist report - * NODATA. Otherwise report a cache miss. */ + /* We only found an NSEC record that matches our name. If it says the type doesn't exist + * report NODATA. Otherwise report a cache miss. */ - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - *authenticated = nsec->authenticated; + if (ret_rcode) + *ret_rcode = DNS_RCODE_SUCCESS; + if (ret_answer) + *ret_answer = TAKE_PTR(answer); + if (ret_full_packet) + *ret_full_packet = TAKE_PTR(full_packet); + if (ret_authenticated) + *ret_authenticated = nsec->authenticated; + if (ret_dnssec_result) + *ret_dnssec_result = nsec->dnssec_result; if (!bitmap_isset(nsec->rr->nsec.types, key->type) && !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && @@ -940,46 +1121,49 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcod if (n <= 0) { c->n_hit++; - *ret = NULL; - *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; - *authenticated = have_authenticated && !have_non_authenticated; + if (ret_rcode) + *ret_rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; + if (ret_answer) + *ret_answer = TAKE_PTR(answer); + if (ret_full_packet) + *ret_full_packet = TAKE_PTR(full_packet); + if (ret_authenticated) + *ret_authenticated = have_authenticated && !have_non_authenticated; + if (ret_dnssec_result) + *ret_dnssec_result = dnssec_result; + return 1; } - answer = dns_answer_new(n); - if (!answer) - return -ENOMEM; - - if (clamp_ttl) - current = now(clock_boottime_or_monotonic()); - - LIST_FOREACH(by_key, j, first) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - if (!j->rr) - continue; - - if (clamp_ttl) { - rr = dns_resource_record_ref(j->rr); - - r = dns_resource_record_clamp_ttl(&rr, LESS_BY(j->until, current) / USEC_PER_SEC); - if (r < 0) - return r; - } - - r = dns_answer_add(answer, rr ?: j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0); - if (r < 0) - return r; - } - c->n_hit++; - *ret = answer; - *rcode = DNS_RCODE_SUCCESS; - *authenticated = have_authenticated && !have_non_authenticated; - answer = NULL; + if (ret_rcode) + *ret_rcode = DNS_RCODE_SUCCESS; + if (ret_answer) + *ret_answer = TAKE_PTR(answer); + if (ret_full_packet) + *ret_full_packet = TAKE_PTR(full_packet); + if (ret_authenticated) + *ret_authenticated = have_authenticated && !have_non_authenticated; + if (ret_dnssec_result) + *ret_dnssec_result = dnssec_result; return n; + +miss: + if (ret_rcode) + *ret_rcode = DNS_RCODE_SUCCESS; + if (ret_answer) + *ret_answer = NULL; + if (ret_full_packet) + *ret_full_packet = NULL; + if (ret_authenticated) + *ret_authenticated = false; + if (ret_dnssec_result) + *ret_dnssec_result = _DNSSEC_RESULT_INVALID; + + c->n_miss++; + return 0; } int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) { diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h index 4ab213dc9cd..cfae24fb71c 100644 --- a/src/resolve/resolved-dns-cache.h +++ b/src/resolve/resolved-dns-cache.h @@ -5,6 +5,7 @@ #include "list.h" #include "prioq.h" #include "resolve-util.h" +#include "resolved-dns-dnssec.h" #include "time-util.h" typedef struct DnsCache { @@ -22,8 +23,28 @@ typedef struct DnsCache { void dns_cache_flush(DnsCache *c); void dns_cache_prune(DnsCache *c); -int dns_cache_put(DnsCache *c, DnsCacheMode cache_mode, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); -int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcode, DnsAnswer **answer, bool *authenticated); +int dns_cache_put( + DnsCache *c, + DnsCacheMode cache_mode, + DnsResourceKey *key, + int rcode, + DnsAnswer *answer, + DnsPacket *full_packet, + bool authenticated, + DnssecResult dnssec_result, + uint32_t nsec_ttl, + int owner_family, + const union in_addr_union *owner_address); + +int dns_cache_lookup( + DnsCache *c, + DnsResourceKey *key, + uint64_t query_flags, + int *ret_rcode, + DnsAnswer **ret_answer, + DnsPacket **ret_full_packet, + bool *ret_authenticated, + DnssecResult *ret_dnssec_result); int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 9a0e2d51614..59be55a0eb0 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -160,6 +160,38 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t min_alloc return 0; } +int dns_packet_dup(DnsPacket **ret, DnsPacket *p) { + DnsPacket *c; + int r; + + assert(ret); + assert(p); + + r = dns_packet_validate(p); + if (r < 0) + return r; + + c = malloc(ALIGN(sizeof(DnsPacket)) + p->size); + if (!c) + return -ENOMEM; + + *c = (DnsPacket) { + .n_ref = 1, + .protocol = p->protocol, + .size = p->size, + .rindex = DNS_PACKET_HEADER_SIZE, + .allocated = p->size, + .max_size = p->max_size, + .opt_start = (size_t) -1, + .opt_size = (size_t) -1, + }; + + memcpy(DNS_PACKET_DATA(c), DNS_PACKET_DATA(p), p->size); + + *ret = c; + return 0; +} + DnsPacket *dns_packet_ref(DnsPacket *p) { if (!p) @@ -1183,7 +1215,7 @@ int dns_packet_append_question(DnsPacket *p, DnsQuestion *q) { return 0; } -int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a) { +int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a, unsigned *completed) { DnsResourceRecord *rr; DnsAnswerFlags flags; int r; @@ -1194,6 +1226,9 @@ int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a) { r = dns_packet_append_rr(p, rr, flags, NULL, NULL); if (r < 0) return r; + + if (completed) + (*completed)++; } return 0; @@ -1298,7 +1333,8 @@ int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { if (r < 0) return r; - *ret = unaligned_read_be16(d); + if (ret) + *ret = unaligned_read_be16(d); return 0; } @@ -1384,19 +1420,19 @@ int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, siz int dns_packet_read_name( DnsPacket *p, - char **_ret, + char **ret, bool allow_compression, - size_t *start) { + size_t *ret_start) { _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; size_t after_rindex = 0, jump_barrier; - _cleanup_free_ char *ret = NULL; + _cleanup_free_ char *name = NULL; size_t n = 0, allocated = 0; bool first = true; int r; assert(p); - assert(_ret); + INIT_REWINDER(rewinder, p); jump_barrier = p->rindex; @@ -1421,15 +1457,15 @@ int dns_packet_read_name( if (r < 0) return r; - if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + if (!GREEDY_REALLOC(name, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) return -ENOMEM; if (first) first = false; else - ret[n++] = '.'; + name[n++] = '.'; - r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX); if (r < 0) return r; @@ -1457,18 +1493,19 @@ int dns_packet_read_name( return -EBADMSG; } - if (!GREEDY_REALLOC(ret, allocated, n + 1)) + if (!GREEDY_REALLOC(name, allocated, n + 1)) return -ENOMEM; - ret[n] = 0; + name[n] = 0; if (after_rindex != 0) p->rindex= after_rindex; - *_ret = TAKE_PTR(ret); + if (ret) + *ret = TAKE_PTR(name); + if (ret_start) + *ret_start = rewinder.saved_rindex; - if (start) - *start = rewinder.saved_rindex; CANCEL_REWINDER(rewinder); return 0; @@ -1570,16 +1607,19 @@ static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t siz return 0; } -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { +int dns_packet_read_key( + DnsPacket *p, + DnsResourceKey **ret, + bool *ret_cache_flush, + size_t *ret_start) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; _cleanup_free_ char *name = NULL; bool cache_flush = false; uint16_t class, type; - DnsResourceKey *key; int r; assert(p); - assert(ret); INIT_REWINDER(rewinder, p); r = dns_packet_read_name(p, &name, true, NULL); @@ -1603,19 +1643,23 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flus } } - key = dns_resource_key_new_consume(class, type, name); - if (!key) - return -ENOMEM; + if (ret) { + DnsResourceKey *key; - name = NULL; - *ret = key; + key = dns_resource_key_new_consume(class, type, name); + if (!key) + return -ENOMEM; + + TAKE_PTR(name); + *ret = key; + } if (ret_cache_flush) *ret_cache_flush = cache_flush; - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); + if (ret_start) + *ret_start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); return 0; } @@ -1625,7 +1669,12 @@ static bool loc_size_ok(uint8_t size) { return m <= 9 && e <= 9 && (m > 0 || e == 0); } -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) { +int dns_packet_read_rr( + DnsPacket *p, + DnsResourceRecord **ret, + bool *ret_cache_flush, + size_t *ret_start) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; @@ -1635,7 +1684,6 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl int r; assert(p); - assert(ret); INIT_REWINDER(rewinder, p); @@ -2076,14 +2124,14 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_fl if (p->rindex != offset + rdlength) return -EBADMSG; - *ret = TAKE_PTR(rr); - + if (ret) + *ret = TAKE_PTR(rr); if (ret_cache_flush) *ret_cache_flush = cache_flush; - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); + if (ret_start) + *ret_start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); return 0; } @@ -2203,8 +2251,9 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) { for (i = 0; i < n; i++) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; bool cache_flush = false; + size_t start; - r = dns_packet_read_rr(p, &rr, &cache_flush, NULL); + r = dns_packet_read_rr(p, &rr, &cache_flush, &start); if (r < 0) return r; @@ -2272,15 +2321,26 @@ static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) { } p->opt = dns_resource_record_ref(rr); + p->opt_start = start; + assert(p->rindex >= start); + p->opt_size = p->rindex - start; } else { - /* According to RFC 4795, section 2.9. only the RRs from the Answer section - * shall be cached. Hence mark only those RRs as cacheable by default, but - * not the ones from the Additional or Authority sections. */ - DnsAnswerFlags flags = - (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) | - (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0); + DnsAnswerFlags flags = 0; - r = dns_answer_add(answer, rr, p->ifindex, flags); + if (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush) + flags |= DNS_ANSWER_SHARED_OWNER; + + /* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be + * cached. Hence mark only those RRs as cacheable by default, but not the ones from + * the Additional or Authority sections. */ + if (i < DNS_PACKET_ANCOUNT(p)) + flags |= DNS_ANSWER_CACHEABLE|DNS_ANSWER_SECTION_ANSWER; + else if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) + flags |= DNS_ANSWER_SECTION_AUTHORITY; + else + flags |= DNS_ANSWER_SECTION_ADDITIONAL; + + r = dns_answer_add(answer, rr, p->ifindex, flags, NULL); if (r < 0) return r; } @@ -2356,6 +2416,106 @@ int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) { return dns_resource_key_equal(p->question->keys[0], key); } +int dns_packet_patch_max_udp_size(DnsPacket *p, uint16_t max_udp_size) { + assert(p); + assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX); + + if (p->opt_start == (size_t) -1) /* No OPT section, nothing to patch */ + return 0; + + assert(p->opt_size != (size_t) -1); + assert(p->opt_size >= 5); + + unaligned_write_be16(DNS_PACKET_DATA(p) + p->opt_start + 3, max_udp_size); + return 1; +} + +static int patch_rr(DnsPacket *p, usec_t age) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + size_t ttl_index; + uint32_t ttl; + uint16_t type, rdlength; + int r; + + INIT_REWINDER(rewinder, p); + + /* Patches the RR at the current rindex, substracts the specified time from the TTL */ + + r = dns_packet_read_name(p, NULL, true, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, &type, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, NULL, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &ttl, &ttl_index); + if (r < 0) + return r; + + if (type != DNS_TYPE_OPT) { /* The TTL of the OPT field is not actually a TTL, skip it */ + ttl = LESS_BY(ttl * USEC_PER_SEC, age) / USEC_PER_SEC; + unaligned_write_be32(DNS_PACKET_DATA(p) + ttl_index, ttl); + } + + r = dns_packet_read_uint16(p, &rdlength, NULL); + if (r < 0) + return r; + + r = dns_packet_read(p, rdlength, NULL, NULL); + if (r < 0) + return r; + + CANCEL_REWINDER(rewinder); + return 0; +} + +int dns_packet_patch_ttls(DnsPacket *p, usec_t timestamp) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {}; + unsigned i, n; + usec_t k; + int r; + + assert(p); + assert(timestamp_is_set(timestamp)); + + /* Adjusts all TTLs in the packet by subtracting the time difference between now and the specified timestamp */ + + k = now(clock_boottime_or_monotonic()); + assert(k >= timestamp); + k -= timestamp; + + INIT_REWINDER(rewinder, p); + + dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); + + n = DNS_PACKET_QDCOUNT(p); + for (i = 0; i < n; i++) { + r = dns_packet_read_key(p, NULL, NULL, NULL); + if (r < 0) + return r; + } + + n = DNS_PACKET_RRCOUNT(p); + for (i = 0; i < n; i++) { + + /* DNS servers suck, hence the RR count is in many servers off. If we reached the end + * prematurely, accept that, exit early */ + if (p->rindex == p->size) + break; + + r = patch_rr(p, k); + if (r < 0) + return r; + } + + return 0; +} + static void dns_packet_hash_func(const DnsPacket *s, struct siphash *state) { assert(s); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index e7d6cb19043..9d93f314695 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -176,6 +176,8 @@ static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) { int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t min_alloc_dsize, size_t max_size); int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t min_alloc_dsize, bool dnssec_checking_disabled); +int dns_packet_dup(DnsPacket **ret, DnsPacket *p); + void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated); DnsPacket *dns_packet_ref(DnsPacket *p); @@ -201,7 +203,10 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, const DnsAnsw int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAnswerFlags flags, size_t *start, size_t *rdata_start); int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, bool include_rfc6975, int rcode, size_t *start); int dns_packet_append_question(DnsPacket *p, DnsQuestion *q); -int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a); +int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a, unsigned *completed); + +int dns_packet_patch_max_udp_size(DnsPacket *p, uint16_t max_udp_size); +int dns_packet_patch_ttls(DnsPacket *p, usec_t timestamp); void dns_packet_truncate(DnsPacket *p, size_t sz); int dns_packet_truncate_opt(DnsPacket *p); diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 8ee4fd83093..562924e67c3 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -97,20 +97,35 @@ static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { return 1; } -static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResourceKey *key) { +static int dns_query_candidate_add_transaction( + DnsQueryCandidate *c, + DnsResourceKey *key, + DnsPacket *bypass) { + _cleanup_(dns_transaction_gcp) DnsTransaction *t = NULL; int r; assert(c); - assert(key); - t = dns_scope_find_transaction(c->scope, key, true); - if (!t) { - r = dns_transaction_new(&t, c->scope, key); + if (key) { + /* Regular lookup with a resource key */ + assert(!bypass); + + t = dns_scope_find_transaction(c->scope, key, c->query->flags); + if (!t) { + r = dns_transaction_new(&t, c->scope, key, NULL, c->query->flags); + if (r < 0) + return r; + } else if (set_contains(c->transactions, t)) + return 0; + } else { + /* "Bypass" lookup with a query packet */ + assert(bypass); + + r = dns_transaction_new(&t, c->scope, NULL, bypass, c->query->flags); if (r < 0) return r; - } else if (set_contains(c->transactions, t)) - return 0; + } r = set_ensure_allocated(&t->notify_query_candidates_done, NULL); if (r < 0) @@ -126,7 +141,6 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource return r; } - t->clamp_ttl = c->query->clamp_ttl; TAKE_PTR(t); return 1; } @@ -214,6 +228,21 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { dns_query_candidate_stop(c); + if (c->query->question_bypass) { + /* If this is a bypass query, then pass the original query packet along to the transaction */ + + assert(dns_question_size(c->query->question_bypass->question) == 1); + + if (!dns_scope_good_key(c->scope, c->query->question_bypass->question->keys[0])) + return 0; + + r = dns_query_candidate_add_transaction(c, NULL, c->query->question_bypass); + if (r < 0) + goto fail; + + return 1; + } + question = dns_query_question_for_protocol(c->query, c->scope->protocol); /* Create one transaction per question key */ @@ -233,7 +262,7 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { if (!dns_scope_good_key(c->scope, qkey)) continue; - r = dns_query_candidate_add_transaction(c, qkey); + r = dns_query_candidate_add_transaction(c, qkey, NULL); if (r < 0) goto fail; @@ -321,6 +350,7 @@ static void dns_query_reset_answer(DnsQuery *q) { q->answer_protocol = _DNS_PROTOCOL_INVALID; q->answer_family = AF_UNSPEC; q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain); + q->answer_full_packet = dns_packet_unref(q->answer_full_packet); } DnsQuery *dns_query_free(DnsQuery *q) { @@ -340,6 +370,7 @@ DnsQuery *dns_query_free(DnsQuery *q) { dns_question_unref(q->question_idna); dns_question_unref(q->question_utf8); + dns_packet_unref(q->question_bypass); dns_query_reset_answer(q); @@ -351,13 +382,15 @@ DnsQuery *dns_query_free(DnsQuery *q) { varlink_unref(q->varlink_request); } - dns_packet_unref(q->request_dns_packet); - dns_packet_unref(q->reply_dns_packet); + dns_packet_unref(q->request_packet); + dns_answer_unref(q->reply_answer); + dns_answer_unref(q->reply_authoritative); + dns_answer_unref(q->reply_additional); - if (q->request_dns_stream) { + if (q->request_stream) { /* Detach the stream from our query, in case something else keeps a reference to it. */ - (void) set_remove(q->request_dns_stream->queries, q); - q->request_dns_stream = dns_stream_unref(q->request_dns_stream); + (void) set_remove(q->request_stream->queries, q); + q->request_stream = dns_stream_unref(q->request_stream); } free(q->request_address_string); @@ -375,36 +408,27 @@ int dns_query_new( DnsQuery **ret, DnsQuestion *question_utf8, DnsQuestion *question_idna, + DnsPacket *question_bypass, int ifindex, uint64_t flags) { _cleanup_(dns_query_freep) DnsQuery *q = NULL; - DnsResourceKey *key; - bool good = false; - int r; char key_str[DNS_RESOURCE_KEY_STRING_MAX]; + DnsResourceKey *key; + int r; assert(m); - if (dns_question_size(question_utf8) > 0) { - r = dns_question_is_valid_for_query(question_utf8); - if (r < 0) - return r; - if (r == 0) + if (question_bypass) { + /* It's either a "bypass" query, or a regular one, but can't be both. */ + if (question_utf8 || question_idna) return -EINVAL; - good = true; - } + } else { + bool good = false; - /* If the IDNA and UTF8 questions are the same, merge their references */ - r = dns_question_is_equal(question_idna, question_utf8); - if (r < 0) - return r; - if (r > 0) - question_idna = question_utf8; - else { - if (dns_question_size(question_idna) > 0) { - r = dns_question_is_valid_for_query(question_idna); + if (dns_question_size(question_utf8) > 0) { + r = dns_question_is_valid_for_query(question_utf8); if (r < 0) return r; if (r == 0) @@ -412,10 +436,28 @@ int dns_query_new( good = true; } - } - if (!good) /* don't allow empty queries */ - return -EINVAL; + /* If the IDNA and UTF8 questions are the same, merge their references */ + r = dns_question_is_equal(question_idna, question_utf8); + if (r < 0) + return r; + if (r > 0) + question_idna = question_utf8; + else { + if (dns_question_size(question_idna) > 0) { + r = dns_question_is_valid_for_query(question_idna); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + good = true; + } + } + + if (!good) /* don't allow empty queries */ + return -EINVAL; + } if (m->n_dns_queries >= QUERIES_MAX) return -EBUSY; @@ -427,6 +469,7 @@ int dns_query_new( *q = (DnsQuery) { .question_utf8 = dns_question_ref(question_utf8), .question_idna = dns_question_ref(question_idna), + .question_bypass = dns_packet_ref(question_bypass), .ifindex = ifindex, .flags = flags, .answer_dnssec_result = _DNSSEC_RESULT_INVALID, @@ -434,21 +477,27 @@ int dns_query_new( .answer_family = AF_UNSPEC, }; - /* First dump UTF8 question */ - DNS_QUESTION_FOREACH(key, question_utf8) - log_debug("Looking up RR for %s.", - dns_resource_key_to_string(key, key_str, sizeof key_str)); + if (question_bypass) { + DNS_QUESTION_FOREACH(key, question_bypass->question) + log_debug("Looking up bypass packet for %s.", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + } else { + /* First dump UTF8 question */ + DNS_QUESTION_FOREACH(key, question_utf8) + log_debug("Looking up RR for %s.", + dns_resource_key_to_string(key, key_str, sizeof key_str)); - /* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */ - DNS_QUESTION_FOREACH(key, question_idna) { - r = dns_question_contains(question_utf8, key); - if (r < 0) - return r; - if (r > 0) - continue; + /* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */ + DNS_QUESTION_FOREACH(key, question_idna) { + r = dns_question_contains(question_utf8, key); + if (r < 0) + return r; + if (r > 0) + continue; - log_debug("Looking up IDNA RR for %s.", - dns_resource_key_to_string(key, key_str, sizeof key_str)); + log_debug("Looking up IDNA RR for %s.", + dns_resource_key_to_string(key, key_str, sizeof key_str)); + } } LIST_PREPEND(queries, m->dns_queries, q); @@ -457,8 +506,8 @@ int dns_query_new( if (ret) *ret = q; - q = NULL; + TAKE_PTR(q); return 0; } @@ -559,9 +608,12 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { DNS_TRANSACTION_NOT_FOUND)) return 0; + if (FLAGS_SET(q->flags, SD_RESOLVED_NO_SYNTHESIZE)) + return 0; + r = dns_synthesize_answer( q->manager, - q->question_utf8, + q->question_bypass ? q->question_bypass->question : q->question_utf8, q->ifindex, &answer); if (r == -ENXIO) { @@ -601,9 +653,12 @@ static int dns_query_try_etc_hosts(DnsQuery *q) { /* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is * done. The data from /etc/hosts hence takes precedence over the network. */ + if (FLAGS_SET(q->flags, SD_RESOLVED_NO_SYNTHESIZE)) + return 0; + r = manager_etc_hosts_lookup( q->manager, - q->question_utf8, + q->question_bypass ? q->question_bypass->question : q->question_utf8, &answer); if (r <= 0) return r; @@ -757,6 +812,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; q->answer_authenticated = false; q->answer_errno = c->error_code; + q->answer_full_packet = dns_packet_unref(q->answer_full_packet); } SET_FOREACH(t, c->transactions) { @@ -764,14 +820,24 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { switch (t->state) { case DNS_TRANSACTION_SUCCESS: { - /* We found a successfully reply, merge it into the answer */ - r = dns_answer_extend(&q->answer, t->answer); - if (r < 0) - goto fail; + /* We found a successful reply, merge it into the answer */ + + if (state == DNS_TRANSACTION_SUCCESS) { + r = dns_answer_extend(&q->answer, t->answer); + if (r < 0) + goto fail; + } else { + /* Override non-successful previous answers */ + dns_answer_unref(q->answer); + q->answer = dns_answer_ref(t->answer); + } q->answer_rcode = t->answer_rcode; q->answer_errno = 0; + dns_packet_unref(q->answer_full_packet); + q->answer_full_packet = dns_packet_ref(t->received); + if (t->answer_authenticated) { has_authenticated = true; dnssec_result_authenticated = t->answer_dnssec_result; @@ -800,11 +866,14 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { if (q->answer_authenticated && !t->answer_authenticated) continue; - q->answer = dns_answer_unref(q->answer); + dns_answer_unref(q->answer); + q->answer = dns_answer_ref(t->answer); q->answer_rcode = t->answer_rcode; q->answer_dnssec_result = t->answer_dnssec_result; q->answer_authenticated = t->answer_authenticated; q->answer_errno = t->answer_errno; + dns_packet_unref(q->answer_full_packet); + q->answer_full_packet = dns_packet_ref(t->received); state = t->state; break; @@ -998,6 +1067,9 @@ int dns_query_process_cname(DnsQuery *q) { DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) { assert(q); + if (q->question_bypass) + return q->question_bypass->question; + switch (protocol) { case DNS_PROTOCOL_DNS: @@ -1018,6 +1090,9 @@ const char *dns_query_string(DnsQuery *q) { /* Returns a somewhat useful human-readable lookup key string for this query */ + if (q->question_bypass) + return dns_question_first_name(q->question_bypass->question); + if (q->request_address_string) return q->request_address_string; diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 133076dbf07..a7916712c11 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -49,12 +49,13 @@ struct DnsQuery { DnsQuestion *question_idna; DnsQuestion *question_utf8; + /* If this is not a question by ourselves, but a "bypass" request, we propagate the original packet + * here, and use that instead. */ + DnsPacket *question_bypass; + uint64_t flags; int ifindex; - /* If true, the RR TTLs of the answer will be clamped by their current left validity in the cache */ - bool clamp_ttl; - DnsTransactionState state; unsigned n_cname_redirects; @@ -71,6 +72,7 @@ struct DnsQuery { DnsSearchDomain *answer_search_domain; int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ bool previous_redirect_unauthenticated; + DnsPacket *answer_full_packet; /* Bus + Varlink client information */ sd_bus_message *bus_request; @@ -82,9 +84,11 @@ struct DnsQuery { char *request_address_string; /* DNS stub information */ - DnsPacket *request_dns_packet; - DnsStream *request_dns_stream; - DnsPacket *reply_dns_packet; + DnsPacket *request_packet; + DnsStream *request_stream; + DnsAnswer *reply_answer; + DnsAnswer *reply_authoritative; + DnsAnswer *reply_additional; DnsStubListenerExtra *stub_listener_extra; /* Completion callback */ @@ -109,7 +113,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQueryCandidate*, dns_query_candidate_unref); void dns_query_candidate_notify(DnsQueryCandidate *c); -int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags); +int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, DnsPacket *question_bypass, int family, uint64_t flags); DnsQuery *dns_query_free(DnsQuery *q); int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index d77e81ae398..cee93a2c04a 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -646,11 +646,10 @@ bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) { assert(s); assert(key); - /* Check if it makes sense to resolve the specified key on - * this scope. Note that this call assumes as fully qualified - * name, i.e. the search suffixes already appended. */ + /* Check if it makes sense to resolve the specified key on this scope. Note that this call assumes a + * fully qualified name, i.e. the search suffixes already appended. */ - if (key->class != DNS_CLASS_IN) + if (!IN_SET(key->class, DNS_CLASS_IN, DNS_CLASS_ANY)) return false; if (s->protocol == DNS_PROTOCOL_DNS) { @@ -672,8 +671,7 @@ bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) { return !dns_name_is_root(name); } - /* On mDNS and LLMNR, send A and AAAA queries only on the - * respective scopes */ + /* On mDNS and LLMNR, send A and AAAA queries only on the respective scopes */ key_family = dns_type_to_af(key->type); if (key_family < 0) @@ -765,6 +763,7 @@ int dns_scope_make_reply_packet( DnsPacket **ret) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + unsigned n_answer = 0, n_soa = 0; int r; assert(s); @@ -796,15 +795,15 @@ int dns_scope_make_reply_packet( return r; DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q)); - r = dns_packet_append_answer(p, answer); + r = dns_packet_append_answer(p, answer, &n_answer); if (r < 0) return r; - DNS_PACKET_HEADER(p)->ancount = htobe16(dns_answer_size(answer)); + DNS_PACKET_HEADER(p)->ancount = htobe16(n_answer); - r = dns_packet_append_answer(p, soa); + r = dns_packet_append_answer(p, soa, &n_soa); if (r < 0) return r; - DNS_PACKET_HEADER(p)->arcount = htobe16(dns_answer_size(soa)); + DNS_PACKET_HEADER(p)->arcount = htobe16(n_soa); *ret = TAKE_PTR(p); @@ -926,26 +925,50 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { } } -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok) { - DnsTransaction *t; +DnsTransaction *dns_scope_find_transaction( + DnsScope *scope, + DnsResourceKey *key, + uint64_t query_flags) { + + DnsTransaction *first, *t; assert(scope); assert(key); - /* Try to find an ongoing transaction that is a equal to the - * specified question */ - t = hashmap_get(scope->transactions_by_key, key); - if (!t) - return NULL; + /* Iterate through the list of transactions with a matching key */ + first = hashmap_get(scope->transactions_by_key, key); + LIST_FOREACH(transactions_by_key, t, first) { - /* Refuse reusing transactions that completed based on cached - * data instead of a real packet, if that's requested. */ - if (!cache_ok && - IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_RCODE_FAILURE) && - t->answer_source != DNS_TRANSACTION_NETWORK) - return NULL; + /* These four flags must match exactly: we cannot use a validated response for a + * non-validating client, and we cannot use a non-validated response for a validating + * client. Similar, if the sources don't match things aren't usable either. */ + if (((query_flags ^ t->query_flags) & + (SD_RESOLVED_NO_VALIDATE| + SD_RESOLVED_NO_ZONE| + SD_RESOLVED_NO_TRUST_ANCHOR| + SD_RESOLVED_NO_NETWORK)) != 0) + continue; - return t; + /* We can reuse a primary query if a regular one is requested, but not vice versa */ + if ((query_flags & SD_RESOLVED_REQUIRE_PRIMARY) && + !(t->query_flags & SD_RESOLVED_REQUIRE_PRIMARY)) + continue; + + /* Don't reuse a transaction that allowed caching when we got told not to use it */ + if ((query_flags & SD_RESOLVED_NO_CACHE) && + !(t->query_flags & SD_RESOLVED_NO_CACHE)) + continue; + + /* If we are are asked to clamp ttls an the existing transaction doesn't do it, we can't + * reuse */ + if ((query_flags & SD_RESOLVED_CLAMP_TTL) && + !(t->query_flags & SD_RESOLVED_CLAMP_TTL)) + continue; + + return t; + } + + return NULL; } static int dns_scope_make_conflict_packet( @@ -1289,7 +1312,7 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { else flags = goodbye ? (DNS_ANSWER_GOODBYE|DNS_ANSWER_CACHE_FLUSH) : DNS_ANSWER_CACHE_FLUSH; - r = dns_answer_add(answer, i->rr, 0 , flags); + r = dns_answer_add(answer, i->rr, 0, flags, NULL); if (r < 0) return log_debug_errno(r, "Failed to add RR to announce: %m"); } @@ -1307,7 +1330,7 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { if (r < 0) log_warning_errno(r, "Failed to add DNS-SD PTR record to MDNS zone: %m"); - r = dns_answer_add(answer, rr, 0 , 0); + r = dns_answer_add(answer, rr, 0, 0, NULL); if (r < 0) return log_debug_errno(r, "Failed to add RR to announce: %m"); } diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 82cd0becab7..532f4d6b800 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -53,14 +53,12 @@ struct DnsScope { LIST_HEAD(DnsQueryCandidate, query_candidates); - /* Note that we keep track of ongoing transactions in two - * ways: once in a hashmap, indexed by the rr key, and once in - * a linked list. We use the hashmap to quickly find - * transactions we can reuse for a key. But note that there - * might be multiple transactions for the same key (because - * the zone probing can't reuse a transaction answered from - * the zone or the cache), and the hashmap only tracks the - * most recent entry. */ + /* Note that we keep track of ongoing transactions in two ways: once in a hashmap, indexed by the rr + * key, and once in a linked list. We use the hashmap to quickly find transactions we can reuse for a + * key. But note that there might be multiple transactions for the same key (because the associated + * query flags might differ in incompatible ways: e.g. we may not reuse a non-validating transaction + * as validating. Hence we maintain a per-key list of transactions, which we iterate through to find + * one we can reuse with matching flags. */ Hashmap *transactions_by_key; LIST_HEAD(DnsTransaction, transactions); @@ -90,7 +88,7 @@ int dns_scope_mdns_membership(DnsScope *s, bool b); int dns_scope_make_reply_packet(DnsScope *s, uint16_t id, int rcode, DnsQuestion *q, DnsAnswer *answer, DnsAnswer *soa, bool tentative, DnsPacket **ret); void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok); +DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, uint64_t query_flags); int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 6a3dc9901ae..c54e269927a 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -85,69 +85,259 @@ DnsStubListenerExtra *dns_stub_listener_extra_free(DnsStubListenerExtra *p) { return mfree(p); } -static int dns_stub_make_reply_packet( - DnsPacket **p, - size_t max_size, - DnsQuestion *q, +static int dns_stub_collect_answer_by_question( + DnsAnswer **reply, DnsAnswer *answer, - bool *ret_truncated) { + DnsQuestion *question, + bool with_rrsig) { /* Add RRSIG RR matching each RR */ - bool truncated = false; - DnsResourceRecord *rr; + DnsAnswerItem *item; + int r; + + assert(reply); + + /* Copies all RRs from 'answer' into 'reply', if they match 'question'. */ + + DNS_ANSWER_FOREACH_ITEM(item, answer) { + + if (question) { + bool match = false; + + r = dns_question_matches_rr(question, item->rr, NULL); + if (r < 0) + return r; + else if (r > 0) + match = true; + else { + r = dns_question_matches_cname_or_dname(question, item->rr, NULL); + if (r < 0) + return r; + if (r > 0) + match = true; + } + + if (!match) + continue; + } + + r = dns_answer_add_extend(reply, item->rr, item->ifindex, item->flags, item->rrsig); + if (r < 0) + return r; + + if (with_rrsig && item->rrsig) { + r = dns_answer_add_extend(reply, item->rrsig, item->ifindex, item->flags, NULL); + if (r < 0) + return r; + } + } + + return 0; +} + +static int dns_stub_collect_answer_by_section( + DnsAnswer **reply, + DnsAnswer *answer, + DnsAnswerFlags section, + DnsAnswer *exclude1, + DnsAnswer *exclude2, + bool with_dnssec) { /* Include DNSSEC RRs. RRSIG, NSEC, … */ + + DnsAnswerItem *item; unsigned c = 0; int r; - assert(p); + assert(reply); - /* Note that we don't bother with any additional RRs, as this is stub is for local lookups only, and hence - * roundtrips aren't expensive. */ + /* Copies all RRs from 'answer' into 'reply', if they originate from the specified section. Also, + * avoid any RRs listed in 'exclude'. */ - if (!*p) { - r = dns_packet_new(p, DNS_PROTOCOL_DNS, 0, max_size); - if (r < 0) - return r; + DNS_ANSWER_FOREACH_ITEM(item, answer) { - r = dns_packet_append_question(*p, q); - if (r < 0) - return r; + if (dns_answer_contains(exclude1, item->rr) || + dns_answer_contains(exclude2, item->rr)) + continue; - DNS_PACKET_HEADER(*p)->qdcount = htobe16(dns_question_size(q)); - } + if (!with_dnssec && + dns_type_is_dnssec(item->rr->key->type)) + continue; - DNS_ANSWER_FOREACH(rr, answer) { + if (((item->flags ^ section) & (DNS_ANSWER_SECTION_ANSWER|DNS_ANSWER_SECTION_AUTHORITY|DNS_ANSWER_SECTION_ADDITIONAL)) != 0) + continue; - r = dns_question_matches_rr(q, rr, NULL); - if (r < 0) - return r; - if (r > 0) - goto add; - - r = dns_question_matches_cname_or_dname(q, rr, NULL); - if (r < 0) - return r; - if (r > 0) - goto add; - - continue; - add: - r = dns_packet_append_rr(*p, rr, 0, NULL, NULL); - if (r == -EMSGSIZE) { - truncated = true; - break; - } + r = dns_answer_add_extend(reply, item->rr, item->ifindex, item->flags, item->rrsig); if (r < 0) return r; c++; + + if (with_dnssec && item->rrsig) { + r = dns_answer_add_extend(reply, item->rrsig, item->ifindex, item->flags, NULL); + if (r < 0) + return r; + + c++; + } } + return (int) c; +} + +static int dns_stub_assign_sections( + DnsQuery *q, + DnsQuestion *question, + bool edns0_do) { + + int r; + + assert(q); + assert(question); + + /* Let's assign the 'answer' and 'answer_auxiliary' RRs we collected to their respective sections in + * the reply datagram. We try to reproduce a section assignment similar to what the upstream DNS + * server responded to us. We use the DNS_ANSWER_SECTION_xyz flags to match things up, which is where + * the original upstream's packet section assignment is stored in the DnsAnswer object. Not all RRs + * in the 'answer' and 'answer_auxiliary' objects come with section information though (for example, + * because they were synthesized locally, and not from a DNS packet). To deal with that we extend the + * assignment logic a bit: anything from the 'answer' object that directly matches the original + * question is always put in the ANSWER section, regardless if it carries section info, or what that + * section info says. Then, anything from the 'answer' and 'answer_auxiliary' objects that is from + * the ANSWER or AUTHORITY sections, and wasn't already added to the ANSWER section is placed in the + * AUTHORITY section. Everything else from either object is added to the ADDITIONAL section. */ + + /* Include all RRs that directly answer the question in the answer section */ + r = dns_stub_collect_answer_by_question( + &q->reply_answer, + q->answer, + question, + edns0_do); + if (r < 0) + return r; + + /* Include all RRs that originate from the answer or authority sections, and aren't listed in the + * answer section, in the authority section */ + r = dns_stub_collect_answer_by_section( + &q->reply_authoritative, + q->answer, + DNS_ANSWER_SECTION_ANSWER, + q->reply_answer, NULL, + edns0_do); + if (r < 0) + return r; + + /* Include all RRs that originate from the answer or authority sections, and aren't listed in the + * answer section, in the authority section */ + r = dns_stub_collect_answer_by_section( + &q->reply_authoritative, + q->answer, + DNS_ANSWER_SECTION_AUTHORITY, + q->reply_answer, NULL, + edns0_do); + if (r < 0) + return r; + + /* Include all RRs that originate from the additional sections in the additional section (except if + * already listed in the other two sections). Also add all RRs with no section marking. */ + r = dns_stub_collect_answer_by_section( + &q->reply_additional, + q->answer, + DNS_ANSWER_SECTION_ADDITIONAL, + q->reply_answer, q->reply_authoritative, + edns0_do); + if (r < 0) + return r; + r = dns_stub_collect_answer_by_section( + &q->reply_additional, + q->answer, + 0, + q->reply_answer, q->reply_authoritative, + edns0_do); + if (r < 0) + return r; + + return 0; +} + +static int dns_stub_make_reply_packet( + DnsPacket **ret, + size_t max_size, + DnsQuestion *q, + bool *ret_truncated) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + bool tc = false; + int r; + + assert(ret); + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, max_size); + if (r < 0) + return r; + + r = dns_packet_append_question(p, q); + if (r == -EMSGSIZE) + tc = true; + else if (r < 0) + return r; + if (ret_truncated) - *ret_truncated = truncated; - else if (truncated) + *ret_truncated = tc; + else if (tc) return -EMSGSIZE; - DNS_PACKET_HEADER(*p)->ancount = htobe16(be16toh(DNS_PACKET_HEADER(*p)->ancount) + c); + DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q)); + *ret = TAKE_PTR(p); + return 0; +} + +static int dns_stub_add_reply_packet_body( + DnsPacket *p, + DnsAnswer *answer, + DnsAnswer *authoritative, + DnsAnswer *additional, + bool edns0_do, /* Client expects DNSSEC RRs? */ + bool *truncated) { + + unsigned n_answer = 0, n_authoritative = 0, n_additional = 0; + bool tc = false; + int r; + + assert(p); + + /* Add the three sections to the packet. If the answer section doesn't fit we'll signal that as + * truncation. If the authoritative section doesn't fit and we are in DNSSEC mode, also signal + * truncation. In all other cases where things don't fit don't signal truncation, as for those cases + * the dropped RRs should not be essential. */ + + r = dns_packet_append_answer(p, answer, &n_answer); + if (r == -EMSGSIZE) + tc = true; + else if (r < 0) + return r; + else { + r = dns_packet_append_answer(p, authoritative, &n_authoritative); + if (r == -EMSGSIZE) { + if (edns0_do) + tc = true; + } else if (r < 0) + return r; + else { + r = dns_packet_append_answer(p, additional, &n_additional); + if (r < 0 && r != -EMSGSIZE) + return r; + } + } + + if (tc) { + if (!truncated) + return -EMSGSIZE; + + *truncated = true; + } + + DNS_PACKET_HEADER(p)->ancount = htobe16(n_answer); + DNS_PACKET_HEADER(p)->nscount = htobe16(n_authoritative); + DNS_PACKET_HEADER(p)->arcount = htobe16(n_additional); return 0; } @@ -159,6 +349,7 @@ static int dns_stub_finish_reply_packet( bool add_opt, /* add an OPT RR to this packet? */ bool edns0_do, /* set the EDNS0 DNSSEC OK bit? */ bool ad, /* set the DNSSEC authenticated data bit? */ + bool cd, /* set the DNSSEC checking disabled bit? */ uint16_t max_udp_size) { /* The maximum UDP datagram size to advertise to clients */ int r; @@ -171,19 +362,21 @@ static int dns_stub_finish_reply_packet( tc = true; else if (r < 0) return r; - } else { /* If the client can't to EDNS0, don't do DO either */ edns0_do = false; - /* If the client didn't do EDNS, clamp the rcode to 4 bit */ + /* If we don't do EDNS, clamp the rcode to 4 bit */ if (rcode > 0xF) rcode = DNS_RCODE_SERVFAIL; } - /* Don't set the AD bit unless DO is on, too */ - if (!edns0_do) + /* Don't set the AD or CD bit unless DO is on, too */ + if (!edns0_do) { ad = false; + cd = false; + + } DNS_PACKET_HEADER(p)->id = id; @@ -195,7 +388,7 @@ static int dns_stub_finish_reply_packet( 1 /* rd */, 1 /* ra */, ad /* ad */, - 0 /* cd */, + cd /* cd */, rcode)); return 0; @@ -231,6 +424,66 @@ static int dns_stub_send( return 0; } +static int dns_stub_send_reply( + DnsQuery *q, + int rcode) { + + _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; + bool truncated, edns0_do; + int r; + + assert(q); + + /* Reply with DNSSEC DO set? Only if client supports it; and we did any DNSSEC verification + * ourselves, or consider the data fully authenticated because we generated it locally, or + * the client set cd */ + edns0_do = + DNS_PACKET_DO(q->request_packet) && + (q->answer_dnssec_result >= 0 || /* we did proper DNSSEC validation … */ + dns_query_fully_authenticated(q) || /* … or we considered it authentic otherwise … */ + DNS_PACKET_CD(q->request_packet)); /* … or client set CD */ + + r = dns_stub_assign_sections( + q, + q->request_packet->question, + edns0_do); + if (r < 0) + return log_debug_errno(r, "Failed to assign sections: %m"); + + r = dns_stub_make_reply_packet( + &reply, + DNS_PACKET_PAYLOAD_SIZE_MAX(q->request_packet), + q->request_packet->question, + &truncated); + if (r < 0) + return log_debug_errno(r, "Failed to build reply packet: %m"); + + r = dns_stub_add_reply_packet_body( + reply, + q->reply_answer, + q->reply_authoritative, + q->reply_additional, + edns0_do, + &truncated); + if (r < 0) + return log_debug_errno(r, "Failed to append reply packet body: %m"); + + r = dns_stub_finish_reply_packet( + reply, + DNS_PACKET_ID(q->request_packet), + rcode, + truncated, + !!q->request_packet->opt, + edns0_do, + dns_query_fully_authenticated(q), + DNS_PACKET_CD(q->request_packet), + q->stub_listener_extra ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX); + if (r < 0) + return log_debug_errno(r, "Failed to build failure packet: %m"); + + return dns_stub_send(q->manager, q->stub_listener_extra, q->request_stream, q->request_packet, reply); +} + static int dns_stub_send_failure( Manager *m, DnsStubListenerExtra *l, @@ -240,12 +493,17 @@ static int dns_stub_send_failure( bool authenticated) { _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; + bool truncated; int r; assert(m); assert(p); - r = dns_stub_make_reply_packet(&reply, DNS_PACKET_PAYLOAD_SIZE_MAX(p), p->question, NULL, NULL); + r = dns_stub_make_reply_packet( + &reply, + DNS_PACKET_PAYLOAD_SIZE_MAX(p), + p->question, + &truncated); if (r < 0) return log_debug_errno(r, "Failed to make failure packet: %m"); @@ -253,10 +511,11 @@ static int dns_stub_send_failure( reply, DNS_PACKET_ID(p), rcode, - /* truncated = */ false, + truncated, !!p->opt, DNS_PACKET_DO(p), authenticated, + DNS_PACKET_CD(p), l ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX); if (r < 0) return log_debug_errno(r, "Failed to build failure packet: %m"); @@ -264,27 +523,87 @@ static int dns_stub_send_failure( return dns_stub_send(m, l, s, p, reply); } +static int dns_stub_patch_bypass_reply_packet( + DnsPacket **ret, /* Where to place the patched packet */ + DnsPacket *original, /* The packet to patch */ + DnsPacket *request) { /* The packet the patched packet shall look like a reply to */ + _cleanup_(dns_packet_unrefp) DnsPacket *c = NULL; + int r; + + assert(ret); + assert(original); + assert(request); + + r = dns_packet_dup(&c, original); + if (r < 0) + return r; + + /* Extract the packet, so that we know where the OPT field is */ + r = dns_packet_extract(c); + if (r < 0) + return r; + + /* Copy over the original client request ID, so that we can make the upstream query look like our own reply. */ + DNS_PACKET_HEADER(c)->id = DNS_PACKET_HEADER(request)->id; + + /* Patch in our own maximum datagram size, if EDNS0 was on */ + r = dns_packet_patch_max_udp_size(c, ADVERTISE_DATAGRAM_SIZE_MAX); + if (r < 0) + return r; + + /* Lower all TTLs by the time passed since we received the datagram. */ + if (timestamp_is_set(original->timestamp)) { + r = dns_packet_patch_ttls(c, original->timestamp); + if (r < 0) + return r; + } + + /* Our upstream connection might have supported larger DNS requests than our downstream one, hence + * set the TC bit if our reply is larger than what the client supports, and truncate. */ + if (c->size > DNS_PACKET_PAYLOAD_SIZE_MAX(request)) { + log_debug("Artificially truncating stub response, as advertised size of client is smaller than upstream one."); + dns_packet_truncate(c, DNS_PACKET_PAYLOAD_SIZE_MAX(request)); + DNS_PACKET_HEADER(c)->flags = htobe16(be16toh(DNS_PACKET_HEADER(c)->flags) | DNS_PACKET_FLAG_TC); + } + + *ret = TAKE_PTR(c); + return 0; +} + static void dns_stub_query_complete(DnsQuery *q) { int r; assert(q); - assert(q->request_dns_packet); + assert(q->request_packet); + + if (q->question_bypass) { + /* This is a bypass reply. If so, let's propagate the upstream packet, if we have it and it + * is regular DNS. (We can't do this if the upstream packet is LLMNR or mDNS, since the + * packets are not 100% compatible.) */ + + if (q->answer_full_packet && + q->answer_full_packet->protocol == DNS_PROTOCOL_DNS) { + _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; + + r = dns_stub_patch_bypass_reply_packet(&reply, q->answer_full_packet, q->request_packet); + if (r < 0) + log_debug_errno(r, "Failed to patch bypass reply packet: %m"); + else + (void) dns_stub_send(q->manager, q->stub_listener_extra, q->request_stream, q->request_packet, reply); + + dns_query_free(q); + return; + } + } switch (q->state) { - case DNS_TRANSACTION_SUCCESS: { - bool truncated; - - r = dns_stub_make_reply_packet(&q->reply_dns_packet, DNS_PACKET_PAYLOAD_SIZE_MAX(q->request_dns_packet), q->question_idna, q->answer, &truncated); - if (r < 0) { - log_debug_errno(r, "Failed to build reply packet: %m"); - break; - } - - if (!truncated) { + case DNS_TRANSACTION_SUCCESS: + /* Follow CNAMEs, and accumulate answers. Except if DNSSEC is requested, then let the client do that. */ + if (!DNS_PACKET_DO(q->request_packet)) { r = dns_query_process_cname(q); - if (r == -ELOOP) { - (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false); + if (r == -ELOOP) { /* CNAME loop */ + (void) dns_stub_send_reply(q, DNS_RCODE_SERVFAIL); break; } if (r < 0) { @@ -295,30 +614,15 @@ static void dns_stub_query_complete(DnsQuery *q) { return; } - r = dns_stub_finish_reply_packet( - q->reply_dns_packet, - DNS_PACKET_ID(q->request_dns_packet), - q->answer_rcode, - truncated, - !!q->request_dns_packet->opt, - DNS_PACKET_DO(q->request_dns_packet), - dns_query_fully_authenticated(q), - q->stub_listener_extra ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX); - if (r < 0) { - log_debug_errno(r, "Failed to finish reply packet: %m"); - break; - } - - (void) dns_stub_send(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, q->reply_dns_packet); + (void) dns_stub_send_reply(q, q->answer_rcode); break; - } case DNS_TRANSACTION_RCODE_FAILURE: - (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, q->answer_rcode, dns_query_fully_authenticated(q)); + (void) dns_stub_send_reply(q, q->answer_rcode); break; case DNS_TRANSACTION_NOT_FOUND: - (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN, dns_query_fully_authenticated(q)); + (void) dns_stub_send_reply(q, DNS_RCODE_NXDOMAIN); break; case DNS_TRANSACTION_TIMEOUT: @@ -334,7 +638,8 @@ static void dns_stub_query_complete(DnsQuery *q) { case DNS_TRANSACTION_NO_TRUST_ANCHOR: case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: case DNS_TRANSACTION_NETWORK_DOWN: - (void) dns_stub_send_failure(q->manager, q->stub_listener_extra, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL, false); + case DNS_TRANSACTION_NO_SOURCE: + (void) dns_stub_send_reply(q, DNS_RCODE_SERVFAIL); break; case DNS_TRANSACTION_NULL: @@ -398,13 +703,13 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea if (dns_type_is_obsolete(p->question->keys[0]->type)) { log_debug("Got message with obsolete key type, refusing."); - dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false); + dns_stub_send_failure(m, l, s, p, DNS_RCODE_REFUSED, false); return; } if (dns_type_is_zone_transer(p->question->keys[0]->type)) { log_debug("Got request for zone transfer, refusing."); - dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false); + dns_stub_send_failure(m, l, s, p, DNS_RCODE_REFUSED, false); return; } @@ -416,23 +721,29 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea } if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) { - log_debug("Got request with DNSSEC CD bit set, refusing."); - dns_stub_send_failure(m, l, s, p, DNS_RCODE_NOTIMP, false); - return; - } + log_debug("Got request with DNSSEC checking disabled, enabling bypass logic."); - r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH); + r = dns_query_new(m, &q, NULL, NULL, p, 0, + SD_RESOLVED_PROTOCOLS_ALL| + SD_RESOLVED_NO_CNAME| + SD_RESOLVED_NO_SEARCH| + SD_RESOLVED_NO_VALIDATE| + SD_RESOLVED_REQUIRE_PRIMARY| + SD_RESOLVED_CLAMP_TTL); + } else + r = dns_query_new(m, &q, p->question, p->question, NULL, 0, + SD_RESOLVED_PROTOCOLS_ALL| + SD_RESOLVED_NO_SEARCH| + (DNS_PACKET_DO(p) ? SD_RESOLVED_NO_CNAME|SD_RESOLVED_REQUIRE_PRIMARY : 0)| + SD_RESOLVED_CLAMP_TTL); if (r < 0) { log_error_errno(r, "Failed to generate query object: %m"); dns_stub_send_failure(m, l, s, p, DNS_RCODE_SERVFAIL, false); return; } - /* Request that the TTL is corrected by the cached time for this lookup, so that we return vaguely useful TTLs */ - q->clamp_ttl = true; - - q->request_dns_packet = dns_packet_ref(p); - q->request_dns_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */ + q->request_packet = dns_packet_ref(p); + q->request_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */ q->stub_listener_extra = l; q->complete = dns_stub_query_complete; diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c index f08d62116ab..23c9ba14a98 100644 --- a/src/resolve/resolved-dns-synthesize.c +++ b/src/resolve/resolved-dns-synthesize.c @@ -76,7 +76,7 @@ static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int if rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); - r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED, NULL); if (r < 0) return r; } @@ -90,7 +90,7 @@ static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int if rr->aaaa.in6_addr = in6addr_loopback; - r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED, NULL); if (r < 0) return r; } @@ -109,7 +109,7 @@ static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, if (!rr->ptr.name) return -ENOMEM; - return dns_answer_add(*answer, rr, ifindex, flags); + return dns_answer_add(*answer, rr, ifindex, flags, NULL); } static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { @@ -155,7 +155,7 @@ static int answer_add_addresses_rr( if (r < 0) return r; - r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED, NULL); if (r < 0) return r; } @@ -197,7 +197,7 @@ static int answer_add_addresses_ptr( if (r < 0) return r; - r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED, NULL); if (r < 0) return r; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 7c966affe1b..c5233a1e195 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -88,13 +88,23 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { dns_server_unref(t->server); if (t->scope) { - hashmap_remove_value(t->scope->transactions_by_key, t->key, t); - LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); + if (t->key) { + DnsTransaction *first; - if (t->id != 0) - hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); + first = hashmap_get(t->scope->transactions_by_key, t->key); + LIST_REMOVE(transactions_by_key, first, t); + if (first) + hashmap_replace(t->scope->transactions_by_key, first->key, first); + else + hashmap_remove(t->scope->transactions_by_key, t->key); + } + + LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); } + if (t->id != 0) + hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); + while ((c = set_steal_first(t->notify_query_candidates))) set_remove(c->transactions, t); set_free(t->notify_query_candidates); @@ -124,6 +134,7 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { dns_answer_unref(t->validated_keys); dns_resource_key_unref(t->key); + dns_packet_unref(t->bypass); return mfree(t); } @@ -165,13 +176,9 @@ static uint16_t pick_new_id(Manager *m) { return new_id; } -int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) { - _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; - int r; - - assert(ret); - assert(s); - assert(key); +static int key_ok( + DnsScope *scope, + DnsResourceKey *key) { /* Don't allow looking up invalid or pseudo RRs */ if (!dns_type_is_valid_query(key->type)) @@ -183,6 +190,49 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) if (!IN_SET(key->class, DNS_CLASS_IN, DNS_CLASS_ANY)) return -EOPNOTSUPP; + /* Don't allows DNSSEC RRs to be looked up via LLMNR/mDNS. They don't really make sense + * there, and it speeds up our queries if we refuse this early */ + if (scope->protocol != DNS_PROTOCOL_DNS && + dns_type_is_dnssec(key->type)) + return -EOPNOTSUPP; + + return 0; +} + +int dns_transaction_new( + DnsTransaction **ret, + DnsScope *s, + DnsResourceKey *key, + DnsPacket *bypass, + uint64_t query_flags) { + + _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; + int r; + + assert(ret); + assert(s); + + if (key) { + assert(!bypass); + + r = key_ok(s, key); + if (r < 0) + return r; + } else { + DnsResourceKey *qk; + assert(bypass); + + r = dns_packet_validate_query(bypass); + if (r < 0) + return r; + + DNS_QUESTION_FOREACH(qk, bypass->question) { + r = key_ok(s, qk); + if (r < 0) + return r; + } + } + if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX) return -EBUSY; @@ -190,9 +240,11 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) if (r < 0) return r; - r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; + if (key) { + r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + } t = new(DnsTransaction, 1); if (!t) @@ -204,6 +256,8 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) .answer_dnssec_result = _DNSSEC_RESULT_INVALID, .answer_nsec_ttl = (uint32_t) -1, .key = dns_resource_key_ref(key), + .query_flags = query_flags, + .bypass = dns_packet_ref(bypass), .current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID, .clamp_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID, .id = pick_new_id(s->manager), @@ -215,10 +269,17 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) return r; } - r = hashmap_replace(s->transactions_by_key, t->key, t); - if (r < 0) { - hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); - return r; + if (t->key) { + DnsTransaction *first; + + first = hashmap_get(s->transactions_by_key, t->key); + LIST_PREPEND(transactions_by_key, first, t); + + r = hashmap_replace(s->transactions_by_key, first->key, first); + if (r < 0) { + LIST_REMOVE(transactions_by_key, first, t); + return r; + } } LIST_PREPEND(transactions_by_scope, s->transactions, t); @@ -229,8 +290,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) if (ret) *ret = t; - t = NULL; - + TAKE_PTR(t); return 0; } @@ -265,7 +325,7 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s got tentative packet from %s.", t->id, - dns_resource_key_to_string(t->key, key_str, sizeof key_str), + dns_resource_key_to_string(dns_transaction_key(t), key_str, sizeof key_str), dns_protocol_to_string(t->scope->protocol), t->scope->link ? t->scope->link->ifname : "*", af_to_name_short(t->scope->family), @@ -307,7 +367,7 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { assert(!DNS_TRANSACTION_IS_LIVE(state)); if (state == DNS_TRANSACTION_DNSSEC_FAILED) { - dns_resource_key_to_string(t->key, key_str, sizeof key_str); + dns_resource_key_to_string(dns_transaction_key(t), key_str, sizeof key_str); log_struct(LOG_NOTICE, "MESSAGE_ID=" SD_MESSAGE_DNSSEC_FAILURE_STR, @@ -328,15 +388,17 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { else st = dns_transaction_state_to_string(state); - log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).", + log_debug("%s transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).", + t->bypass ? "Bypass" : "Regular", t->id, - dns_resource_key_to_string(t->key, key_str, sizeof key_str), + dns_resource_key_to_string(dns_transaction_key(t), key_str, sizeof key_str), dns_protocol_to_string(t->scope->protocol), t->scope->link ? t->scope->link->ifname : "*", af_to_name_short(t->scope->family), st, t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), - t->answer_authenticated ? "authenticated" : "unsigned"); + FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) ? "not validated" : + (t->answer_authenticated ? "authenticated" : "unsigned")); t->state = state; @@ -550,8 +612,11 @@ static int on_stream_packet(DnsStream *s) { } static uint16_t dns_transaction_port(DnsTransaction *t) { + assert(t); + if (t->server->port > 0) return t->server->port; + return DNS_SERVER_FEATURE_LEVEL_IS_TLS(t->current_feature_level) ? 853 : 53; } @@ -563,6 +628,7 @@ static int dns_transaction_emit_tcp(DnsTransaction *t) { int r; assert(t); + assert(t->sent); dns_transaction_close_connection(t); @@ -573,12 +639,14 @@ static int dns_transaction_emit_tcp(DnsTransaction *t) { if (r < 0) return r; - if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) - return -EOPNOTSUPP; + if (!t->bypass) { + if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(dns_transaction_key(t)->type)) + return -EOPNOTSUPP; - r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); - if (r < 0) - return r; + r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); + if (r < 0) + return r; + } if (t->server->stream && (DNS_SERVER_FEATURE_LEVEL_IS_TLS(t->current_feature_level) == t->server->stream->encrypted)) s = dns_stream_ref(t->server->stream); @@ -600,7 +668,7 @@ static int dns_transaction_emit_tcp(DnsTransaction *t) { * the IP address, in case this is a reverse * PTR lookup */ - r = dns_name_address(dns_resource_key_name(t->key), &family, &address); + r = dns_name_address(dns_resource_key_name(dns_transaction_key(t)), &family, &address); if (r < 0) return r; if (r == 0) @@ -682,6 +750,10 @@ static void dns_transaction_cache_answer(DnsTransaction *t) { if (t->scope->manager->enable_cache == DNS_CACHE_MODE_NO) return; + /* If validation is turned off for this transaction, but DNSSEC is on, then let's not cache this */ + if (FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) && t->scope->dnssec_mode != DNSSEC_NO) + return; + /* Packet from localhost? */ if (!t->scope->manager->cache_from_localhost && in_addr_is_localhost(t->received->family, &t->received->sender) != 0) @@ -689,12 +761,16 @@ static void dns_transaction_cache_answer(DnsTransaction *t) { dns_cache_put(&t->scope->cache, t->scope->manager->enable_cache, - t->key, + dns_transaction_key(t), t->answer_rcode, t->answer, + DNS_PACKET_CD(t->received) ? t->received : NULL, /* only cache full packets with CD on, + * since our usecase for caching them + * is "bypass" mode which is only + * enabled for CD packets. */ t->answer_authenticated, + t->answer_dnssec_result, t->answer_nsec_ttl, - 0, t->received->family, &t->received->sender); } @@ -848,11 +924,11 @@ static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags /* Checks whether the answer is positive, i.e. either a direct * answer to the question, or a CNAME/DNAME for it */ - r = dns_answer_match_key(t->answer, t->key, flags); + r = dns_answer_match_key(t->answer, dns_transaction_key(t), flags); if (r != 0) return r; - r = dns_answer_find_cname_or_dname(t->answer, t->key, NULL, flags); + r = dns_answer_find_cname_or_dname(t->answer, dns_transaction_key(t), NULL, flags); if (r != 0) return r; @@ -977,7 +1053,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { case DNS_PROTOCOL_DNS: assert(t->server); - if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) { + if (!t->bypass && + IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) { /* Request failed, immediately try again with reduced features */ @@ -1112,7 +1189,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { * to the request. For mDNS this check doesn't make sense, because the section 6 of RFC6762 states * that "Multicast DNS responses MUST NOT contain any questions in the Question Section". */ if (t->scope->protocol != DNS_PROTOCOL_MDNS) { - r = dns_packet_is_reply_for(p, t->key); + r = dns_packet_is_reply_for(p, dns_transaction_key(t)); if (r < 0) goto fail; if (r == 0) { @@ -1121,7 +1198,10 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { } } - /* Install the answer as answer to the transaction */ + /* Install the answer as answer to the transaction. We ref the answer twice here: the main `answer` + * field is later replaced by the DNSSEC validated subset. The 'answer_auxiliary' field carries the + * original complete record set, including RRSIG and friends. We use this when passing data to + * clients that ask for DNSSEC metadata. */ dns_answer_unref(t->answer); t->answer = dns_answer_ref(p->answer); t->answer_rcode = DNS_PACKET_RCODE(p); @@ -1226,7 +1306,7 @@ static int dns_transaction_emit_udp(DnsTransaction *t) { if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP || DNS_SERVER_FEATURE_LEVEL_IS_TLS(t->current_feature_level)) return -EAGAIN; /* Sorry, can't do UDP, try TCP! */ - if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) + if (!t->bypass && !dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(dns_transaction_key(t)->type)) return -EOPNOTSUPP; if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */ @@ -1248,9 +1328,11 @@ static int dns_transaction_emit_udp(DnsTransaction *t) { t->dns_udp_fd = fd; } - r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); - if (r < 0) - return r; + if (!t->bypass) { + r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); + if (r < 0) + return r; + } } else dns_transaction_close_connection(t); @@ -1372,8 +1454,9 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { dns_transaction_flush_dnssec_transactions(t); /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */ - if (t->scope->protocol == DNS_PROTOCOL_DNS) { - r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, t->key, &t->answer); + if (t->scope->protocol == DNS_PROTOCOL_DNS && + !FLAGS_SET(t->query_flags, SD_RESOLVED_NO_TRUST_ANCHOR)) { + r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, dns_transaction_key(t), &t->answer); if (r < 0) return r; if (r > 0) { @@ -1384,46 +1467,36 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { return 0; } - if (dns_name_is_root(dns_resource_key_name(t->key)) && - t->key->type == DNS_TYPE_DS) { + if (dns_name_is_root(dns_resource_key_name(dns_transaction_key(t))) && + dns_transaction_key(t)->type == DNS_TYPE_DS) { - /* Hmm, this is a request for the root DS? A - * DS RR doesn't exist in the root zone, and - * if our trust anchor didn't know it either, - * this means we cannot do any DNSSEC logic - * anymore. */ + /* Hmm, this is a request for the root DS? A DS RR doesn't exist in the root zone, + * and if our trust anchor didn't know it either, this means we cannot do any DNSSEC + * logic anymore. */ if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { - /* We are in downgrade mode. In this - * case, synthesize an unsigned empty - * response, so that the any lookup - * depending on this one can continue - * assuming there was no DS, and hence - * the root zone was unsigned. */ + /* We are in downgrade mode. In this case, synthesize an unsigned empty + * response, so that the any lookup depending on this one can continue + * assuming there was no DS, and hence the root zone was unsigned. */ t->answer_rcode = DNS_RCODE_SUCCESS; t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; t->answer_authenticated = false; dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); } else - /* If we are not in downgrade mode, - * then fail the lookup, because we - * cannot reasonably answer it. There - * might be DS RRs, but we don't know - * them, and the DNS server won't tell - * them to us (and even if it would, - * we couldn't validate and trust them. */ + /* If we are not in downgrade mode, then fail the lookup, because we cannot + * reasonably answer it. There might be DS RRs, but we don't know them, and + * the DNS server won't tell them to us (and even if it would, we couldn't + * validate and trust them. */ dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR); return 0; } } - /* Check the zone, but only if this transaction is not used - * for probing or verifying a zone item. */ - if (set_isempty(t->notify_zone_items)) { - - r = dns_zone_lookup(&t->scope->zone, t->key, dns_scope_ifindex(t->scope), &t->answer, NULL, NULL); + /* Check the zone. */ + if (!FLAGS_SET(t->query_flags, SD_RESOLVED_NO_ZONE)) { + r = dns_zone_lookup(&t->scope->zone, dns_transaction_key(t), dns_scope_ifindex(t->scope), &t->answer, NULL, NULL); if (r < 0) return r; if (r > 0) { @@ -1435,31 +1508,48 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { } } - /* Check the cache, but only if this transaction is not used - * for probing or verifying a zone item. */ - if (set_isempty(t->notify_zone_items)) { + /* Check the cache. */ + if (!FLAGS_SET(t->query_flags, SD_RESOLVED_NO_CACHE)) { - /* Before trying the cache, let's make sure we figured out a - * server to use. Should this cause a change of server this - * might flush the cache. */ + /* Before trying the cache, let's make sure we figured out a server to use. Should this cause + * a change of server this might flush the cache. */ (void) dns_scope_get_dns_server(t->scope); /* Let's then prune all outdated entries */ dns_cache_prune(&t->scope->cache); - r = dns_cache_lookup(&t->scope->cache, t->key, t->clamp_ttl, &t->answer_rcode, &t->answer, &t->answer_authenticated); + r = dns_cache_lookup( + &t->scope->cache, + dns_transaction_key(t), + t->query_flags, + &t->answer_rcode, + &t->answer, + &t->received, + &t->answer_authenticated, + &t->answer_dnssec_result); if (r < 0) return r; if (r > 0) { - t->answer_source = DNS_TRANSACTION_CACHE; - if (t->answer_rcode == DNS_RCODE_SUCCESS) - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - else - dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); - return 0; + if (t->bypass && t->scope->protocol == DNS_PROTOCOL_DNS && !t->received) + /* When bypass mode is on, do not use cached data unless it came with a full + * packet. */ + dns_transaction_reset_answer(t); + else { + t->answer_source = DNS_TRANSACTION_CACHE; + if (t->answer_rcode == DNS_RCODE_SUCCESS) + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + else + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); + return 0; + } } } + if (FLAGS_SET(t->query_flags, SD_RESOLVED_NO_NETWORK)) { + dns_transaction_complete(t, DNS_TRANSACTION_NO_SOURCE); + return 0; + } + return 1; } @@ -1484,17 +1574,17 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) { if (r < 0) return r; - r = dns_packet_append_key(p, t->key, 0, NULL); + r = dns_packet_append_key(p, dns_transaction_key(t), 0, NULL); if (r < 0) return r; qdcount = 1; - if (dns_key_is_shared(t->key)) + if (dns_key_is_shared(dns_transaction_key(t))) add_known_answers = true; - if (t->key->type == DNS_TYPE_ANY) { - r = set_ensure_put(&keys, &dns_resource_key_hash_ops, t->key); + if (dns_transaction_key(t)->type == DNS_TYPE_ANY) { + r = set_ensure_put(&keys, &dns_resource_key_hash_ops, dns_transaction_key(t)); if (r < 0) return r; } @@ -1522,7 +1612,7 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) { if (qdcount >= UINT16_MAX) break; - r = dns_packet_append_key(p, other->key, 0, NULL); + r = dns_packet_append_key(p, dns_transaction_key(other), 0, NULL); /* * If we can't stuff more questions into the packet, just give up. @@ -1556,11 +1646,11 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) { qdcount++; - if (dns_key_is_shared(other->key)) + if (dns_key_is_shared(dns_transaction_key(other))) add_known_answers = true; - if (other->key->type == DNS_TYPE_ANY) { - r = set_ensure_put(&keys, &dns_resource_key_hash_ops, other->key); + if (dns_transaction_key(other)->type == DNS_TYPE_ANY) { + r = set_ensure_put(&keys, &dns_resource_key_hash_ops, dns_transaction_key(other)); if (r < 0) return r; } @@ -1583,11 +1673,9 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) { if (r < 0) return r; - r = dns_packet_append_answer(p, answer); + r = dns_packet_append_answer(p, answer, &nscount); if (r < 0) return r; - - nscount += dns_answer_size(answer); } DNS_PACKET_HEADER(p)->nscount = htobe16(nscount); @@ -1608,19 +1696,31 @@ static int dns_transaction_make_packet(DnsTransaction *t) { if (t->sent) return 0; - r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO); - if (r < 0) - return r; + if (t->bypass && t->bypass->protocol == t->scope->protocol) { + /* If bypass logic is enabled and the protocol if the original packet and our scope match, + * take the original packet, copy it, and patch in our new ID */ + r = dns_packet_dup(&p, t->bypass); + if (r < 0) + return r; + } else { + r = dns_packet_new_query( + &p, t->scope->protocol, + /* min_alloc_dsize = */ 0, + /* dnssec_cd = */ !FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) && + t->scope->dnssec_mode != DNSSEC_NO); + if (r < 0) + return r; - r = dns_packet_append_key(p, t->key, 0, NULL); - if (r < 0) - return r; + r = dns_packet_append_key(p, dns_transaction_key(t), 0, NULL); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->qdcount = htobe16(1); + } - DNS_PACKET_HEADER(p)->qdcount = htobe16(1); DNS_PACKET_HEADER(p)->id = t->id; t->sent = TAKE_PTR(p); - return 0; } @@ -1641,12 +1741,14 @@ int dns_transaction_go(DnsTransaction *t) { if (r <= 0) return r; - log_debug("Transaction %" PRIu16 " for <%s> scope %s on %s/%s.", + log_debug("%s transaction %" PRIu16 " for <%s> scope %s on %s/%s (validate=%s).", + t->bypass ? "Bypass" : "Regular", t->id, - dns_resource_key_to_string(t->key, key_str, sizeof key_str), + dns_resource_key_to_string(dns_transaction_key(t), key_str, sizeof key_str), dns_protocol_to_string(t->scope->protocol), t->scope->link ? t->scope->link->ifname : "*", - af_to_name_short(t->scope->family)); + af_to_name_short(t->scope->family), + yes_no(!FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE))); if (!t->initial_jitter_scheduled && IN_SET(t->scope->protocol, DNS_PROTOCOL_LLMNR, DNS_PROTOCOL_MDNS)) { @@ -1700,8 +1802,8 @@ int dns_transaction_go(DnsTransaction *t) { return r; if (t->scope->protocol == DNS_PROTOCOL_LLMNR && - (dns_name_endswith(dns_resource_key_name(t->key), "in-addr.arpa") > 0 || - dns_name_endswith(dns_resource_key_name(t->key), "ip6.arpa") > 0)) { + (dns_name_endswith(dns_resource_key_name(dns_transaction_key(t)), "in-addr.arpa") > 0 || + dns_name_endswith(dns_resource_key_name(dns_transaction_key(t)), "ip6.arpa") > 0)) { /* RFC 4795, Section 2.4. says reverse lookups shall * always be made via TCP on LLMNR */ @@ -1792,9 +1894,9 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource assert(ret); assert(key); - aux = dns_scope_find_transaction(t->scope, key, true); + aux = dns_scope_find_transaction(t->scope, key, t->query_flags); if (!aux) { - r = dns_transaction_new(&aux, t->scope, key); + r = dns_transaction_new(&aux, t->scope, key, NULL, t->query_flags); if (r < 0) return r; } else { @@ -1812,9 +1914,9 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource return log_debug_errno(SYNTHETIC_ERRNO(ELOOP), "Potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).", aux->id, - dns_resource_key_to_string(t->key, s, sizeof s), + dns_resource_key_to_string(dns_transaction_key(t), s, sizeof s), t->id, - dns_resource_key_to_string(aux->key, saux, sizeof saux)); + dns_resource_key_to_string(dns_transaction_key(aux), saux, sizeof saux)); } } @@ -1907,7 +2009,7 @@ static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) { /* Is this key explicitly listed as a negative trust anchor? * If so, it's nothing we need to care about */ - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(t->key)); + r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(dns_transaction_key(t))); if (r < 0) return r; if (r > 0) @@ -1936,11 +2038,11 @@ static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRec * i.e. either matches the question precisely or is a * CNAME/DNAME for it. */ - r = dns_resource_key_match_rr(t->key, rr, NULL); + r = dns_resource_key_match_rr(dns_transaction_key(t), rr, NULL); if (r != 0) return r; - return dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); + return dns_resource_key_match_cname_or_dname(dns_transaction_key(t), rr->key, NULL); } static bool dns_transaction_dnssec_supported(DnsTransaction *t) { @@ -2004,7 +2106,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR */ - if (t->scope->dnssec_mode == DNSSEC_NO) + if (FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) || t->scope->dnssec_mode == DNSSEC_NO) return 0; if (t->answer_source != DNS_TRANSACTION_NETWORK) return 0; /* We only need to validate stuff from the network */ @@ -2052,7 +2154,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { * in another transaction whose additional RRs * point back to the original transaction, and * we deadlock. */ - r = dns_name_endswith(dns_resource_key_name(t->key), rr->rrsig.signer); + r = dns_name_endswith(dns_resource_key_name(dns_transaction_key(t)), rr->rrsig.signer); if (r < 0) return r; if (r == 0) @@ -2081,7 +2183,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { * up in request loops, and want to keep * additional traffic down. */ - r = dns_name_endswith(dns_resource_key_name(t->key), dns_resource_key_name(rr->key)); + r = dns_name_endswith(dns_resource_key_name(dns_transaction_key(t)), dns_resource_key_name(rr->key)); if (r < 0) return r; if (r == 0) @@ -2111,7 +2213,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { * this RR matches our original question, * however. */ - r = dns_resource_key_match_rr(t->key, rr, NULL); + r = dns_resource_key_match_rr(dns_transaction_key(t), rr, NULL); if (r < 0) return r; if (r == 0) { @@ -2119,7 +2221,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { * a negative reply, and we need the SOA RR's TTL in order to cache a negative entry? * If so, we need to validate it, too. */ - r = dns_answer_match_key(t->answer, t->key, NULL); + r = dns_answer_match_key(t->answer, dns_transaction_key(t), NULL); if (r < 0) return r; if (r > 0) /* positive reply, we won't need the SOA and hence don't need to validate @@ -2128,7 +2230,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { /* Only bother with this if the SOA/NS RR we are looking at is actually a parent of * what we are looking for, otherwise there's no value in it for us. */ - r = dns_name_endswith(dns_resource_key_name(t->key), dns_resource_key_name(rr->key)); + r = dns_name_endswith(dns_resource_key_name(dns_transaction_key(t)), dns_resource_key_name(rr->key)); if (r < 0) return r; if (r == 0) @@ -2254,7 +2356,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { const char *name; uint16_t type = 0; - name = dns_resource_key_name(t->key); + name = dns_resource_key_name(dns_transaction_key(t)); /* If this was a SOA or NS request, then check if there's a DS RR for the same domain. Note that this * could also be used as indication that we are not at a zone apex, but in real world setups there are @@ -2263,16 +2365,16 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { * is signed, hence ask the parent SOA in that case. If this was any other RR then ask for the SOA RR, * to see if that is signed. */ - if (t->key->type == DNS_TYPE_DS) { + if (dns_transaction_key(t)->type == DNS_TYPE_DS) { r = dns_name_parent(&name); if (r > 0) { type = DNS_TYPE_SOA; log_debug("Requesting parent SOA (→ %s) to validate transaction %" PRIu16 " (%s, unsigned empty DS response).", - name, t->id, dns_resource_key_name(t->key)); + name, t->id, dns_resource_key_name(dns_transaction_key(t))); } else name = NULL; - } else if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS)) { + } else if (IN_SET(dns_transaction_key(t)->type, DNS_TYPE_SOA, DNS_TYPE_NS)) { type = DNS_TYPE_DS; log_debug("Requesting DS (→ %s) to validate transaction %" PRIu16 " (%s, unsigned empty SOA/NS response).", @@ -2287,7 +2389,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (name) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; - soa = dns_resource_key_new(t->key->class, type, name); + soa = dns_resource_key_new(dns_transaction_key(t)->class, type, name); if (!soa) return -ENOMEM; @@ -2313,8 +2415,8 @@ void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) { } static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { - DnsResourceRecord *rr; - int ifindex, r; + DnsAnswerItem *item; + int r; assert(t); @@ -2322,16 +2424,16 @@ static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { * RRs from the list of validated keys to the list of * validated keys. */ - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { + DNS_ANSWER_FOREACH_ITEM(item, t->answer) { - r = dnssec_verify_dnskey_by_ds_search(rr, t->validated_keys); + r = dnssec_verify_dnskey_by_ds_search(item->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, DNS_ANSWER_AUTHENTICATED); + r = dns_answer_add_extend(&t->validated_keys, item->rr, item->ifindex, item->flags|DNS_ANSWER_AUTHENTICATED, item->rrsig); if (r < 0) return r; } @@ -2374,12 +2476,12 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * SET_FOREACH(dt, t->dnssec_transactions) { - if (dt->key->class != rr->key->class) + if (dns_transaction_key(dt)->class != rr->key->class) continue; - if (dt->key->type != DNS_TYPE_DS) + if (dns_transaction_key(dt)->type != DNS_TYPE_DS) continue; - r = dns_name_equal(dns_resource_key_name(dt->key), dns_resource_key_name(rr->key)); + r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), dns_resource_key_name(rr->key)); if (r < 0) return r; if (r == 0) @@ -2392,7 +2494,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * if (!dt->answer_authenticated) return false; - return dns_answer_match_key(dt->answer, dt->key, NULL); + return dns_answer_match_key(dt->answer, dns_transaction_key(dt), NULL); } /* We found nothing that proves this is safe to leave @@ -2415,9 +2517,9 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * SET_FOREACH(dt, t->dnssec_transactions) { - if (dt->key->class != rr->key->class) + if (dns_transaction_key(dt)->class != rr->key->class) continue; - if (dt->key->type != DNS_TYPE_SOA) + if (dns_transaction_key(dt)->type != DNS_TYPE_SOA) continue; if (!parent) { @@ -2435,7 +2537,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * } } - r = dns_name_equal(dns_resource_key_name(dt->key), parent); + r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), parent); if (r < 0) return r; if (r == 0) @@ -2454,12 +2556,12 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * SET_FOREACH(dt, t->dnssec_transactions) { - if (dt->key->class != rr->key->class) + if (dns_transaction_key(dt)->class != rr->key->class) continue; - if (dt->key->type != DNS_TYPE_SOA) + if (dns_transaction_key(dt)->type != DNS_TYPE_SOA) continue; - r = dns_name_equal(dns_resource_key_name(dt->key), dns_resource_key_name(rr->key)); + r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), dns_resource_key_name(rr->key)); if (r < 0) return r; if (r == 0) @@ -2517,10 +2619,10 @@ static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKe SET_FOREACH(dt, t->dnssec_transactions) { - if (dt->key->class != key->class) + if (dns_transaction_key(dt)->class != key->class) continue; - r = dns_name_equal(dns_resource_key_name(dt->key), tld); + r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), tld); if (r < 0) return r; if (r == 0) @@ -2551,16 +2653,16 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) { if (t->scope->dnssec_mode == DNSSEC_NO) return false; - if (dns_type_is_pseudo(t->key->type)) + if (dns_type_is_pseudo(dns_transaction_key(t)->type)) return -EINVAL; - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(t->key)); + r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(dns_transaction_key(t))); if (r < 0) return r; if (r > 0) return false; - r = dns_transaction_in_private_tld(t, t->key); + r = dns_transaction_in_private_tld(t, dns_transaction_key(t)); if (r < 0) return r; if (r > 0) { @@ -2569,13 +2671,13 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) { * that fact that we didn't get any NSEC RRs. */ log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.", - dns_resource_key_to_string(t->key, key_str, sizeof key_str)); + dns_resource_key_to_string(dns_transaction_key(t), key_str, sizeof key_str)); return false; } - name = dns_resource_key_name(t->key); + name = dns_resource_key_name(dns_transaction_key(t)); - if (t->key->type == DNS_TYPE_DS) { + if (dns_transaction_key(t)->type == DNS_TYPE_DS) { /* We got a negative reply for this DS lookup? DS RRs are signed when their parent zone is signed, * hence check the parent SOA in this case. */ @@ -2588,7 +2690,7 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) { type = DNS_TYPE_SOA; - } else if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS)) + } else if (IN_SET(dns_transaction_key(t)->type, DNS_TYPE_SOA, DNS_TYPE_NS)) /* We got a negative reply for this SOA/NS lookup? If so, check if there's a DS RR for this */ type = DNS_TYPE_DS; else @@ -2600,12 +2702,12 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) { SET_FOREACH(dt, t->dnssec_transactions) { - if (dt->key->class != t->key->class) + if (dns_transaction_key(dt)->class != dns_transaction_key(t)->class) continue; - if (dt->key->type != type) + if (dns_transaction_key(dt)->type != type) continue; - r = dns_name_equal(dns_resource_key_name(dt->key), name); + r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), name); if (r < 0) return r; if (r == 0) @@ -2644,12 +2746,12 @@ static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRe SET_FOREACH(dt, t->dnssec_transactions) { - if (dt->key->class != rr->key->class) + if (dns_transaction_key(dt)->class != rr->key->class) continue; - if (dt->key->type == DNS_TYPE_DNSKEY) { + if (dns_transaction_key(dt)->type == DNS_TYPE_DNSKEY) { - r = dns_name_equal(dns_resource_key_name(dt->key), rrsig->rrsig.signer); + r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), rrsig->rrsig.signer); if (r < 0) return r; if (r == 0) @@ -2664,9 +2766,9 @@ static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRe found = true; - } else if (dt->key->type == DNS_TYPE_DS) { + } else if (dns_transaction_key(dt)->type == DNS_TYPE_DS) { - r = dns_name_equal(dns_resource_key_name(dt->key), rrsig->rrsig.signer); + r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), rrsig->rrsig.signer); if (r < 0) return r; if (r == 0) @@ -2680,7 +2782,7 @@ static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRe if (!dt->answer_authenticated) return false; - return dns_answer_match_key(dt->answer, dt->key, NULL); + return dns_answer_match_key(dt->answer, dns_transaction_key(dt), NULL); } } } @@ -2822,19 +2924,26 @@ static int dnssec_validate_records( continue; } - r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig); + r = dnssec_verify_rrset_search( + t->answer, + rr->key, + t->validated_keys, + USEC_INFINITY, + &result, + &rrsig); if (r < 0) return r; log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result)); if (result == DNSSEC_VALIDATED) { + assert(rrsig); 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, DNS_ANSWER_AUTHENTICATED); + r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED, rrsig); if (r < 0) return r; @@ -2845,10 +2954,9 @@ static int dnssec_validate_records( return r; } - /* Add the validated RRset to the new list of validated - * RRsets, and remove it from the unvalidated RRsets. - * We mark the RRset as authenticated and cacheable. */ - r = dns_answer_move_by_key(validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE); + /* Add the validated RRset to the new list of validated RRsets, and remove it from + * the unvalidated RRsets. We mark the RRset as authenticated and cacheable. */ + r = dns_answer_move_by_key(validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE, rrsig); if (r < 0) return r; @@ -2868,6 +2976,8 @@ static int dnssec_validate_records( bool authenticated = false; const char *source; + assert(rrsig); + /* This RRset validated, but as a wildcard. This means we need * to prove via NSEC/NSEC3 that no matching non-wildcard RR exists. */ @@ -2886,8 +2996,12 @@ static int dnssec_validate_records( if (r == 0) result = DNSSEC_INVALID; else { - r = dns_answer_move_by_key(validated, &t->answer, rr->key, - authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0); + r = dns_answer_move_by_key( + validated, + &t->answer, + rr->key, + authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0, + rrsig); if (r < 0) return r; @@ -2905,7 +3019,12 @@ static int dnssec_validate_records( if (r == 0) { /* Data does not require signing. In that case, just copy it over, * but remember that this is by no means authenticated. */ - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); + r = dns_answer_move_by_key( + validated, + &t->answer, + rr->key, + 0, + NULL); if (r < 0) return r; @@ -2926,7 +3045,7 @@ static int dnssec_validate_records( /* Downgrading is OK? If so, just consider the information unsigned */ - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); + r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0, NULL); if (r < 0) return r; @@ -2951,7 +3070,7 @@ static int dnssec_validate_records( log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", dns_resource_key_to_string(rr->key, s, sizeof s)); - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); + r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0, NULL); if (r < 0) return r; @@ -2972,7 +3091,7 @@ static int dnssec_validate_records( /* The DNSKEY transaction was not authenticated, this means there's * no DS for this, which means it's OK if no keys are found for this signature. */ - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); + r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0, NULL); if (r < 0) return r; @@ -3037,11 +3156,10 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { assert(t); - /* We have now collected all DS and DNSKEY RRs in - * t->validated_keys, let's see which RRs we can now + /* 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_NO) + if (FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) || t->scope->dnssec_mode == DNSSEC_NO) return 0; /* Already validated */ @@ -3068,7 +3186,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, - dns_resource_key_to_string(t->key, key_str, sizeof key_str)); + dns_resource_key_to_string(dns_transaction_key(t), key_str, sizeof key_str)); /* First, see if this response contains any revoked trust * anchors we care about */ @@ -3151,7 +3269,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { bool authenticated = false; /* Bummer! Let's check NSEC/NSEC3 */ - r = dnssec_nsec_test(t->answer, t->key, &nr, &authenticated, &t->answer_nsec_ttl); + r = dnssec_nsec_test(t->answer, dns_transaction_key(t), &nr, &authenticated, &t->answer_nsec_ttl); if (r < 0) return r; @@ -3164,7 +3282,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer_rcode = DNS_RCODE_NXDOMAIN; t->answer_authenticated = authenticated; - manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, dns_transaction_key(t)); break; case DNSSEC_NSEC_NODATA: @@ -3174,7 +3292,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer_rcode = DNS_RCODE_SUCCESS; t->answer_authenticated = authenticated; - manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, dns_transaction_key(t)); break; case DNSSEC_NSEC_OPTOUT: @@ -3183,7 +3301,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer_dnssec_result = DNSSEC_UNSIGNED; t->answer_authenticated = false; - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, dns_transaction_key(t)); break; case DNSSEC_NSEC_NO_RR: @@ -3194,11 +3312,11 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { return r; if (r > 0) { t->answer_dnssec_result = DNSSEC_NO_SIGNATURE; - manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, dns_transaction_key(t)); } else { t->answer_dnssec_result = DNSSEC_UNSIGNED; t->answer_authenticated = false; - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, dns_transaction_key(t)); } break; @@ -3206,14 +3324,14 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { case DNSSEC_NSEC_UNSUPPORTED_ALGORITHM: /* We don't know the NSEC3 algorithm used? */ t->answer_dnssec_result = DNSSEC_UNSUPPORTED_ALGORITHM; - manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, t->key); + manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, dns_transaction_key(t)); break; case DNSSEC_NSEC_FOUND: case DNSSEC_NSEC_CNAME: /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ t->answer_dnssec_result = DNSSEC_NSEC_MISMATCH; - manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, dns_transaction_key(t)); break; default: @@ -3241,6 +3359,7 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] [DNS_TRANSACTION_RR_TYPE_UNSUPPORTED] = "rr-type-unsupported", [DNS_TRANSACTION_NETWORK_DOWN] = "network-down", [DNS_TRANSACTION_NOT_FOUND] = "not-found", + [DNS_TRANSACTION_NO_SOURCE] = "no-source", }; 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 3cad567a8c9..db7cf6743b2 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -5,6 +5,7 @@ #include "in-addr-util.h" typedef struct DnsTransaction DnsTransaction; +typedef struct DnsTransactionFinder DnsTransactionFinder; typedef enum DnsTransactionState DnsTransactionState; typedef enum DnsTransactionSource DnsTransactionSource; @@ -31,6 +32,7 @@ enum DnsTransactionState { DNS_TRANSACTION_RR_TYPE_UNSUPPORTED, DNS_TRANSACTION_NETWORK_DOWN, DNS_TRANSACTION_NOT_FOUND, /* like NXDOMAIN, but when LLMNR/TCP connections fail */ + DNS_TRANSACTION_NO_SOURCE, /* All suitable DnsTransactionSource turned off */ _DNS_TRANSACTION_STATE_MAX, _DNS_TRANSACTION_STATE_INVALID = -EINVAL, }; @@ -49,7 +51,10 @@ enum DnsTransactionSource { struct DnsTransaction { DnsScope *scope; - DnsResourceKey *key; + DnsResourceKey *key; /* For regular lookups the RR key to look for */ + DnsPacket *bypass; /* For bypass lookups the full original request packet */ + + uint64_t query_flags; DnsTransactionState state; @@ -60,8 +65,6 @@ struct DnsTransaction { bool initial_jitter_scheduled:1; bool initial_jitter_elapsed:1; - bool clamp_ttl:1; - bool probing:1; DnsPacket *sent, *received; @@ -133,9 +136,10 @@ struct DnsTransaction { LIST_FIELDS(DnsTransaction, transactions_by_scope); LIST_FIELDS(DnsTransaction, transactions_by_stream); + LIST_FIELDS(DnsTransaction, transactions_by_key); }; -int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); +int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key, DnsPacket *bypass, uint64_t flags); DnsTransaction* dns_transaction_free(DnsTransaction *t); bool dns_transaction_gc(DnsTransaction *t); @@ -150,6 +154,23 @@ void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source); int dns_transaction_validate_dnssec(DnsTransaction *t); int dns_transaction_request_dnssec_keys(DnsTransaction *t); +static inline DnsResourceKey *dns_transaction_key(DnsTransaction *t) { + assert(t); + + /* Return the lookup key of this transaction. Either takes the lookup key from the bypass packet if + * we are a bypass transaction. Or take the configured key for regular transactions. */ + + if (t->key) + return t->key; + + assert(t->bypass); + + if (dns_question_isempty(t->bypass->question)) + return NULL; + + return t->bypass->question->keys[0]; +} + const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c index 3e5d25586c7..8ba459b3e53 100644 --- a/src/resolve/resolved-dns-trust-anchor.c +++ b/src/resolve/resolved-dns-trust-anchor.c @@ -60,7 +60,7 @@ static int add_root_ksk( if (!rr->ds.digest) return -ENOMEM; - r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL); if (r < 0) return r; @@ -354,7 +354,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u old_answer = hashmap_get(d->positive_by_key, rr->key); answer = dns_answer_ref(old_answer); - r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL); if (r < 0) return log_error_errno(r, "Failed to add trust anchor RR: %m"); diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 00eb6725f9f..4c5e1fe3727 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -170,7 +170,10 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) { if (i->probe_transaction) return 0; - t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), false); + t = dns_scope_find_transaction( + i->scope, + &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), + SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE); if (!t) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; @@ -178,7 +181,7 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) { if (!key) return -ENOMEM; - r = dns_transaction_new(&t, i->scope, key); + r = dns_transaction_new(&t, i->scope, key, NULL, SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE); if (r < 0) return r; } @@ -296,7 +299,7 @@ static int dns_zone_add_authenticated_answer(DnsAnswer *a, DnsZoneItem *i, int i else flags = DNS_ANSWER_AUTHENTICATED; - return dns_answer_add(a, i->rr, ifindex, flags); + return dns_answer_add(a, i->rr, ifindex, flags, NULL); } int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) { diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c index 2cb90d201a3..0c35d91a83d 100644 --- a/src/resolve/resolved-etc-hosts.c +++ b/src/resolve/resolved-etc-hosts.c @@ -406,7 +406,7 @@ int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) { if (!rr->ptr.name) return -ENOMEM; - r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL); if (r < 0) return r; } @@ -458,7 +458,7 @@ int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) { if (r < 0) return r; - r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED, NULL); if (r < 0) return r; } diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index a0937471989..149d3a45263 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -301,25 +301,25 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us rr->ttl = 1; } - t = dns_scope_find_transaction(scope, rr->key, false); + t = dns_scope_find_transaction(scope, rr->key, SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE); if (t) dns_transaction_process_reply(t, p); /* Also look for the various types of ANY transactions */ - t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false); + t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE); if (t) dns_transaction_process_reply(t, p); - t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, rr->key->type, dns_resource_key_name(rr->key)), false); + t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, rr->key->type, dns_resource_key_name(rr->key)), SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE); if (t) dns_transaction_process_reply(t, p); - t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), false); + t = dns_scope_find_transaction(scope, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_ANY, DNS_TYPE_ANY, dns_resource_key_name(rr->key)), SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE); if (t) dns_transaction_process_reply(t, p); } - dns_cache_put(&scope->cache, scope->manager->enable_cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); + dns_cache_put(&scope->cache, scope->manager->enable_cache, NULL, DNS_PACKET_RCODE(p), p->answer, NULL, false, _DNSSEC_RESULT_INVALID, (uint32_t) -1, 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)); diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index 9a8a4f3013f..f948206e478 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -57,6 +57,9 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_NETWORK_DOWN: return varlink_error(q->varlink_request, "io.systemd.Resolve.NetworkDown", NULL); + case DNS_TRANSACTION_NO_SOURCE: + return varlink_error(q->varlink_request, "io.systemd.Resolve.NoSource", NULL); + case DNS_TRANSACTION_NOT_FOUND: /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ @@ -103,7 +106,7 @@ static bool validate_and_mangle_flags( /* This checks that the specified client-provided flags parameter actually makes sense, and mangles * it slightly. Specifically: * - * 1. We check that only the protocol flags and the NO_CNAME flag are on at most, plus the + * 1. We check that only the protocol flags and a bunch of NO_XYZ flags are on at most, plus the * method-specific flags specified in 'ok'. * * 2. If no protocols are enabled we automatically convert that to "all protocols are enabled". @@ -114,7 +117,15 @@ static bool validate_and_mangle_flags( * "everything". */ - if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) + if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL| + SD_RESOLVED_NO_CNAME| + SD_RESOLVED_NO_VALIDATE| + SD_RESOLVED_NO_SYNTHESIZE| + SD_RESOLVED_NO_CACHE| + SD_RESOLVED_NO_ZONE| + SD_RESOLVED_NO_TRUST_ANCHOR| + SD_RESOLVED_NO_NETWORK| + ok)) return false; if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ @@ -312,7 +323,7 @@ static int vl_method_resolve_hostname(Varlink *link, JsonVariant *parameters, Va if (r < 0 && r != -EALREADY) return r; - r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, p.ifindex, p.flags); + r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, NULL, p.ifindex, p.flags); if (r < 0) return r; @@ -481,7 +492,7 @@ static int vl_method_resolve_address(Varlink *link, JsonVariant *parameters, Var if (r < 0) return r; - r = dns_query_new(m, &q, question, question, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c index 0275d0eb948..b0763694dc9 100644 --- a/src/resolve/test-dnssec.c +++ b/src/resolve/test-dnssec.c @@ -170,7 +170,7 @@ static void test_dnssec_verify_rfc8080_ed25519_example1(void) { answer = dns_answer_new(1); assert_se(answer); - assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED) >= 0); + assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0); assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey, rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0); @@ -262,7 +262,7 @@ static void test_dnssec_verify_rfc8080_ed25519_example2(void) { answer = dns_answer_new(1); assert_se(answer); - assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED) >= 0); + assert_se(dns_answer_add(answer, mx, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0); assert_se(dnssec_verify_rrset(answer, mx->key, rrsig, dnskey, rrsig->rrsig.inception * USEC_PER_SEC, &result) >= 0); @@ -344,7 +344,7 @@ static void test_dnssec_verify_rrset(void) { answer = dns_answer_new(1); assert_se(answer); - assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED) >= 0); + assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 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, &result) >= 0); @@ -436,7 +436,7 @@ static void test_dnssec_verify_rrset2(void) { answer = dns_answer_new(1); assert_se(answer); - assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED) >= 0); + assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 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); @@ -563,10 +563,10 @@ static void test_dnssec_verify_rrset3(void) { answer = dns_answer_new(4); assert_se(answer); - assert_se(dns_answer_add(answer, mx1, 0, DNS_ANSWER_AUTHENTICATED) >= 0); - assert_se(dns_answer_add(answer, mx2, 0, DNS_ANSWER_AUTHENTICATED) >= 0); - assert_se(dns_answer_add(answer, mx3, 0, DNS_ANSWER_AUTHENTICATED) >= 0); - assert_se(dns_answer_add(answer, mx4, 0, DNS_ANSWER_AUTHENTICATED) >= 0); + assert_se(dns_answer_add(answer, mx1, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0); + assert_se(dns_answer_add(answer, mx2, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0); + assert_se(dns_answer_add(answer, mx3, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0); + assert_se(dns_answer_add(answer, mx4, 0, DNS_ANSWER_AUTHENTICATED, NULL) >= 0); /* Validate the RR as it if was 2020-02-24 today */ assert_se(dnssec_verify_rrset(answer, mx1->key, rrsig, dnskey, 1582534685*USEC_PER_SEC, &result) >= 0);