1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include "alloc-util.h"
23 #include "dns-domain.h"
24 #include "resolved-dns-answer.h"
25 #include "resolved-dns-cache.h"
26 #include "resolved-dns-packet.h"
27 #include "string-util.h"
29 /* Never cache more than 4K entries. RFC 1536, Section 5 suggests to
30 * leave DNS caches unbounded, but that's crazy. */
31 #define CACHE_MAX 4096
33 /* We never keep any item longer than 2h in our cache */
34 #define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR)
36 typedef enum DnsCacheItemType DnsCacheItemType
;
37 typedef struct DnsCacheItem DnsCacheItem
;
39 enum DnsCacheItemType
{
46 DnsCacheItemType type
;
48 DnsResourceRecord
*rr
;
55 union in_addr_union owner_address
;
58 LIST_FIELDS(DnsCacheItem
, by_key
);
61 static void dns_cache_item_free(DnsCacheItem
*i
) {
65 dns_resource_record_unref(i
->rr
);
66 dns_resource_key_unref(i
->key
);
70 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem
*, dns_cache_item_free
);
72 static void dns_cache_item_unlink_and_free(DnsCache
*c
, DnsCacheItem
*i
) {
80 first
= hashmap_get(c
->by_key
, i
->key
);
81 LIST_REMOVE(by_key
, first
, i
);
84 assert_se(hashmap_replace(c
->by_key
, first
->key
, first
) >= 0);
86 hashmap_remove(c
->by_key
, i
->key
);
88 prioq_remove(c
->by_expiry
, i
, &i
->prioq_idx
);
90 dns_cache_item_free(i
);
93 static bool dns_cache_remove_by_rr(DnsCache
*c
, DnsResourceRecord
*rr
) {
94 DnsCacheItem
*first
, *i
;
97 first
= hashmap_get(c
->by_key
, rr
->key
);
98 LIST_FOREACH(by_key
, i
, first
) {
99 r
= dns_resource_record_equal(i
->rr
, rr
);
103 dns_cache_item_unlink_and_free(c
, i
);
111 static bool dns_cache_remove_by_key(DnsCache
*c
, DnsResourceKey
*key
) {
112 DnsCacheItem
*first
, *i
, *n
;
117 first
= hashmap_remove(c
->by_key
, key
);
121 LIST_FOREACH_SAFE(by_key
, i
, n
, first
) {
122 prioq_remove(c
->by_expiry
, i
, &i
->prioq_idx
);
123 dns_cache_item_free(i
);
129 void dns_cache_flush(DnsCache
*c
) {
134 while ((key
= hashmap_first_key(c
->by_key
)))
135 dns_cache_remove_by_key(c
, key
);
137 assert(hashmap_size(c
->by_key
) == 0);
138 assert(prioq_size(c
->by_expiry
) == 0);
140 c
->by_key
= hashmap_free(c
->by_key
);
141 c
->by_expiry
= prioq_free(c
->by_expiry
);
144 static void dns_cache_make_space(DnsCache
*c
, unsigned add
) {
150 /* Makes space for n new entries. Note that we actually allow
151 * the cache to grow beyond CACHE_MAX, but only when we shall
152 * add more RRs to the cache than CACHE_MAX at once. In that
153 * case the cache will be emptied completely otherwise. */
156 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
159 if (prioq_size(c
->by_expiry
) <= 0)
162 if (prioq_size(c
->by_expiry
) + add
< CACHE_MAX
)
165 i
= prioq_peek(c
->by_expiry
);
168 /* Take an extra reference to the key so that it
169 * doesn't go away in the middle of the remove call */
170 key
= dns_resource_key_ref(i
->key
);
171 dns_cache_remove_by_key(c
, key
);
175 void dns_cache_prune(DnsCache
*c
) {
180 /* Remove all entries that are past their TTL */
185 i
= prioq_peek(c
->by_expiry
);
190 t
= now(clock_boottime_or_monotonic());
195 /* Depending whether this is an mDNS shared entry
196 * either remove only this one RR or the whole
199 dns_cache_item_unlink_and_free(c
, i
);
201 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
203 /* Take an extra reference to the key so that it
204 * doesn't go away in the middle of the remove call */
205 key
= dns_resource_key_ref(i
->key
);
206 dns_cache_remove_by_key(c
, key
);
211 static int dns_cache_item_prioq_compare_func(const void *a
, const void *b
) {
212 const DnsCacheItem
*x
= a
, *y
= b
;
214 if (x
->until
< y
->until
)
216 if (x
->until
> y
->until
)
221 static int dns_cache_init(DnsCache
*c
) {
226 r
= prioq_ensure_allocated(&c
->by_expiry
, dns_cache_item_prioq_compare_func
);
230 r
= hashmap_ensure_allocated(&c
->by_key
, &dns_resource_key_hash_ops
);
237 static int dns_cache_link_item(DnsCache
*c
, DnsCacheItem
*i
) {
244 r
= prioq_put(c
->by_expiry
, i
, &i
->prioq_idx
);
248 first
= hashmap_get(c
->by_key
, i
->key
);
250 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*k
= NULL
;
252 /* Keep a reference to the original key, while we manipulate the list. */
253 k
= dns_resource_key_ref(first
->key
);
255 /* Now, try to reduce the number of keys we keep */
256 dns_resource_key_reduce(&first
->key
, &i
->key
);
259 dns_resource_key_reduce(&first
->rr
->key
, &i
->key
);
261 dns_resource_key_reduce(&i
->rr
->key
, &i
->key
);
263 LIST_PREPEND(by_key
, first
, i
);
264 assert_se(hashmap_replace(c
->by_key
, first
->key
, first
) >= 0);
266 r
= hashmap_put(c
->by_key
, i
->key
, i
);
268 prioq_remove(c
->by_expiry
, i
, &i
->prioq_idx
);
276 static DnsCacheItem
* dns_cache_get(DnsCache
*c
, DnsResourceRecord
*rr
) {
282 LIST_FOREACH(by_key
, i
, hashmap_get(c
->by_key
, rr
->key
))
283 if (i
->rr
&& dns_resource_record_equal(i
->rr
, rr
) > 0)
289 static usec_t
calculate_until(DnsResourceRecord
*rr
, uint32_t nsec_ttl
, usec_t timestamp
, bool use_soa_minimum
) {
295 ttl
= MIN(rr
->ttl
, nsec_ttl
);
296 if (rr
->key
->type
== DNS_TYPE_SOA
&& use_soa_minimum
) {
297 /* If this is a SOA RR, and it is requested, clamp to
298 * the SOA's minimum field. This is used when we do
299 * negative caching, to determine the TTL for the
300 * negative caching entry. See RFC 2308, Section
303 if (ttl
> rr
->soa
.minimum
)
304 ttl
= rr
->soa
.minimum
;
307 u
= ttl
* USEC_PER_SEC
;
308 if (u
> CACHE_TTL_MAX_USEC
)
309 u
= CACHE_TTL_MAX_USEC
;
311 if (rr
->expiry
!= USEC_INFINITY
) {
314 /* Make use of the DNSSEC RRSIG expiry info, if we
317 left
= LESS_BY(rr
->expiry
, now(CLOCK_REALTIME
));
322 return timestamp
+ u
;
325 static void dns_cache_item_update_positive(
328 DnsResourceRecord
*rr
,
333 const union in_addr_union
*owner_address
) {
338 assert(owner_address
);
340 i
->type
= DNS_CACHE_POSITIVE
;
343 /* We are the first item in the list, we need to
344 * update the key used in the hashmap */
346 assert_se(hashmap_replace(c
->by_key
, rr
->key
, i
) >= 0);
348 dns_resource_record_ref(rr
);
349 dns_resource_record_unref(i
->rr
);
352 dns_resource_key_unref(i
->key
);
353 i
->key
= dns_resource_key_ref(rr
->key
);
355 i
->until
= calculate_until(rr
, (uint32_t) -1, timestamp
, false);
356 i
->authenticated
= authenticated
;
357 i
->shared_owner
= shared_owner
;
359 i
->owner_family
= owner_family
;
360 i
->owner_address
= *owner_address
;
362 prioq_reshuffle(c
->by_expiry
, i
, &i
->prioq_idx
);
365 static int dns_cache_put_positive(
367 DnsResourceRecord
*rr
,
372 const union in_addr_union
*owner_address
) {
374 _cleanup_(dns_cache_item_freep
) DnsCacheItem
*i
= NULL
;
375 _cleanup_free_
char *key_str
= NULL
;
376 DnsCacheItem
*existing
;
381 assert(owner_address
);
383 /* Never cache pseudo RRs */
384 if (dns_class_is_pseudo(rr
->key
->class))
386 if (dns_type_is_pseudo(rr
->key
->type
))
389 /* New TTL is 0? Delete this specific entry... */
391 k
= dns_cache_remove_by_rr(c
, rr
);
393 if (log_get_max_level() >= LOG_DEBUG
) {
394 r
= dns_resource_key_to_string(rr
->key
, &key_str
);
399 log_debug("Removed zero TTL entry from cache: %s", key_str
);
401 log_debug("Not caching zero TTL cache entry: %s", key_str
);
407 /* Entry exists already? Update TTL, timestamp and owner*/
408 existing
= dns_cache_get(c
, rr
);
410 dns_cache_item_update_positive(
422 /* Otherwise, add the new RR */
423 r
= dns_cache_init(c
);
427 dns_cache_make_space(c
, 1);
429 i
= new0(DnsCacheItem
, 1);
433 i
->type
= DNS_CACHE_POSITIVE
;
434 i
->key
= dns_resource_key_ref(rr
->key
);
435 i
->rr
= dns_resource_record_ref(rr
);
436 i
->until
= calculate_until(rr
, (uint32_t) -1, timestamp
, false);
437 i
->authenticated
= authenticated
;
438 i
->shared_owner
= shared_owner
;
439 i
->owner_family
= owner_family
;
440 i
->owner_address
= *owner_address
;
441 i
->prioq_idx
= PRIOQ_IDX_NULL
;
443 r
= dns_cache_link_item(c
, i
);
447 if (log_get_max_level() >= LOG_DEBUG
) {
448 r
= dns_resource_key_to_string(i
->key
, &key_str
);
452 log_debug("Added positive cache entry for %s", key_str
);
459 static int dns_cache_put_negative(
466 DnsResourceRecord
*soa
,
468 const union in_addr_union
*owner_address
) {
470 _cleanup_(dns_cache_item_freep
) DnsCacheItem
*i
= NULL
;
471 _cleanup_free_
char *key_str
= NULL
;
477 assert(owner_address
);
479 /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly
480 * important to filter out as we use this as a pseudo-type for
481 * NXDOMAIN entries */
482 if (dns_class_is_pseudo(key
->class))
484 if (dns_type_is_pseudo(key
->type
))
487 if (nsec_ttl
<= 0 || soa
->soa
.minimum
<= 0 || soa
->ttl
<= 0) {
488 if (log_get_max_level() >= LOG_DEBUG
) {
489 r
= dns_resource_key_to_string(key
, &key_str
);
493 log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", key_str
);
499 if (!IN_SET(rcode
, DNS_RCODE_SUCCESS
, DNS_RCODE_NXDOMAIN
))
502 r
= dns_cache_init(c
);
506 dns_cache_make_space(c
, 1);
508 i
= new0(DnsCacheItem
, 1);
512 i
->type
= rcode
== DNS_RCODE_SUCCESS
? DNS_CACHE_NODATA
: DNS_CACHE_NXDOMAIN
;
513 i
->until
= calculate_until(soa
, nsec_ttl
, timestamp
, true);
514 i
->authenticated
= authenticated
;
515 i
->owner_family
= owner_family
;
516 i
->owner_address
= *owner_address
;
517 i
->prioq_idx
= PRIOQ_IDX_NULL
;
519 if (i
->type
== DNS_CACHE_NXDOMAIN
) {
520 /* NXDOMAIN entries should apply equally to all types, so we use ANY as
521 * a pseudo type for this purpose here. */
522 i
->key
= dns_resource_key_new(key
->class, DNS_TYPE_ANY
, DNS_RESOURCE_KEY_NAME(key
));
526 /* Make sure to remove any previous entry for this
527 * specific ANY key. (For non-ANY keys the cache data
528 * is already cleared by the caller.) Note that we
529 * don't bother removing positive or NODATA cache
530 * items in this case, because it would either be slow
531 * or require explicit indexing by name */
532 dns_cache_remove_by_key(c
, key
);
534 i
->key
= dns_resource_key_ref(key
);
536 r
= dns_cache_link_item(c
, i
);
540 if (log_get_max_level() >= LOG_DEBUG
) {
541 r
= dns_resource_key_to_string(i
->key
, &key_str
);
545 log_debug("Added %s cache entry for %s", i
->type
== DNS_CACHE_NODATA
? "NODATA" : "NXDOMAIN", key_str
);
552 static void dns_cache_remove_previous(
557 DnsResourceRecord
*rr
;
558 DnsAnswerFlags flags
;
562 /* First, if we were passed a key (i.e. on LLMNR/DNS, but
563 * not on mDNS), delete all matching old RRs, so that we only
564 * keep complete by_key in place. */
566 dns_cache_remove_by_key(c
, key
);
568 /* Second, flush all entries matching the answer, unless this
569 * is an RR that is explicitly marked to be "shared" between
570 * peers (i.e. mDNS RRs without the flush-cache bit set). */
571 DNS_ANSWER_FOREACH_FLAGS(rr
, flags
, answer
) {
572 if ((flags
& DNS_ANSWER_CACHEABLE
) == 0)
575 if (flags
& DNS_ANSWER_SHARED_OWNER
)
578 dns_cache_remove_by_key(c
, rr
->key
);
591 const union in_addr_union
*owner_address
) {
593 DnsResourceRecord
*soa
= NULL
, *rr
;
594 DnsAnswerFlags flags
;
599 assert(owner_address
);
601 dns_cache_remove_previous(c
, key
, answer
);
603 if (dns_answer_size(answer
) <= 0) {
604 if (log_get_max_level() >= LOG_DEBUG
) {
605 _cleanup_free_
char *key_str
= NULL
;
607 r
= dns_resource_key_to_string(key
, &key_str
);
611 log_debug("Not caching negative entry without a SOA record: %s", key_str
);
617 /* We only care for positive replies and NXDOMAINs, on all
618 * other replies we will simply flush the respective entries,
620 if (!IN_SET(rcode
, DNS_RCODE_SUCCESS
, DNS_RCODE_NXDOMAIN
))
623 cache_keys
= dns_answer_size(answer
);
627 /* Make some space for our new entries */
628 dns_cache_make_space(c
, cache_keys
);
631 timestamp
= now(clock_boottime_or_monotonic());
633 /* Second, add in positive entries for all contained RRs */
634 DNS_ANSWER_FOREACH_FLAGS(rr
, flags
, answer
) {
635 if ((flags
& DNS_ANSWER_CACHEABLE
) == 0)
638 r
= dns_cache_put_positive(
641 flags
& DNS_ANSWER_AUTHENTICATED
,
642 flags
& DNS_ANSWER_SHARED_OWNER
,
644 owner_family
, owner_address
);
649 if (!key
) /* mDNS doesn't know negative caching, really */
652 /* Third, add in negative entries if the key has no RR */
653 r
= dns_answer_match_key(answer
, key
, NULL
);
659 /* But not if it has a matching CNAME/DNAME (the negative
660 * caching will be done on the canonical name, not on the
662 r
= dns_answer_find_cname_or_dname(answer
, key
, NULL
, NULL
);
668 /* See https://tools.ietf.org/html/rfc2308, which say that a
669 * matching SOA record in the packet is used to to enable
670 * negative caching. */
671 r
= dns_answer_find_soa(answer
, key
, &soa
, &flags
);
677 /* Refuse using the SOA data if it is unsigned, but the key is
679 if (authenticated
&& (flags
& DNS_ANSWER_AUTHENTICATED
) == 0)
682 r
= dns_cache_put_negative(
690 owner_family
, owner_address
);
697 /* Adding all RRs failed. Let's clean up what we already
698 * added, just in case */
701 dns_cache_remove_by_key(c
, key
);
703 DNS_ANSWER_FOREACH_FLAGS(rr
, flags
, answer
) {
704 if ((flags
& DNS_ANSWER_CACHEABLE
) == 0)
707 dns_cache_remove_by_key(c
, rr
->key
);
713 static DnsCacheItem
*dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache
*c
, DnsResourceKey
*k
) {
721 /* If we hit some OOM error, or suchlike, we don't care too
722 * much, after all this is just a cache */
724 i
= hashmap_get(c
->by_key
, k
);
728 n
= DNS_RESOURCE_KEY_NAME(k
);
730 /* Check if we have an NXDOMAIN cache item for the name, notice that we use
731 * the pseudo-type ANY for NXDOMAIN cache items. */
732 i
= hashmap_get(c
->by_key
, &DNS_RESOURCE_KEY_CONST(k
->class, DNS_TYPE_ANY
, n
));
733 if (i
&& i
->type
== DNS_CACHE_NXDOMAIN
)
736 if (dns_type_may_redirect(k
->type
)) {
737 /* Check if we have a CNAME record instead */
738 i
= hashmap_get(c
->by_key
, &DNS_RESOURCE_KEY_CONST(k
->class, DNS_TYPE_CNAME
, n
));
742 /* OK, let's look for cached DNAME records. */
747 i
= hashmap_get(c
->by_key
, &DNS_RESOURCE_KEY_CONST(k
->class, DNS_TYPE_DNAME
, n
));
751 /* Jump one label ahead */
752 r
= dns_name_parent(&n
);
758 if (k
->type
!= DNS_TYPE_NSEC
) {
759 /* Check if we have an NSEC record instead for the name. */
760 i
= hashmap_get(c
->by_key
, &DNS_RESOURCE_KEY_CONST(k
->class, DNS_TYPE_NSEC
, n
));
768 int dns_cache_lookup(DnsCache
*c
, DnsResourceKey
*key
, int *rcode
, DnsAnswer
**ret
, bool *authenticated
) {
769 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
;
772 bool nxdomain
= false;
773 _cleanup_free_
char *key_str
= NULL
;
774 DnsCacheItem
*j
, *first
, *nsec
= NULL
;
775 bool have_authenticated
= false, have_non_authenticated
= false;
781 assert(authenticated
);
783 if (key
->type
== DNS_TYPE_ANY
||
784 key
->class == DNS_CLASS_ANY
) {
786 /* If we have ANY lookups we don't use the cache, so
787 * that the caller refreshes via the network. */
789 if (log_get_max_level() >= LOG_DEBUG
) {
790 r
= dns_resource_key_to_string(key
, &key_str
);
794 log_debug("Ignoring cache for ANY lookup: %s", key_str
);
800 *rcode
= DNS_RCODE_SUCCESS
;
804 first
= dns_cache_get_by_key_follow_cname_dname_nsec(c
, key
);
806 /* If one question cannot be answered we need to refresh */
808 if (log_get_max_level() >= LOG_DEBUG
) {
809 r
= dns_resource_key_to_string(key
, &key_str
);
813 log_debug("Cache miss for %s", key_str
);
819 *rcode
= DNS_RCODE_SUCCESS
;
823 LIST_FOREACH(by_key
, j
, first
) {
825 if (j
->rr
->key
->type
== DNS_TYPE_NSEC
)
829 } else if (j
->type
== DNS_CACHE_NXDOMAIN
)
832 if (j
->authenticated
)
833 have_authenticated
= true;
835 have_non_authenticated
= true;
838 if (nsec
&& key
->type
!= DNS_TYPE_NSEC
) {
839 if (log_get_max_level() >= LOG_DEBUG
) {
840 r
= dns_resource_key_to_string(key
, &key_str
);
844 log_debug("NSEC NODATA cache hit for %s", key_str
);
847 /* We only found an NSEC record that matches our name.
848 * If it says the type doesn't exist report
849 * NODATA. Otherwise report a cache miss. */
852 *rcode
= DNS_RCODE_SUCCESS
;
853 *authenticated
= nsec
->authenticated
;
855 if (!bitmap_isset(nsec
->rr
->nsec
.types
, key
->type
) &&
856 !bitmap_isset(nsec
->rr
->nsec
.types
, DNS_TYPE_CNAME
) &&
857 !bitmap_isset(nsec
->rr
->nsec
.types
, DNS_TYPE_DNAME
)) {
866 if (log_get_max_level() >= LOG_DEBUG
) {
867 r
= dns_resource_key_to_string(key
, &key_str
);
871 log_debug("%s cache hit for %s",
873 nxdomain
? "NXDOMAIN" : "NODATA",
881 *rcode
= nxdomain
? DNS_RCODE_NXDOMAIN
: DNS_RCODE_SUCCESS
;
882 *authenticated
= have_authenticated
&& !have_non_authenticated
;
886 answer
= dns_answer_new(n
);
890 LIST_FOREACH(by_key
, j
, first
) {
894 r
= dns_answer_add(answer
, j
->rr
, 0, j
->authenticated
? DNS_ANSWER_AUTHENTICATED
: 0);
902 *rcode
= DNS_RCODE_SUCCESS
;
903 *authenticated
= have_authenticated
&& !have_non_authenticated
;
909 int dns_cache_check_conflicts(DnsCache
*cache
, DnsResourceRecord
*rr
, int owner_family
, const union in_addr_union
*owner_address
) {
910 DnsCacheItem
*i
, *first
;
911 bool same_owner
= true;
916 dns_cache_prune(cache
);
918 /* See if there's a cache entry for the same key. If there
919 * isn't there's no conflict */
920 first
= hashmap_get(cache
->by_key
, rr
->key
);
924 /* See if the RR key is owned by the same owner, if so, there
925 * isn't a conflict either */
926 LIST_FOREACH(by_key
, i
, first
) {
927 if (i
->owner_family
!= owner_family
||
928 !in_addr_equal(owner_family
, &i
->owner_address
, owner_address
)) {
936 /* See if there's the exact same RR in the cache. If yes, then
937 * there's no conflict. */
938 if (dns_cache_get(cache
, rr
))
941 /* There's a conflict */
945 int dns_cache_export_shared_to_packet(DnsCache
*cache
, DnsPacket
*p
) {
946 unsigned ancount
= 0;
954 HASHMAP_FOREACH(i
, cache
->by_key
, iterator
) {
957 LIST_FOREACH(by_key
, j
, i
) {
961 if (!j
->shared_owner
)
964 r
= dns_packet_append_rr(p
, j
->rr
, NULL
, NULL
);
965 if (r
== -EMSGSIZE
&& p
->protocol
== DNS_PROTOCOL_MDNS
) {
966 /* For mDNS, if we're unable to stuff all known answers into the given packet,
967 * allocate a new one, push the RR into that one and link it to the current one.
970 DNS_PACKET_HEADER(p
)->ancount
= htobe16(ancount
);
973 r
= dns_packet_new_query(&p
->more
, p
->protocol
, 0, true);
977 /* continue with new packet */
979 r
= dns_packet_append_rr(p
, j
->rr
, NULL
, NULL
);
989 DNS_PACKET_HEADER(p
)->ancount
= htobe16(ancount
);
994 void dns_cache_dump(DnsCache
*cache
, FILE *f
) {
1005 HASHMAP_FOREACH(i
, cache
->by_key
, iterator
) {
1008 LIST_FOREACH(by_key
, j
, i
) {
1014 t
= dns_resource_record_to_string(j
->rr
);
1023 _cleanup_free_
char *z
= NULL
;
1024 r
= dns_resource_key_to_string(j
->key
, &z
);
1032 fputs(j
->type
== DNS_CACHE_NODATA
? "NODATA" : "NXDOMAIN", f
);
1039 bool dns_cache_is_empty(DnsCache
*cache
) {
1043 return hashmap_isempty(cache
->by_key
);
1046 unsigned dns_cache_size(DnsCache
*cache
) {
1050 return hashmap_size(cache
->by_key
);