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 "resolved-dns-cache.h"
24 #include "resolved-dns-packet.h"
26 /* Never cache more than 1K entries */
27 #define CACHE_MAX 1024
29 /* We never keep any item longer than 10min in our cache */
30 #define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE)
32 typedef enum DnsCacheItemType DnsCacheItemType
;
33 typedef struct DnsCacheItem DnsCacheItem
;
35 enum DnsCacheItemType
{
43 DnsResourceRecord
*rr
;
45 DnsCacheItemType type
;
48 union in_addr_union owner_address
;
49 LIST_FIELDS(DnsCacheItem
, by_key
);
52 static void dns_cache_item_free(DnsCacheItem
*i
) {
56 dns_resource_record_unref(i
->rr
);
57 dns_resource_key_unref(i
->key
);
61 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem
*, dns_cache_item_free
);
63 static void dns_cache_item_remove_and_free(DnsCache
*c
, DnsCacheItem
*i
) {
71 first
= hashmap_get(c
->by_key
, i
->key
);
72 LIST_REMOVE(by_key
, first
, i
);
75 assert_se(hashmap_replace(c
->by_key
, first
->key
, first
) >= 0);
77 hashmap_remove(c
->by_key
, i
->key
);
79 prioq_remove(c
->by_expiry
, i
, &i
->prioq_idx
);
81 dns_cache_item_free(i
);
84 void dns_cache_flush(DnsCache
*c
) {
89 while ((i
= hashmap_first(c
->by_key
)))
90 dns_cache_item_remove_and_free(c
, i
);
92 assert(hashmap_size(c
->by_key
) == 0);
93 assert(prioq_size(c
->by_expiry
) == 0);
95 c
->by_key
= hashmap_free(c
->by_key
);
96 c
->by_expiry
= prioq_free(c
->by_expiry
);
99 static bool dns_cache_remove(DnsCache
*c
, DnsResourceKey
*key
) {
106 while ((i
= hashmap_get(c
->by_key
, key
))) {
107 dns_cache_item_remove_and_free(c
, i
);
114 static void dns_cache_make_space(DnsCache
*c
, unsigned add
) {
120 /* Makes space for n new entries. Note that we actually allow
121 * the cache to grow beyond CACHE_MAX, but only when we shall
122 * add more RRs to the cache than CACHE_MAX at once. In that
123 * case the cache will be emptied completely otherwise. */
126 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
129 if (prioq_size(c
->by_expiry
) <= 0)
132 if (prioq_size(c
->by_expiry
) + add
< CACHE_MAX
)
135 i
= prioq_peek(c
->by_expiry
);
138 /* Take an extra reference to the key so that it
139 * doesn't go away in the middle of the remove call */
140 key
= dns_resource_key_ref(i
->key
);
141 dns_cache_remove(c
, key
);
145 void dns_cache_prune(DnsCache
*c
) {
150 /* Remove all entries that are past their TTL */
153 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
156 i
= prioq_peek(c
->by_expiry
);
161 t
= now(clock_boottime_or_monotonic());
166 /* Take an extra reference to the key so that it
167 * doesn't go away in the middle of the remove call */
168 key
= dns_resource_key_ref(i
->key
);
169 dns_cache_remove(c
, key
);
173 static int dns_cache_item_prioq_compare_func(const void *a
, const void *b
) {
174 const DnsCacheItem
*x
= a
, *y
= b
;
176 if (x
->until
< y
->until
)
178 if (x
->until
> y
->until
)
183 static int dns_cache_init(DnsCache
*c
) {
188 r
= prioq_ensure_allocated(&c
->by_expiry
, dns_cache_item_prioq_compare_func
);
192 r
= hashmap_ensure_allocated(&c
->by_key
, &dns_resource_key_hash_ops
);
199 static int dns_cache_link_item(DnsCache
*c
, DnsCacheItem
*i
) {
206 r
= prioq_put(c
->by_expiry
, i
, &i
->prioq_idx
);
210 first
= hashmap_get(c
->by_key
, i
->key
);
212 LIST_PREPEND(by_key
, first
, i
);
213 assert_se(hashmap_replace(c
->by_key
, first
->key
, first
) >= 0);
215 r
= hashmap_put(c
->by_key
, i
->key
, i
);
217 prioq_remove(c
->by_expiry
, i
, &i
->prioq_idx
);
225 static DnsCacheItem
* dns_cache_get(DnsCache
*c
, DnsResourceRecord
*rr
) {
231 LIST_FOREACH(by_key
, i
, hashmap_get(c
->by_key
, rr
->key
))
232 if (i
->rr
&& dns_resource_record_equal(i
->rr
, rr
) > 0)
238 static void dns_cache_item_update_positive(DnsCache
*c
, DnsCacheItem
*i
, DnsResourceRecord
*rr
, usec_t timestamp
) {
243 i
->type
= DNS_CACHE_POSITIVE
;
246 /* We are the first item in the list, we need to
247 * update the key used in the hashmap */
249 assert_se(hashmap_replace(c
->by_key
, rr
->key
, i
) >= 0);
251 dns_resource_record_ref(rr
);
252 dns_resource_record_unref(i
->rr
);
255 dns_resource_key_unref(i
->key
);
256 i
->key
= dns_resource_key_ref(rr
->key
);
258 i
->until
= timestamp
+ MIN(rr
->ttl
* USEC_PER_SEC
, CACHE_TTL_MAX_USEC
);
260 prioq_reshuffle(c
->by_expiry
, i
, &i
->prioq_idx
);
263 static int dns_cache_put_positive(
265 DnsResourceRecord
*rr
,
268 const union in_addr_union
*owner_address
) {
270 _cleanup_(dns_cache_item_freep
) DnsCacheItem
*i
= NULL
;
271 _cleanup_free_
char *key_str
= NULL
;
272 DnsCacheItem
*existing
;
277 assert(owner_address
);
279 /* New TTL is 0? Delete the entry... */
281 r
= dns_resource_key_to_string(rr
->key
, &key_str
);
285 if (dns_cache_remove(c
, rr
->key
))
286 log_debug("Removed zero TTL entry from cache: %s", key_str
);
288 log_debug("Not caching zero TTL cache entry: %s", key_str
);
293 if (rr
->key
->class == DNS_CLASS_ANY
)
295 if (rr
->key
->type
== DNS_TYPE_ANY
)
298 /* Entry exists already? Update TTL and timestamp */
299 existing
= dns_cache_get(c
, rr
);
301 dns_cache_item_update_positive(c
, existing
, rr
, timestamp
);
305 /* Otherwise, add the new RR */
306 r
= dns_cache_init(c
);
310 dns_cache_make_space(c
, 1);
312 i
= new0(DnsCacheItem
, 1);
316 i
->type
= DNS_CACHE_POSITIVE
;
317 i
->key
= dns_resource_key_ref(rr
->key
);
318 i
->rr
= dns_resource_record_ref(rr
);
319 i
->until
= timestamp
+ MIN(i
->rr
->ttl
* USEC_PER_SEC
, CACHE_TTL_MAX_USEC
);
320 i
->prioq_idx
= PRIOQ_IDX_NULL
;
321 i
->owner_family
= owner_family
;
322 i
->owner_address
= *owner_address
;
324 r
= dns_cache_link_item(c
, i
);
328 r
= dns_resource_key_to_string(i
->key
, &key_str
);
332 log_debug("Added cache entry for %s", key_str
);
338 static int dns_cache_put_negative(
345 const union in_addr_union
*owner_address
) {
347 _cleanup_(dns_cache_item_freep
) DnsCacheItem
*i
= NULL
;
348 _cleanup_free_
char *key_str
= NULL
;
353 assert(owner_address
);
355 dns_cache_remove(c
, key
);
357 if (key
->class == DNS_CLASS_ANY
)
359 if (key
->type
== DNS_TYPE_ANY
)
362 r
= dns_resource_key_to_string(key
, &key_str
);
366 log_debug("Not caching negative entry with zero SOA TTL: %s", key_str
);
371 if (!IN_SET(rcode
, DNS_RCODE_SUCCESS
, DNS_RCODE_NXDOMAIN
))
374 r
= dns_cache_init(c
);
378 dns_cache_make_space(c
, 1);
380 i
= new0(DnsCacheItem
, 1);
384 i
->type
= rcode
== DNS_RCODE_SUCCESS
? DNS_CACHE_NODATA
: DNS_CACHE_NXDOMAIN
;
385 i
->key
= dns_resource_key_ref(key
);
386 i
->until
= timestamp
+ MIN(soa_ttl
* USEC_PER_SEC
, CACHE_TTL_MAX_USEC
);
387 i
->prioq_idx
= PRIOQ_IDX_NULL
;
388 i
->owner_family
= owner_family
;
389 i
->owner_address
= *owner_address
;
391 r
= dns_cache_link_item(c
, i
);
395 r
= dns_resource_key_to_string(i
->key
, &key_str
);
399 log_debug("Added %s cache entry for %s", i
->type
== DNS_CACHE_NODATA
? "NODATA" : "NXDOMAIN", key_str
);
413 const union in_addr_union
*owner_address
) {
415 DnsResourceRecord
*soa
= NULL
;
416 unsigned cache_keys
, i
;
422 /* First, if we were passed a key, delete all matching old RRs,
423 * so that we only keep complete by_key in place. */
424 dns_cache_remove(c
, key
);
430 for (i
= 0; i
< answer
->n_rrs
; i
++)
431 dns_cache_remove(c
, answer
->items
[i
].rr
->key
);
433 /* We only care for positive replies and NXDOMAINs, on all
434 * other replies we will simply flush the respective entries,
437 if (!IN_SET(rcode
, DNS_RCODE_SUCCESS
, DNS_RCODE_NXDOMAIN
))
440 cache_keys
= answer
->n_rrs
;
445 /* Make some space for our new entries */
446 dns_cache_make_space(c
, cache_keys
);
449 timestamp
= now(clock_boottime_or_monotonic());
451 /* Second, add in positive entries for all contained RRs */
452 for (i
= 0; i
< MIN(max_rrs
, answer
->n_rrs
); i
++) {
453 r
= dns_cache_put_positive(c
, answer
->items
[i
].rr
, timestamp
, owner_family
, owner_address
);
461 /* Third, add in negative entries if the key has no RR */
462 r
= dns_answer_contains(answer
, key
);
468 /* See https://tools.ietf.org/html/rfc2308, which
469 * say that a matching SOA record in the packet
470 * is used to to enable negative caching. */
472 r
= dns_answer_find_soa(answer
, key
, &soa
);
478 /* Also, if the requested key is an alias, the negative response should
479 be cached for each name in the redirect chain. Any CNAME record in
480 the response is from the redirection chain, though only the final one
481 is guaranteed to be included. This means that we cannot verify the
482 chain and that we need to cache them all as it may be incomplete. */
483 for (i
= 0; i
< answer
->n_rrs
; i
++) {
484 DnsResourceRecord
*answer_rr
= answer
->items
[i
].rr
;
486 if (answer_rr
->key
->type
== DNS_TYPE_CNAME
) {
487 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*canonical_key
= NULL
;
489 canonical_key
= dns_resource_key_new_redirect(key
, answer_rr
);
493 /* Let's not add negative cache entries for records outside the current zone. */
494 if (!dns_answer_match_soa(canonical_key
, soa
->key
))
497 r
= dns_cache_put_negative(c
, canonical_key
, rcode
, timestamp
, MIN(soa
->soa
.minimum
, soa
->ttl
), owner_family
, owner_address
);
503 r
= dns_cache_put_negative(c
, key
, rcode
, timestamp
, MIN(soa
->soa
.minimum
, soa
->ttl
), owner_family
, owner_address
);
510 /* Adding all RRs failed. Let's clean up what we already
511 * added, just in case */
514 dns_cache_remove(c
, key
);
516 for (i
= 0; i
< answer
->n_rrs
; i
++)
517 dns_cache_remove(c
, answer
->items
[i
].rr
->key
);
522 static DnsCacheItem
*dns_cache_get_by_key_follow_cname(DnsCache
*c
, DnsResourceKey
*k
) {
523 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*cname_key
= NULL
;
529 i
= hashmap_get(c
->by_key
, k
);
530 if (i
|| k
->type
== DNS_TYPE_CNAME
)
533 /* check if we have a CNAME record instead */
534 cname_key
= dns_resource_key_new_cname(k
);
538 j
= hashmap_get(c
->by_key
, cname_key
);
545 int dns_cache_lookup(DnsCache
*c
, DnsResourceKey
*key
, int *rcode
, DnsAnswer
**ret
) {
546 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
;
549 bool nxdomain
= false;
550 _cleanup_free_
char *key_str
= NULL
;
551 DnsCacheItem
*j
, *first
;
558 if (key
->type
== DNS_TYPE_ANY
||
559 key
->class == DNS_CLASS_ANY
) {
561 /* If we have ANY lookups we simply refresh */
563 r
= dns_resource_key_to_string(key
, &key_str
);
567 log_debug("Ignoring cache for ANY lookup: %s", key_str
);
570 *rcode
= DNS_RCODE_SUCCESS
;
574 first
= dns_cache_get_by_key_follow_cname(c
, key
);
576 /* If one question cannot be answered we need to refresh */
578 r
= dns_resource_key_to_string(key
, &key_str
);
582 log_debug("Cache miss for %s", key_str
);
585 *rcode
= DNS_RCODE_SUCCESS
;
589 LIST_FOREACH(by_key
, j
, first
) {
592 else if (j
->type
== DNS_CACHE_NXDOMAIN
)
596 r
= dns_resource_key_to_string(key
, &key_str
);
600 log_debug("%s cache hit for %s",
601 nxdomain
? "NXDOMAIN" :
602 n
> 0 ? "Positive" : "NODATA",
607 *rcode
= nxdomain
? DNS_RCODE_NXDOMAIN
: DNS_RCODE_SUCCESS
;
611 answer
= dns_answer_new(n
);
615 LIST_FOREACH(by_key
, j
, first
) {
619 r
= dns_answer_add(answer
, j
->rr
, 0);
625 *rcode
= DNS_RCODE_SUCCESS
;
631 int dns_cache_check_conflicts(DnsCache
*cache
, DnsResourceRecord
*rr
, int owner_family
, const union in_addr_union
*owner_address
) {
632 DnsCacheItem
*i
, *first
;
633 bool same_owner
= true;
638 dns_cache_prune(cache
);
640 /* See if there's a cache entry for the same key. If there
641 * isn't there's no conflict */
642 first
= hashmap_get(cache
->by_key
, rr
->key
);
646 /* See if the RR key is owned by the same owner, if so, there
647 * isn't a conflict either */
648 LIST_FOREACH(by_key
, i
, first
) {
649 if (i
->owner_family
!= owner_family
||
650 !in_addr_equal(owner_family
, &i
->owner_address
, owner_address
)) {
658 /* See if there's the exact same RR in the cache. If yes, then
659 * there's no conflict. */
660 if (dns_cache_get(cache
, rr
))
663 /* There's a conflict */
667 void dns_cache_dump(DnsCache
*cache
, FILE *f
) {
678 HASHMAP_FOREACH(i
, cache
->by_key
, iterator
) {
681 LIST_FOREACH(by_key
, j
, i
) {
682 _cleanup_free_
char *t
= NULL
;
687 r
= dns_resource_record_to_string(j
->rr
, &t
);
696 r
= dns_resource_key_to_string(j
->key
, &t
);
704 fputs(j
->type
== DNS_CACHE_NODATA
? "NODATA" : "NXDOMAIN", f
);
711 bool dns_cache_is_empty(DnsCache
*cache
) {
715 return hashmap_isempty(cache
->by_key
);