1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <net/if.h>
4
5 #include "af-list.h"
6 #include "alloc-util.h"
7 #include "dns-domain.h"
8 #include "format-util.h"
9 #include "resolved-dns-answer.h"
10 #include "resolved-dns-cache.h"
11 #include "resolved-dns-packet.h"
12 #include "string-util.h"
13
14 /* Never cache more than 4K entries. RFC 1536, Section 5 suggests to
15 * leave DNS caches unbounded, but that's crazy. */
16 #define CACHE_MAX 4096
17
18 /* We never keep any item longer than 2h in our cache */
19 #define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR)
20
21 /* How long to cache strange rcodes, i.e. rcodes != SUCCESS and != NXDOMAIN (specifically: that's only SERVFAIL for
22 * now) */
23 #define CACHE_TTL_STRANGE_RCODE_USEC (10 * USEC_PER_SEC)
24
25 #define CACHEABLE_QUERY_FLAGS (SD_RESOLVED_AUTHENTICATED|SD_RESOLVED_CONFIDENTIAL)
26
27 typedef enum DnsCacheItemType DnsCacheItemType;
28 typedef struct DnsCacheItem DnsCacheItem;
29
30 enum DnsCacheItemType {
31 DNS_CACHE_POSITIVE,
32 DNS_CACHE_NODATA,
33 DNS_CACHE_NXDOMAIN,
34 DNS_CACHE_RCODE, /* "strange" RCODE (effective only SERVFAIL for now) */
35 };
36
37 struct DnsCacheItem {
38 DnsCacheItemType type;
39 int rcode;
40 DnsResourceKey *key; /* The key for this item, i.e. the lookup key */
41 DnsResourceRecord *rr; /* The RR for this item, i.e. the lookup value for positive queries */
42 DnsAnswer *answer; /* The full validated answer, if this is an RRset acquired via a "primary" lookup */
43 DnsPacket *full_packet; /* The full packet this information was acquired with */
44
45 usec_t until;
46 uint64_t query_flags; /* SD_RESOLVED_AUTHENTICATED and/or SD_RESOLVED_CONFIDENTIAL */
47 DnssecResult dnssec_result;
48
49 int ifindex;
50 int owner_family;
51 union in_addr_union owner_address;
52
53 unsigned prioq_idx;
54 LIST_FIELDS(DnsCacheItem, by_key);
55
56 bool shared_owner;
57 };
58
59 /* Returns true if this is a cache item created as result of an explicit lookup, or created as "side-effect"
60 * of another request. "Primary" entries will carry the full answer data (with NSEC, …) that can aso prove
61 * wildcard expansion, non-existance and such, while entries that were created as "side-effect" just contain
62 * immediate RR data for the specified RR key, but nothing else. */
63 #define DNS_CACHE_ITEM_IS_PRIMARY(item) (!!(item)->answer)
64
dns_cache_item_type_to_string(DnsCacheItem * item)65 static const char *dns_cache_item_type_to_string(DnsCacheItem *item) {
66 assert(item);
67
68 switch (item->type) {
69
70 case DNS_CACHE_POSITIVE:
71 return "POSITIVE";
72
73 case DNS_CACHE_NODATA:
74 return "NODATA";
75
76 case DNS_CACHE_NXDOMAIN:
77 return "NXDOMAIN";
78
79 case DNS_CACHE_RCODE:
80 return dns_rcode_to_string(item->rcode);
81 }
82
83 return NULL;
84 }
85
dns_cache_item_free(DnsCacheItem * i)86 static DnsCacheItem* dns_cache_item_free(DnsCacheItem *i) {
87 if (!i)
88 return NULL;
89
90 dns_resource_record_unref(i->rr);
91 dns_resource_key_unref(i->key);
92 dns_answer_unref(i->answer);
93 dns_packet_unref(i->full_packet);
94 return mfree(i);
95 }
96 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free);
97
dns_cache_item_unlink_and_free(DnsCache * c,DnsCacheItem * i)98 static void dns_cache_item_unlink_and_free(DnsCache *c, DnsCacheItem *i) {
99 DnsCacheItem *first;
100
101 assert(c);
102
103 if (!i)
104 return;
105
106 first = hashmap_get(c->by_key, i->key);
107 LIST_REMOVE(by_key, first, i);
108
109 if (first)
110 assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
111 else
112 hashmap_remove(c->by_key, i->key);
113
114 prioq_remove(c->by_expiry, i, &i->prioq_idx);
115
116 dns_cache_item_free(i);
117 }
118
dns_cache_remove_by_rr(DnsCache * c,DnsResourceRecord * rr)119 static bool dns_cache_remove_by_rr(DnsCache *c, DnsResourceRecord *rr) {
120 DnsCacheItem *first;
121 int r;
122
123 first = hashmap_get(c->by_key, rr->key);
124 LIST_FOREACH(by_key, i, first) {
125 r = dns_resource_record_equal(i->rr, rr);
126 if (r < 0)
127 return r;
128 if (r > 0) {
129 dns_cache_item_unlink_and_free(c, i);
130 return true;
131 }
132 }
133
134 return false;
135 }
136
dns_cache_remove_by_key(DnsCache * c,DnsResourceKey * key)137 static bool dns_cache_remove_by_key(DnsCache *c, DnsResourceKey *key) {
138 DnsCacheItem *first;
139
140 assert(c);
141 assert(key);
142
143 first = hashmap_remove(c->by_key, key);
144 if (!first)
145 return false;
146
147 LIST_FOREACH(by_key, i, first) {
148 prioq_remove(c->by_expiry, i, &i->prioq_idx);
149 dns_cache_item_free(i);
150 }
151
152 return true;
153 }
154
dns_cache_flush(DnsCache * c)155 void dns_cache_flush(DnsCache *c) {
156 DnsResourceKey *key;
157
158 assert(c);
159
160 while ((key = hashmap_first_key(c->by_key)))
161 dns_cache_remove_by_key(c, key);
162
163 assert(hashmap_size(c->by_key) == 0);
164 assert(prioq_size(c->by_expiry) == 0);
165
166 c->by_key = hashmap_free(c->by_key);
167 c->by_expiry = prioq_free(c->by_expiry);
168 }
169
dns_cache_make_space(DnsCache * c,unsigned add)170 static void dns_cache_make_space(DnsCache *c, unsigned add) {
171 assert(c);
172
173 if (add <= 0)
174 return;
175
176 /* Makes space for n new entries. Note that we actually allow
177 * the cache to grow beyond CACHE_MAX, but only when we shall
178 * add more RRs to the cache than CACHE_MAX at once. In that
179 * case the cache will be emptied completely otherwise. */
180
181 for (;;) {
182 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
183 DnsCacheItem *i;
184
185 if (prioq_size(c->by_expiry) <= 0)
186 break;
187
188 if (prioq_size(c->by_expiry) + add < CACHE_MAX)
189 break;
190
191 i = prioq_peek(c->by_expiry);
192 assert(i);
193
194 /* Take an extra reference to the key so that it
195 * doesn't go away in the middle of the remove call */
196 key = dns_resource_key_ref(i->key);
197 dns_cache_remove_by_key(c, key);
198 }
199 }
200
dns_cache_prune(DnsCache * c)201 void dns_cache_prune(DnsCache *c) {
202 usec_t t = 0;
203
204 assert(c);
205
206 /* Remove all entries that are past their TTL */
207
208 for (;;) {
209 DnsCacheItem *i;
210 char key_str[DNS_RESOURCE_KEY_STRING_MAX];
211
212 i = prioq_peek(c->by_expiry);
213 if (!i)
214 break;
215
216 if (t <= 0)
217 t = now(CLOCK_BOOTTIME);
218
219 if (i->until > t)
220 break;
221
222 /* Depending whether this is an mDNS shared entry
223 * either remove only this one RR or the whole RRset */
224 log_debug("Removing %scache entry for %s (expired "USEC_FMT"s ago)",
225 i->shared_owner ? "shared " : "",
226 dns_resource_key_to_string(i->key, key_str, sizeof key_str),
227 (t - i->until) / USEC_PER_SEC);
228
229 if (i->shared_owner)
230 dns_cache_item_unlink_and_free(c, i);
231 else {
232 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
233
234 /* Take an extra reference to the key so that it
235 * doesn't go away in the middle of the remove call */
236 key = dns_resource_key_ref(i->key);
237 dns_cache_remove_by_key(c, key);
238 }
239 }
240 }
241
dns_cache_item_prioq_compare_func(const void * a,const void * b)242 static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
243 const DnsCacheItem *x = a, *y = b;
244
245 return CMP(x->until, y->until);
246 }
247
dns_cache_init(DnsCache * c)248 static int dns_cache_init(DnsCache *c) {
249 int r;
250
251 assert(c);
252
253 r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func);
254 if (r < 0)
255 return r;
256
257 r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops);
258 if (r < 0)
259 return r;
260
261 return r;
262 }
263
dns_cache_link_item(DnsCache * c,DnsCacheItem * i)264 static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) {
265 DnsCacheItem *first;
266 int r;
267
268 assert(c);
269 assert(i);
270
271 r = prioq_put(c->by_expiry, i, &i->prioq_idx);
272 if (r < 0)
273 return r;
274
275 first = hashmap_get(c->by_key, i->key);
276 if (first) {
277 _unused_ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
278
279 /* Keep a reference to the original key, while we manipulate the list. */
280 k = dns_resource_key_ref(first->key);
281
282 /* Now, try to reduce the number of keys we keep */
283 dns_resource_key_reduce(&first->key, &i->key);
284
285 if (first->rr)
286 dns_resource_key_reduce(&first->rr->key, &i->key);
287 if (i->rr)
288 dns_resource_key_reduce(&i->rr->key, &i->key);
289
290 LIST_PREPEND(by_key, first, i);
291 assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
292 } else {
293 r = hashmap_put(c->by_key, i->key, i);
294 if (r < 0) {
295 prioq_remove(c->by_expiry, i, &i->prioq_idx);
296 return r;
297 }
298 }
299
300 return 0;
301 }
302
dns_cache_get(DnsCache * c,DnsResourceRecord * rr)303 static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
304 assert(c);
305 assert(rr);
306
307 LIST_FOREACH(by_key, i, (DnsCacheItem*) hashmap_get(c->by_key, rr->key))
308 if (i->rr && dns_resource_record_equal(i->rr, rr) > 0)
309 return i;
310
311 return NULL;
312 }
313
calculate_until(DnsResourceRecord * rr,uint32_t min_ttl,uint32_t nsec_ttl,usec_t timestamp,bool use_soa_minimum)314 static usec_t calculate_until(
315 DnsResourceRecord *rr,
316 uint32_t min_ttl,
317 uint32_t nsec_ttl,
318 usec_t timestamp,
319 bool use_soa_minimum) {
320
321 uint32_t ttl;
322 usec_t u;
323
324 assert(rr);
325
326 ttl = MIN(min_ttl, nsec_ttl);
327 if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) {
328 /* If this is a SOA RR, and it is requested, clamp to the SOA's minimum field. This is used
329 * when we do negative caching, to determine the TTL for the negative caching entry. See RFC
330 * 2308, Section 5. */
331
332 if (ttl > rr->soa.minimum)
333 ttl = rr->soa.minimum;
334 }
335
336 u = ttl * USEC_PER_SEC;
337 if (u > CACHE_TTL_MAX_USEC)
338 u = CACHE_TTL_MAX_USEC;
339
340 if (rr->expiry != USEC_INFINITY) {
341 usec_t left;
342
343 /* Make use of the DNSSEC RRSIG expiry info, if we have it */
344
345 left = LESS_BY(rr->expiry, now(CLOCK_REALTIME));
346 if (u > left)
347 u = left;
348 }
349
350 return timestamp + u;
351 }
352
dns_cache_item_update_positive(DnsCache * c,DnsCacheItem * i,DnsResourceRecord * rr,DnsAnswer * answer,DnsPacket * full_packet,uint32_t min_ttl,uint64_t query_flags,bool shared_owner,DnssecResult dnssec_result,usec_t timestamp,int ifindex,int owner_family,const union in_addr_union * owner_address)353 static void dns_cache_item_update_positive(
354 DnsCache *c,
355 DnsCacheItem *i,
356 DnsResourceRecord *rr,
357 DnsAnswer *answer,
358 DnsPacket *full_packet,
359 uint32_t min_ttl,
360 uint64_t query_flags,
361 bool shared_owner,
362 DnssecResult dnssec_result,
363 usec_t timestamp,
364 int ifindex,
365 int owner_family,
366 const union in_addr_union *owner_address) {
367
368 assert(c);
369 assert(i);
370 assert(rr);
371 assert(owner_address);
372
373 i->type = DNS_CACHE_POSITIVE;
374
375 if (!i->by_key_prev)
376 /* We are the first item in the list, we need to
377 * update the key used in the hashmap */
378
379 assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0);
380
381 DNS_RR_REPLACE(i->rr, dns_resource_record_ref(rr));
382
383 DNS_RESOURCE_KEY_REPLACE(i->key, dns_resource_key_ref(rr->key));
384
385 DNS_ANSWER_REPLACE(i->answer, dns_answer_ref(answer));
386
387 DNS_PACKET_REPLACE(i->full_packet, dns_packet_ref(full_packet));
388
389 i->until = calculate_until(rr, min_ttl, UINT32_MAX, timestamp, false);
390 i->query_flags = query_flags & CACHEABLE_QUERY_FLAGS;
391 i->shared_owner = shared_owner;
392 i->dnssec_result = dnssec_result;
393
394 i->ifindex = ifindex;
395
396 i->owner_family = owner_family;
397 i->owner_address = *owner_address;
398
399 prioq_reshuffle(c->by_expiry, i, &i->prioq_idx);
400 }
401
dns_cache_put_positive(DnsCache * c,DnsResourceRecord * rr,DnsAnswer * answer,DnsPacket * full_packet,uint64_t query_flags,bool shared_owner,DnssecResult dnssec_result,usec_t timestamp,int ifindex,int owner_family,const union in_addr_union * owner_address)402 static int dns_cache_put_positive(
403 DnsCache *c,
404 DnsResourceRecord *rr,
405 DnsAnswer *answer,
406 DnsPacket *full_packet,
407 uint64_t query_flags,
408 bool shared_owner,
409 DnssecResult dnssec_result,
410 usec_t timestamp,
411 int ifindex,
412 int owner_family,
413 const union in_addr_union *owner_address) {
414
415 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
416 char key_str[DNS_RESOURCE_KEY_STRING_MAX];
417 DnsCacheItem *existing;
418 uint32_t min_ttl;
419 int r;
420
421 assert(c);
422 assert(rr);
423 assert(owner_address);
424
425 /* Never cache pseudo RRs */
426 if (dns_class_is_pseudo(rr->key->class))
427 return 0;
428 if (dns_type_is_pseudo(rr->key->type))
429 return 0;
430
431 /* Determine the minimal TTL of all RRs in the answer plus the one by the main RR we are supposed to
432 * cache. Since we cache whole answers to questions we should never return answers where only some
433 * RRs are still valid, hence find the lowest here */
434 min_ttl = MIN(dns_answer_min_ttl(answer), rr->ttl);
435
436 /* New TTL is 0? Delete this specific entry... */
437 if (min_ttl <= 0) {
438 r = dns_cache_remove_by_rr(c, rr);
439 log_debug("%s: %s",
440 r > 0 ? "Removed zero TTL entry from cache" : "Not caching zero TTL cache entry",
441 dns_resource_key_to_string(rr->key, key_str, sizeof key_str));
442 return 0;
443 }
444
445 /* Entry exists already? Update TTL, timestamp and owner */
446 existing = dns_cache_get(c, rr);
447 if (existing) {
448 dns_cache_item_update_positive(
449 c,
450 existing,
451 rr,
452 answer,
453 full_packet,
454 min_ttl,
455 query_flags,
456 shared_owner,
457 dnssec_result,
458 timestamp,
459 ifindex,
460 owner_family,
461 owner_address);
462 return 0;
463 }
464
465 /* Otherwise, add the new RR */
466 r = dns_cache_init(c);
467 if (r < 0)
468 return r;
469
470 dns_cache_make_space(c, 1);
471
472 i = new(DnsCacheItem, 1);
473 if (!i)
474 return -ENOMEM;
475
476 *i = (DnsCacheItem) {
477 .type = DNS_CACHE_POSITIVE,
478 .key = dns_resource_key_ref(rr->key),
479 .rr = dns_resource_record_ref(rr),
480 .answer = dns_answer_ref(answer),
481 .full_packet = dns_packet_ref(full_packet),
482 .until = calculate_until(rr, min_ttl, UINT32_MAX, timestamp, false),
483 .query_flags = query_flags & CACHEABLE_QUERY_FLAGS,
484 .shared_owner = shared_owner,
485 .dnssec_result = dnssec_result,
486 .ifindex = ifindex,
487 .owner_family = owner_family,
488 .owner_address = *owner_address,
489 .prioq_idx = PRIOQ_IDX_NULL,
490 };
491
492 r = dns_cache_link_item(c, i);
493 if (r < 0)
494 return r;
495
496 if (DEBUG_LOGGING) {
497 _cleanup_free_ char *t = NULL;
498
499 (void) in_addr_to_string(i->owner_family, &i->owner_address, &t);
500
501 log_debug("Added positive %s %s%s cache entry for %s "USEC_FMT"s on %s/%s/%s",
502 FLAGS_SET(i->query_flags, SD_RESOLVED_AUTHENTICATED) ? "authenticated" : "unauthenticated",
503 FLAGS_SET(i->query_flags, SD_RESOLVED_CONFIDENTIAL) ? "confidential" : "non-confidential",
504 i->shared_owner ? " shared" : "",
505 dns_resource_key_to_string(i->key, key_str, sizeof key_str),
506 (i->until - timestamp) / USEC_PER_SEC,
507 i->ifindex == 0 ? "*" : FORMAT_IFNAME(i->ifindex),
508 af_to_name_short(i->owner_family),
509 strna(t));
510 }
511
512 i = NULL;
513 return 0;
514 }
515
dns_cache_put_negative(DnsCache * c,DnsResourceKey * key,int rcode,DnsAnswer * answer,DnsPacket * full_packet,uint64_t query_flags,DnssecResult dnssec_result,uint32_t nsec_ttl,usec_t timestamp,DnsResourceRecord * soa,int owner_family,const union in_addr_union * owner_address)516 static int dns_cache_put_negative(
517 DnsCache *c,
518 DnsResourceKey *key,
519 int rcode,
520 DnsAnswer *answer,
521 DnsPacket *full_packet,
522 uint64_t query_flags,
523 DnssecResult dnssec_result,
524 uint32_t nsec_ttl,
525 usec_t timestamp,
526 DnsResourceRecord *soa,
527 int owner_family,
528 const union in_addr_union *owner_address) {
529
530 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
531 char key_str[DNS_RESOURCE_KEY_STRING_MAX];
532 int r;
533
534 assert(c);
535 assert(key);
536 assert(owner_address);
537
538 /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly
539 * important to filter out as we use this as a pseudo-type for
540 * NXDOMAIN entries */
541 if (dns_class_is_pseudo(key->class))
542 return 0;
543 if (dns_type_is_pseudo(key->type))
544 return 0;
545
546 if (IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) {
547 if (!soa)
548 return 0;
549
550 /* For negative replies, check if we have a TTL of a SOA */
551 if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) {
552 log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s",
553 dns_resource_key_to_string(key, key_str, sizeof key_str));
554 return 0;
555 }
556 } else if (rcode != DNS_RCODE_SERVFAIL)
557 return 0;
558
559 r = dns_cache_init(c);
560 if (r < 0)
561 return r;
562
563 dns_cache_make_space(c, 1);
564
565 i = new(DnsCacheItem, 1);
566 if (!i)
567 return -ENOMEM;
568
569 *i = (DnsCacheItem) {
570 .type =
571 rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA :
572 rcode == DNS_RCODE_NXDOMAIN ? DNS_CACHE_NXDOMAIN : DNS_CACHE_RCODE,
573 .query_flags = query_flags & CACHEABLE_QUERY_FLAGS,
574 .dnssec_result = dnssec_result,
575 .owner_family = owner_family,
576 .owner_address = *owner_address,
577 .prioq_idx = PRIOQ_IDX_NULL,
578 .rcode = rcode,
579 .answer = dns_answer_ref(answer),
580 .full_packet = dns_packet_ref(full_packet),
581 };
582
583 /* Determine how long to cache this entry. In case we have some RRs in the answer use the lowest TTL
584 * of any of them. Typically that's the SOA's TTL, which is OK, but could possibly be lower because
585 * of some other RR. Let's better take the lowest option here than a needlessly high one */
586 i->until =
587 i->type == DNS_CACHE_RCODE ? timestamp + CACHE_TTL_STRANGE_RCODE_USEC :
588 calculate_until(soa, dns_answer_min_ttl(answer), nsec_ttl, timestamp, true);
589
590 if (i->type == DNS_CACHE_NXDOMAIN) {
591 /* NXDOMAIN entries should apply equally to all types, so we use ANY as
592 * a pseudo type for this purpose here. */
593 i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, dns_resource_key_name(key));
594 if (!i->key)
595 return -ENOMEM;
596
597 /* Make sure to remove any previous entry for this
598 * specific ANY key. (For non-ANY keys the cache data
599 * is already cleared by the caller.) Note that we
600 * don't bother removing positive or NODATA cache
601 * items in this case, because it would either be slow
602 * or require explicit indexing by name */
603 dns_cache_remove_by_key(c, key);
604 } else
605 i->key = dns_resource_key_ref(key);
606
607 r = dns_cache_link_item(c, i);
608 if (r < 0)
609 return r;
610
611 log_debug("Added %s cache entry for %s "USEC_FMT"s",
612 dns_cache_item_type_to_string(i),
613 dns_resource_key_to_string(i->key, key_str, sizeof key_str),
614 (i->until - timestamp) / USEC_PER_SEC);
615
616 i = NULL;
617 return 0;
618 }
619
dns_cache_remove_previous(DnsCache * c,DnsResourceKey * key,DnsAnswer * answer)620 static void dns_cache_remove_previous(
621 DnsCache *c,
622 DnsResourceKey *key,
623 DnsAnswer *answer) {
624
625 DnsResourceRecord *rr;
626 DnsAnswerFlags flags;
627
628 assert(c);
629
630 /* First, if we were passed a key (i.e. on LLMNR/DNS, but
631 * not on mDNS), delete all matching old RRs, so that we only
632 * keep complete by_key in place. */
633 if (key)
634 dns_cache_remove_by_key(c, key);
635
636 /* Second, flush all entries matching the answer, unless this
637 * is an RR that is explicitly marked to be "shared" between
638 * peers (i.e. mDNS RRs without the flush-cache bit set). */
639 DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
640 if ((flags & DNS_ANSWER_CACHEABLE) == 0)
641 continue;
642
643 if (flags & DNS_ANSWER_SHARED_OWNER)
644 continue;
645
646 dns_cache_remove_by_key(c, rr->key);
647 }
648 }
649
rr_eligible(DnsResourceRecord * rr)650 static bool rr_eligible(DnsResourceRecord *rr) {
651 assert(rr);
652
653 /* When we see an NSEC/NSEC3 RR, we'll only cache it if it is from the lower zone, not the upper zone, since
654 * that's where the interesting bits are (with exception of DS RRs). Of course, this way we cannot derive DS
655 * existence from any cached NSEC/NSEC3, but that should be fine. */
656
657 switch (rr->key->type) {
658
659 case DNS_TYPE_NSEC:
660 return !bitmap_isset(rr->nsec.types, DNS_TYPE_NS) ||
661 bitmap_isset(rr->nsec.types, DNS_TYPE_SOA);
662
663 case DNS_TYPE_NSEC3:
664 return !bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) ||
665 bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA);
666
667 default:
668 return true;
669 }
670 }
671
dns_cache_put(DnsCache * c,DnsCacheMode cache_mode,DnsResourceKey * key,int rcode,DnsAnswer * answer,DnsPacket * full_packet,uint64_t query_flags,DnssecResult dnssec_result,uint32_t nsec_ttl,int owner_family,const union in_addr_union * owner_address)672 int dns_cache_put(
673 DnsCache *c,
674 DnsCacheMode cache_mode,
675 DnsResourceKey *key,
676 int rcode,
677 DnsAnswer *answer,
678 DnsPacket *full_packet,
679 uint64_t query_flags,
680 DnssecResult dnssec_result,
681 uint32_t nsec_ttl,
682 int owner_family,
683 const union in_addr_union *owner_address) {
684
685 DnsResourceRecord *soa = NULL;
686 bool weird_rcode = false;
687 DnsAnswerItem *item;
688 DnsAnswerFlags flags;
689 unsigned cache_keys;
690 usec_t timestamp;
691 int r;
692
693 assert(c);
694 assert(owner_address);
695
696 dns_cache_remove_previous(c, key, answer);
697
698 /* We only care for positive replies and NXDOMAINs, on all other replies we will simply flush the respective
699 * entries, and that's it. (Well, with one further exception: since some DNS zones (akamai!) return SERVFAIL
700 * consistently for some lookups, and forwarders tend to propagate that we'll cache that too, but only for a
701 * short time.) */
702
703 if (IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) {
704 if (dns_answer_isempty(answer)) {
705 if (key) {
706 char key_str[DNS_RESOURCE_KEY_STRING_MAX];
707
708 log_debug("Not caching negative entry without a SOA record: %s",
709 dns_resource_key_to_string(key, key_str, sizeof key_str));
710 }
711
712 return 0;
713 }
714
715 } else {
716 /* Only cache SERVFAIL as "weird" rcode for now. We can add more later, should that turn out to be
717 * beneficial. */
718 if (rcode != DNS_RCODE_SERVFAIL)
719 return 0;
720
721 weird_rcode = true;
722 }
723
724 cache_keys = dns_answer_size(answer);
725 if (key)
726 cache_keys++;
727
728 /* Make some space for our new entries */
729 dns_cache_make_space(c, cache_keys);
730
731 timestamp = now(CLOCK_BOOTTIME);
732
733 /* Second, add in positive entries for all contained RRs */
734 DNS_ANSWER_FOREACH_ITEM(item, answer) {
735 int primary = false;
736
737 if (!FLAGS_SET(item->flags, DNS_ANSWER_CACHEABLE) ||
738 !rr_eligible(item->rr))
739 continue;
740
741 if (key) {
742 /* We store the auxiliary RRs and packet data in the cache only if they were in
743 * direct response to the original query. If we cache an RR we also received, and
744 * that is just auxiliary information we can't use the data, hence don't. */
745
746 primary = dns_resource_key_match_rr(key, item->rr, NULL);
747 if (primary < 0)
748 return primary;
749 if (primary == 0) {
750 primary = dns_resource_key_match_cname_or_dname(key, item->rr->key, NULL);
751 if (primary < 0)
752 return primary;
753 }
754 }
755
756 if (!primary) {
757 DnsCacheItem *first;
758
759 /* Do not replace existing cache items for primary lookups with non-primary
760 * data. After all the primary lookup data is a lot more useful. */
761 first = hashmap_get(c->by_key, item->rr->key);
762 if (first && DNS_CACHE_ITEM_IS_PRIMARY(first))
763 return 0;
764 }
765
766 r = dns_cache_put_positive(
767 c,
768 item->rr,
769 primary ? answer : NULL,
770 primary ? full_packet : NULL,
771 ((item->flags & DNS_ANSWER_AUTHENTICATED) ? SD_RESOLVED_AUTHENTICATED : 0) |
772 (query_flags & SD_RESOLVED_CONFIDENTIAL),
773 item->flags & DNS_ANSWER_SHARED_OWNER,
774 dnssec_result,
775 timestamp,
776 item->ifindex,
777 owner_family,
778 owner_address);
779 if (r < 0)
780 goto fail;
781 }
782
783 if (!key) /* mDNS doesn't know negative caching, really */
784 return 0;
785
786 /* Third, add in negative entries if the key has no RR */
787 r = dns_answer_match_key(answer, key, NULL);
788 if (r < 0)
789 goto fail;
790 if (r > 0)
791 return 0;
792
793 /* But not if it has a matching CNAME/DNAME (the negative caching will be done on the canonical name,
794 * not on the alias) */
795 r = dns_answer_find_cname_or_dname(answer, key, NULL, NULL);
796 if (r < 0)
797 goto fail;
798 if (r > 0)
799 return 0;
800
801 /* See https://tools.ietf.org/html/rfc2308, which say that a matching SOA record in the packet is used to
802 * enable negative caching. We apply one exception though: if we are about to cache a weird rcode we do so
803 * regardless of a SOA. */
804 r = dns_answer_find_soa(answer, key, &soa, &flags);
805 if (r < 0)
806 goto fail;
807 if (r == 0 && !weird_rcode)
808 return 0;
809 if (r > 0) {
810 /* Refuse using the SOA data if it is unsigned, but the key is signed */
811 if (FLAGS_SET(query_flags, SD_RESOLVED_AUTHENTICATED) &&
812 (flags & DNS_ANSWER_AUTHENTICATED) == 0)
813 return 0;
814 }
815
816 if (cache_mode == DNS_CACHE_MODE_NO_NEGATIVE) {
817 char key_str[DNS_RESOURCE_KEY_STRING_MAX];
818 log_debug("Not caching negative entry for: %s, cache mode set to no-negative",
819 dns_resource_key_to_string(key, key_str, sizeof key_str));
820 return 0;
821 }
822
823 r = dns_cache_put_negative(
824 c,
825 key,
826 rcode,
827 answer,
828 full_packet,
829 query_flags,
830 dnssec_result,
831 nsec_ttl,
832 timestamp,
833 soa,
834 owner_family, owner_address);
835 if (r < 0)
836 goto fail;
837
838 return 0;
839
840 fail:
841 /* Adding all RRs failed. Let's clean up what we already
842 * added, just in case */
843
844 if (key)
845 dns_cache_remove_by_key(c, key);
846
847 DNS_ANSWER_FOREACH_ITEM(item, answer) {
848 if ((item->flags & DNS_ANSWER_CACHEABLE) == 0)
849 continue;
850
851 dns_cache_remove_by_key(c, item->rr->key);
852 }
853
854 return r;
855 }
856
dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache * c,DnsResourceKey * k)857 static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) {
858 DnsCacheItem *i;
859 const char *n;
860 int r;
861
862 assert(c);
863 assert(k);
864
865 /* If we hit some OOM error, or suchlike, we don't care too
866 * much, after all this is just a cache */
867
868 i = hashmap_get(c->by_key, k);
869 if (i)
870 return i;
871
872 n = dns_resource_key_name(k);
873
874 /* Check if we have an NXDOMAIN cache item for the name, notice that we use
875 * the pseudo-type ANY for NXDOMAIN cache items. */
876 i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n));
877 if (i && i->type == DNS_CACHE_NXDOMAIN)
878 return i;
879
880 if (dns_type_may_redirect(k->type)) {
881 /* Check if we have a CNAME record instead */
882 i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n));
883 if (i && i->type != DNS_CACHE_NODATA)
884 return i;
885
886 /* OK, let's look for cached DNAME records. */
887 for (;;) {
888 if (isempty(n))
889 return NULL;
890
891 i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n));
892 if (i && i->type != DNS_CACHE_NODATA)
893 return i;
894
895 /* Jump one label ahead */
896 r = dns_name_parent(&n);
897 if (r <= 0)
898 return NULL;
899 }
900 }
901
902 if (k->type != DNS_TYPE_NSEC) {
903 /* Check if we have an NSEC record instead for the name. */
904 i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n));
905 if (i)
906 return i;
907 }
908
909 return NULL;
910 }
911
answer_add_clamp_ttl(DnsAnswer ** answer,DnsResourceRecord * rr,int ifindex,DnsAnswerFlags answer_flags,DnsResourceRecord * rrsig,uint64_t query_flags,usec_t until,usec_t current)912 static int answer_add_clamp_ttl(
913 DnsAnswer **answer,
914 DnsResourceRecord *rr,
915 int ifindex,
916 DnsAnswerFlags answer_flags,
917 DnsResourceRecord *rrsig,
918 uint64_t query_flags,
919 usec_t until,
920 usec_t current) {
921
922 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *patched = NULL, *patched_rrsig = NULL;
923 int r;
924
925 assert(answer);
926 assert(rr);
927
928 if (FLAGS_SET(query_flags, SD_RESOLVED_CLAMP_TTL)) {
929 uint32_t left_ttl;
930
931 assert(current > 0);
932
933 /* Let's determine how much time is left for this cache entry. Note that we round down, but
934 * clamp this to be 1s at minimum, since we usually want records to remain cached better too
935 * short a time than too long a time, but otoh don't want to return 0 ever, since that has
936 * special semantics in various contexts — in particular in mDNS */
937
938 left_ttl = MAX(1U, LESS_BY(until, current) / USEC_PER_SEC);
939
940 patched = dns_resource_record_ref(rr);
941
942 r = dns_resource_record_clamp_ttl(&patched, left_ttl);
943 if (r < 0)
944 return r;
945
946 rr = patched;
947
948 if (rrsig) {
949 patched_rrsig = dns_resource_record_ref(rrsig);
950 r = dns_resource_record_clamp_ttl(&patched_rrsig, left_ttl);
951 if (r < 0)
952 return r;
953
954 rrsig = patched_rrsig;
955 }
956 }
957
958 r = dns_answer_add_extend(answer, rr, ifindex, answer_flags, rrsig);
959 if (r < 0)
960 return r;
961
962 return 0;
963 }
964
dns_cache_lookup(DnsCache * c,DnsResourceKey * key,uint64_t query_flags,int * ret_rcode,DnsAnswer ** ret_answer,DnsPacket ** ret_full_packet,uint64_t * ret_query_flags,DnssecResult * ret_dnssec_result)965 int dns_cache_lookup(
966 DnsCache *c,
967 DnsResourceKey *key,
968 uint64_t query_flags,
969 int *ret_rcode,
970 DnsAnswer **ret_answer,
971 DnsPacket **ret_full_packet,
972 uint64_t *ret_query_flags,
973 DnssecResult *ret_dnssec_result) {
974
975 _cleanup_(dns_packet_unrefp) DnsPacket *full_packet = NULL;
976 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
977 char key_str[DNS_RESOURCE_KEY_STRING_MAX];
978 unsigned n = 0;
979 int r;
980 bool nxdomain = false;
981 DnsCacheItem *first, *nsec = NULL;
982 bool have_authenticated = false, have_non_authenticated = false, have_confidential = false, have_non_confidential = false;
983 usec_t current = 0;
984 int found_rcode = -1;
985 DnssecResult dnssec_result = -1;
986 int have_dnssec_result = -1;
987
988 assert(c);
989 assert(key);
990
991 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
992 /* If we have ANY lookups we don't use the cache, so that the caller refreshes via the
993 * network. */
994
995 log_debug("Ignoring cache for ANY lookup: %s",
996 dns_resource_key_to_string(key, key_str, sizeof key_str));
997 goto miss;
998 }
999
1000 first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key);
1001 if (!first) {
1002 /* If one question cannot be answered we need to refresh */
1003
1004 log_debug("Cache miss for %s",
1005 dns_resource_key_to_string(key, key_str, sizeof key_str));
1006 goto miss;
1007 }
1008
1009 if (FLAGS_SET(query_flags, SD_RESOLVED_CLAMP_TTL)) {
1010 /* 'current' is always passed to answer_add_clamp_ttl(), but is only used conditionally.
1011 * We'll do the same assert there to make sure that it was initialized properly. */
1012 current = now(CLOCK_BOOTTIME);
1013 assert(current > 0);
1014 }
1015
1016 LIST_FOREACH(by_key, j, first) {
1017 /* If the caller doesn't allow us to answer questions from cache data learned from
1018 * "side-effect", skip this entry. */
1019 if (FLAGS_SET(query_flags, SD_RESOLVED_REQUIRE_PRIMARY) &&
1020 !DNS_CACHE_ITEM_IS_PRIMARY(j)) {
1021 log_debug("Primary answer was requested for cache lookup for %s, which we don't have.",
1022 dns_resource_key_to_string(key, key_str, sizeof key_str));
1023
1024 goto miss;
1025 }
1026
1027 if (j->type == DNS_CACHE_NXDOMAIN)
1028 nxdomain = true;
1029 else if (j->type == DNS_CACHE_RCODE)
1030 found_rcode = j->rcode;
1031 else if (j->rr) {
1032 if (j->rr->key->type == DNS_TYPE_NSEC)
1033 nsec = j;
1034
1035 n++;
1036 }
1037
1038 if (FLAGS_SET(j->query_flags, SD_RESOLVED_AUTHENTICATED))
1039 have_authenticated = true;
1040 else
1041 have_non_authenticated = true;
1042
1043 if (FLAGS_SET(j->query_flags, SD_RESOLVED_CONFIDENTIAL))
1044 have_confidential = true;
1045 else
1046 have_non_confidential = true;
1047
1048 if (j->dnssec_result < 0) {
1049 have_dnssec_result = false; /* an entry without dnssec result? then invalidate things for good */
1050 dnssec_result = _DNSSEC_RESULT_INVALID;
1051 } else if (have_dnssec_result < 0) {
1052 have_dnssec_result = true; /* So far no result seen, let's pick this one up */
1053 dnssec_result = j->dnssec_result;
1054 } else if (have_dnssec_result > 0 && j->dnssec_result != dnssec_result) {
1055 have_dnssec_result = false; /* conflicting result seen? then invalidate for good */
1056 dnssec_result = _DNSSEC_RESULT_INVALID;
1057 }
1058
1059 /* Append the answer RRs to our answer. Ideally we have the answer object, which we
1060 * preferably use. But if the cached entry was generated as "side-effect" of a reply,
1061 * i.e. from validated auxiliary records rather than from the main reply, then we use the
1062 * individual RRs only instead. */
1063 if (j->answer) {
1064
1065 /* Minor optimization, if the full answer object of this and the previous RR is the
1066 * same, don't bother adding it again. Typically we store a full RRset here, hence
1067 * that should be the case. */
1068 if (!j->by_key_prev || j->answer != j->by_key_prev->answer) {
1069 DnsAnswerItem *item;
1070
1071 DNS_ANSWER_FOREACH_ITEM(item, j->answer) {
1072 r = answer_add_clamp_ttl(
1073 &answer,
1074 item->rr,
1075 item->ifindex,
1076 item->flags,
1077 item->rrsig,
1078 query_flags,
1079 j->until,
1080 current);
1081 if (r < 0)
1082 return r;
1083 }
1084 }
1085
1086 } else if (j->rr) {
1087 r = answer_add_clamp_ttl(
1088 &answer,
1089 j->rr,
1090 j->ifindex,
1091 FLAGS_SET(j->query_flags, SD_RESOLVED_AUTHENTICATED) ? DNS_ANSWER_AUTHENTICATED : 0,
1092 NULL,
1093 query_flags,
1094 j->until,
1095 current);
1096 if (r < 0)
1097 return r;
1098 }
1099
1100 /* We'll return any packet we have for this. Typically all cache entries for the same key
1101 * should come from the same packet anyway, hence it doesn't really matter which packet we
1102 * return here, they should all resolve to the same anyway. */
1103 if (!full_packet && j->full_packet)
1104 full_packet = dns_packet_ref(j->full_packet);
1105 }
1106
1107 if (found_rcode >= 0) {
1108 log_debug("RCODE %s cache hit for %s",
1109 dns_rcode_to_string(found_rcode),
1110 dns_resource_key_to_string(key, key_str, sizeof(key_str)));
1111
1112 if (ret_rcode)
1113 *ret_rcode = found_rcode;
1114 if (ret_answer)
1115 *ret_answer = TAKE_PTR(answer);
1116 if (ret_full_packet)
1117 *ret_full_packet = TAKE_PTR(full_packet);
1118 if (ret_query_flags)
1119 *ret_query_flags = 0;
1120 if (ret_dnssec_result)
1121 *ret_dnssec_result = dnssec_result;
1122
1123 c->n_hit++;
1124 return 1;
1125 }
1126
1127 if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) {
1128 /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC
1129 * RRs from the lower-zone of a zone cut, but the DS RRs are on the upper zone. */
1130
1131 log_debug("NSEC NODATA cache hit for %s",
1132 dns_resource_key_to_string(key, key_str, sizeof key_str));
1133
1134 /* We only found an NSEC record that matches our name. If it says the type doesn't exist
1135 * report NODATA. Otherwise report a cache miss. */
1136
1137 if (ret_rcode)
1138 *ret_rcode = DNS_RCODE_SUCCESS;
1139 if (ret_answer)
1140 *ret_answer = TAKE_PTR(answer);
1141 if (ret_full_packet)
1142 *ret_full_packet = TAKE_PTR(full_packet);
1143 if (ret_query_flags)
1144 *ret_query_flags = nsec->query_flags;
1145 if (ret_dnssec_result)
1146 *ret_dnssec_result = nsec->dnssec_result;
1147
1148 if (!bitmap_isset(nsec->rr->nsec.types, key->type) &&
1149 !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) &&
1150 !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME)) {
1151 c->n_hit++;
1152 return 1;
1153 }
1154
1155 c->n_miss++;
1156 return 0;
1157 }
1158
1159 log_debug("%s cache hit for %s",
1160 n > 0 ? "Positive" :
1161 nxdomain ? "NXDOMAIN" : "NODATA",
1162 dns_resource_key_to_string(key, key_str, sizeof key_str));
1163
1164 if (n <= 0) {
1165 c->n_hit++;
1166
1167 if (ret_rcode)
1168 *ret_rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
1169 if (ret_answer)
1170 *ret_answer = TAKE_PTR(answer);
1171 if (ret_full_packet)
1172 *ret_full_packet = TAKE_PTR(full_packet);
1173 if (ret_query_flags)
1174 *ret_query_flags =
1175 ((have_authenticated && !have_non_authenticated) ? SD_RESOLVED_AUTHENTICATED : 0) |
1176 ((have_confidential && !have_non_confidential) ? SD_RESOLVED_CONFIDENTIAL : 0);
1177 if (ret_dnssec_result)
1178 *ret_dnssec_result = dnssec_result;
1179
1180 return 1;
1181 }
1182
1183 c->n_hit++;
1184
1185 if (ret_rcode)
1186 *ret_rcode = DNS_RCODE_SUCCESS;
1187 if (ret_answer)
1188 *ret_answer = TAKE_PTR(answer);
1189 if (ret_full_packet)
1190 *ret_full_packet = TAKE_PTR(full_packet);
1191 if (ret_query_flags)
1192 *ret_query_flags =
1193 ((have_authenticated && !have_non_authenticated) ? SD_RESOLVED_AUTHENTICATED : 0) |
1194 ((have_confidential && !have_non_confidential) ? SD_RESOLVED_CONFIDENTIAL : 0);
1195 if (ret_dnssec_result)
1196 *ret_dnssec_result = dnssec_result;
1197
1198 return n;
1199
1200 miss:
1201 if (ret_rcode)
1202 *ret_rcode = DNS_RCODE_SUCCESS;
1203 if (ret_answer)
1204 *ret_answer = NULL;
1205 if (ret_full_packet)
1206 *ret_full_packet = NULL;
1207 if (ret_query_flags)
1208 *ret_query_flags = 0;
1209 if (ret_dnssec_result)
1210 *ret_dnssec_result = _DNSSEC_RESULT_INVALID;
1211
1212 c->n_miss++;
1213 return 0;
1214 }
1215
dns_cache_check_conflicts(DnsCache * cache,DnsResourceRecord * rr,int owner_family,const union in_addr_union * owner_address)1216 int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) {
1217 DnsCacheItem *first;
1218 bool same_owner = true;
1219
1220 assert(cache);
1221 assert(rr);
1222
1223 dns_cache_prune(cache);
1224
1225 /* See if there's a cache entry for the same key. If there
1226 * isn't there's no conflict */
1227 first = hashmap_get(cache->by_key, rr->key);
1228 if (!first)
1229 return 0;
1230
1231 /* See if the RR key is owned by the same owner, if so, there
1232 * isn't a conflict either */
1233 LIST_FOREACH(by_key, i, first) {
1234 if (i->owner_family != owner_family ||
1235 !in_addr_equal(owner_family, &i->owner_address, owner_address)) {
1236 same_owner = false;
1237 break;
1238 }
1239 }
1240 if (same_owner)
1241 return 0;
1242
1243 /* See if there's the exact same RR in the cache. If yes, then
1244 * there's no conflict. */
1245 if (dns_cache_get(cache, rr))
1246 return 0;
1247
1248 /* There's a conflict */
1249 return 1;
1250 }
1251
dns_cache_export_shared_to_packet(DnsCache * cache,DnsPacket * p)1252 int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) {
1253 unsigned ancount = 0;
1254 DnsCacheItem *i;
1255 int r;
1256
1257 assert(cache);
1258 assert(p);
1259
1260 HASHMAP_FOREACH(i, cache->by_key)
1261 LIST_FOREACH(by_key, j, i) {
1262 if (!j->rr)
1263 continue;
1264
1265 if (!j->shared_owner)
1266 continue;
1267
1268 r = dns_packet_append_rr(p, j->rr, 0, NULL, NULL);
1269 if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) {
1270 /* For mDNS, if we're unable to stuff all known answers into the given packet,
1271 * allocate a new one, push the RR into that one and link it to the current one.
1272 */
1273
1274 DNS_PACKET_HEADER(p)->ancount = htobe16(ancount);
1275 ancount = 0;
1276
1277 r = dns_packet_new_query(&p->more, p->protocol, 0, true);
1278 if (r < 0)
1279 return r;
1280
1281 /* continue with new packet */
1282 p = p->more;
1283 r = dns_packet_append_rr(p, j->rr, 0, NULL, NULL);
1284 }
1285
1286 if (r < 0)
1287 return r;
1288
1289 ancount++;
1290 }
1291
1292 DNS_PACKET_HEADER(p)->ancount = htobe16(ancount);
1293
1294 return 0;
1295 }
1296
dns_cache_dump(DnsCache * cache,FILE * f)1297 void dns_cache_dump(DnsCache *cache, FILE *f) {
1298 DnsCacheItem *i;
1299
1300 if (!cache)
1301 return;
1302
1303 if (!f)
1304 f = stdout;
1305
1306 HASHMAP_FOREACH(i, cache->by_key)
1307 LIST_FOREACH(by_key, j, i) {
1308
1309 fputc('\t', f);
1310
1311 if (j->rr) {
1312 const char *t;
1313 t = dns_resource_record_to_string(j->rr);
1314 if (!t) {
1315 log_oom();
1316 continue;
1317 }
1318
1319 fputs(t, f);
1320 fputc('\n', f);
1321 } else {
1322 char key_str[DNS_RESOURCE_KEY_STRING_MAX];
1323
1324 fputs(dns_resource_key_to_string(j->key, key_str, sizeof key_str), f);
1325 fputs(" -- ", f);
1326 fputs(dns_cache_item_type_to_string(j), f);
1327 fputc('\n', f);
1328 }
1329 }
1330 }
1331
dns_cache_is_empty(DnsCache * cache)1332 bool dns_cache_is_empty(DnsCache *cache) {
1333 if (!cache)
1334 return true;
1335
1336 return hashmap_isempty(cache->by_key);
1337 }
1338
dns_cache_size(DnsCache * cache)1339 unsigned dns_cache_size(DnsCache *cache) {
1340 if (!cache)
1341 return 0;
1342
1343 return hashmap_size(cache->by_key);
1344 }
1345