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 "resolved-dns-cache.h"
23 #include "resolved-dns-packet.h"
25 /* Never cache more than 1K entries */
26 #define CACHE_MAX 1024
28 /* We never keep any item longer than 10min in our cache */
29 #define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE)
31 typedef enum DnsCacheItemType DnsCacheItemType;
32 typedef struct DnsCacheItem DnsCacheItem;
34 enum DnsCacheItemType {
42 DnsResourceRecord *rr;
44 DnsCacheItemType type;
47 union in_addr_union owner_address;
48 LIST_FIELDS(DnsCacheItem, by_key);
51 static void dns_cache_item_free(DnsCacheItem *i) {
55 dns_resource_record_unref(i->rr);
56 dns_resource_key_unref(i->key);
60 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free);
62 static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) {
70 first = hashmap_get(c->by_key, i->key);
71 LIST_REMOVE(by_key, first, i);
74 assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
76 hashmap_remove(c->by_key, i->key);
78 prioq_remove(c->by_expiry, i, &i->prioq_idx);
80 dns_cache_item_free(i);
83 void dns_cache_flush(DnsCache *c) {
88 while ((i = hashmap_first(c->by_key)))
89 dns_cache_item_remove_and_free(c, i);
91 assert(hashmap_size(c->by_key) == 0);
92 assert(prioq_size(c->by_expiry) == 0);
94 hashmap_free(c->by_key);
97 prioq_free(c->by_expiry);
101 static void dns_cache_remove(DnsCache *c, DnsResourceKey *key) {
107 while ((i = hashmap_get(c->by_key, key)))
108 dns_cache_item_remove_and_free(c, i);
111 static void dns_cache_make_space(DnsCache *c, unsigned add) {
117 /* Makes space for n new entries. Note that we actually allow
118 * the cache to grow beyond CACHE_MAX, but only when we shall
119 * add more RRs to the cache than CACHE_MAX at once. In that
120 * case the cache will be emptied completely otherwise. */
123 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
126 if (prioq_size(c->by_expiry) <= 0)
129 if (prioq_size(c->by_expiry) + add < CACHE_MAX)
132 i = prioq_peek(c->by_expiry);
135 /* Take an extra reference to the key so that it
136 * doesn't go away in the middle of the remove call */
137 key = dns_resource_key_ref(i->key);
138 dns_cache_remove(c, key);
142 void dns_cache_prune(DnsCache *c) {
147 /* Remove all entries that are past their TTL */
150 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
153 i = prioq_peek(c->by_expiry);
158 t = now(CLOCK_BOOTTIME);
163 /* Take an extra reference to the key so that it
164 * doesn't go away in the middle of the remove call */
165 key = dns_resource_key_ref(i->key);
166 dns_cache_remove(c, key);
170 static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
171 const DnsCacheItem *x = a, *y = b;
173 if (x->until < y->until)
175 if (x->until > y->until)
180 static int dns_cache_init(DnsCache *c) {
185 r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func);
189 r = hashmap_ensure_allocated(&c->by_key, dns_resource_key_hash_func, dns_resource_key_compare_func);
196 static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) {
203 r = prioq_put(c->by_expiry, i, &i->prioq_idx);
207 first = hashmap_get(c->by_key, i->key);
209 LIST_PREPEND(by_key, first, i);
210 assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
212 r = hashmap_put(c->by_key, i->key, i);
214 prioq_remove(c->by_expiry, i, &i->prioq_idx);
222 static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
228 LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key))
229 if (i->rr && dns_resource_record_equal(i->rr, rr) > 0)
235 static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, usec_t timestamp) {
240 i->type = DNS_CACHE_POSITIVE;
242 if (!i->by_key_prev) {
243 /* We are the first item in the list, we need to
244 * update the key used in the hashmap */
246 assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0);
249 dns_resource_record_ref(rr);
250 dns_resource_record_unref(i->rr);
253 dns_resource_key_unref(i->key);
254 i->key = dns_resource_key_ref(rr->key);
256 i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
258 prioq_reshuffle(c->by_expiry, i, &i->prioq_idx);
261 static int dns_cache_put_positive(
263 DnsResourceRecord *rr,
266 const union in_addr_union *owner_address) {
268 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
269 DnsCacheItem *existing;
274 assert(owner_address);
276 /* New TTL is 0? Delete the entry... */
278 dns_cache_remove(c, rr->key);
282 if (rr->key->class == DNS_CLASS_ANY)
284 if (rr->key->type == DNS_TYPE_ANY)
287 /* Entry exists already? Update TTL and timestamp */
288 existing = dns_cache_get(c, rr);
290 dns_cache_item_update_positive(c, existing, rr, timestamp);
294 /* Otherwise, add the new RR */
295 r = dns_cache_init(c);
299 dns_cache_make_space(c, 1);
301 i = new0(DnsCacheItem, 1);
305 i->type = DNS_CACHE_POSITIVE;
306 i->key = dns_resource_key_ref(rr->key);
307 i->rr = dns_resource_record_ref(rr);
308 i->until = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
309 i->prioq_idx = PRIOQ_IDX_NULL;
310 i->owner_family = owner_family;
311 i->owner_address = *owner_address;
313 r = dns_cache_link_item(c, i);
321 static int dns_cache_put_negative(
328 const union in_addr_union *owner_address) {
330 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
335 assert(owner_address);
337 dns_cache_remove(c, key);
339 if (key->class == DNS_CLASS_ANY)
341 if (key->type == DNS_TYPE_ANY)
346 if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
349 r = dns_cache_init(c);
353 dns_cache_make_space(c, 1);
355 i = new0(DnsCacheItem, 1);
359 i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN;
360 i->key = dns_resource_key_ref(key);
361 i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
362 i->prioq_idx = PRIOQ_IDX_NULL;
363 i->owner_family = owner_family;
364 i->owner_address = *owner_address;
366 r = dns_cache_link_item(c, i);
382 const union in_addr_union *owner_address) {
390 /* First, delete all matching old RRs, so that we only keep
391 * complete by_key in place. */
392 for (i = 0; i < q->n_keys; i++)
393 dns_cache_remove(c, q->keys[i]);
398 for (i = 0; i < answer->n_rrs; i++)
399 dns_cache_remove(c, answer->rrs[i]->key);
401 /* We only care for positive replies and NXDOMAINs, on all
402 * other replies we will simply flush the respective entries,
405 if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
408 /* Make some space for our new entries */
409 dns_cache_make_space(c, answer->n_rrs + q->n_keys);
412 timestamp = now(CLOCK_BOOTTIME);
414 /* Second, add in positive entries for all contained RRs */
415 for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) {
416 r = dns_cache_put_positive(c, answer->rrs[i], timestamp, owner_family, owner_address);
421 /* Third, add in negative entries for all keys with no RR */
422 for (i = 0; i < q->n_keys; i++) {
423 DnsResourceRecord *soa = NULL;
425 r = dns_answer_contains(answer, q->keys[i]);
431 r = dns_answer_find_soa(answer, q->keys[i], &soa);
437 r = dns_cache_put_negative(c, q->keys[i], rcode, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
445 /* Adding all RRs failed. Let's clean up what we already
446 * added, just in case */
448 for (i = 0; i < q->n_keys; i++)
449 dns_cache_remove(c, q->keys[i]);
450 for (i = 0; i < answer->n_rrs; i++)
451 dns_cache_remove(c, answer->rrs[i]->key);
456 int dns_cache_lookup(DnsCache *c, DnsQuestion *q, int *rcode, DnsAnswer **ret) {
457 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
460 bool nxdomain = false;
467 if (q->n_keys <= 0) {
473 for (i = 0; i < q->n_keys; i++) {
476 if (q->keys[i]->type == DNS_TYPE_ANY ||
477 q->keys[i]->class == DNS_CLASS_ANY) {
478 /* If we have ANY lookups we simply refresh */
484 j = hashmap_get(c->by_key, q->keys[i]);
486 /* If one question cannot be answered we need to refresh */
492 LIST_FOREACH(by_key, j, j) {
495 else if (j->type == DNS_CACHE_NXDOMAIN)
502 *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
506 answer = dns_answer_new(n);
510 for (i = 0; i < q->n_keys; i++) {
513 j = hashmap_get(c->by_key, q->keys[i]);
514 LIST_FOREACH(by_key, j, j) {
516 r = dns_answer_add(answer, j->rr);
524 *rcode = DNS_RCODE_SUCCESS;
530 int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) {
531 DnsCacheItem *i, *first;
532 bool same_owner = true;
537 dns_cache_prune(cache);
539 /* See if there's a cache entry for the same key. If there
540 * isn't there's no conflict */
541 first = hashmap_get(cache->by_key, rr->key);
545 /* See if the RR key is owned by the same owner, if so, there
546 * isn't a conflict either */
547 LIST_FOREACH(by_key, i, first) {
548 if (i->owner_family != owner_family ||
549 !in_addr_equal(owner_family, &i->owner_address, owner_address)) {
557 /* See if there's the exact same RR in the cache. If yes, then
558 * there's no conflict. */
559 if (dns_cache_get(cache, rr))
562 /* There's a conflict */