-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
/***
This file is part of systemd.
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#include <net/if.h>
+
+#include "af-list.h"
#include "alloc-util.h"
#include "dns-domain.h"
#include "resolved-dns-answer.h"
#include "resolved-dns-packet.h"
#include "string-util.h"
-/* Never cache more than 4K entries */
+/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to
+ * leave DNS caches unbounded, but that's crazy. */
#define CACHE_MAX 4096
/* We never keep any item longer than 2h in our cache */
bool authenticated:1;
bool shared_owner:1;
+ int ifindex;
int owner_family;
union in_addr_union owner_address;
for (;;) {
DnsCacheItem *i;
+ char key_str[DNS_RESOURCE_KEY_STRING_MAX];
i = prioq_peek(c->by_expiry);
if (!i)
break;
/* Depending whether this is an mDNS shared entry
- * either remove only this one RR or the whole
- * RRset */
+ * either remove only this one RR or the whole RRset */
+ log_debug("Removing %scache entry for %s (expired "USEC_FMT"s ago)",
+ i->shared_owner ? "shared " : "",
+ dns_resource_key_to_string(i->key, key_str, sizeof key_str),
+ (t - i->until) / USEC_PER_SEC);
+
if (i->shared_owner)
dns_cache_item_unlink_and_free(c, i);
else {
first = hashmap_get(c->by_key, i->key);
if (first) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
+
+ /* Keep a reference to the original key, while we manipulate the list. */
+ k = dns_resource_key_ref(first->key);
+
+ /* Now, try to reduce the number of keys we keep */
+ dns_resource_key_reduce(&first->key, &i->key);
+
+ if (first->rr)
+ dns_resource_key_reduce(&first->rr->key, &i->key);
+ if (i->rr)
+ dns_resource_key_reduce(&i->rr->key, &i->key);
+
LIST_PREPEND(by_key, first, i);
assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
} else {
return NULL;
}
-static usec_t calculate_until(DnsResourceRecord *rr, usec_t timestamp, bool use_soa_minimum) {
+static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) {
uint32_t ttl;
usec_t u;
assert(rr);
- ttl = rr->ttl;
+ ttl = MIN(rr->ttl, nsec_ttl);
if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) {
/* If this is a SOA RR, and it is requested, clamp to
* the SOA's minimum field. This is used when we do
bool authenticated,
bool shared_owner,
usec_t timestamp,
+ int ifindex,
int owner_family,
const union in_addr_union *owner_address) {
dns_resource_key_unref(i->key);
i->key = dns_resource_key_ref(rr->key);
- i->until = calculate_until(rr, timestamp, false);
+ i->until = calculate_until(rr, (uint32_t) -1, timestamp, false);
i->authenticated = authenticated;
i->shared_owner = shared_owner;
+ i->ifindex = ifindex;
+
i->owner_family = owner_family;
i->owner_address = *owner_address;
bool authenticated,
bool shared_owner,
usec_t timestamp,
+ int ifindex,
int owner_family,
const union in_addr_union *owner_address) {
_cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
- _cleanup_free_ char *key_str = NULL;
DnsCacheItem *existing;
+ char key_str[DNS_RESOURCE_KEY_STRING_MAX], ifname[IF_NAMESIZE];
int r, k;
assert(c);
/* New TTL is 0? Delete this specific entry... */
if (rr->ttl <= 0) {
k = dns_cache_remove_by_rr(c, rr);
-
- if (log_get_max_level() >= LOG_DEBUG) {
- r = dns_resource_key_to_string(rr->key, &key_str);
- if (r < 0)
- return r;
-
- if (k > 0)
- log_debug("Removed zero TTL entry from cache: %s", key_str);
- else
- log_debug("Not caching zero TTL cache entry: %s", key_str);
- }
-
+ log_debug("%s: %s",
+ k > 0 ? "Removed zero TTL entry from cache" : "Not caching zero TTL cache entry",
+ dns_resource_key_to_string(rr->key, key_str, sizeof key_str));
return 0;
}
authenticated,
shared_owner,
timestamp,
+ ifindex,
owner_family,
owner_address);
return 0;
i->type = DNS_CACHE_POSITIVE;
i->key = dns_resource_key_ref(rr->key);
i->rr = dns_resource_record_ref(rr);
- i->until = calculate_until(rr, timestamp, false);
+ i->until = calculate_until(rr, (uint32_t) -1, timestamp, false);
i->authenticated = authenticated;
i->shared_owner = shared_owner;
+ i->ifindex = ifindex;
i->owner_family = owner_family;
i->owner_address = *owner_address;
i->prioq_idx = PRIOQ_IDX_NULL;
return r;
if (log_get_max_level() >= LOG_DEBUG) {
- r = dns_resource_key_to_string(i->key, &key_str);
- if (r < 0)
- return r;
-
- log_debug("Added positive cache entry for %s", key_str);
+ _cleanup_free_ char *t = NULL;
+
+ (void) in_addr_to_string(i->owner_family, &i->owner_address, &t);
+
+ log_debug("Added positive %s%s cache entry for %s "USEC_FMT"s on %s/%s/%s",
+ i->authenticated ? "authenticated" : "unauthenticated",
+ i->shared_owner ? " shared" : "",
+ dns_resource_key_to_string(i->key, key_str, sizeof key_str),
+ (i->until - timestamp) / USEC_PER_SEC,
+ i->ifindex == 0 ? "*" : strna(if_indextoname(i->ifindex, ifname)),
+ af_to_name_short(i->owner_family),
+ strna(t));
}
i = NULL;
DnsResourceKey *key,
int rcode,
bool authenticated,
+ uint32_t nsec_ttl,
usec_t timestamp,
DnsResourceRecord *soa,
int owner_family,
const union in_addr_union *owner_address) {
_cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
- _cleanup_free_ char *key_str = NULL;
+ char key_str[DNS_RESOURCE_KEY_STRING_MAX];
int r;
assert(c);
if (dns_type_is_pseudo(key->type))
return 0;
- if (soa->soa.minimum <= 0 || soa->ttl <= 0) {
- if (log_get_max_level() >= LOG_DEBUG) {
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
-
- log_debug("Not caching negative entry with zero SOA TTL: %s", key_str);
- }
-
+ if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) {
+ log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s",
+ dns_resource_key_to_string(key, key_str, sizeof key_str));
return 0;
}
return -ENOMEM;
i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN;
- i->until = calculate_until(soa, timestamp, true);
+ i->until = calculate_until(soa, nsec_ttl, timestamp, true);
i->authenticated = authenticated;
i->owner_family = owner_family;
i->owner_address = *owner_address;
if (i->type == DNS_CACHE_NXDOMAIN) {
/* NXDOMAIN entries should apply equally to all types, so we use ANY as
* a pseudo type for this purpose here. */
- i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(key));
+ i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, dns_resource_key_name(key));
if (!i->key)
return -ENOMEM;
if (r < 0)
return r;
- if (log_get_max_level() >= LOG_DEBUG) {
- r = dns_resource_key_to_string(i->key, &key_str);
- if (r < 0)
- return r;
-
- log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str);
- }
+ log_debug("Added %s cache entry for %s "USEC_FMT"s",
+ i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN",
+ dns_resource_key_to_string(i->key, key_str, sizeof key_str),
+ (i->until - timestamp) / USEC_PER_SEC);
i = NULL;
return 0;
}
}
+static bool rr_eligible(DnsResourceRecord *rr) {
+ assert(rr);
+
+ /* When we see an NSEC/NSEC3 RR, we'll only cache it if it is from the lower zone, not the upper zone, since
+ * that's where the interesting bits are (with exception of DS RRs). Of course, this way we cannot derive DS
+ * existence from any cached NSEC/NSEC3, but that should be fine. */
+
+ switch (rr->key->type) {
+
+ case DNS_TYPE_NSEC:
+ return !bitmap_isset(rr->nsec.types, DNS_TYPE_NS) ||
+ bitmap_isset(rr->nsec.types, DNS_TYPE_SOA);
+
+ case DNS_TYPE_NSEC3:
+ return !bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) ||
+ bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA);
+
+ default:
+ return true;
+ }
+}
+
int dns_cache_put(
DnsCache *c,
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) {
DnsResourceRecord *soa = NULL, *rr;
DnsAnswerFlags flags;
unsigned cache_keys;
- int r;
+ int r, ifindex;
assert(c);
assert(owner_address);
dns_cache_remove_previous(c, key, answer);
- if (dns_answer_size(answer) <= 0) {
- if (log_get_max_level() >= LOG_DEBUG) {
- _cleanup_free_ char *key_str = NULL;
-
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
-
- log_debug("Not caching negative entry without a SOA record: %s", key_str);
- }
-
- return 0;
- }
-
/* We only care for positive replies and NXDOMAINs, on all
* other replies we will simply flush the respective entries,
* and that's it */
if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
return 0;
+ if (dns_answer_size(answer) <= 0) {
+ char key_str[DNS_RESOURCE_KEY_STRING_MAX];
+
+ log_debug("Not caching negative entry without a SOA record: %s",
+ dns_resource_key_to_string(key, key_str, sizeof key_str));
+ return 0;
+ }
+
cache_keys = dns_answer_size(answer);
if (key)
- cache_keys ++;
+ cache_keys++;
/* Make some space for our new entries */
dns_cache_make_space(c, cache_keys);
timestamp = now(clock_boottime_or_monotonic());
/* Second, add in positive entries for all contained RRs */
- DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
+ DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) {
if ((flags & DNS_ANSWER_CACHEABLE) == 0)
continue;
+ r = rr_eligible(rr);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
r = dns_cache_put_positive(
c,
rr,
flags & DNS_ANSWER_AUTHENTICATED,
flags & DNS_ANSWER_SHARED_OWNER,
timestamp,
+ ifindex,
owner_family, owner_address);
if (r < 0)
goto fail;
return 0;
/* See https://tools.ietf.org/html/rfc2308, which say that a
- * matching SOA record in the packet is used to to enable
+ * matching SOA record in the packet is used to enable
* negative caching. */
r = dns_answer_find_soa(answer, key, &soa, &flags);
if (r < 0)
key,
rcode,
authenticated,
+ nsec_ttl,
timestamp,
soa,
owner_family, owner_address);
if (i)
return i;
- n = DNS_RESOURCE_KEY_NAME(k);
+ n = dns_resource_key_name(k);
/* Check if we have an NXDOMAIN cache item for the name, notice that we use
* the pseudo-type ANY for NXDOMAIN cache items. */
return NULL;
}
-int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) {
+int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcode, DnsAnswer **ret, bool *authenticated) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+ char key_str[DNS_RESOURCE_KEY_STRING_MAX];
unsigned n = 0;
int r;
bool nxdomain = false;
- _cleanup_free_ char *key_str = NULL;
DnsCacheItem *j, *first, *nsec = NULL;
bool have_authenticated = false, have_non_authenticated = false;
+ usec_t current;
assert(c);
assert(key);
assert(ret);
assert(authenticated);
- if (key->type == DNS_TYPE_ANY ||
- key->class == DNS_CLASS_ANY) {
-
+ 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 (log_get_max_level() >= LOG_DEBUG) {
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
-
- log_debug("Ignoring cache for ANY lookup: %s", key_str);
- }
+ log_debug("Ignoring cache for ANY lookup: %s",
+ dns_resource_key_to_string(key, key_str, sizeof key_str));
c->n_miss++;
if (!first) {
/* If one question cannot be answered we need to refresh */
- if (log_get_max_level() >= LOG_DEBUG) {
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
-
- log_debug("Cache miss for %s", key_str);
- }
+ log_debug("Cache miss for %s",
+ dns_resource_key_to_string(key, key_str, sizeof key_str));
c->n_miss++;
have_non_authenticated = true;
}
- if (nsec && key->type != DNS_TYPE_NSEC) {
- if (log_get_max_level() >= LOG_DEBUG) {
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
+ 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. */
- log_debug("NSEC NODATA cache hit for %s", key_str);
- }
+ 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
return 0;
}
- if (log_get_max_level() >= LOG_DEBUG) {
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
-
- log_debug("%s cache hit for %s",
- n > 0 ? "Positive" :
- nxdomain ? "NXDOMAIN" : "NODATA",
- key_str);
- }
+ log_debug("%s cache hit for %s",
+ n > 0 ? "Positive" :
+ nxdomain ? "NXDOMAIN" : "NODATA",
+ dns_resource_key_to_string(key, key_str, sizeof key_str));
if (n <= 0) {
c->n_hit++;
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;
- r = dns_answer_add(answer, j->rr, 0, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0);
+ if (clamp_ttl) {
+ rr = dns_resource_record_ref(j->rr);
+
+ r = dns_resource_record_clamp_ttl(&rr, LESS_BY(j->until, current) / USEC_PER_SEC);
+ if (r < 0)
+ return r;
+ }
+
+ r = dns_answer_add(answer, rr ?: j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0);
if (r < 0)
return r;
}
if (r < 0)
return r;
- ancount ++;
+ ancount++;
}
}
void dns_cache_dump(DnsCache *cache, FILE *f) {
Iterator iterator;
DnsCacheItem *i;
- int r;
if (!cache)
return;
fputs(t, f);
fputc('\n', f);
} else {
- _cleanup_free_ char *z = NULL;
- r = dns_resource_key_to_string(j->key, &z);
- if (r < 0) {
- log_oom();
- continue;
- }
+ char key_str[DNS_RESOURCE_KEY_STRING_MAX];
- fputs(z, f);
+ fputs(dns_resource_key_to_string(j->key, key_str, sizeof key_str), f);
fputs(" -- ", f);
fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f);
fputc('\n', f);