]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-cache.c
resolved: use dns_name_parent() where appropriate
[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
b5efdb8a 22#include "alloc-util.h"
58db254a 23#include "dns-domain.h"
02c2857b 24#include "resolved-dns-answer.h"
322345fd 25#include "resolved-dns-cache.h"
7e8e0422 26#include "resolved-dns-packet.h"
58db254a 27#include "string-util.h"
322345fd 28
d98e5504
LP
29/* Never cache more than 4K entries */
30#define CACHE_MAX 4096
cbd4560e 31
d98e5504
LP
32/* We never keep any item longer than 2h in our cache */
33#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR)
322345fd 34
623a4c97
LP
35typedef enum DnsCacheItemType DnsCacheItemType;
36typedef struct DnsCacheItem DnsCacheItem;
37
38enum DnsCacheItemType {
39 DNS_CACHE_POSITIVE,
40 DNS_CACHE_NODATA,
41 DNS_CACHE_NXDOMAIN,
42};
43
44struct DnsCacheItem {
45 DnsResourceKey *key;
46 DnsResourceRecord *rr;
47 usec_t until;
48 DnsCacheItemType type;
49 unsigned prioq_idx;
931851e8 50 bool authenticated;
a4076574
LP
51 int owner_family;
52 union in_addr_union owner_address;
623a4c97
LP
53 LIST_FIELDS(DnsCacheItem, by_key);
54};
55
322345fd
LP
56static void dns_cache_item_free(DnsCacheItem *i) {
57 if (!i)
58 return;
59
60 dns_resource_record_unref(i->rr);
7e8e0422 61 dns_resource_key_unref(i->key);
322345fd
LP
62 free(i);
63}
64
65DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free);
66
67static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) {
68 DnsCacheItem *first;
69
70 assert(c);
71
72 if (!i)
73 return;
74
7e8e0422
LP
75 first = hashmap_get(c->by_key, i->key);
76 LIST_REMOVE(by_key, first, i);
322345fd
LP
77
78 if (first)
7e8e0422 79 assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
322345fd 80 else
7e8e0422 81 hashmap_remove(c->by_key, i->key);
322345fd 82
7e8e0422 83 prioq_remove(c->by_expiry, i, &i->prioq_idx);
322345fd
LP
84
85 dns_cache_item_free(i);
86}
87
88void dns_cache_flush(DnsCache *c) {
89 DnsCacheItem *i;
90
91 assert(c);
92
7e8e0422 93 while ((i = hashmap_first(c->by_key)))
322345fd
LP
94 dns_cache_item_remove_and_free(c, i);
95
7e8e0422
LP
96 assert(hashmap_size(c->by_key) == 0);
97 assert(prioq_size(c->by_expiry) == 0);
322345fd 98
cab5b059
LP
99 c->by_key = hashmap_free(c->by_key);
100 c->by_expiry = prioq_free(c->by_expiry);
322345fd
LP
101}
102
6b34a6c9 103static bool dns_cache_remove(DnsCache *c, DnsResourceKey *key) {
322345fd 104 DnsCacheItem *i;
6b34a6c9 105 bool exist = false;
322345fd
LP
106
107 assert(c);
108 assert(key);
109
6b34a6c9 110 while ((i = hashmap_get(c->by_key, key))) {
322345fd 111 dns_cache_item_remove_and_free(c, i);
6b34a6c9
TG
112 exist = true;
113 }
114
115 return exist;
322345fd
LP
116}
117
118static void dns_cache_make_space(DnsCache *c, unsigned add) {
119 assert(c);
120
121 if (add <= 0)
122 return;
123
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. */
128
129 for (;;) {
faa133f3 130 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
322345fd
LP
131 DnsCacheItem *i;
132
7e8e0422 133 if (prioq_size(c->by_expiry) <= 0)
322345fd
LP
134 break;
135
7e8e0422 136 if (prioq_size(c->by_expiry) + add < CACHE_MAX)
322345fd
LP
137 break;
138
7e8e0422 139 i = prioq_peek(c->by_expiry);
cbd4560e
LP
140 assert(i);
141
faa133f3 142 /* Take an extra reference to the key so that it
cbd4560e 143 * doesn't go away in the middle of the remove call */
7e8e0422 144 key = dns_resource_key_ref(i->key);
faa133f3 145 dns_cache_remove(c, key);
322345fd
LP
146 }
147}
148
149void dns_cache_prune(DnsCache *c) {
150 usec_t t = 0;
151
152 assert(c);
153
154 /* Remove all entries that are past their TTL */
155
156 for (;;) {
faa133f3 157 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
322345fd 158 DnsCacheItem *i;
322345fd 159
7e8e0422 160 i = prioq_peek(c->by_expiry);
322345fd
LP
161 if (!i)
162 break;
163
322345fd 164 if (t <= 0)
240b589b 165 t = now(clock_boottime_or_monotonic());
322345fd 166
7e8e0422 167 if (i->until > t)
322345fd
LP
168 break;
169
faa133f3 170 /* Take an extra reference to the key so that it
cbd4560e 171 * doesn't go away in the middle of the remove call */
7e8e0422 172 key = dns_resource_key_ref(i->key);
faa133f3 173 dns_cache_remove(c, key);
322345fd
LP
174 }
175}
176
177static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
322345fd
LP
178 const DnsCacheItem *x = a, *y = b;
179
7e8e0422 180 if (x->until < y->until)
322345fd 181 return -1;
7e8e0422 182 if (x->until > y->until)
322345fd
LP
183 return 1;
184 return 0;
185}
186
623a4c97 187static int dns_cache_init(DnsCache *c) {
7e8e0422
LP
188 int r;
189
623a4c97
LP
190 assert(c);
191
7e8e0422
LP
192 r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func);
193 if (r < 0)
194 return r;
195
d5099efc 196 r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops);
7e8e0422
LP
197 if (r < 0)
198 return r;
199
200 return r;
201}
202
203static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) {
204 DnsCacheItem *first;
205 int r;
206
322345fd
LP
207 assert(c);
208 assert(i);
322345fd 209
7e8e0422
LP
210 r = prioq_put(c->by_expiry, i, &i->prioq_idx);
211 if (r < 0)
212 return r;
322345fd 213
7e8e0422
LP
214 first = hashmap_get(c->by_key, i->key);
215 if (first) {
216 LIST_PREPEND(by_key, first, i);
217 assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
218 } else {
219 r = hashmap_put(c->by_key, i->key, i);
220 if (r < 0) {
221 prioq_remove(c->by_expiry, i, &i->prioq_idx);
222 return r;
223 }
322345fd
LP
224 }
225
7e8e0422 226 return 0;
322345fd
LP
227}
228
faa133f3
LP
229static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
230 DnsCacheItem *i;
231
232 assert(c);
233 assert(rr);
234
7e8e0422 235 LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key))
3ef77d04 236 if (i->rr && dns_resource_record_equal(i->rr, rr) > 0)
faa133f3
LP
237 return i;
238
239 return NULL;
240}
241
931851e8 242static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, bool authenticated, usec_t timestamp) {
7e8e0422
LP
243 assert(c);
244 assert(i);
245 assert(rr);
246
247 i->type = DNS_CACHE_POSITIVE;
248
ece174c5 249 if (!i->by_key_prev)
7e8e0422
LP
250 /* We are the first item in the list, we need to
251 * update the key used in the hashmap */
252
253 assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0);
7e8e0422
LP
254
255 dns_resource_record_ref(rr);
256 dns_resource_record_unref(i->rr);
257 i->rr = rr;
258
259 dns_resource_key_unref(i->key);
260 i->key = dns_resource_key_ref(rr->key);
261
931851e8 262 i->authenticated = authenticated;
7e8e0422
LP
263 i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
264
265 prioq_reshuffle(c->by_expiry, i, &i->prioq_idx);
266}
267
a4076574
LP
268static int dns_cache_put_positive(
269 DnsCache *c,
270 DnsResourceRecord *rr,
931851e8 271 bool authenticated,
a4076574
LP
272 usec_t timestamp,
273 int owner_family,
274 const union in_addr_union *owner_address) {
275
322345fd 276 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
6b34a6c9 277 _cleanup_free_ char *key_str = NULL;
7e8e0422 278 DnsCacheItem *existing;
a257f9d4 279 int r, k;
322345fd
LP
280
281 assert(c);
282 assert(rr);
a4076574 283 assert(owner_address);
322345fd 284
222148b6
LP
285 /* Never cache pseudo RRs */
286 if (dns_class_is_pseudo(rr->key->class))
287 return 0;
288 if (dns_type_is_pseudo(rr->key->type))
289 return 0;
290
322345fd
LP
291 /* New TTL is 0? Delete the entry... */
292 if (rr->ttl <= 0) {
a257f9d4 293 k = dns_cache_remove(c, rr->key);
6b34a6c9 294
a257f9d4
TG
295 if (log_get_max_level() >= LOG_DEBUG) {
296 r = dns_resource_key_to_string(rr->key, &key_str);
297 if (r < 0)
298 return r;
299
300 if (k > 0)
301 log_debug("Removed zero TTL entry from cache: %s", key_str);
302 else
303 log_debug("Not caching zero TTL cache entry: %s", key_str);
304 }
6b34a6c9 305
322345fd
LP
306 return 0;
307 }
308
309 /* Entry exists already? Update TTL and timestamp */
310 existing = dns_cache_get(c, rr);
311 if (existing) {
931851e8 312 dns_cache_item_update_positive(c, existing, rr, authenticated, timestamp);
322345fd
LP
313 return 0;
314 }
315
316 /* Otherwise, add the new RR */
623a4c97 317 r = dns_cache_init(c);
322345fd
LP
318 if (r < 0)
319 return r;
320
7e8e0422
LP
321 dns_cache_make_space(c, 1);
322
323 i = new0(DnsCacheItem, 1);
324 if (!i)
325 return -ENOMEM;
326
327 i->type = DNS_CACHE_POSITIVE;
328 i->key = dns_resource_key_ref(rr->key);
329 i->rr = dns_resource_record_ref(rr);
330 i->until = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
331 i->prioq_idx = PRIOQ_IDX_NULL;
a4076574
LP
332 i->owner_family = owner_family;
333 i->owner_address = *owner_address;
931851e8 334 i->authenticated = authenticated;
7e8e0422
LP
335
336 r = dns_cache_link_item(c, i);
337 if (r < 0)
338 return r;
339
a257f9d4
TG
340 if (log_get_max_level() >= LOG_DEBUG) {
341 r = dns_resource_key_to_string(i->key, &key_str);
342 if (r < 0)
343 return r;
6b34a6c9 344
a257f9d4
TG
345 log_debug("Added cache entry for %s", key_str);
346 }
6b34a6c9 347
7e8e0422
LP
348 i = NULL;
349 return 0;
350}
351
a4076574
LP
352static int dns_cache_put_negative(
353 DnsCache *c,
354 DnsResourceKey *key,
355 int rcode,
931851e8 356 bool authenticated,
a4076574
LP
357 usec_t timestamp,
358 uint32_t soa_ttl,
359 int owner_family,
360 const union in_addr_union *owner_address) {
361
7e8e0422 362 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
6b34a6c9 363 _cleanup_free_ char *key_str = NULL;
7e8e0422
LP
364 int r;
365
366 assert(c);
367 assert(key);
a4076574 368 assert(owner_address);
7e8e0422 369
222148b6
LP
370 /* Never cache pseudo RR keys */
371 if (dns_class_is_pseudo(key->class))
ddf16339 372 return 0;
c33be4a6 373 if (dns_type_is_pseudo(key->type))
222148b6
LP
374 /* DNS_TYPE_ANY is particularly important to filter
375 * out as we use this as a pseudo-type for NXDOMAIN
376 * entries */
ddf16339 377 return 0;
222148b6 378
6b34a6c9 379 if (soa_ttl <= 0) {
a257f9d4
TG
380 if (log_get_max_level() >= LOG_DEBUG) {
381 r = dns_resource_key_to_string(key, &key_str);
382 if (r < 0)
383 return r;
6b34a6c9 384
a257f9d4
TG
385 log_debug("Not caching negative entry with zero SOA TTL: %s", key_str);
386 }
6b34a6c9 387
95dd6257 388 return 0;
6b34a6c9 389 }
ddf16339 390
7e8e0422
LP
391 if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
392 return 0;
393
623a4c97 394 r = dns_cache_init(c);
322345fd
LP
395 if (r < 0)
396 return r;
397
398 dns_cache_make_space(c, 1);
399
400 i = new0(DnsCacheItem, 1);
401 if (!i)
402 return -ENOMEM;
403
7e8e0422 404 i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN;
7e8e0422
LP
405 i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
406 i->prioq_idx = PRIOQ_IDX_NULL;
a4076574
LP
407 i->owner_family = owner_family;
408 i->owner_address = *owner_address;
931851e8 409 i->authenticated = authenticated;
322345fd 410
71e13669
TG
411 if (i->type == DNS_CACHE_NXDOMAIN) {
412 /* NXDOMAIN entries should apply equally to all types, so we use ANY as
413 * a pseudo type for this purpose here. */
414 i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(key));
415 if (!i->key)
416 return -ENOMEM;
417 } else
418 i->key = dns_resource_key_ref(key);
419
7e8e0422 420 r = dns_cache_link_item(c, i);
322345fd
LP
421 if (r < 0)
422 return r;
423
a257f9d4
TG
424 if (log_get_max_level() >= LOG_DEBUG) {
425 r = dns_resource_key_to_string(i->key, &key_str);
426 if (r < 0)
427 return r;
6b34a6c9 428
a257f9d4
TG
429 log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str);
430 }
6b34a6c9 431
322345fd 432 i = NULL;
322345fd
LP
433 return 0;
434}
435
a4076574
LP
436int dns_cache_put(
437 DnsCache *c,
8e427d9b 438 DnsResourceKey *key,
a4076574
LP
439 int rcode,
440 DnsAnswer *answer,
931851e8 441 bool authenticated,
a4076574
LP
442 usec_t timestamp,
443 int owner_family,
444 const union in_addr_union *owner_address) {
445
02c2857b 446 DnsResourceRecord *soa = NULL, *rr;
105e1512
LP
447 DnsAnswerFlags flags;
448 unsigned cache_keys;
322345fd
LP
449 int r;
450
451 assert(c);
322345fd 452
8e427d9b
TG
453 if (key) {
454 /* First, if we were passed a key, delete all matching old RRs,
eff91ee0 455 * so that we only keep complete by_key in place. */
8e427d9b 456 dns_cache_remove(c, key);
eff91ee0 457 }
0ec7c46e 458
c3cb6dc2 459 if (!answer) {
a257f9d4
TG
460 if (log_get_max_level() >= LOG_DEBUG) {
461 _cleanup_free_ char *key_str = NULL;
c3cb6dc2 462
a257f9d4
TG
463 r = dns_resource_key_to_string(key, &key_str);
464 if (r < 0)
465 return r;
c3cb6dc2 466
a257f9d4
TG
467 log_debug("Not caching negative entry without a SOA record: %s", key_str);
468 }
c3cb6dc2 469
0ec7c46e 470 return 0;
c3cb6dc2 471 }
0ec7c46e 472
105e1512
LP
473 DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
474 if ((flags & DNS_ANSWER_CACHEABLE) == 0)
475 continue;
476
02c2857b
TG
477 if (rr->key->cache_flush)
478 dns_cache_remove(c, rr->key);
105e1512 479 }
322345fd 480
7e8e0422
LP
481 /* We only care for positive replies and NXDOMAINs, on all
482 * other replies we will simply flush the respective entries,
483 * and that's it */
484
485 if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
486 return 0;
487
eff91ee0 488 cache_keys = answer->n_rrs;
8e427d9b
TG
489 if (key)
490 cache_keys ++;
eff91ee0 491
7e8e0422 492 /* Make some space for our new entries */
eff91ee0 493 dns_cache_make_space(c, cache_keys);
322345fd 494
7e8e0422 495 if (timestamp <= 0)
240b589b 496 timestamp = now(clock_boottime_or_monotonic());
322345fd 497
7e8e0422 498 /* Second, add in positive entries for all contained RRs */
90325e8c 499
105e1512
LP
500 DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
501 if ((flags & DNS_ANSWER_CACHEABLE) == 0)
502 continue;
503
504 r = dns_cache_put_positive(c, rr, flags & DNS_ANSWER_AUTHENTICATED, timestamp, owner_family, owner_address);
7e8e0422
LP
505 if (r < 0)
506 goto fail;
507 }
508
8e427d9b 509 if (!key)
eff91ee0
DM
510 return 0;
511
8e427d9b 512 /* Third, add in negative entries if the key has no RR */
105e1512 513 r = dns_answer_match_key(answer, key, NULL);
8e427d9b
TG
514 if (r < 0)
515 goto fail;
516 if (r > 0)
517 return 0;
7e8e0422 518
5d27351f
TG
519 /* But not if it has a matching CNAME/DNAME (the negative
520 * caching will be done on the canonical name, not on the
521 * alias) */
105e1512 522 r = dns_answer_find_cname_or_dname(answer, key, NULL, NULL);
5d27351f
TG
523 if (r < 0)
524 goto fail;
525 if (r > 0)
526 return 0;
527
547973de
LP
528 /* See https://tools.ietf.org/html/rfc2308, which say that a
529 * matching SOA record in the packet is used to to enable
530 * negative caching. */
0a18f3e5 531
fd009cd8 532 r = dns_answer_find_soa(answer, key, &soa, &flags);
8e427d9b
TG
533 if (r < 0)
534 goto fail;
535 if (r == 0)
536 return 0;
7e8e0422 537
fd009cd8
LP
538 /* Refuse using the SOA data if it is unsigned, but the key is
539 * signed */
540 if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0)
541 return 0;
542
931851e8 543 r = dns_cache_put_negative(c, key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
8e427d9b
TG
544 if (r < 0)
545 goto fail;
322345fd
LP
546
547 return 0;
548
549fail:
550 /* Adding all RRs failed. Let's clean up what we already
551 * added, just in case */
552
8e427d9b
TG
553 if (key)
554 dns_cache_remove(c, key);
eff91ee0 555
105e1512
LP
556 DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
557 if ((flags & DNS_ANSWER_CACHEABLE) == 0)
558 continue;
559
560 dns_cache_remove(c, rr->key);
561 }
322345fd
LP
562
563 return r;
564}
565
37da8931 566static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) {
58db254a
LP
567 DnsCacheItem *i;
568 const char *n;
569 int r;
5643c00a
TG
570
571 assert(c);
572 assert(k);
573
58db254a
LP
574 /* If we hit some OOM error, or suchlike, we don't care too
575 * much, after all this is just a cache */
576
5643c00a 577 i = hashmap_get(c->by_key, k);
d7ce6c94 578 if (i)
37da8931
LP
579 return i;
580
581 n = DNS_RESOURCE_KEY_NAME(k);
582
71e13669
TG
583 /* Check if we have an NXDOMAIN cache item for the name, notice that we use
584 * the pseudo-type ANY for NXDOMAIN cache items. */
585 i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n));
586 if (i && i->type == DNS_CACHE_NXDOMAIN)
587 return i;
588
d7ce6c94
TG
589 /* The following record types should never be redirected. See
590 * <https://tools.ietf.org/html/rfc4035#section-2.5>. */
591 if (!IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME,
592 DNS_TYPE_NSEC3, DNS_TYPE_NSEC, DNS_TYPE_RRSIG,
593 DNS_TYPE_NXT, DNS_TYPE_SIG, DNS_TYPE_KEY)) {
594 /* Check if we have a CNAME record instead */
595 i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n));
596 if (i)
597 return i;
5643c00a 598
d7ce6c94
TG
599 /* OK, let's look for cached DNAME records. */
600 for (;;) {
d7ce6c94
TG
601 if (isempty(n))
602 return NULL;
603
604 i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n));
605 if (i)
606 return i;
58db254a 607
d7ce6c94 608 /* Jump one label ahead */
950b692b 609 r = dns_name_parent(&n);
d7ce6c94
TG
610 if (r <= 0)
611 return NULL;
612 }
613 }
5643c00a 614
950b692b 615 if (k->type != DNS_TYPE_NSEC) {
d7ce6c94
TG
616 /* Check if we have an NSEC record instead for the name. */
617 i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n));
58db254a
LP
618 if (i)
619 return i;
58db254a
LP
620 }
621
622 return NULL;
5643c00a
TG
623}
624
931851e8 625int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) {
faa133f3 626 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
f52e61da 627 unsigned n = 0;
322345fd 628 int r;
7e8e0422 629 bool nxdomain = false;
f52e61da 630 _cleanup_free_ char *key_str = NULL;
931851e8
LP
631 DnsCacheItem *j, *first, *nsec = NULL;
632 bool have_authenticated = false, have_non_authenticated = false;
322345fd
LP
633
634 assert(c);
f52e61da 635 assert(key);
623a4c97 636 assert(rcode);
faa133f3 637 assert(ret);
931851e8 638 assert(authenticated);
322345fd 639
f52e61da
LP
640 if (key->type == DNS_TYPE_ANY ||
641 key->class == DNS_CLASS_ANY) {
322345fd 642
931851e8
LP
643 /* If we have ANY lookups we don't use the cache, so
644 * that the caller refreshes via the network. */
322345fd 645
a257f9d4
TG
646 if (log_get_max_level() >= LOG_DEBUG) {
647 r = dns_resource_key_to_string(key, &key_str);
648 if (r < 0)
649 return r;
6b34a6c9 650
a257f9d4
TG
651 log_debug("Ignoring cache for ANY lookup: %s", key_str);
652 }
6b34a6c9 653
f52e61da
LP
654 *ret = NULL;
655 *rcode = DNS_RCODE_SUCCESS;
656 return 0;
657 }
6b34a6c9 658
37da8931 659 first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key);
f52e61da
LP
660 if (!first) {
661 /* If one question cannot be answered we need to refresh */
ddf16339 662
a257f9d4
TG
663 if (log_get_max_level() >= LOG_DEBUG) {
664 r = dns_resource_key_to_string(key, &key_str);
665 if (r < 0)
666 return r;
6b34a6c9 667
a257f9d4
TG
668 log_debug("Cache miss for %s", key_str);
669 }
6b34a6c9 670
f52e61da
LP
671 *ret = NULL;
672 *rcode = DNS_RCODE_SUCCESS;
673 return 0;
674 }
6b34a6c9 675
f52e61da 676 LIST_FOREACH(by_key, j, first) {
37da8931
LP
677 if (j->rr) {
678 if (j->rr->key->type == DNS_TYPE_NSEC)
931851e8
LP
679 nsec = j;
680
f52e61da 681 n++;
37da8931 682 } else if (j->type == DNS_CACHE_NXDOMAIN)
f52e61da 683 nxdomain = true;
931851e8
LP
684
685 if (j->authenticated)
686 have_authenticated = true;
687 else
688 have_non_authenticated = true;
f52e61da 689 }
6b34a6c9 690
37da8931 691 if (nsec && key->type != DNS_TYPE_NSEC) {
a257f9d4
TG
692 if (log_get_max_level() >= LOG_DEBUG) {
693 r = dns_resource_key_to_string(key, &key_str);
694 if (r < 0)
695 return r;
696
697 log_debug("NSEC NODATA cache hit for %s", key_str);
698 }
37da8931
LP
699
700 /* We only found an NSEC record that matches our name.
a257f9d4 701 * If it says the type doesn't exist report
37da8931
LP
702 * NODATA. Otherwise report a cache miss. */
703
704 *ret = NULL;
705 *rcode = DNS_RCODE_SUCCESS;
931851e8 706 *authenticated = nsec->authenticated;
37da8931 707
931851e8
LP
708 return !bitmap_isset(nsec->rr->nsec.types, key->type) &&
709 !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) &&
710 !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME);
37da8931
LP
711 }
712
a257f9d4
TG
713 if (log_get_max_level() >= LOG_DEBUG) {
714 r = dns_resource_key_to_string(key, &key_str);
715 if (r < 0)
716 return r;
717
718 log_debug("%s cache hit for %s",
719 n > 0 ? "Positive" :
720 nxdomain ? "NXDOMAIN" : "NODATA",
721 key_str);
722 }
faa133f3 723
7e8e0422
LP
724 if (n <= 0) {
725 *ret = NULL;
726 *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
931851e8 727 *authenticated = have_authenticated && !have_non_authenticated;
7e8e0422
LP
728 return 1;
729 }
faa133f3
LP
730
731 answer = dns_answer_new(n);
732 if (!answer)
733 return -ENOMEM;
322345fd 734
f52e61da
LP
735 LIST_FOREACH(by_key, j, first) {
736 if (!j->rr)
737 continue;
738
105e1512 739 r = dns_answer_add(answer, j->rr, 0, have_authenticated && !have_non_authenticated ? DNS_ANSWER_AUTHENTICATED : 0);
f52e61da
LP
740 if (r < 0)
741 return r;
322345fd
LP
742 }
743
faa133f3 744 *ret = answer;
7e8e0422 745 *rcode = DNS_RCODE_SUCCESS;
931851e8 746 *authenticated = have_authenticated && !have_non_authenticated;
faa133f3
LP
747 answer = NULL;
748
749 return n;
322345fd 750}
a4076574
LP
751
752int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) {
753 DnsCacheItem *i, *first;
754 bool same_owner = true;
755
756 assert(cache);
757 assert(rr);
758
759 dns_cache_prune(cache);
760
761 /* See if there's a cache entry for the same key. If there
762 * isn't there's no conflict */
763 first = hashmap_get(cache->by_key, rr->key);
764 if (!first)
765 return 0;
766
767 /* See if the RR key is owned by the same owner, if so, there
768 * isn't a conflict either */
769 LIST_FOREACH(by_key, i, first) {
770 if (i->owner_family != owner_family ||
771 !in_addr_equal(owner_family, &i->owner_address, owner_address)) {
772 same_owner = false;
773 break;
774 }
775 }
776 if (same_owner)
777 return 0;
778
779 /* See if there's the exact same RR in the cache. If yes, then
780 * there's no conflict. */
781 if (dns_cache_get(cache, rr))
782 return 0;
783
784 /* There's a conflict */
785 return 1;
786}
4d506d6b 787
7778dfff
DM
788int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) {
789 unsigned ancount = 0;
790 Iterator iterator;
791 DnsCacheItem *i;
792 int r;
793
794 assert(cache);
261f3673 795 assert(p);
7778dfff
DM
796
797 HASHMAP_FOREACH(i, cache->by_key, iterator) {
798 DnsCacheItem *j;
799
800 LIST_FOREACH(by_key, j, i) {
7778dfff
DM
801 if (!j->rr)
802 continue;
803
804 if (!dns_key_is_shared(j->rr->key))
805 continue;
806
807 r = dns_packet_append_rr(p, j->rr, NULL, NULL);
261f3673
DM
808 if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) {
809 /* For mDNS, if we're unable to stuff all known answers into the given packet,
810 * allocate a new one, push the RR into that one and link it to the current one.
811 */
812
813 DNS_PACKET_HEADER(p)->ancount = htobe16(ancount);
814 ancount = 0;
815
816 r = dns_packet_new_query(&p->more, p->protocol, 0, true);
817 if (r < 0)
818 return r;
819
820 /* continue with new packet */
821 p = p->more;
822 r = dns_packet_append_rr(p, j->rr, NULL, NULL);
823 }
824
7778dfff
DM
825 if (r < 0)
826 return r;
827
828 ancount ++;
829 }
830 }
831
832 DNS_PACKET_HEADER(p)->ancount = htobe16(ancount);
833
834 return 0;
835}
836
4d506d6b
LP
837void dns_cache_dump(DnsCache *cache, FILE *f) {
838 Iterator iterator;
839 DnsCacheItem *i;
840 int r;
841
842 if (!cache)
843 return;
844
845 if (!f)
846 f = stdout;
847
848 HASHMAP_FOREACH(i, cache->by_key, iterator) {
849 DnsCacheItem *j;
850
851 LIST_FOREACH(by_key, j, i) {
852 _cleanup_free_ char *t = NULL;
853
854 fputc('\t', f);
855
856 if (j->rr) {
857 r = dns_resource_record_to_string(j->rr, &t);
858 if (r < 0) {
859 log_oom();
860 continue;
861 }
862
863 fputs(t, f);
864 fputc('\n', f);
865 } else {
866 r = dns_resource_key_to_string(j->key, &t);
867 if (r < 0) {
868 log_oom();
869 continue;
870 }
871
872 fputs(t, f);
873 fputs(" -- ", f);
874 fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f);
875 fputc('\n', f);
876 }
877 }
878 }
879}
880
881bool dns_cache_is_empty(DnsCache *cache) {
882 if (!cache)
883 return true;
884
885 return hashmap_isempty(cache->by_key);
886}