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