]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-cache.c
tree-wide: drop {} from one-line if blocks
[thirdparty/systemd.git] / src / resolve / resolved-dns-cache.c
CommitLineData
322345fd
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2014 Lennart Poettering
7
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.
12
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.
17
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/>.
20***/
21
22#include "resolved-dns-cache.h"
7e8e0422 23#include "resolved-dns-packet.h"
322345fd 24
cbd4560e 25/* Never cache more than 1K entries */
322345fd 26#define CACHE_MAX 1024
cbd4560e
LP
27
28/* We never keep any item longer than 10min in our cache */
322345fd
LP
29#define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE)
30
623a4c97
LP
31typedef enum DnsCacheItemType DnsCacheItemType;
32typedef struct DnsCacheItem DnsCacheItem;
33
34enum DnsCacheItemType {
35 DNS_CACHE_POSITIVE,
36 DNS_CACHE_NODATA,
37 DNS_CACHE_NXDOMAIN,
38};
39
40struct DnsCacheItem {
41 DnsResourceKey *key;
42 DnsResourceRecord *rr;
43 usec_t until;
44 DnsCacheItemType type;
45 unsigned prioq_idx;
a4076574
LP
46 int owner_family;
47 union in_addr_union owner_address;
623a4c97
LP
48 LIST_FIELDS(DnsCacheItem, by_key);
49};
50
322345fd
LP
51static void dns_cache_item_free(DnsCacheItem *i) {
52 if (!i)
53 return;
54
55 dns_resource_record_unref(i->rr);
7e8e0422 56 dns_resource_key_unref(i->key);
322345fd
LP
57 free(i);
58}
59
60DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free);
61
62static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) {
63 DnsCacheItem *first;
64
65 assert(c);
66
67 if (!i)
68 return;
69
7e8e0422
LP
70 first = hashmap_get(c->by_key, i->key);
71 LIST_REMOVE(by_key, first, i);
322345fd
LP
72
73 if (first)
7e8e0422 74 assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
322345fd 75 else
7e8e0422 76 hashmap_remove(c->by_key, i->key);
322345fd 77
7e8e0422 78 prioq_remove(c->by_expiry, i, &i->prioq_idx);
322345fd
LP
79
80 dns_cache_item_free(i);
81}
82
83void dns_cache_flush(DnsCache *c) {
84 DnsCacheItem *i;
85
86 assert(c);
87
7e8e0422 88 while ((i = hashmap_first(c->by_key)))
322345fd
LP
89 dns_cache_item_remove_and_free(c, i);
90
7e8e0422
LP
91 assert(hashmap_size(c->by_key) == 0);
92 assert(prioq_size(c->by_expiry) == 0);
322345fd 93
cab5b059
LP
94 c->by_key = hashmap_free(c->by_key);
95 c->by_expiry = prioq_free(c->by_expiry);
322345fd
LP
96}
97
6b34a6c9 98static bool dns_cache_remove(DnsCache *c, DnsResourceKey *key) {
322345fd 99 DnsCacheItem *i;
6b34a6c9 100 bool exist = false;
322345fd
LP
101
102 assert(c);
103 assert(key);
104
6b34a6c9 105 while ((i = hashmap_get(c->by_key, key))) {
322345fd 106 dns_cache_item_remove_and_free(c, i);
6b34a6c9
TG
107 exist = true;
108 }
109
110 return exist;
322345fd
LP
111}
112
113static void dns_cache_make_space(DnsCache *c, unsigned add) {
114 assert(c);
115
116 if (add <= 0)
117 return;
118
119 /* Makes space for n new entries. Note that we actually allow
120 * the cache to grow beyond CACHE_MAX, but only when we shall
121 * add more RRs to the cache than CACHE_MAX at once. In that
122 * case the cache will be emptied completely otherwise. */
123
124 for (;;) {
faa133f3 125 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
322345fd
LP
126 DnsCacheItem *i;
127
7e8e0422 128 if (prioq_size(c->by_expiry) <= 0)
322345fd
LP
129 break;
130
7e8e0422 131 if (prioq_size(c->by_expiry) + add < CACHE_MAX)
322345fd
LP
132 break;
133
7e8e0422 134 i = prioq_peek(c->by_expiry);
cbd4560e
LP
135 assert(i);
136
faa133f3 137 /* Take an extra reference to the key so that it
cbd4560e 138 * doesn't go away in the middle of the remove call */
7e8e0422 139 key = dns_resource_key_ref(i->key);
faa133f3 140 dns_cache_remove(c, key);
322345fd
LP
141 }
142}
143
144void dns_cache_prune(DnsCache *c) {
145 usec_t t = 0;
146
147 assert(c);
148
149 /* Remove all entries that are past their TTL */
150
151 for (;;) {
faa133f3 152 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
322345fd 153 DnsCacheItem *i;
322345fd 154
7e8e0422 155 i = prioq_peek(c->by_expiry);
322345fd
LP
156 if (!i)
157 break;
158
322345fd 159 if (t <= 0)
240b589b 160 t = now(clock_boottime_or_monotonic());
322345fd 161
7e8e0422 162 if (i->until > t)
322345fd
LP
163 break;
164
faa133f3 165 /* Take an extra reference to the key so that it
cbd4560e 166 * doesn't go away in the middle of the remove call */
7e8e0422 167 key = dns_resource_key_ref(i->key);
faa133f3 168 dns_cache_remove(c, key);
322345fd
LP
169 }
170}
171
172static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
322345fd
LP
173 const DnsCacheItem *x = a, *y = b;
174
7e8e0422 175 if (x->until < y->until)
322345fd 176 return -1;
7e8e0422 177 if (x->until > y->until)
322345fd
LP
178 return 1;
179 return 0;
180}
181
623a4c97 182static int dns_cache_init(DnsCache *c) {
7e8e0422
LP
183 int r;
184
623a4c97
LP
185 assert(c);
186
7e8e0422
LP
187 r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func);
188 if (r < 0)
189 return r;
190
d5099efc 191 r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops);
7e8e0422
LP
192 if (r < 0)
193 return r;
194
195 return r;
196}
197
198static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) {
199 DnsCacheItem *first;
200 int r;
201
322345fd
LP
202 assert(c);
203 assert(i);
322345fd 204
7e8e0422
LP
205 r = prioq_put(c->by_expiry, i, &i->prioq_idx);
206 if (r < 0)
207 return r;
322345fd 208
7e8e0422
LP
209 first = hashmap_get(c->by_key, i->key);
210 if (first) {
211 LIST_PREPEND(by_key, first, i);
212 assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
213 } else {
214 r = hashmap_put(c->by_key, i->key, i);
215 if (r < 0) {
216 prioq_remove(c->by_expiry, i, &i->prioq_idx);
217 return r;
218 }
322345fd
LP
219 }
220
7e8e0422 221 return 0;
322345fd
LP
222}
223
faa133f3
LP
224static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
225 DnsCacheItem *i;
226
227 assert(c);
228 assert(rr);
229
7e8e0422 230 LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key))
3ef77d04 231 if (i->rr && dns_resource_record_equal(i->rr, rr) > 0)
faa133f3
LP
232 return i;
233
234 return NULL;
235}
236
7e8e0422
LP
237static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, usec_t timestamp) {
238 assert(c);
239 assert(i);
240 assert(rr);
241
242 i->type = DNS_CACHE_POSITIVE;
243
ece174c5 244 if (!i->by_key_prev)
7e8e0422
LP
245 /* We are the first item in the list, we need to
246 * update the key used in the hashmap */
247
248 assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0);
7e8e0422
LP
249
250 dns_resource_record_ref(rr);
251 dns_resource_record_unref(i->rr);
252 i->rr = rr;
253
254 dns_resource_key_unref(i->key);
255 i->key = dns_resource_key_ref(rr->key);
256
257 i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
258
259 prioq_reshuffle(c->by_expiry, i, &i->prioq_idx);
260}
261
a4076574
LP
262static int dns_cache_put_positive(
263 DnsCache *c,
264 DnsResourceRecord *rr,
265 usec_t timestamp,
266 int owner_family,
267 const union in_addr_union *owner_address) {
268
322345fd 269 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
6b34a6c9 270 _cleanup_free_ char *key_str = NULL;
7e8e0422 271 DnsCacheItem *existing;
322345fd
LP
272 int r;
273
274 assert(c);
275 assert(rr);
a4076574 276 assert(owner_address);
322345fd
LP
277
278 /* New TTL is 0? Delete the entry... */
279 if (rr->ttl <= 0) {
6b34a6c9
TG
280 if (dns_cache_remove(c, rr->key)) {
281 r = dns_resource_key_to_string(rr->key, &key_str);
282 if (r < 0)
283 return r;
284
285 log_debug("Removed zero TTL entry from cache: %s", key_str);
286 }
287
322345fd
LP
288 return 0;
289 }
290
ddf16339
LP
291 if (rr->key->class == DNS_CLASS_ANY)
292 return 0;
293 if (rr->key->type == DNS_TYPE_ANY)
294 return 0;
295
322345fd
LP
296 /* Entry exists already? Update TTL and timestamp */
297 existing = dns_cache_get(c, rr);
298 if (existing) {
7e8e0422 299 dns_cache_item_update_positive(c, existing, rr, timestamp);
322345fd
LP
300 return 0;
301 }
302
303 /* Otherwise, add the new RR */
623a4c97 304 r = dns_cache_init(c);
322345fd
LP
305 if (r < 0)
306 return r;
307
7e8e0422
LP
308 dns_cache_make_space(c, 1);
309
310 i = new0(DnsCacheItem, 1);
311 if (!i)
312 return -ENOMEM;
313
314 i->type = DNS_CACHE_POSITIVE;
315 i->key = dns_resource_key_ref(rr->key);
316 i->rr = dns_resource_record_ref(rr);
317 i->until = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
318 i->prioq_idx = PRIOQ_IDX_NULL;
a4076574
LP
319 i->owner_family = owner_family;
320 i->owner_address = *owner_address;
7e8e0422
LP
321
322 r = dns_cache_link_item(c, i);
323 if (r < 0)
324 return r;
325
6b34a6c9
TG
326 r = dns_resource_key_to_string(i->key, &key_str);
327 if (r < 0)
328 return r;
329
330 log_debug("Added cache entry for %s", key_str);
331
7e8e0422
LP
332 i = NULL;
333 return 0;
334}
335
a4076574
LP
336static int dns_cache_put_negative(
337 DnsCache *c,
338 DnsResourceKey *key,
339 int rcode,
340 usec_t timestamp,
341 uint32_t soa_ttl,
342 int owner_family,
343 const union in_addr_union *owner_address) {
344
7e8e0422 345 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
6b34a6c9 346 _cleanup_free_ char *key_str = NULL;
7e8e0422
LP
347 int r;
348
349 assert(c);
350 assert(key);
a4076574 351 assert(owner_address);
7e8e0422
LP
352
353 dns_cache_remove(c, key);
354
ddf16339
LP
355 if (key->class == DNS_CLASS_ANY)
356 return 0;
357 if (key->type == DNS_TYPE_ANY)
358 return 0;
6b34a6c9
TG
359 if (soa_ttl <= 0) {
360 r = dns_resource_key_to_string(key, &key_str);
361 if (r < 0)
362 return r;
363
364 log_debug("Ignored negative cache entry with zero SOA TTL: %s", key_str);
365
95dd6257 366 return 0;
6b34a6c9 367 }
ddf16339 368
7e8e0422
LP
369 if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
370 return 0;
371
623a4c97 372 r = dns_cache_init(c);
322345fd
LP
373 if (r < 0)
374 return r;
375
376 dns_cache_make_space(c, 1);
377
378 i = new0(DnsCacheItem, 1);
379 if (!i)
380 return -ENOMEM;
381
7e8e0422
LP
382 i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN;
383 i->key = dns_resource_key_ref(key);
384 i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
385 i->prioq_idx = PRIOQ_IDX_NULL;
a4076574
LP
386 i->owner_family = owner_family;
387 i->owner_address = *owner_address;
322345fd 388
7e8e0422 389 r = dns_cache_link_item(c, i);
322345fd
LP
390 if (r < 0)
391 return r;
392
6b34a6c9
TG
393 r = dns_resource_key_to_string(i->key, &key_str);
394 if (r < 0)
395 return r;
396
397 log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str);
398
322345fd 399 i = NULL;
322345fd
LP
400 return 0;
401}
402
a4076574
LP
403int dns_cache_put(
404 DnsCache *c,
405 DnsQuestion *q,
406 int rcode,
407 DnsAnswer *answer,
408 unsigned max_rrs,
409 usec_t timestamp,
410 int owner_family,
411 const union in_addr_union *owner_address) {
412
eff91ee0 413 unsigned cache_keys, i;
322345fd
LP
414 int r;
415
416 assert(c);
322345fd 417
eff91ee0
DM
418 if (q) {
419 /* First, if we were passed a question, delete all matching old RRs,
420 * so that we only keep complete by_key in place. */
421 for (i = 0; i < q->n_keys; i++)
422 dns_cache_remove(c, q->keys[i]);
423 }
0ec7c46e
LP
424
425 if (!answer)
426 return 0;
427
faa133f3 428 for (i = 0; i < answer->n_rrs; i++)
78c6a153 429 dns_cache_remove(c, answer->items[i].rr->key);
322345fd 430
7e8e0422
LP
431 /* We only care for positive replies and NXDOMAINs, on all
432 * other replies we will simply flush the respective entries,
433 * and that's it */
434
435 if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
436 return 0;
437
eff91ee0
DM
438 cache_keys = answer->n_rrs;
439
440 if (q)
441 cache_keys += q->n_keys;
442
7e8e0422 443 /* Make some space for our new entries */
eff91ee0 444 dns_cache_make_space(c, cache_keys);
322345fd 445
7e8e0422 446 if (timestamp <= 0)
240b589b 447 timestamp = now(clock_boottime_or_monotonic());
322345fd 448
7e8e0422 449 /* Second, add in positive entries for all contained RRs */
d2f47562 450 for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) {
78c6a153 451 r = dns_cache_put_positive(c, answer->items[i].rr, timestamp, owner_family, owner_address);
7e8e0422
LP
452 if (r < 0)
453 goto fail;
454 }
455
eff91ee0
DM
456 if (!q)
457 return 0;
458
7e8e0422
LP
459 /* Third, add in negative entries for all keys with no RR */
460 for (i = 0; i < q->n_keys; i++) {
461 DnsResourceRecord *soa = NULL;
462
463 r = dns_answer_contains(answer, q->keys[i]);
464 if (r < 0)
465 goto fail;
466 if (r > 0)
467 continue;
468
0a18f3e5
LP
469 /* See https://tools.ietf.org/html/rfc2308, which
470 * say that a matching SOA record in the packet
471 * is used to to enable negative caching. */
472
7e8e0422
LP
473 r = dns_answer_find_soa(answer, q->keys[i], &soa);
474 if (r < 0)
475 goto fail;
476 if (r == 0)
477 continue;
478
a4076574 479 r = dns_cache_put_negative(c, q->keys[i], rcode, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
322345fd
LP
480 if (r < 0)
481 goto fail;
322345fd
LP
482 }
483
484 return 0;
485
486fail:
487 /* Adding all RRs failed. Let's clean up what we already
488 * added, just in case */
489
eff91ee0
DM
490 if (q) {
491 for (i = 0; i < q->n_keys; i++)
492 dns_cache_remove(c, q->keys[i]);
493 }
494
7e8e0422 495 for (i = 0; i < answer->n_rrs; i++)
78c6a153 496 dns_cache_remove(c, answer->items[i].rr->key);
322345fd
LP
497
498 return r;
499}
500
f52e61da 501int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret) {
faa133f3 502 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
f52e61da 503 unsigned n = 0;
322345fd 504 int r;
7e8e0422 505 bool nxdomain = false;
f52e61da
LP
506 _cleanup_free_ char *key_str = NULL;
507 DnsCacheItem *j, *first;
322345fd
LP
508
509 assert(c);
f52e61da 510 assert(key);
623a4c97 511 assert(rcode);
faa133f3 512 assert(ret);
322345fd 513
f52e61da
LP
514 if (key->type == DNS_TYPE_ANY ||
515 key->class == DNS_CLASS_ANY) {
322345fd 516
f52e61da 517 /* If we have ANY lookups we simply refresh */
322345fd 518
f52e61da
LP
519 r = dns_resource_key_to_string(key, &key_str);
520 if (r < 0)
521 return r;
6b34a6c9 522
f52e61da 523 log_debug("Ignoring cache for ANY lookup: %s", key_str);
6b34a6c9 524
f52e61da
LP
525 *ret = NULL;
526 *rcode = DNS_RCODE_SUCCESS;
527 return 0;
528 }
6b34a6c9 529
f52e61da
LP
530 first = hashmap_get(c->by_key, key);
531 if (!first) {
532 /* If one question cannot be answered we need to refresh */
ddf16339 533
f52e61da
LP
534 r = dns_resource_key_to_string(key, &key_str);
535 if (r < 0)
536 return r;
6b34a6c9 537
f52e61da 538 log_debug("Cache miss for %s", key_str);
6b34a6c9 539
f52e61da
LP
540 *ret = NULL;
541 *rcode = DNS_RCODE_SUCCESS;
542 return 0;
543 }
6b34a6c9 544
f52e61da
LP
545 LIST_FOREACH(by_key, j, first) {
546 if (j->rr)
547 n++;
548 else if (j->type == DNS_CACHE_NXDOMAIN)
549 nxdomain = true;
550 }
6b34a6c9 551
f52e61da
LP
552 r = dns_resource_key_to_string(key, &key_str);
553 if (r < 0)
554 return r;
322345fd 555
f52e61da
LP
556 log_debug("%s cache hit for %s",
557 nxdomain ? "NXDOMAIN" :
558 n > 0 ? "Positive" : "NODATA",
559 key_str);
faa133f3 560
7e8e0422
LP
561 if (n <= 0) {
562 *ret = NULL;
563 *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
564 return 1;
565 }
faa133f3
LP
566
567 answer = dns_answer_new(n);
568 if (!answer)
569 return -ENOMEM;
322345fd 570
f52e61da
LP
571 LIST_FOREACH(by_key, j, first) {
572 if (!j->rr)
573 continue;
574
575 r = dns_answer_add(answer, j->rr, 0);
576 if (r < 0)
577 return r;
322345fd
LP
578 }
579
faa133f3 580 *ret = answer;
7e8e0422 581 *rcode = DNS_RCODE_SUCCESS;
faa133f3
LP
582 answer = NULL;
583
584 return n;
322345fd 585}
a4076574
LP
586
587int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) {
588 DnsCacheItem *i, *first;
589 bool same_owner = true;
590
591 assert(cache);
592 assert(rr);
593
594 dns_cache_prune(cache);
595
596 /* See if there's a cache entry for the same key. If there
597 * isn't there's no conflict */
598 first = hashmap_get(cache->by_key, rr->key);
599 if (!first)
600 return 0;
601
602 /* See if the RR key is owned by the same owner, if so, there
603 * isn't a conflict either */
604 LIST_FOREACH(by_key, i, first) {
605 if (i->owner_family != owner_family ||
606 !in_addr_equal(owner_family, &i->owner_address, owner_address)) {
607 same_owner = false;
608 break;
609 }
610 }
611 if (same_owner)
612 return 0;
613
614 /* See if there's the exact same RR in the cache. If yes, then
615 * there's no conflict. */
616 if (dns_cache_get(cache, rr))
617 return 0;
618
619 /* There's a conflict */
620 return 1;
621}
4d506d6b
LP
622
623void dns_cache_dump(DnsCache *cache, FILE *f) {
624 Iterator iterator;
625 DnsCacheItem *i;
626 int r;
627
628 if (!cache)
629 return;
630
631 if (!f)
632 f = stdout;
633
634 HASHMAP_FOREACH(i, cache->by_key, iterator) {
635 DnsCacheItem *j;
636
637 LIST_FOREACH(by_key, j, i) {
638 _cleanup_free_ char *t = NULL;
639
640 fputc('\t', f);
641
642 if (j->rr) {
643 r = dns_resource_record_to_string(j->rr, &t);
644 if (r < 0) {
645 log_oom();
646 continue;
647 }
648
649 fputs(t, f);
650 fputc('\n', f);
651 } else {
652 r = dns_resource_key_to_string(j->key, &t);
653 if (r < 0) {
654 log_oom();
655 continue;
656 }
657
658 fputs(t, f);
659 fputs(" -- ", f);
660 fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f);
661 fputc('\n', f);
662 }
663 }
664 }
665}
666
667bool dns_cache_is_empty(DnsCache *cache) {
668 if (!cache)
669 return true;
670
671 return hashmap_isempty(cache->by_key);
672}