1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include "alloc-util.h"
23 #include "dns-domain.h"
24 #include "resolved-dns-answer.h"
25 #include "resolved-dns-cache.h"
26 #include "resolved-dns-packet.h"
27 #include "string-util.h"
29 /* Never cache more than 1K entries */
30 #define CACHE_MAX 1024
32 /* We never keep any item longer than 10min in our cache */
33 #define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE)
35 typedef enum DnsCacheItemType DnsCacheItemType
;
36 typedef struct DnsCacheItem DnsCacheItem
;
38 enum DnsCacheItemType
{
46 DnsResourceRecord
*rr
;
48 DnsCacheItemType type
;
52 union in_addr_union owner_address
;
53 LIST_FIELDS(DnsCacheItem
, by_key
);
56 static void dns_cache_item_free(DnsCacheItem
*i
) {
60 dns_resource_record_unref(i
->rr
);
61 dns_resource_key_unref(i
->key
);
65 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem
*, dns_cache_item_free
);
67 static void dns_cache_item_remove_and_free(DnsCache
*c
, DnsCacheItem
*i
) {
75 first
= hashmap_get(c
->by_key
, i
->key
);
76 LIST_REMOVE(by_key
, first
, i
);
79 assert_se(hashmap_replace(c
->by_key
, first
->key
, first
) >= 0);
81 hashmap_remove(c
->by_key
, i
->key
);
83 prioq_remove(c
->by_expiry
, i
, &i
->prioq_idx
);
85 dns_cache_item_free(i
);
88 void dns_cache_flush(DnsCache
*c
) {
93 while ((i
= hashmap_first(c
->by_key
)))
94 dns_cache_item_remove_and_free(c
, i
);
96 assert(hashmap_size(c
->by_key
) == 0);
97 assert(prioq_size(c
->by_expiry
) == 0);
99 c
->by_key
= hashmap_free(c
->by_key
);
100 c
->by_expiry
= prioq_free(c
->by_expiry
);
103 static bool dns_cache_remove(DnsCache
*c
, DnsResourceKey
*key
) {
110 while ((i
= hashmap_get(c
->by_key
, key
))) {
111 dns_cache_item_remove_and_free(c
, i
);
118 static void dns_cache_make_space(DnsCache
*c
, unsigned add
) {
124 /* Makes space for n new entries. Note that we actually allow
125 * the cache to grow beyond CACHE_MAX, but only when we shall
126 * add more RRs to the cache than CACHE_MAX at once. In that
127 * case the cache will be emptied completely otherwise. */
130 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
133 if (prioq_size(c
->by_expiry
) <= 0)
136 if (prioq_size(c
->by_expiry
) + add
< CACHE_MAX
)
139 i
= prioq_peek(c
->by_expiry
);
142 /* Take an extra reference to the key so that it
143 * doesn't go away in the middle of the remove call */
144 key
= dns_resource_key_ref(i
->key
);
145 dns_cache_remove(c
, key
);
149 void dns_cache_prune(DnsCache
*c
) {
154 /* Remove all entries that are past their TTL */
157 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
160 i
= prioq_peek(c
->by_expiry
);
165 t
= now(clock_boottime_or_monotonic());
170 /* Take an extra reference to the key so that it
171 * doesn't go away in the middle of the remove call */
172 key
= dns_resource_key_ref(i
->key
);
173 dns_cache_remove(c
, key
);
177 static int dns_cache_item_prioq_compare_func(const void *a
, const void *b
) {
178 const DnsCacheItem
*x
= a
, *y
= b
;
180 if (x
->until
< y
->until
)
182 if (x
->until
> y
->until
)
187 static int dns_cache_init(DnsCache
*c
) {
192 r
= prioq_ensure_allocated(&c
->by_expiry
, dns_cache_item_prioq_compare_func
);
196 r
= hashmap_ensure_allocated(&c
->by_key
, &dns_resource_key_hash_ops
);
203 static int dns_cache_link_item(DnsCache
*c
, DnsCacheItem
*i
) {
210 r
= prioq_put(c
->by_expiry
, i
, &i
->prioq_idx
);
214 first
= hashmap_get(c
->by_key
, i
->key
);
216 LIST_PREPEND(by_key
, first
, i
);
217 assert_se(hashmap_replace(c
->by_key
, first
->key
, first
) >= 0);
219 r
= hashmap_put(c
->by_key
, i
->key
, i
);
221 prioq_remove(c
->by_expiry
, i
, &i
->prioq_idx
);
229 static DnsCacheItem
* dns_cache_get(DnsCache
*c
, DnsResourceRecord
*rr
) {
235 LIST_FOREACH(by_key
, i
, hashmap_get(c
->by_key
, rr
->key
))
236 if (i
->rr
&& dns_resource_record_equal(i
->rr
, rr
) > 0)
242 static void dns_cache_item_update_positive(DnsCache
*c
, DnsCacheItem
*i
, DnsResourceRecord
*rr
, bool authenticated
, usec_t timestamp
) {
247 i
->type
= DNS_CACHE_POSITIVE
;
250 /* We are the first item in the list, we need to
251 * update the key used in the hashmap */
253 assert_se(hashmap_replace(c
->by_key
, rr
->key
, i
) >= 0);
255 dns_resource_record_ref(rr
);
256 dns_resource_record_unref(i
->rr
);
259 dns_resource_key_unref(i
->key
);
260 i
->key
= dns_resource_key_ref(rr
->key
);
262 i
->authenticated
= authenticated
;
263 i
->until
= timestamp
+ MIN(rr
->ttl
* USEC_PER_SEC
, CACHE_TTL_MAX_USEC
);
265 prioq_reshuffle(c
->by_expiry
, i
, &i
->prioq_idx
);
268 static int dns_cache_put_positive(
270 DnsResourceRecord
*rr
,
274 const union in_addr_union
*owner_address
) {
276 _cleanup_(dns_cache_item_freep
) DnsCacheItem
*i
= NULL
;
277 _cleanup_free_
char *key_str
= NULL
;
278 DnsCacheItem
*existing
;
283 assert(owner_address
);
285 /* New TTL is 0? Delete the entry... */
287 k
= dns_cache_remove(c
, rr
->key
);
289 if (log_get_max_level() >= LOG_DEBUG
) {
290 r
= dns_resource_key_to_string(rr
->key
, &key_str
);
295 log_debug("Removed zero TTL entry from cache: %s", key_str
);
297 log_debug("Not caching zero TTL cache entry: %s", key_str
);
303 if (rr
->key
->class == DNS_CLASS_ANY
)
305 if (dns_type_is_pseudo(rr
->key
->type
))
308 /* Entry exists already? Update TTL and timestamp */
309 existing
= dns_cache_get(c
, rr
);
311 dns_cache_item_update_positive(c
, existing
, rr
, authenticated
, timestamp
);
315 /* Otherwise, add the new RR */
316 r
= dns_cache_init(c
);
320 dns_cache_make_space(c
, 1);
322 i
= new0(DnsCacheItem
, 1);
326 i
->type
= DNS_CACHE_POSITIVE
;
327 i
->key
= dns_resource_key_ref(rr
->key
);
328 i
->rr
= dns_resource_record_ref(rr
);
329 i
->until
= timestamp
+ MIN(i
->rr
->ttl
* USEC_PER_SEC
, CACHE_TTL_MAX_USEC
);
330 i
->prioq_idx
= PRIOQ_IDX_NULL
;
331 i
->owner_family
= owner_family
;
332 i
->owner_address
= *owner_address
;
333 i
->authenticated
= authenticated
;
335 r
= dns_cache_link_item(c
, i
);
339 if (log_get_max_level() >= LOG_DEBUG
) {
340 r
= dns_resource_key_to_string(i
->key
, &key_str
);
344 log_debug("Added cache entry for %s", key_str
);
351 static int dns_cache_put_negative(
359 const union in_addr_union
*owner_address
) {
361 _cleanup_(dns_cache_item_freep
) DnsCacheItem
*i
= NULL
;
362 _cleanup_free_
char *key_str
= NULL
;
367 assert(owner_address
);
369 dns_cache_remove(c
, key
);
371 if (key
->class == DNS_CLASS_ANY
)
373 if (dns_type_is_pseudo(key
->type
))
374 /* ANY is particularly important to filter out as we
375 * use this as a pseudo-type for NXDOMAIN entries */
378 if (log_get_max_level() >= LOG_DEBUG
) {
379 r
= dns_resource_key_to_string(key
, &key_str
);
383 log_debug("Not caching negative entry with zero SOA TTL: %s", key_str
);
389 if (!IN_SET(rcode
, DNS_RCODE_SUCCESS
, DNS_RCODE_NXDOMAIN
))
392 r
= dns_cache_init(c
);
396 dns_cache_make_space(c
, 1);
398 i
= new0(DnsCacheItem
, 1);
402 i
->type
= rcode
== DNS_RCODE_SUCCESS
? DNS_CACHE_NODATA
: DNS_CACHE_NXDOMAIN
;
403 i
->until
= timestamp
+ MIN(soa_ttl
* USEC_PER_SEC
, CACHE_TTL_MAX_USEC
);
404 i
->prioq_idx
= PRIOQ_IDX_NULL
;
405 i
->owner_family
= owner_family
;
406 i
->owner_address
= *owner_address
;
407 i
->authenticated
= authenticated
;
409 if (i
->type
== DNS_CACHE_NXDOMAIN
) {
410 /* NXDOMAIN entries should apply equally to all types, so we use ANY as
411 * a pseudo type for this purpose here. */
412 i
->key
= dns_resource_key_new(key
->class, DNS_TYPE_ANY
, DNS_RESOURCE_KEY_NAME(key
));
416 i
->key
= dns_resource_key_ref(key
);
418 r
= dns_cache_link_item(c
, i
);
422 if (log_get_max_level() >= LOG_DEBUG
) {
423 r
= dns_resource_key_to_string(i
->key
, &key_str
);
427 log_debug("Added %s cache entry for %s", i
->type
== DNS_CACHE_NODATA
? "NODATA" : "NXDOMAIN", key_str
);
443 const union in_addr_union
*owner_address
) {
445 DnsResourceRecord
*soa
= NULL
, *rr
;
446 unsigned cache_keys
, i
;
452 /* First, if we were passed a key, delete all matching old RRs,
453 * so that we only keep complete by_key in place. */
454 dns_cache_remove(c
, key
);
458 if (log_get_max_level() >= LOG_DEBUG
) {
459 _cleanup_free_
char *key_str
= NULL
;
461 r
= dns_resource_key_to_string(key
, &key_str
);
465 log_debug("Not caching negative entry without a SOA record: %s", key_str
);
471 DNS_ANSWER_FOREACH(rr
, answer
)
472 if (rr
->key
->cache_flush
)
473 dns_cache_remove(c
, rr
->key
);
475 /* We only care for positive replies and NXDOMAINs, on all
476 * other replies we will simply flush the respective entries,
479 if (!IN_SET(rcode
, DNS_RCODE_SUCCESS
, DNS_RCODE_NXDOMAIN
))
482 cache_keys
= answer
->n_rrs
;
487 /* Make some space for our new entries */
488 dns_cache_make_space(c
, cache_keys
);
491 timestamp
= now(clock_boottime_or_monotonic());
493 /* Second, add in positive entries for all contained RRs */
494 for (i
= 0; i
< MIN(max_rrs
, answer
->n_rrs
); i
++) {
495 rr
= answer
->items
[i
].rr
;
497 r
= dns_cache_put_positive(c
, rr
, authenticated
, timestamp
, owner_family
, owner_address
);
505 /* Third, add in negative entries if the key has no RR */
506 r
= dns_answer_match_key(answer
, key
);
512 /* But not if it has a matching CNAME/DNAME (the negative
513 * caching will be done on the canonical name, not on the
515 r
= dns_answer_find_cname_or_dname(answer
, key
, NULL
);
521 /* See https://tools.ietf.org/html/rfc2308, which say that a
522 * matching SOA record in the packet is used to to enable
523 * negative caching. */
525 r
= dns_answer_find_soa(answer
, key
, &soa
);
531 r
= dns_cache_put_negative(c
, key
, rcode
, authenticated
, timestamp
, MIN(soa
->soa
.minimum
, soa
->ttl
), owner_family
, owner_address
);
538 /* Adding all RRs failed. Let's clean up what we already
539 * added, just in case */
542 dns_cache_remove(c
, key
);
544 for (i
= 0; i
< answer
->n_rrs
; i
++)
545 dns_cache_remove(c
, answer
->items
[i
].rr
->key
);
550 static DnsCacheItem
*dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache
*c
, DnsResourceKey
*k
) {
558 /* If we hit some OOM error, or suchlike, we don't care too
559 * much, after all this is just a cache */
561 i
= hashmap_get(c
->by_key
, k
);
565 n
= DNS_RESOURCE_KEY_NAME(k
);
567 /* Check if we have an NXDOMAIN cache item for the name, notice that we use
568 * the pseudo-type ANY for NXDOMAIN cache items. */
569 i
= hashmap_get(c
->by_key
, &DNS_RESOURCE_KEY_CONST(k
->class, DNS_TYPE_ANY
, n
));
570 if (i
&& i
->type
== DNS_CACHE_NXDOMAIN
)
573 /* The following record types should never be redirected. See
574 * <https://tools.ietf.org/html/rfc4035#section-2.5>. */
575 if (!IN_SET(k
->type
, DNS_TYPE_CNAME
, DNS_TYPE_DNAME
,
576 DNS_TYPE_NSEC3
, DNS_TYPE_NSEC
, DNS_TYPE_RRSIG
,
577 DNS_TYPE_NXT
, DNS_TYPE_SIG
, DNS_TYPE_KEY
)) {
578 /* Check if we have a CNAME record instead */
579 i
= hashmap_get(c
->by_key
, &DNS_RESOURCE_KEY_CONST(k
->class, DNS_TYPE_CNAME
, n
));
583 /* OK, let's look for cached DNAME records. */
585 char label
[DNS_LABEL_MAX
];
590 i
= hashmap_get(c
->by_key
, &DNS_RESOURCE_KEY_CONST(k
->class, DNS_TYPE_DNAME
, n
));
594 /* Jump one label ahead */
595 r
= dns_label_unescape(&n
, label
, sizeof(label
));
601 if (k
-> type
!= DNS_TYPE_NSEC
) {
602 /* Check if we have an NSEC record instead for the name. */
603 i
= hashmap_get(c
->by_key
, &DNS_RESOURCE_KEY_CONST(k
->class, DNS_TYPE_NSEC
, n
));
611 int dns_cache_lookup(DnsCache
*c
, DnsResourceKey
*key
, int *rcode
, DnsAnswer
**ret
, bool *authenticated
) {
612 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
;
615 bool nxdomain
= false;
616 _cleanup_free_
char *key_str
= NULL
;
617 DnsCacheItem
*j
, *first
, *nsec
= NULL
;
618 bool have_authenticated
= false, have_non_authenticated
= false;
624 assert(authenticated
);
626 if (key
->type
== DNS_TYPE_ANY
||
627 key
->class == DNS_CLASS_ANY
) {
629 /* If we have ANY lookups we don't use the cache, so
630 * that the caller refreshes via the network. */
632 if (log_get_max_level() >= LOG_DEBUG
) {
633 r
= dns_resource_key_to_string(key
, &key_str
);
637 log_debug("Ignoring cache for ANY lookup: %s", key_str
);
641 *rcode
= DNS_RCODE_SUCCESS
;
645 first
= dns_cache_get_by_key_follow_cname_dname_nsec(c
, key
);
647 /* If one question cannot be answered we need to refresh */
649 if (log_get_max_level() >= LOG_DEBUG
) {
650 r
= dns_resource_key_to_string(key
, &key_str
);
654 log_debug("Cache miss for %s", key_str
);
658 *rcode
= DNS_RCODE_SUCCESS
;
662 LIST_FOREACH(by_key
, j
, first
) {
664 if (j
->rr
->key
->type
== DNS_TYPE_NSEC
)
668 } else if (j
->type
== DNS_CACHE_NXDOMAIN
)
671 if (j
->authenticated
)
672 have_authenticated
= true;
674 have_non_authenticated
= true;
677 if (nsec
&& key
->type
!= DNS_TYPE_NSEC
) {
678 if (log_get_max_level() >= LOG_DEBUG
) {
679 r
= dns_resource_key_to_string(key
, &key_str
);
683 log_debug("NSEC NODATA cache hit for %s", key_str
);
686 /* We only found an NSEC record that matches our name.
687 * If it says the type doesn't exist report
688 * NODATA. Otherwise report a cache miss. */
691 *rcode
= DNS_RCODE_SUCCESS
;
692 *authenticated
= nsec
->authenticated
;
694 return !bitmap_isset(nsec
->rr
->nsec
.types
, key
->type
) &&
695 !bitmap_isset(nsec
->rr
->nsec
.types
, DNS_TYPE_CNAME
) &&
696 !bitmap_isset(nsec
->rr
->nsec
.types
, DNS_TYPE_DNAME
);
699 if (log_get_max_level() >= LOG_DEBUG
) {
700 r
= dns_resource_key_to_string(key
, &key_str
);
704 log_debug("%s cache hit for %s",
706 nxdomain
? "NXDOMAIN" : "NODATA",
712 *rcode
= nxdomain
? DNS_RCODE_NXDOMAIN
: DNS_RCODE_SUCCESS
;
713 *authenticated
= have_authenticated
&& !have_non_authenticated
;
717 answer
= dns_answer_new(n
);
721 LIST_FOREACH(by_key
, j
, first
) {
725 r
= dns_answer_add(answer
, j
->rr
, 0);
731 *rcode
= DNS_RCODE_SUCCESS
;
732 *authenticated
= have_authenticated
&& !have_non_authenticated
;
738 int dns_cache_check_conflicts(DnsCache
*cache
, DnsResourceRecord
*rr
, int owner_family
, const union in_addr_union
*owner_address
) {
739 DnsCacheItem
*i
, *first
;
740 bool same_owner
= true;
745 dns_cache_prune(cache
);
747 /* See if there's a cache entry for the same key. If there
748 * isn't there's no conflict */
749 first
= hashmap_get(cache
->by_key
, rr
->key
);
753 /* See if the RR key is owned by the same owner, if so, there
754 * isn't a conflict either */
755 LIST_FOREACH(by_key
, i
, first
) {
756 if (i
->owner_family
!= owner_family
||
757 !in_addr_equal(owner_family
, &i
->owner_address
, owner_address
)) {
765 /* See if there's the exact same RR in the cache. If yes, then
766 * there's no conflict. */
767 if (dns_cache_get(cache
, rr
))
770 /* There's a conflict */
774 int dns_cache_export_shared_to_packet(DnsCache
*cache
, DnsPacket
*p
) {
775 unsigned ancount
= 0;
783 HASHMAP_FOREACH(i
, cache
->by_key
, iterator
) {
786 LIST_FOREACH(by_key
, j
, i
) {
790 if (!dns_key_is_shared(j
->rr
->key
))
793 r
= dns_packet_append_rr(p
, j
->rr
, NULL
, NULL
);
794 if (r
== -EMSGSIZE
&& p
->protocol
== DNS_PROTOCOL_MDNS
) {
795 /* For mDNS, if we're unable to stuff all known answers into the given packet,
796 * allocate a new one, push the RR into that one and link it to the current one.
799 DNS_PACKET_HEADER(p
)->ancount
= htobe16(ancount
);
802 r
= dns_packet_new_query(&p
->more
, p
->protocol
, 0, true);
806 /* continue with new packet */
808 r
= dns_packet_append_rr(p
, j
->rr
, NULL
, NULL
);
818 DNS_PACKET_HEADER(p
)->ancount
= htobe16(ancount
);
823 void dns_cache_dump(DnsCache
*cache
, FILE *f
) {
834 HASHMAP_FOREACH(i
, cache
->by_key
, iterator
) {
837 LIST_FOREACH(by_key
, j
, i
) {
838 _cleanup_free_
char *t
= NULL
;
843 r
= dns_resource_record_to_string(j
->rr
, &t
);
852 r
= dns_resource_key_to_string(j
->key
, &t
);
860 fputs(j
->type
== DNS_CACHE_NODATA
? "NODATA" : "NXDOMAIN", f
);
867 bool dns_cache_is_empty(DnsCache
*cache
) {
871 return hashmap_isempty(cache
->by_key
);