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-cache.c b/src/resolve/resolved-dns-cache.c index 925d5a3af2b..478141e730c 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,7 +668,9 @@ 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, @@ -661,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; } @@ -685,18 +726,49 @@ int dns_cache_put( /* Second, add in positive entries for all contained RRs */ DNS_ANSWER_FOREACH_ITEM(item, answer) { - if ((item->flags & DNS_ANSWER_CACHEABLE) == 0 || + 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, item->rr, + primary ? answer : NULL, + primary ? full_packet : NULL, item->flags & DNS_ANSWER_AUTHENTICATED, item->flags & DNS_ANSWER_SHARED_OWNER, + dnssec_result, timestamp, item->ifindex, - owner_family, owner_address); + owner_family, + owner_address); if (r < 0) goto fail; } @@ -746,7 +818,10 @@ int dns_cache_put( c, key, rcode, + answer, + full_packet, authenticated, + dnssec_result, nsec_ttl, timestamp, soa, @@ -828,7 +903,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; @@ -838,27 +965,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); @@ -867,31 +986,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) { @@ -899,28 +1067,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) && @@ -941,46 +1122,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, NULL); - 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..1d53e5da0da 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,29 @@ 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, + usec_t timestamp, + 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-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 6573edd630e..cee93a2c04a 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -925,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( diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index de05c0838f4..c7317a6bd86 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..8c252bfafe5 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: @@ -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-transaction.c b/src/resolve/resolved-dns-transaction.c index 8b3cb7fe1fd..a9b9ebcfe17 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; } @@ -328,7 +388,8 @@ 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(dns_transaction_key(t), key_str, sizeof key_str), dns_protocol_to_string(t->scope->protocol), @@ -336,7 +397,8 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { 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(dns_transaction_key(t)->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); @@ -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) @@ -692,7 +764,12 @@ static void dns_transaction_cache_answer(DnsTransaction *t) { 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, @@ -977,7 +1054,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 */ @@ -1121,7 +1199,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 +1307,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(dns_transaction_key(t)->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 +1329,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,7 +1455,8 @@ 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) { + 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; @@ -1387,42 +1471,32 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { 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)) { - + /* 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; @@ -1435,31 +1509,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, dns_transaction_key(t), 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; } @@ -1606,19 +1697,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, dns_transaction_key(t), 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; } @@ -1639,12 +1742,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(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)) { @@ -1790,9 +1895,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 { @@ -2002,7 +2107,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 */ @@ -3052,11 +3157,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 */ @@ -3256,6 +3360,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 9a3b54061c0..93ff6b3d584 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 = -1 }; @@ -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); @@ -153,7 +157,18 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t); static inline DnsResourceKey *dns_transaction_key(DnsTransaction *t) { assert(t); - return t->key; + /* 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_; diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index b8d3ee3941a..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; } diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index a0937471989..9f570153261 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, 0, _DNSSEC_RESULT_INVALID, (uint32_t) -1, 0, p->family, &p->sender); } else if (dns_packet_validate_query(p) > 0) { log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); 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;