]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-cache.c
hashmap: introduce hash_ops to make struct Hashmap smaller
[thirdparty/systemd.git] / src / resolve / resolved-dns-cache.c
CommitLineData
322345fd
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2014 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include "resolved-dns-cache.h"
7e8e0422 23#include "resolved-dns-packet.h"
322345fd 24
cbd4560e 25/* Never cache more than 1K entries */
322345fd 26#define CACHE_MAX 1024
cbd4560e
LP
27
28/* We never keep any item longer than 10min in our cache */
322345fd
LP
29#define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE)
30
623a4c97
LP
31typedef enum DnsCacheItemType DnsCacheItemType;
32typedef struct DnsCacheItem DnsCacheItem;
33
34enum DnsCacheItemType {
35 DNS_CACHE_POSITIVE,
36 DNS_CACHE_NODATA,
37 DNS_CACHE_NXDOMAIN,
38};
39
40struct DnsCacheItem {
41 DnsResourceKey *key;
42 DnsResourceRecord *rr;
43 usec_t until;
44 DnsCacheItemType type;
45 unsigned prioq_idx;
a4076574
LP
46 int owner_family;
47 union in_addr_union owner_address;
623a4c97
LP
48 LIST_FIELDS(DnsCacheItem, by_key);
49};
50
322345fd
LP
51static void dns_cache_item_free(DnsCacheItem *i) {
52 if (!i)
53 return;
54
55 dns_resource_record_unref(i->rr);
7e8e0422 56 dns_resource_key_unref(i->key);
322345fd
LP
57 free(i);
58}
59
60DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free);
61
62static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) {
63 DnsCacheItem *first;
64
65 assert(c);
66
67 if (!i)
68 return;
69
7e8e0422
LP
70 first = hashmap_get(c->by_key, i->key);
71 LIST_REMOVE(by_key, first, i);
322345fd
LP
72
73 if (first)
7e8e0422 74 assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
322345fd 75 else
7e8e0422 76 hashmap_remove(c->by_key, i->key);
322345fd 77
7e8e0422 78 prioq_remove(c->by_expiry, i, &i->prioq_idx);
322345fd
LP
79
80 dns_cache_item_free(i);
81}
82
83void dns_cache_flush(DnsCache *c) {
84 DnsCacheItem *i;
85
86 assert(c);
87
7e8e0422 88 while ((i = hashmap_first(c->by_key)))
322345fd
LP
89 dns_cache_item_remove_and_free(c, i);
90
7e8e0422
LP
91 assert(hashmap_size(c->by_key) == 0);
92 assert(prioq_size(c->by_expiry) == 0);
322345fd 93
7e8e0422
LP
94 hashmap_free(c->by_key);
95 c->by_key = NULL;
322345fd 96
7e8e0422
LP
97 prioq_free(c->by_expiry);
98 c->by_expiry = NULL;
322345fd
LP
99}
100
7e8e0422 101static void dns_cache_remove(DnsCache *c, DnsResourceKey *key) {
322345fd
LP
102 DnsCacheItem *i;
103
104 assert(c);
105 assert(key);
106
7e8e0422 107 while ((i = hashmap_get(c->by_key, key)))
322345fd
LP
108 dns_cache_item_remove_and_free(c, i);
109}
110
111static void dns_cache_make_space(DnsCache *c, unsigned add) {
112 assert(c);
113
114 if (add <= 0)
115 return;
116
117 /* Makes space for n new entries. Note that we actually allow
118 * the cache to grow beyond CACHE_MAX, but only when we shall
119 * add more RRs to the cache than CACHE_MAX at once. In that
120 * case the cache will be emptied completely otherwise. */
121
122 for (;;) {
faa133f3 123 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
322345fd
LP
124 DnsCacheItem *i;
125
7e8e0422 126 if (prioq_size(c->by_expiry) <= 0)
322345fd
LP
127 break;
128
7e8e0422 129 if (prioq_size(c->by_expiry) + add < CACHE_MAX)
322345fd
LP
130 break;
131
7e8e0422 132 i = prioq_peek(c->by_expiry);
cbd4560e
LP
133 assert(i);
134
faa133f3 135 /* Take an extra reference to the key so that it
cbd4560e 136 * doesn't go away in the middle of the remove call */
7e8e0422 137 key = dns_resource_key_ref(i->key);
faa133f3 138 dns_cache_remove(c, key);
322345fd
LP
139 }
140}
141
142void dns_cache_prune(DnsCache *c) {
143 usec_t t = 0;
144
145 assert(c);
146
147 /* Remove all entries that are past their TTL */
148
149 for (;;) {
faa133f3 150 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
322345fd 151 DnsCacheItem *i;
322345fd 152
7e8e0422 153 i = prioq_peek(c->by_expiry);
322345fd
LP
154 if (!i)
155 break;
156
322345fd 157 if (t <= 0)
9a015429 158 t = now(CLOCK_BOOTTIME);
322345fd 159
7e8e0422 160 if (i->until > t)
322345fd
LP
161 break;
162
faa133f3 163 /* Take an extra reference to the key so that it
cbd4560e 164 * doesn't go away in the middle of the remove call */
7e8e0422 165 key = dns_resource_key_ref(i->key);
faa133f3 166 dns_cache_remove(c, key);
322345fd
LP
167 }
168}
169
170static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
322345fd
LP
171 const DnsCacheItem *x = a, *y = b;
172
7e8e0422 173 if (x->until < y->until)
322345fd 174 return -1;
7e8e0422 175 if (x->until > y->until)
322345fd
LP
176 return 1;
177 return 0;
178}
179
623a4c97 180static int dns_cache_init(DnsCache *c) {
7e8e0422
LP
181 int r;
182
623a4c97
LP
183 assert(c);
184
7e8e0422
LP
185 r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func);
186 if (r < 0)
187 return r;
188
d5099efc 189 r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops);
7e8e0422
LP
190 if (r < 0)
191 return r;
192
193 return r;
194}
195
196static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) {
197 DnsCacheItem *first;
198 int r;
199
322345fd
LP
200 assert(c);
201 assert(i);
322345fd 202
7e8e0422
LP
203 r = prioq_put(c->by_expiry, i, &i->prioq_idx);
204 if (r < 0)
205 return r;
322345fd 206
7e8e0422
LP
207 first = hashmap_get(c->by_key, i->key);
208 if (first) {
209 LIST_PREPEND(by_key, first, i);
210 assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
211 } else {
212 r = hashmap_put(c->by_key, i->key, i);
213 if (r < 0) {
214 prioq_remove(c->by_expiry, i, &i->prioq_idx);
215 return r;
216 }
322345fd
LP
217 }
218
7e8e0422 219 return 0;
322345fd
LP
220}
221
faa133f3
LP
222static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
223 DnsCacheItem *i;
224
225 assert(c);
226 assert(rr);
227
7e8e0422 228 LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key))
3ef77d04 229 if (i->rr && dns_resource_record_equal(i->rr, rr) > 0)
faa133f3
LP
230 return i;
231
232 return NULL;
233}
234
7e8e0422
LP
235static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, usec_t timestamp) {
236 assert(c);
237 assert(i);
238 assert(rr);
239
240 i->type = DNS_CACHE_POSITIVE;
241
242 if (!i->by_key_prev) {
243 /* We are the first item in the list, we need to
244 * update the key used in the hashmap */
245
246 assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0);
247 }
248
249 dns_resource_record_ref(rr);
250 dns_resource_record_unref(i->rr);
251 i->rr = rr;
252
253 dns_resource_key_unref(i->key);
254 i->key = dns_resource_key_ref(rr->key);
255
256 i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
257
258 prioq_reshuffle(c->by_expiry, i, &i->prioq_idx);
259}
260
a4076574
LP
261static int dns_cache_put_positive(
262 DnsCache *c,
263 DnsResourceRecord *rr,
264 usec_t timestamp,
265 int owner_family,
266 const union in_addr_union *owner_address) {
267
322345fd 268 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
7e8e0422 269 DnsCacheItem *existing;
322345fd
LP
270 int r;
271
272 assert(c);
273 assert(rr);
a4076574 274 assert(owner_address);
322345fd
LP
275
276 /* New TTL is 0? Delete the entry... */
277 if (rr->ttl <= 0) {
faa133f3 278 dns_cache_remove(c, rr->key);
322345fd
LP
279 return 0;
280 }
281
ddf16339
LP
282 if (rr->key->class == DNS_CLASS_ANY)
283 return 0;
284 if (rr->key->type == DNS_TYPE_ANY)
285 return 0;
286
322345fd
LP
287 /* Entry exists already? Update TTL and timestamp */
288 existing = dns_cache_get(c, rr);
289 if (existing) {
7e8e0422 290 dns_cache_item_update_positive(c, existing, rr, timestamp);
322345fd
LP
291 return 0;
292 }
293
294 /* Otherwise, add the new RR */
623a4c97 295 r = dns_cache_init(c);
322345fd
LP
296 if (r < 0)
297 return r;
298
7e8e0422
LP
299 dns_cache_make_space(c, 1);
300
301 i = new0(DnsCacheItem, 1);
302 if (!i)
303 return -ENOMEM;
304
305 i->type = DNS_CACHE_POSITIVE;
306 i->key = dns_resource_key_ref(rr->key);
307 i->rr = dns_resource_record_ref(rr);
308 i->until = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
309 i->prioq_idx = PRIOQ_IDX_NULL;
a4076574
LP
310 i->owner_family = owner_family;
311 i->owner_address = *owner_address;
7e8e0422
LP
312
313 r = dns_cache_link_item(c, i);
314 if (r < 0)
315 return r;
316
317 i = NULL;
318 return 0;
319}
320
a4076574
LP
321static int dns_cache_put_negative(
322 DnsCache *c,
323 DnsResourceKey *key,
324 int rcode,
325 usec_t timestamp,
326 uint32_t soa_ttl,
327 int owner_family,
328 const union in_addr_union *owner_address) {
329
7e8e0422
LP
330 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
331 int r;
332
333 assert(c);
334 assert(key);
a4076574 335 assert(owner_address);
7e8e0422
LP
336
337 dns_cache_remove(c, key);
338
ddf16339
LP
339 if (key->class == DNS_CLASS_ANY)
340 return 0;
341 if (key->type == DNS_TYPE_ANY)
342 return 0;
95dd6257
LP
343 if (soa_ttl <= 0)
344 return 0;
ddf16339 345
7e8e0422
LP
346 if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
347 return 0;
348
623a4c97 349 r = dns_cache_init(c);
322345fd
LP
350 if (r < 0)
351 return r;
352
353 dns_cache_make_space(c, 1);
354
355 i = new0(DnsCacheItem, 1);
356 if (!i)
357 return -ENOMEM;
358
7e8e0422
LP
359 i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN;
360 i->key = dns_resource_key_ref(key);
361 i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
362 i->prioq_idx = PRIOQ_IDX_NULL;
a4076574
LP
363 i->owner_family = owner_family;
364 i->owner_address = *owner_address;
322345fd 365
7e8e0422 366 r = dns_cache_link_item(c, i);
322345fd
LP
367 if (r < 0)
368 return r;
369
322345fd 370 i = NULL;
322345fd
LP
371 return 0;
372}
373
a4076574
LP
374int dns_cache_put(
375 DnsCache *c,
376 DnsQuestion *q,
377 int rcode,
378 DnsAnswer *answer,
379 unsigned max_rrs,
380 usec_t timestamp,
381 int owner_family,
382 const union in_addr_union *owner_address) {
383
7e8e0422 384 unsigned i;
322345fd
LP
385 int r;
386
387 assert(c);
0ec7c46e 388 assert(q);
322345fd 389
7e8e0422
LP
390 /* First, delete all matching old RRs, so that we only keep
391 * complete by_key in place. */
392 for (i = 0; i < q->n_keys; i++)
393 dns_cache_remove(c, q->keys[i]);
0ec7c46e
LP
394
395 if (!answer)
396 return 0;
397
faa133f3
LP
398 for (i = 0; i < answer->n_rrs; i++)
399 dns_cache_remove(c, answer->rrs[i]->key);
322345fd 400
7e8e0422
LP
401 /* We only care for positive replies and NXDOMAINs, on all
402 * other replies we will simply flush the respective entries,
403 * and that's it */
404
405 if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
406 return 0;
407
408 /* Make some space for our new entries */
409 dns_cache_make_space(c, answer->n_rrs + q->n_keys);
322345fd 410
7e8e0422 411 if (timestamp <= 0)
9a015429 412 timestamp = now(CLOCK_BOOTTIME);
322345fd 413
7e8e0422 414 /* Second, add in positive entries for all contained RRs */
d2f47562 415 for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) {
a4076574 416 r = dns_cache_put_positive(c, answer->rrs[i], timestamp, owner_family, owner_address);
7e8e0422
LP
417 if (r < 0)
418 goto fail;
419 }
420
421 /* Third, add in negative entries for all keys with no RR */
422 for (i = 0; i < q->n_keys; i++) {
423 DnsResourceRecord *soa = NULL;
424
425 r = dns_answer_contains(answer, q->keys[i]);
426 if (r < 0)
427 goto fail;
428 if (r > 0)
429 continue;
430
431 r = dns_answer_find_soa(answer, q->keys[i], &soa);
432 if (r < 0)
433 goto fail;
434 if (r == 0)
435 continue;
436
a4076574 437 r = dns_cache_put_negative(c, q->keys[i], rcode, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
322345fd
LP
438 if (r < 0)
439 goto fail;
322345fd
LP
440 }
441
442 return 0;
443
444fail:
445 /* Adding all RRs failed. Let's clean up what we already
446 * added, just in case */
447
7e8e0422
LP
448 for (i = 0; i < q->n_keys; i++)
449 dns_cache_remove(c, q->keys[i]);
450 for (i = 0; i < answer->n_rrs; i++)
faa133f3 451 dns_cache_remove(c, answer->rrs[i]->key);
322345fd
LP
452
453 return r;
454}
455
7e8e0422 456int dns_cache_lookup(DnsCache *c, DnsQuestion *q, int *rcode, DnsAnswer **ret) {
faa133f3
LP
457 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
458 unsigned i, n = 0;
322345fd 459 int r;
7e8e0422 460 bool nxdomain = false;
322345fd
LP
461
462 assert(c);
faa133f3 463 assert(q);
623a4c97 464 assert(rcode);
faa133f3 465 assert(ret);
322345fd 466
faa133f3
LP
467 if (q->n_keys <= 0) {
468 *ret = NULL;
7e8e0422 469 *rcode = 0;
322345fd
LP
470 return 0;
471 }
472
faa133f3 473 for (i = 0; i < q->n_keys; i++) {
322345fd
LP
474 DnsCacheItem *j;
475
ddf16339
LP
476 if (q->keys[i]->type == DNS_TYPE_ANY ||
477 q->keys[i]->class == DNS_CLASS_ANY) {
478 /* If we have ANY lookups we simply refresh */
479 *ret = NULL;
480 *rcode = 0;
481 return 0;
482 }
483
7e8e0422 484 j = hashmap_get(c->by_key, q->keys[i]);
322345fd 485 if (!j) {
faa133f3
LP
486 /* If one question cannot be answered we need to refresh */
487 *ret = NULL;
7e8e0422 488 *rcode = 0;
faa133f3 489 return 0;
322345fd
LP
490 }
491
7e8e0422
LP
492 LIST_FOREACH(by_key, j, j) {
493 if (j->rr)
494 n++;
495 else if (j->type == DNS_CACHE_NXDOMAIN)
496 nxdomain = true;
497 }
faa133f3
LP
498 }
499
7e8e0422
LP
500 if (n <= 0) {
501 *ret = NULL;
502 *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
503 return 1;
504 }
faa133f3
LP
505
506 answer = dns_answer_new(n);
507 if (!answer)
508 return -ENOMEM;
322345fd 509
faa133f3
LP
510 for (i = 0; i < q->n_keys; i++) {
511 DnsCacheItem *j;
322345fd 512
7e8e0422
LP
513 j = hashmap_get(c->by_key, q->keys[i]);
514 LIST_FOREACH(by_key, j, j) {
515 if (j->rr) {
516 r = dns_answer_add(answer, j->rr);
517 if (r < 0)
518 return r;
519 }
322345fd
LP
520 }
521 }
522
faa133f3 523 *ret = answer;
7e8e0422 524 *rcode = DNS_RCODE_SUCCESS;
faa133f3
LP
525 answer = NULL;
526
527 return n;
322345fd 528}
a4076574
LP
529
530int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) {
531 DnsCacheItem *i, *first;
532 bool same_owner = true;
533
534 assert(cache);
535 assert(rr);
536
537 dns_cache_prune(cache);
538
539 /* See if there's a cache entry for the same key. If there
540 * isn't there's no conflict */
541 first = hashmap_get(cache->by_key, rr->key);
542 if (!first)
543 return 0;
544
545 /* See if the RR key is owned by the same owner, if so, there
546 * isn't a conflict either */
547 LIST_FOREACH(by_key, i, first) {
548 if (i->owner_family != owner_family ||
549 !in_addr_equal(owner_family, &i->owner_address, owner_address)) {
550 same_owner = false;
551 break;
552 }
553 }
554 if (same_owner)
555 return 0;
556
557 /* See if there's the exact same RR in the cache. If yes, then
558 * there's no conflict. */
559 if (dns_cache_get(cache, rr))
560 return 0;
561
562 /* There's a conflict */
563 return 1;
564}