1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2025-01-12 09:17:44 +03:00

resolved: add support for answering DNSSEC questions on the stub

This substantially beefs up the local DNS stub feature set in order to
allow local clients to do DNSSEC validation through the stub.

Previously we'd return NOTIMP if we'd get a DO or DO+CD lookup. With
this change we'll instead:

1. If we get DO+CD requests (i.e. DNSSEC with no local checking) we'll
   proxy DNS queries and response mostly unmodified to/from upstream DNS
   servers if possible (this is called "bypass" mode).  We will patch in
   new request IDs, (and patch them back out on reply), so that we can
   sanely keep track of things.  We'll also maintain a minimal local
   cache for such lookups, always keeping the whole DNS packets in it
   (if we reply from cache we'll patch the TTLs of all included RRs).

2. If we get DO requests without CD (i.e. DNSSEC with local checking)
   we'll resolve and validate locally. In this mode we will not proxy
   packets, but generate our own. We will however cache the combination
   of answer RRs (along with their packet section assignments) we got
   back in the cache, and use this information to generate reply packets
   from the DNS stub.

In both cases: if we determine a lookup is to be answered from LLMNR or
mDNS we'll always revert to non-DNSSEC, non-proxy operation as before.
Answers will lack the DO bit then, since the data cannot be validated
via DNSSEC by the clients.

To make this logic more debuggable, this also adds query flags for
turning off RR sources. i.e. cache/network/zone/trust anchor/local
synthesis may now be disabled individually for each lookup.

The cache is substantially updated to make all this work: in addition to
caching simple RRs for lookup RR keys, we'll now cache the whole packets
and the whole combination of RRs, so that we can answer DO and DO+CD
replies sensibly according to the rules described above. This sounds
wasteful, but given that the
DnsResourceRecord/DnsResourceKey/DnsAnswer/DnsPacket
objects are all ref-counted and we try to merge references the actual
additional memory used should be limited (but this might be something to
optimize further later on).

To implement classic RR key lookups and new-style packet proxy lookups
(i.e. the ones necessary for DO+CD packet proxying, as described above)
DnsTransaction and DnsQuery objects now always maintain either a
DnsResourceKey/DnsQuestion as lookup key or a DnsPacket for "bypass"
mode.

Fixes: #4621 #17218
This commit is contained in:
Lennart Poettering 2020-11-05 11:01:52 +01:00
parent d8f3836ea9
commit 775ae35403
16 changed files with 1157 additions and 369 deletions

View File

@ -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),

View File

@ -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."

View File

@ -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;

View File

@ -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)

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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(

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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_;

View File

@ -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;
}

View File

@ -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));

View File

@ -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;