]>
Commit | Line | Data |
---|---|---|
322345fd LP |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2014 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
b5efdb8a | 22 | #include "alloc-util.h" |
58db254a | 23 | #include "dns-domain.h" |
322345fd | 24 | #include "resolved-dns-cache.h" |
7e8e0422 | 25 | #include "resolved-dns-packet.h" |
58db254a | 26 | #include "string-util.h" |
322345fd | 27 | |
cbd4560e | 28 | /* Never cache more than 1K entries */ |
322345fd | 29 | #define CACHE_MAX 1024 |
cbd4560e LP |
30 | |
31 | /* We never keep any item longer than 10min in our cache */ | |
322345fd LP |
32 | #define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE) |
33 | ||
623a4c97 LP |
34 | typedef enum DnsCacheItemType DnsCacheItemType; |
35 | typedef struct DnsCacheItem DnsCacheItem; | |
36 | ||
37 | enum DnsCacheItemType { | |
38 | DNS_CACHE_POSITIVE, | |
39 | DNS_CACHE_NODATA, | |
40 | DNS_CACHE_NXDOMAIN, | |
41 | }; | |
42 | ||
43 | struct DnsCacheItem { | |
44 | DnsResourceKey *key; | |
45 | DnsResourceRecord *rr; | |
46 | usec_t until; | |
47 | DnsCacheItemType type; | |
48 | unsigned prioq_idx; | |
931851e8 | 49 | bool authenticated; |
a4076574 LP |
50 | int owner_family; |
51 | union in_addr_union owner_address; | |
623a4c97 LP |
52 | LIST_FIELDS(DnsCacheItem, by_key); |
53 | }; | |
54 | ||
322345fd LP |
55 | static void dns_cache_item_free(DnsCacheItem *i) { |
56 | if (!i) | |
57 | return; | |
58 | ||
59 | dns_resource_record_unref(i->rr); | |
7e8e0422 | 60 | dns_resource_key_unref(i->key); |
322345fd LP |
61 | free(i); |
62 | } | |
63 | ||
64 | DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free); | |
65 | ||
66 | static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) { | |
67 | DnsCacheItem *first; | |
68 | ||
69 | assert(c); | |
70 | ||
71 | if (!i) | |
72 | return; | |
73 | ||
7e8e0422 LP |
74 | first = hashmap_get(c->by_key, i->key); |
75 | LIST_REMOVE(by_key, first, i); | |
322345fd LP |
76 | |
77 | if (first) | |
7e8e0422 | 78 | assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); |
322345fd | 79 | else |
7e8e0422 | 80 | hashmap_remove(c->by_key, i->key); |
322345fd | 81 | |
7e8e0422 | 82 | prioq_remove(c->by_expiry, i, &i->prioq_idx); |
322345fd LP |
83 | |
84 | dns_cache_item_free(i); | |
85 | } | |
86 | ||
87 | void dns_cache_flush(DnsCache *c) { | |
88 | DnsCacheItem *i; | |
89 | ||
90 | assert(c); | |
91 | ||
7e8e0422 | 92 | while ((i = hashmap_first(c->by_key))) |
322345fd LP |
93 | dns_cache_item_remove_and_free(c, i); |
94 | ||
7e8e0422 LP |
95 | assert(hashmap_size(c->by_key) == 0); |
96 | assert(prioq_size(c->by_expiry) == 0); | |
322345fd | 97 | |
cab5b059 LP |
98 | c->by_key = hashmap_free(c->by_key); |
99 | c->by_expiry = prioq_free(c->by_expiry); | |
322345fd LP |
100 | } |
101 | ||
6b34a6c9 | 102 | static bool dns_cache_remove(DnsCache *c, DnsResourceKey *key) { |
322345fd | 103 | DnsCacheItem *i; |
6b34a6c9 | 104 | bool exist = false; |
322345fd LP |
105 | |
106 | assert(c); | |
107 | assert(key); | |
108 | ||
6b34a6c9 | 109 | while ((i = hashmap_get(c->by_key, key))) { |
322345fd | 110 | dns_cache_item_remove_and_free(c, i); |
6b34a6c9 TG |
111 | exist = true; |
112 | } | |
113 | ||
114 | return exist; | |
322345fd LP |
115 | } |
116 | ||
117 | static void dns_cache_make_space(DnsCache *c, unsigned add) { | |
118 | assert(c); | |
119 | ||
120 | if (add <= 0) | |
121 | return; | |
122 | ||
123 | /* Makes space for n new entries. Note that we actually allow | |
124 | * the cache to grow beyond CACHE_MAX, but only when we shall | |
125 | * add more RRs to the cache than CACHE_MAX at once. In that | |
126 | * case the cache will be emptied completely otherwise. */ | |
127 | ||
128 | for (;;) { | |
faa133f3 | 129 | _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; |
322345fd LP |
130 | DnsCacheItem *i; |
131 | ||
7e8e0422 | 132 | if (prioq_size(c->by_expiry) <= 0) |
322345fd LP |
133 | break; |
134 | ||
7e8e0422 | 135 | if (prioq_size(c->by_expiry) + add < CACHE_MAX) |
322345fd LP |
136 | break; |
137 | ||
7e8e0422 | 138 | i = prioq_peek(c->by_expiry); |
cbd4560e LP |
139 | assert(i); |
140 | ||
faa133f3 | 141 | /* Take an extra reference to the key so that it |
cbd4560e | 142 | * doesn't go away in the middle of the remove call */ |
7e8e0422 | 143 | key = dns_resource_key_ref(i->key); |
faa133f3 | 144 | dns_cache_remove(c, key); |
322345fd LP |
145 | } |
146 | } | |
147 | ||
148 | void dns_cache_prune(DnsCache *c) { | |
149 | usec_t t = 0; | |
150 | ||
151 | assert(c); | |
152 | ||
153 | /* Remove all entries that are past their TTL */ | |
154 | ||
155 | for (;;) { | |
faa133f3 | 156 | _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; |
322345fd | 157 | DnsCacheItem *i; |
322345fd | 158 | |
7e8e0422 | 159 | i = prioq_peek(c->by_expiry); |
322345fd LP |
160 | if (!i) |
161 | break; | |
162 | ||
322345fd | 163 | if (t <= 0) |
240b589b | 164 | t = now(clock_boottime_or_monotonic()); |
322345fd | 165 | |
7e8e0422 | 166 | if (i->until > t) |
322345fd LP |
167 | break; |
168 | ||
faa133f3 | 169 | /* Take an extra reference to the key so that it |
cbd4560e | 170 | * doesn't go away in the middle of the remove call */ |
7e8e0422 | 171 | key = dns_resource_key_ref(i->key); |
faa133f3 | 172 | dns_cache_remove(c, key); |
322345fd LP |
173 | } |
174 | } | |
175 | ||
176 | static int dns_cache_item_prioq_compare_func(const void *a, const void *b) { | |
322345fd LP |
177 | const DnsCacheItem *x = a, *y = b; |
178 | ||
7e8e0422 | 179 | if (x->until < y->until) |
322345fd | 180 | return -1; |
7e8e0422 | 181 | if (x->until > y->until) |
322345fd LP |
182 | return 1; |
183 | return 0; | |
184 | } | |
185 | ||
623a4c97 | 186 | static int dns_cache_init(DnsCache *c) { |
7e8e0422 LP |
187 | int r; |
188 | ||
623a4c97 LP |
189 | assert(c); |
190 | ||
7e8e0422 LP |
191 | r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func); |
192 | if (r < 0) | |
193 | return r; | |
194 | ||
d5099efc | 195 | r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops); |
7e8e0422 LP |
196 | if (r < 0) |
197 | return r; | |
198 | ||
199 | return r; | |
200 | } | |
201 | ||
202 | static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) { | |
203 | DnsCacheItem *first; | |
204 | int r; | |
205 | ||
322345fd LP |
206 | assert(c); |
207 | assert(i); | |
322345fd | 208 | |
7e8e0422 LP |
209 | r = prioq_put(c->by_expiry, i, &i->prioq_idx); |
210 | if (r < 0) | |
211 | return r; | |
322345fd | 212 | |
7e8e0422 LP |
213 | first = hashmap_get(c->by_key, i->key); |
214 | if (first) { | |
215 | LIST_PREPEND(by_key, first, i); | |
216 | assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); | |
217 | } else { | |
218 | r = hashmap_put(c->by_key, i->key, i); | |
219 | if (r < 0) { | |
220 | prioq_remove(c->by_expiry, i, &i->prioq_idx); | |
221 | return r; | |
222 | } | |
322345fd LP |
223 | } |
224 | ||
7e8e0422 | 225 | return 0; |
322345fd LP |
226 | } |
227 | ||
faa133f3 LP |
228 | static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { |
229 | DnsCacheItem *i; | |
230 | ||
231 | assert(c); | |
232 | assert(rr); | |
233 | ||
7e8e0422 | 234 | LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key)) |
3ef77d04 | 235 | if (i->rr && dns_resource_record_equal(i->rr, rr) > 0) |
faa133f3 LP |
236 | return i; |
237 | ||
238 | return NULL; | |
239 | } | |
240 | ||
931851e8 | 241 | static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, bool authenticated, usec_t timestamp) { |
7e8e0422 LP |
242 | assert(c); |
243 | assert(i); | |
244 | assert(rr); | |
245 | ||
246 | i->type = DNS_CACHE_POSITIVE; | |
247 | ||
ece174c5 | 248 | if (!i->by_key_prev) |
7e8e0422 LP |
249 | /* We are the first item in the list, we need to |
250 | * update the key used in the hashmap */ | |
251 | ||
252 | assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0); | |
7e8e0422 LP |
253 | |
254 | dns_resource_record_ref(rr); | |
255 | dns_resource_record_unref(i->rr); | |
256 | i->rr = rr; | |
257 | ||
258 | dns_resource_key_unref(i->key); | |
259 | i->key = dns_resource_key_ref(rr->key); | |
260 | ||
931851e8 | 261 | i->authenticated = authenticated; |
7e8e0422 LP |
262 | i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); |
263 | ||
264 | prioq_reshuffle(c->by_expiry, i, &i->prioq_idx); | |
265 | } | |
266 | ||
a4076574 LP |
267 | static int dns_cache_put_positive( |
268 | DnsCache *c, | |
269 | DnsResourceRecord *rr, | |
931851e8 | 270 | bool authenticated, |
a4076574 LP |
271 | usec_t timestamp, |
272 | int owner_family, | |
273 | const union in_addr_union *owner_address) { | |
274 | ||
322345fd | 275 | _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; |
6b34a6c9 | 276 | _cleanup_free_ char *key_str = NULL; |
7e8e0422 | 277 | DnsCacheItem *existing; |
322345fd LP |
278 | int r; |
279 | ||
280 | assert(c); | |
281 | assert(rr); | |
a4076574 | 282 | assert(owner_address); |
322345fd LP |
283 | |
284 | /* New TTL is 0? Delete the entry... */ | |
285 | if (rr->ttl <= 0) { | |
04f93201 TG |
286 | r = dns_resource_key_to_string(rr->key, &key_str); |
287 | if (r < 0) | |
288 | return r; | |
6b34a6c9 | 289 | |
04f93201 | 290 | if (dns_cache_remove(c, rr->key)) |
6b34a6c9 | 291 | log_debug("Removed zero TTL entry from cache: %s", key_str); |
04f93201 TG |
292 | else |
293 | log_debug("Not caching zero TTL cache entry: %s", key_str); | |
6b34a6c9 | 294 | |
322345fd LP |
295 | return 0; |
296 | } | |
297 | ||
ddf16339 LP |
298 | if (rr->key->class == DNS_CLASS_ANY) |
299 | return 0; | |
300 | if (rr->key->type == DNS_TYPE_ANY) | |
301 | return 0; | |
302 | ||
322345fd LP |
303 | /* Entry exists already? Update TTL and timestamp */ |
304 | existing = dns_cache_get(c, rr); | |
305 | if (existing) { | |
931851e8 | 306 | dns_cache_item_update_positive(c, existing, rr, authenticated, timestamp); |
322345fd LP |
307 | return 0; |
308 | } | |
309 | ||
310 | /* Otherwise, add the new RR */ | |
623a4c97 | 311 | r = dns_cache_init(c); |
322345fd LP |
312 | if (r < 0) |
313 | return r; | |
314 | ||
7e8e0422 LP |
315 | dns_cache_make_space(c, 1); |
316 | ||
317 | i = new0(DnsCacheItem, 1); | |
318 | if (!i) | |
319 | return -ENOMEM; | |
320 | ||
321 | i->type = DNS_CACHE_POSITIVE; | |
322 | i->key = dns_resource_key_ref(rr->key); | |
323 | i->rr = dns_resource_record_ref(rr); | |
324 | i->until = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); | |
325 | i->prioq_idx = PRIOQ_IDX_NULL; | |
a4076574 LP |
326 | i->owner_family = owner_family; |
327 | i->owner_address = *owner_address; | |
931851e8 | 328 | i->authenticated = authenticated; |
7e8e0422 LP |
329 | |
330 | r = dns_cache_link_item(c, i); | |
331 | if (r < 0) | |
332 | return r; | |
333 | ||
6b34a6c9 TG |
334 | r = dns_resource_key_to_string(i->key, &key_str); |
335 | if (r < 0) | |
336 | return r; | |
337 | ||
338 | log_debug("Added cache entry for %s", key_str); | |
339 | ||
7e8e0422 LP |
340 | i = NULL; |
341 | return 0; | |
342 | } | |
343 | ||
a4076574 LP |
344 | static int dns_cache_put_negative( |
345 | DnsCache *c, | |
346 | DnsResourceKey *key, | |
347 | int rcode, | |
931851e8 | 348 | bool authenticated, |
a4076574 LP |
349 | usec_t timestamp, |
350 | uint32_t soa_ttl, | |
351 | int owner_family, | |
352 | const union in_addr_union *owner_address) { | |
353 | ||
7e8e0422 | 354 | _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; |
6b34a6c9 | 355 | _cleanup_free_ char *key_str = NULL; |
7e8e0422 LP |
356 | int r; |
357 | ||
358 | assert(c); | |
359 | assert(key); | |
a4076574 | 360 | assert(owner_address); |
7e8e0422 LP |
361 | |
362 | dns_cache_remove(c, key); | |
363 | ||
ddf16339 LP |
364 | if (key->class == DNS_CLASS_ANY) |
365 | return 0; | |
366 | if (key->type == DNS_TYPE_ANY) | |
367 | return 0; | |
6b34a6c9 TG |
368 | if (soa_ttl <= 0) { |
369 | r = dns_resource_key_to_string(key, &key_str); | |
370 | if (r < 0) | |
371 | return r; | |
372 | ||
04f93201 | 373 | log_debug("Not caching negative entry with zero SOA TTL: %s", key_str); |
6b34a6c9 | 374 | |
95dd6257 | 375 | return 0; |
6b34a6c9 | 376 | } |
ddf16339 | 377 | |
7e8e0422 LP |
378 | if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) |
379 | return 0; | |
380 | ||
623a4c97 | 381 | r = dns_cache_init(c); |
322345fd LP |
382 | if (r < 0) |
383 | return r; | |
384 | ||
385 | dns_cache_make_space(c, 1); | |
386 | ||
387 | i = new0(DnsCacheItem, 1); | |
388 | if (!i) | |
389 | return -ENOMEM; | |
390 | ||
7e8e0422 LP |
391 | i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; |
392 | i->key = dns_resource_key_ref(key); | |
393 | i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); | |
394 | i->prioq_idx = PRIOQ_IDX_NULL; | |
a4076574 LP |
395 | i->owner_family = owner_family; |
396 | i->owner_address = *owner_address; | |
931851e8 | 397 | i->authenticated = authenticated; |
322345fd | 398 | |
7e8e0422 | 399 | r = dns_cache_link_item(c, i); |
322345fd LP |
400 | if (r < 0) |
401 | return r; | |
402 | ||
6b34a6c9 TG |
403 | r = dns_resource_key_to_string(i->key, &key_str); |
404 | if (r < 0) | |
405 | return r; | |
406 | ||
407 | log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str); | |
408 | ||
322345fd | 409 | i = NULL; |
322345fd LP |
410 | return 0; |
411 | } | |
412 | ||
a4076574 LP |
413 | int dns_cache_put( |
414 | DnsCache *c, | |
8e427d9b | 415 | DnsResourceKey *key, |
a4076574 LP |
416 | int rcode, |
417 | DnsAnswer *answer, | |
418 | unsigned max_rrs, | |
931851e8 | 419 | bool authenticated, |
a4076574 LP |
420 | usec_t timestamp, |
421 | int owner_family, | |
422 | const union in_addr_union *owner_address) { | |
423 | ||
8e427d9b | 424 | DnsResourceRecord *soa = NULL; |
eff91ee0 | 425 | unsigned cache_keys, i; |
322345fd LP |
426 | int r; |
427 | ||
428 | assert(c); | |
322345fd | 429 | |
8e427d9b TG |
430 | if (key) { |
431 | /* First, if we were passed a key, delete all matching old RRs, | |
eff91ee0 | 432 | * so that we only keep complete by_key in place. */ |
8e427d9b | 433 | dns_cache_remove(c, key); |
eff91ee0 | 434 | } |
0ec7c46e LP |
435 | |
436 | if (!answer) | |
437 | return 0; | |
438 | ||
faa133f3 | 439 | for (i = 0; i < answer->n_rrs; i++) |
78c6a153 | 440 | dns_cache_remove(c, answer->items[i].rr->key); |
322345fd | 441 | |
7e8e0422 LP |
442 | /* We only care for positive replies and NXDOMAINs, on all |
443 | * other replies we will simply flush the respective entries, | |
444 | * and that's it */ | |
445 | ||
446 | if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) | |
447 | return 0; | |
448 | ||
eff91ee0 DM |
449 | cache_keys = answer->n_rrs; |
450 | ||
8e427d9b TG |
451 | if (key) |
452 | cache_keys ++; | |
eff91ee0 | 453 | |
7e8e0422 | 454 | /* Make some space for our new entries */ |
eff91ee0 | 455 | dns_cache_make_space(c, cache_keys); |
322345fd | 456 | |
7e8e0422 | 457 | if (timestamp <= 0) |
240b589b | 458 | timestamp = now(clock_boottime_or_monotonic()); |
322345fd | 459 | |
7e8e0422 | 460 | /* Second, add in positive entries for all contained RRs */ |
d2f47562 | 461 | for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) { |
90325e8c DM |
462 | DnsResourceRecord *rr = answer->items[i].rr; |
463 | ||
464 | if (rr->key->cache_flush) | |
465 | dns_cache_remove(c, rr->key); | |
466 | ||
467 | r = dns_cache_put_positive(c, rr, authenticated, timestamp, owner_family, owner_address); | |
7e8e0422 LP |
468 | if (r < 0) |
469 | goto fail; | |
470 | } | |
471 | ||
8e427d9b | 472 | if (!key) |
eff91ee0 DM |
473 | return 0; |
474 | ||
8e427d9b TG |
475 | /* Third, add in negative entries if the key has no RR */ |
476 | r = dns_answer_contains(answer, key); | |
477 | if (r < 0) | |
478 | goto fail; | |
479 | if (r > 0) | |
480 | return 0; | |
7e8e0422 | 481 | |
8e427d9b TG |
482 | /* See https://tools.ietf.org/html/rfc2308, which |
483 | * say that a matching SOA record in the packet | |
484 | * is used to to enable negative caching. */ | |
0a18f3e5 | 485 | |
8e427d9b TG |
486 | r = dns_answer_find_soa(answer, key, &soa); |
487 | if (r < 0) | |
488 | goto fail; | |
489 | if (r == 0) | |
490 | return 0; | |
7e8e0422 | 491 | |
5eefe544 TG |
492 | /* Also, if the requested key is an alias, the negative response should |
493 | be cached for each name in the redirect chain. Any CNAME record in | |
494 | the response is from the redirection chain, though only the final one | |
495 | is guaranteed to be included. This means that we cannot verify the | |
496 | chain and that we need to cache them all as it may be incomplete. */ | |
497 | for (i = 0; i < answer->n_rrs; i++) { | |
498 | DnsResourceRecord *answer_rr = answer->items[i].rr; | |
499 | ||
500 | if (answer_rr->key->type == DNS_TYPE_CNAME) { | |
501 | _cleanup_(dns_resource_key_unrefp) DnsResourceKey *canonical_key = NULL; | |
502 | ||
503 | canonical_key = dns_resource_key_new_redirect(key, answer_rr); | |
504 | if (!canonical_key) | |
505 | goto fail; | |
506 | ||
507 | /* Let's not add negative cache entries for records outside the current zone. */ | |
508 | if (!dns_answer_match_soa(canonical_key, soa->key)) | |
509 | continue; | |
510 | ||
931851e8 | 511 | r = dns_cache_put_negative(c, canonical_key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address); |
5eefe544 TG |
512 | if (r < 0) |
513 | goto fail; | |
514 | } | |
515 | } | |
516 | ||
931851e8 | 517 | r = dns_cache_put_negative(c, key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address); |
8e427d9b TG |
518 | if (r < 0) |
519 | goto fail; | |
322345fd LP |
520 | |
521 | return 0; | |
522 | ||
523 | fail: | |
524 | /* Adding all RRs failed. Let's clean up what we already | |
525 | * added, just in case */ | |
526 | ||
8e427d9b TG |
527 | if (key) |
528 | dns_cache_remove(c, key); | |
eff91ee0 | 529 | |
7e8e0422 | 530 | for (i = 0; i < answer->n_rrs; i++) |
78c6a153 | 531 | dns_cache_remove(c, answer->items[i].rr->key); |
322345fd LP |
532 | |
533 | return r; | |
534 | } | |
535 | ||
37da8931 | 536 | static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) { |
58db254a LP |
537 | DnsCacheItem *i; |
538 | const char *n; | |
539 | int r; | |
5643c00a TG |
540 | |
541 | assert(c); | |
542 | assert(k); | |
543 | ||
58db254a LP |
544 | /* If we hit some OOM error, or suchlike, we don't care too |
545 | * much, after all this is just a cache */ | |
546 | ||
5643c00a | 547 | i = hashmap_get(c->by_key, k); |
37da8931 LP |
548 | if (i || IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME, DNS_TYPE_NSEC)) |
549 | return i; | |
550 | ||
551 | n = DNS_RESOURCE_KEY_NAME(k); | |
552 | ||
553 | /* Check if we have an NSEC record instead for the name. */ | |
1b4f6e79 | 554 | i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n)); |
37da8931 | 555 | if (i) |
5643c00a TG |
556 | return i; |
557 | ||
58db254a | 558 | /* Check if we have a CNAME record instead */ |
1b4f6e79 | 559 | i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n)); |
58db254a LP |
560 | if (i) |
561 | return i; | |
562 | ||
563 | /* OK, let's look for cached DNAME records. */ | |
58db254a | 564 | for (;;) { |
58db254a LP |
565 | char label[DNS_LABEL_MAX]; |
566 | ||
567 | if (isempty(n)) | |
568 | return NULL; | |
5643c00a | 569 | |
1b4f6e79 | 570 | i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n)); |
58db254a LP |
571 | if (i) |
572 | return i; | |
573 | ||
574 | /* Jump one label ahead */ | |
575 | r = dns_label_unescape(&n, label, sizeof(label)); | |
576 | if (r <= 0) | |
577 | return NULL; | |
578 | } | |
579 | ||
580 | return NULL; | |
5643c00a TG |
581 | } |
582 | ||
931851e8 | 583 | int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) { |
faa133f3 | 584 | _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; |
f52e61da | 585 | unsigned n = 0; |
322345fd | 586 | int r; |
7e8e0422 | 587 | bool nxdomain = false; |
f52e61da | 588 | _cleanup_free_ char *key_str = NULL; |
931851e8 LP |
589 | DnsCacheItem *j, *first, *nsec = NULL; |
590 | bool have_authenticated = false, have_non_authenticated = false; | |
322345fd LP |
591 | |
592 | assert(c); | |
f52e61da | 593 | assert(key); |
623a4c97 | 594 | assert(rcode); |
faa133f3 | 595 | assert(ret); |
931851e8 | 596 | assert(authenticated); |
322345fd | 597 | |
f52e61da LP |
598 | if (key->type == DNS_TYPE_ANY || |
599 | key->class == DNS_CLASS_ANY) { | |
322345fd | 600 | |
931851e8 LP |
601 | /* If we have ANY lookups we don't use the cache, so |
602 | * that the caller refreshes via the network. */ | |
322345fd | 603 | |
f52e61da LP |
604 | r = dns_resource_key_to_string(key, &key_str); |
605 | if (r < 0) | |
606 | return r; | |
6b34a6c9 | 607 | |
f52e61da | 608 | log_debug("Ignoring cache for ANY lookup: %s", key_str); |
6b34a6c9 | 609 | |
f52e61da LP |
610 | *ret = NULL; |
611 | *rcode = DNS_RCODE_SUCCESS; | |
612 | return 0; | |
613 | } | |
6b34a6c9 | 614 | |
37da8931 | 615 | first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key); |
f52e61da LP |
616 | if (!first) { |
617 | /* If one question cannot be answered we need to refresh */ | |
ddf16339 | 618 | |
f52e61da LP |
619 | r = dns_resource_key_to_string(key, &key_str); |
620 | if (r < 0) | |
621 | return r; | |
6b34a6c9 | 622 | |
f52e61da | 623 | log_debug("Cache miss for %s", key_str); |
6b34a6c9 | 624 | |
f52e61da LP |
625 | *ret = NULL; |
626 | *rcode = DNS_RCODE_SUCCESS; | |
627 | return 0; | |
628 | } | |
6b34a6c9 | 629 | |
f52e61da | 630 | LIST_FOREACH(by_key, j, first) { |
37da8931 LP |
631 | if (j->rr) { |
632 | if (j->rr->key->type == DNS_TYPE_NSEC) | |
931851e8 LP |
633 | nsec = j; |
634 | ||
f52e61da | 635 | n++; |
37da8931 | 636 | } else if (j->type == DNS_CACHE_NXDOMAIN) |
f52e61da | 637 | nxdomain = true; |
931851e8 LP |
638 | |
639 | if (j->authenticated) | |
640 | have_authenticated = true; | |
641 | else | |
642 | have_non_authenticated = true; | |
f52e61da | 643 | } |
6b34a6c9 | 644 | |
f52e61da LP |
645 | r = dns_resource_key_to_string(key, &key_str); |
646 | if (r < 0) | |
647 | return r; | |
322345fd | 648 | |
37da8931 LP |
649 | if (nsec && key->type != DNS_TYPE_NSEC) { |
650 | log_debug("NSEC NODATA cache hit for %s", key_str); | |
651 | ||
652 | /* We only found an NSEC record that matches our name. | |
653 | * If it says the type doesn't exit report | |
654 | * NODATA. Otherwise report a cache miss. */ | |
655 | ||
656 | *ret = NULL; | |
657 | *rcode = DNS_RCODE_SUCCESS; | |
931851e8 | 658 | *authenticated = nsec->authenticated; |
37da8931 | 659 | |
931851e8 LP |
660 | return !bitmap_isset(nsec->rr->nsec.types, key->type) && |
661 | !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && | |
662 | !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME); | |
37da8931 LP |
663 | } |
664 | ||
f52e61da | 665 | log_debug("%s cache hit for %s", |
37da8931 LP |
666 | n > 0 ? "Positive" : |
667 | nxdomain ? "NXDOMAIN" : "NODATA", | |
f52e61da | 668 | key_str); |
faa133f3 | 669 | |
7e8e0422 LP |
670 | if (n <= 0) { |
671 | *ret = NULL; | |
672 | *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; | |
931851e8 | 673 | *authenticated = have_authenticated && !have_non_authenticated; |
7e8e0422 LP |
674 | return 1; |
675 | } | |
faa133f3 LP |
676 | |
677 | answer = dns_answer_new(n); | |
678 | if (!answer) | |
679 | return -ENOMEM; | |
322345fd | 680 | |
f52e61da LP |
681 | LIST_FOREACH(by_key, j, first) { |
682 | if (!j->rr) | |
683 | continue; | |
684 | ||
685 | r = dns_answer_add(answer, j->rr, 0); | |
686 | if (r < 0) | |
687 | return r; | |
322345fd LP |
688 | } |
689 | ||
faa133f3 | 690 | *ret = answer; |
7e8e0422 | 691 | *rcode = DNS_RCODE_SUCCESS; |
931851e8 | 692 | *authenticated = have_authenticated && !have_non_authenticated; |
faa133f3 LP |
693 | answer = NULL; |
694 | ||
695 | return n; | |
322345fd | 696 | } |
a4076574 LP |
697 | |
698 | int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) { | |
699 | DnsCacheItem *i, *first; | |
700 | bool same_owner = true; | |
701 | ||
702 | assert(cache); | |
703 | assert(rr); | |
704 | ||
705 | dns_cache_prune(cache); | |
706 | ||
707 | /* See if there's a cache entry for the same key. If there | |
708 | * isn't there's no conflict */ | |
709 | first = hashmap_get(cache->by_key, rr->key); | |
710 | if (!first) | |
711 | return 0; | |
712 | ||
713 | /* See if the RR key is owned by the same owner, if so, there | |
714 | * isn't a conflict either */ | |
715 | LIST_FOREACH(by_key, i, first) { | |
716 | if (i->owner_family != owner_family || | |
717 | !in_addr_equal(owner_family, &i->owner_address, owner_address)) { | |
718 | same_owner = false; | |
719 | break; | |
720 | } | |
721 | } | |
722 | if (same_owner) | |
723 | return 0; | |
724 | ||
725 | /* See if there's the exact same RR in the cache. If yes, then | |
726 | * there's no conflict. */ | |
727 | if (dns_cache_get(cache, rr)) | |
728 | return 0; | |
729 | ||
730 | /* There's a conflict */ | |
731 | return 1; | |
732 | } | |
4d506d6b LP |
733 | |
734 | void dns_cache_dump(DnsCache *cache, FILE *f) { | |
735 | Iterator iterator; | |
736 | DnsCacheItem *i; | |
737 | int r; | |
738 | ||
739 | if (!cache) | |
740 | return; | |
741 | ||
742 | if (!f) | |
743 | f = stdout; | |
744 | ||
745 | HASHMAP_FOREACH(i, cache->by_key, iterator) { | |
746 | DnsCacheItem *j; | |
747 | ||
748 | LIST_FOREACH(by_key, j, i) { | |
749 | _cleanup_free_ char *t = NULL; | |
750 | ||
751 | fputc('\t', f); | |
752 | ||
753 | if (j->rr) { | |
754 | r = dns_resource_record_to_string(j->rr, &t); | |
755 | if (r < 0) { | |
756 | log_oom(); | |
757 | continue; | |
758 | } | |
759 | ||
760 | fputs(t, f); | |
761 | fputc('\n', f); | |
762 | } else { | |
763 | r = dns_resource_key_to_string(j->key, &t); | |
764 | if (r < 0) { | |
765 | log_oom(); | |
766 | continue; | |
767 | } | |
768 | ||
769 | fputs(t, f); | |
770 | fputs(" -- ", f); | |
771 | fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); | |
772 | fputc('\n', f); | |
773 | } | |
774 | } | |
775 | } | |
776 | } | |
777 | ||
778 | bool dns_cache_is_empty(DnsCache *cache) { | |
779 | if (!cache) | |
780 | return true; | |
781 | ||
782 | return hashmap_isempty(cache->by_key); | |
783 | } |