/***
This file is part of systemd.
Copyright 2014 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "alloc-util.h"
#include "dns-domain.h"
#include "resolved-dns-answer.h"
#include "resolved-dns-cache.h"
#include "resolved-dns-packet.h"
#include "string-util.h"
/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to
* leave DNS caches unbounded, but that's crazy. */
/* We never keep any item longer than 2h in our cache */
enum DnsCacheItemType {
};
struct DnsCacheItem {
int ifindex;
int owner_family;
unsigned prioq_idx;
};
if (!i)
return;
free(i);
}
assert(c);
if (!i)
return;
if (first)
else
}
int r;
if (r < 0)
return r;
if (r > 0) {
return true;
}
}
return false;
}
assert(c);
if (!first)
return false;
}
return true;
}
assert(c);
}
assert(c);
if (add <= 0)
return;
/* Makes space for n new entries. Note that we actually allow
* the cache to grow beyond CACHE_MAX, but only when we shall
* add more RRs to the cache than CACHE_MAX at once. In that
* case the cache will be emptied completely otherwise. */
for (;;) {
DnsCacheItem *i;
if (prioq_size(c->by_expiry) <= 0)
break;
break;
i = prioq_peek(c->by_expiry);
assert(i);
/* Take an extra reference to the key so that it
* doesn't go away in the middle of the remove call */
}
}
usec_t t = 0;
assert(c);
/* Remove all entries that are past their TTL */
for (;;) {
DnsCacheItem *i;
i = prioq_peek(c->by_expiry);
if (!i)
break;
if (t <= 0)
t = now(clock_boottime_or_monotonic());
if (i->until > t)
break;
/* Depending whether this is an mDNS shared entry
* either remove only this one RR or the whole
* RRset */
if (i->shared_owner)
else {
/* Take an extra reference to the key so that it
* doesn't go away in the middle of the remove call */
}
}
}
static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
const DnsCacheItem *x = a, *y = b;
return -1;
return 1;
return 0;
}
int r;
assert(c);
if (r < 0)
return r;
if (r < 0)
return r;
return r;
}
int r;
assert(c);
assert(i);
if (r < 0)
return r;
if (first) {
/* Keep a reference to the original key, while we manipulate the list. */
/* Now, try to reduce the number of keys we keep */
if (i->rr)
} else {
if (r < 0) {
return r;
}
}
return 0;
}
DnsCacheItem *i;
assert(c);
return i;
return NULL;
}
static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) {
usec_t u;
/* If this is a SOA RR, and it is requested, clamp to
* the SOA's minimum field. This is used when we do
* negative caching, to determine the TTL for the
* negative caching entry. See RFC 2308, Section
* 5. */
}
u = ttl * USEC_PER_SEC;
if (u > CACHE_TTL_MAX_USEC)
u = CACHE_TTL_MAX_USEC;
/* Make use of the DNSSEC RRSIG expiry info, if we
* have it */
if (u > left)
u = left;
}
return timestamp + u;
}
static void dns_cache_item_update_positive(
DnsCache *c,
DnsCacheItem *i,
bool authenticated,
bool shared_owner,
int ifindex,
int owner_family,
const union in_addr_union *owner_address) {
assert(c);
assert(i);
i->type = DNS_CACHE_POSITIVE;
if (!i->by_key_prev)
/* We are the first item in the list, we need to
* update the key used in the hashmap */
i->authenticated = authenticated;
i->shared_owner = shared_owner;
i->owner_family = owner_family;
i->owner_address = *owner_address;
}
static int dns_cache_put_positive(
DnsCache *c,
bool authenticated,
bool shared_owner,
int ifindex,
int owner_family,
const union in_addr_union *owner_address) {
int r, k;
assert(c);
/* Never cache pseudo RRs */
return 0;
return 0;
/* New TTL is 0? Delete this specific entry... */
k = dns_cache_remove_by_rr(c, rr);
if (log_get_max_level() >= LOG_DEBUG) {
if (r < 0)
return r;
if (k > 0)
else
}
return 0;
}
/* Entry exists already? Update TTL, timestamp and owner*/
if (existing) {
c,
rr,
return 0;
}
/* Otherwise, add the new RR */
r = dns_cache_init(c);
if (r < 0)
return r;
dns_cache_make_space(c, 1);
if (!i)
return -ENOMEM;
i->type = DNS_CACHE_POSITIVE;
i->authenticated = authenticated;
i->shared_owner = shared_owner;
i->owner_family = owner_family;
i->owner_address = *owner_address;
i->prioq_idx = PRIOQ_IDX_NULL;
r = dns_cache_link_item(c, i);
if (r < 0)
return r;
if (log_get_max_level() >= LOG_DEBUG) {
if (r < 0)
return r;
}
i = NULL;
return 0;
}
static int dns_cache_put_negative(
DnsCache *c,
int rcode,
bool authenticated,
int owner_family,
const union in_addr_union *owner_address) {
int r;
assert(c);
/* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly
* important to filter out as we use this as a pseudo-type for
* NXDOMAIN entries */
return 0;
return 0;
if (log_get_max_level() >= LOG_DEBUG) {
if (r < 0)
return r;
}
return 0;
}
return 0;
r = dns_cache_init(c);
if (r < 0)
return r;
dns_cache_make_space(c, 1);
if (!i)
return -ENOMEM;
i->authenticated = authenticated;
i->owner_family = owner_family;
i->owner_address = *owner_address;
i->prioq_idx = PRIOQ_IDX_NULL;
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. */
if (!i->key)
return -ENOMEM;
/* Make sure to remove any previous entry for this
* specific ANY key. (For non-ANY keys the cache data
* is already cleared by the caller.) Note that we
* don't bother removing positive or NODATA cache
* items in this case, because it would either be slow
* or require explicit indexing by name */
} else
r = dns_cache_link_item(c, i);
if (r < 0)
return r;
if (log_get_max_level() >= LOG_DEBUG) {
if (r < 0)
return r;
log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str);
}
i = NULL;
return 0;
}
static void dns_cache_remove_previous(
DnsCache *c,
assert(c);
* not on mDNS), delete all matching old RRs, so that we only
* keep complete by_key in place. */
if (key)
/* Second, flush all entries matching the answer, unless this
* is an RR that is explicitly marked to be "shared" between
* peers (i.e. mDNS RRs without the flush-cache bit set). */
if ((flags & DNS_ANSWER_CACHEABLE) == 0)
continue;
if (flags & DNS_ANSWER_SHARED_OWNER)
continue;
}
}
/* 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
case DNS_TYPE_NSEC:
case DNS_TYPE_NSEC3:
default:
return true;
}
}
int dns_cache_put(
DnsCache *c,
int rcode,
bool authenticated,
int owner_family,
const union in_addr_union *owner_address) {
unsigned cache_keys;
int r, ifindex;
assert(c);
if (dns_answer_size(answer) <= 0) {
if (log_get_max_level() >= LOG_DEBUG) {
if (r < 0)
return r;
}
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 */
return 0;
if (key)
cache_keys ++;
/* Make some space for our new entries */
if (timestamp <= 0)
/* Second, add in positive entries for all contained RRs */
if ((flags & DNS_ANSWER_CACHEABLE) == 0)
continue;
r = rr_eligible(rr);
if (r < 0)
return r;
if (r == 0)
continue;
c,
rr,
if (r < 0)
goto fail;
}
if (!key) /* mDNS doesn't know negative caching, really */
return 0;
/* Third, add in negative entries if the key has no RR */
if (r < 0)
goto fail;
if (r > 0)
return 0;
* caching will be done on the canonical name, not on the
* alias) */
if (r < 0)
goto fail;
if (r > 0)
return 0;
/* See https://tools.ietf.org/html/rfc2308, which say that a
* matching SOA record in the packet is used to to enable
* negative caching. */
if (r < 0)
goto fail;
if (r == 0)
return 0;
/* Refuse using the SOA data if it is unsigned, but the key is
* signed */
return 0;
c,
key,
soa,
if (r < 0)
goto fail;
return 0;
fail:
/* Adding all RRs failed. Let's clean up what we already
* added, just in case */
if (key)
if ((flags & DNS_ANSWER_CACHEABLE) == 0)
continue;
}
return r;
}
DnsCacheItem *i;
const char *n;
int r;
assert(c);
assert(k);
/* If we hit some OOM error, or suchlike, we don't care too
* much, after all this is just a cache */
i = hashmap_get(c->by_key, k);
if (i)
return i;
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. */
if (i && i->type == DNS_CACHE_NXDOMAIN)
return i;
if (dns_type_may_redirect(k->type)) {
/* Check if we have a CNAME record instead */
if (i)
return i;
/* OK, let's look for cached DNAME records. */
for (;;) {
if (isempty(n))
return NULL;
if (i)
return i;
/* Jump one label ahead */
r = dns_name_parent(&n);
if (r <= 0)
return NULL;
}
}
if (k->type != DNS_TYPE_NSEC) {
/* Check if we have an NSEC record instead for the name. */
if (i)
return i;
}
return NULL;
}
int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) {
unsigned n = 0;
int r;
bool nxdomain = false;
assert(c);
/* 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) {
if (r < 0)
return r;
}
c->n_miss++;
return 0;
}
if (!first) {
/* If one question cannot be answered we need to refresh */
if (log_get_max_level() >= LOG_DEBUG) {
if (r < 0)
return r;
}
c->n_miss++;
return 0;
}
if (j->rr) {
nsec = j;
n++;
} else if (j->type == DNS_CACHE_NXDOMAIN)
nxdomain = true;
if (j->authenticated)
have_authenticated = true;
else
have_non_authenticated = true;
}
/* 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. */
if (log_get_max_level() >= LOG_DEBUG) {
if (r < 0)
return r;
}
/* 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. */
c->n_hit++;
return 1;
}
c->n_miss++;
return 0;
}
if (log_get_max_level() >= LOG_DEBUG) {
if (r < 0)
return r;
log_debug("%s cache hit for %s",
n > 0 ? "Positive" :
key_str);
}
if (n <= 0) {
c->n_hit++;
return 1;
}
answer = dns_answer_new(n);
if (!answer)
return -ENOMEM;
if (!j->rr)
continue;
if (r < 0)
return r;
}
c->n_hit++;
return n;
}
int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) {
bool same_owner = true;
/* See if there's a cache entry for the same key. If there
* isn't there's no conflict */
if (!first)
return 0;
/* See if the RR key is owned by the same owner, if so, there
* isn't a conflict either */
if (i->owner_family != owner_family ||
same_owner = false;
break;
}
}
if (same_owner)
return 0;
/* See if there's the exact same RR in the cache. If yes, then
* there's no conflict. */
return 0;
/* There's a conflict */
return 1;
}
unsigned ancount = 0;
DnsCacheItem *i;
int r;
assert(p);
DnsCacheItem *j;
if (!j->rr)
continue;
if (!j->shared_owner)
continue;
/* For mDNS, if we're unable to stuff all known answers into the given packet,
* allocate a new one, push the RR into that one and link it to the current one.
*/
ancount = 0;
if (r < 0)
return r;
/* continue with new packet */
p = p->more;
}
if (r < 0)
return r;
ancount ++;
}
}
return 0;
}
DnsCacheItem *i;
int r;
if (!cache)
return;
if (!f)
f = stdout;
DnsCacheItem *j;
fputc('\t', f);
if (j->rr) {
const char *t;
t = dns_resource_record_to_string(j->rr);
if (!t) {
log_oom();
continue;
}
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;
}
fputs(z, f);
fputs(" -- ", f);
fputc('\n', f);
}
}
}
}
if (!cache)
return true;
}
if (!cache)
return 0;
}