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),
#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."
* 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;
*
* 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
* 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 */
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;
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;
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;
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;
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;
/* 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)
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;
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);
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);
}
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,
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;
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,
c,
existing,
rr,
+ answer,
+ full_packet,
authenticated,
shared_owner,
+ dnssec_result,
timestamp,
ifindex,
owner_family,
.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,
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,
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 =
DnsResourceKey *key,
int rcode,
DnsAnswer *answer,
+ DnsPacket *full_packet,
bool authenticated,
+ DnssecResult dnssec_result,
uint32_t nsec_ttl,
usec_t timestamp,
int owner_family,
log_debug("Not caching negative entry without a SOA record: %s",
dns_resource_key_to_string(key, key_str, sizeof key_str));
}
+
return 0;
}
/* 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;
}
c,
key,
rcode,
+ answer,
+ full_packet,
authenticated,
+ dnssec_result,
nsec_ttl,
timestamp,
soa,
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;
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);
log_debug("Cache miss for %s",
dns_resource_key_to_string(key, key_str, sizeof key_str));
+ goto miss;
+ }
- c->n_miss++;
+ if (FLAGS_SET(query_flags, SD_RESOLVED_CLAMP_TTL))
+ current = now(clock_boottime_or_monotonic());
- *ret = NULL;
- *rcode = DNS_RCODE_SUCCESS;
- *authenticated = false;
+ LIST_FOREACH(by_key, j, first) {
+ /* 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));
- return 0;
- }
+ goto miss;
+ }
- LIST_FOREACH(by_key, j, first) {
- if (j->rr) {
+ 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) {
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) &&
if (n <= 0) {
c->n_hit++;
- *ret = NULL;
- *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
- *authenticated = have_authenticated && !have_non_authenticated;
- return 1;
- }
-
- answer = dns_answer_new(n);
- if (!answer)
- return -ENOMEM;
-
- if (clamp_ttl)
- current = now(clock_boottime_or_monotonic());
+ 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;
- 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;
+ return 1;
}
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) {
#include "list.h"
#include "prioq.h"
#include "resolve-util.h"
+#include "resolved-dns-dnssec.h"
#include "time-util.h"
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);
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)
return r;
}
- t->clamp_ttl = c->query->clamp_ttl;
TAKE_PTR(t);
return 1;
}
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 */
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;
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) {
dns_question_unref(q->question_idna);
dns_question_unref(q->question_utf8);
+ dns_packet_unref(q->question_bypass);
dns_query_reset_answer(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);
DnsQuery **ret,
DnsQuestion *question_utf8,
DnsQuestion *question_idna,
+ DnsPacket *question_bypass,
int ifindex,
uint64_t flags) {
_cleanup_(dns_query_freep) DnsQuery *q = NULL;
+ char key_str[DNS_RESOURCE_KEY_STRING_MAX];
DnsResourceKey *key;
- bool good = false;
int r;
- char key_str[DNS_RESOURCE_KEY_STRING_MAX];
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)
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;
*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,
.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));
-
- /* 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;
+ 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;
- 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);
if (ret)
*ret = q;
- q = NULL;
+ TAKE_PTR(q);
return 0;
}
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) {
/* 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;
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) {
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;
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;
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:
/* 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;
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;
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;
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 */
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);
}
}
-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) {
+
+ /* 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;
- /* 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;
+ /* 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 t;
+ return NULL;
}
static int dns_scope_make_conflict_packet(
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);
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);
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;
- unsigned c = 0;
+ DnsAnswerItem *item;
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 match 'question'. */
- if (!*p) {
- r = dns_packet_new(p, DNS_PROTOCOL_DNS, 0, max_size);
- if (r < 0)
- return r;
+ DNS_ANSWER_FOREACH_ITEM(item, answer) {
+
+ if (question) {
+ bool match = false;
- r = dns_packet_append_question(*p, q);
+ 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;
- DNS_PACKET_HEADER(*p)->qdcount = htobe16(dns_question_size(q));
+ if (with_rrsig && item->rrsig) {
+ r = dns_answer_add_extend(reply, item->rrsig, item->ifindex, item->flags, NULL);
+ if (r < 0)
+ return r;
+ }
}
- DNS_ANSWER_FOREACH(rr, answer) {
+ return 0;
+}
- r = dns_question_matches_rr(q, rr, NULL);
- if (r < 0)
- return r;
- if (r > 0)
- goto add;
+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, … */
- r = dns_question_matches_cname_or_dname(q, rr, NULL);
- if (r < 0)
- return r;
- if (r > 0)
- goto add;
+ DnsAnswerItem *item;
+ unsigned c = 0;
+ int r;
- continue;
- add:
- r = dns_packet_append_rr(*p, rr, 0, NULL, NULL);
- if (r == -EMSGSIZE) {
- truncated = true;
- break;
- }
+ assert(reply);
+
+ /* Copies all RRs from 'answer' into 'reply', if they originate from the specified section. Also,
+ * avoid any RRs listed in 'exclude'. */
+
+ DNS_ANSWER_FOREACH_ITEM(item, answer) {
+
+ if (dns_answer_contains(exclude1, item->rr) ||
+ dns_answer_contains(exclude2, item->rr))
+ continue;
+
+ if (!with_dnssec &&
+ dns_type_is_dnssec(item->rr->key->type))
+ continue;
+
+ if (((item->flags ^ section) & (DNS_ANSWER_SECTION_ANSWER|DNS_ANSWER_SECTION_AUTHORITY|DNS_ANSWER_SECTION_ADDITIONAL)) != 0)
+ continue;
+
+ 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;
}
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;
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;
1 /* rd */,
1 /* ra */,
ad /* ad */,
- 0 /* cd */,
+ cd /* cd */,
rcode));
return 0;
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,
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");
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");
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);
- switch (q->state) {
+ 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.) */
- case DNS_TRANSACTION_SUCCESS: {
- bool truncated;
+ if (q->answer_full_packet &&
+ q->answer_full_packet->protocol == DNS_PROTOCOL_DNS) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
- 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;
+ 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) {
- 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) {
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:
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:
}
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;
- }
-
- r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH);
+ log_debug("Got request with DNSSEC checking disabled, enabling bypass logic.");
+
+ 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;
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;
+
+ 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);
+ }
- if (t->id != 0)
- hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id));
+ 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);
dns_answer_unref(t->validated_keys);
dns_resource_key_unref(t->key);
+ dns_packet_unref(t->bypass);
return mfree(t);
}
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))
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;
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)
.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),
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);
if (ret)
*ret = t;
- t = NULL;
-
+ TAKE_PTR(t);
return 0;
}
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),
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;
}
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;
}
int r;
assert(t);
+ assert(t->sent);
dns_transaction_close_connection(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);
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)
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,
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 */
}
}
- /* 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);
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. */
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);
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;
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;
}
}
- /* 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;
}
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;
}
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)) {
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 {
* - 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 */
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 */
[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);
#include "in-addr-util.h"
typedef struct DnsTransaction DnsTransaction;
+typedef struct DnsTransactionFinder DnsTransactionFinder;
typedef enum DnsTransactionState DnsTransactionState;
typedef enum DnsTransactionSource DnsTransactionSource;
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
};
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;
bool initial_jitter_scheduled:1;
bool initial_jitter_elapsed:1;
- bool clamp_ttl:1;
-
bool probing:1;
DnsPacket *sent, *received;
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);
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_;
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;
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;
}
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));
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. */
/* 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".
* "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 */
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;
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;