]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-cache.c
resolved: scope - write() unicast DNS packets
[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
cab5b059
LP
94 c->by_key = hashmap_free(c->by_key);
95 c->by_expiry = prioq_free(c->by_expiry);
322345fd
LP
96}
97
7e8e0422 98static void dns_cache_remove(DnsCache *c, DnsResourceKey *key) {
322345fd
LP
99 DnsCacheItem *i;
100
101 assert(c);
102 assert(key);
103
7e8e0422 104 while ((i = hashmap_get(c->by_key, key)))
322345fd
LP
105 dns_cache_item_remove_and_free(c, i);
106}
107
108static void dns_cache_make_space(DnsCache *c, unsigned add) {
109 assert(c);
110
111 if (add <= 0)
112 return;
113
114 /* Makes space for n new entries. Note that we actually allow
115 * the cache to grow beyond CACHE_MAX, but only when we shall
116 * add more RRs to the cache than CACHE_MAX at once. In that
117 * case the cache will be emptied completely otherwise. */
118
119 for (;;) {
faa133f3 120 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
322345fd
LP
121 DnsCacheItem *i;
122
7e8e0422 123 if (prioq_size(c->by_expiry) <= 0)
322345fd
LP
124 break;
125
7e8e0422 126 if (prioq_size(c->by_expiry) + add < CACHE_MAX)
322345fd
LP
127 break;
128
7e8e0422 129 i = prioq_peek(c->by_expiry);
cbd4560e
LP
130 assert(i);
131
faa133f3 132 /* Take an extra reference to the key so that it
cbd4560e 133 * doesn't go away in the middle of the remove call */
7e8e0422 134 key = dns_resource_key_ref(i->key);
faa133f3 135 dns_cache_remove(c, key);
322345fd
LP
136 }
137}
138
139void dns_cache_prune(DnsCache *c) {
140 usec_t t = 0;
141
142 assert(c);
143
144 /* Remove all entries that are past their TTL */
145
146 for (;;) {
faa133f3 147 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
322345fd 148 DnsCacheItem *i;
322345fd 149
7e8e0422 150 i = prioq_peek(c->by_expiry);
322345fd
LP
151 if (!i)
152 break;
153
322345fd 154 if (t <= 0)
9a015429 155 t = now(CLOCK_BOOTTIME);
322345fd 156
7e8e0422 157 if (i->until > t)
322345fd
LP
158 break;
159
faa133f3 160 /* Take an extra reference to the key so that it
cbd4560e 161 * doesn't go away in the middle of the remove call */
7e8e0422 162 key = dns_resource_key_ref(i->key);
faa133f3 163 dns_cache_remove(c, key);
322345fd
LP
164 }
165}
166
167static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
322345fd
LP
168 const DnsCacheItem *x = a, *y = b;
169
7e8e0422 170 if (x->until < y->until)
322345fd 171 return -1;
7e8e0422 172 if (x->until > y->until)
322345fd
LP
173 return 1;
174 return 0;
175}
176
623a4c97 177static int dns_cache_init(DnsCache *c) {
7e8e0422
LP
178 int r;
179
623a4c97
LP
180 assert(c);
181
7e8e0422
LP
182 r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func);
183 if (r < 0)
184 return r;
185
d5099efc 186 r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops);
7e8e0422
LP
187 if (r < 0)
188 return r;
189
190 return r;
191}
192
193static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) {
194 DnsCacheItem *first;
195 int r;
196
322345fd
LP
197 assert(c);
198 assert(i);
322345fd 199
7e8e0422
LP
200 r = prioq_put(c->by_expiry, i, &i->prioq_idx);
201 if (r < 0)
202 return r;
322345fd 203
7e8e0422
LP
204 first = hashmap_get(c->by_key, i->key);
205 if (first) {
206 LIST_PREPEND(by_key, first, i);
207 assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
208 } else {
209 r = hashmap_put(c->by_key, i->key, i);
210 if (r < 0) {
211 prioq_remove(c->by_expiry, i, &i->prioq_idx);
212 return r;
213 }
322345fd
LP
214 }
215
7e8e0422 216 return 0;
322345fd
LP
217}
218
faa133f3
LP
219static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
220 DnsCacheItem *i;
221
222 assert(c);
223 assert(rr);
224
7e8e0422 225 LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key))
3ef77d04 226 if (i->rr && dns_resource_record_equal(i->rr, rr) > 0)
faa133f3
LP
227 return i;
228
229 return NULL;
230}
231
7e8e0422
LP
232static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, usec_t timestamp) {
233 assert(c);
234 assert(i);
235 assert(rr);
236
237 i->type = DNS_CACHE_POSITIVE;
238
239 if (!i->by_key_prev) {
240 /* We are the first item in the list, we need to
241 * update the key used in the hashmap */
242
243 assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0);
244 }
245
246 dns_resource_record_ref(rr);
247 dns_resource_record_unref(i->rr);
248 i->rr = rr;
249
250 dns_resource_key_unref(i->key);
251 i->key = dns_resource_key_ref(rr->key);
252
253 i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
254
255 prioq_reshuffle(c->by_expiry, i, &i->prioq_idx);
256}
257
a4076574
LP
258static int dns_cache_put_positive(
259 DnsCache *c,
260 DnsResourceRecord *rr,
261 usec_t timestamp,
262 int owner_family,
263 const union in_addr_union *owner_address) {
264
322345fd 265 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
7e8e0422 266 DnsCacheItem *existing;
322345fd
LP
267 int r;
268
269 assert(c);
270 assert(rr);
a4076574 271 assert(owner_address);
322345fd
LP
272
273 /* New TTL is 0? Delete the entry... */
274 if (rr->ttl <= 0) {
faa133f3 275 dns_cache_remove(c, rr->key);
322345fd
LP
276 return 0;
277 }
278
ddf16339
LP
279 if (rr->key->class == DNS_CLASS_ANY)
280 return 0;
281 if (rr->key->type == DNS_TYPE_ANY)
282 return 0;
283
322345fd
LP
284 /* Entry exists already? Update TTL and timestamp */
285 existing = dns_cache_get(c, rr);
286 if (existing) {
7e8e0422 287 dns_cache_item_update_positive(c, existing, rr, timestamp);
322345fd
LP
288 return 0;
289 }
290
291 /* Otherwise, add the new RR */
623a4c97 292 r = dns_cache_init(c);
322345fd
LP
293 if (r < 0)
294 return r;
295
7e8e0422
LP
296 dns_cache_make_space(c, 1);
297
298 i = new0(DnsCacheItem, 1);
299 if (!i)
300 return -ENOMEM;
301
302 i->type = DNS_CACHE_POSITIVE;
303 i->key = dns_resource_key_ref(rr->key);
304 i->rr = dns_resource_record_ref(rr);
305 i->until = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
306 i->prioq_idx = PRIOQ_IDX_NULL;
a4076574
LP
307 i->owner_family = owner_family;
308 i->owner_address = *owner_address;
7e8e0422
LP
309
310 r = dns_cache_link_item(c, i);
311 if (r < 0)
312 return r;
313
314 i = NULL;
315 return 0;
316}
317
a4076574
LP
318static int dns_cache_put_negative(
319 DnsCache *c,
320 DnsResourceKey *key,
321 int rcode,
322 usec_t timestamp,
323 uint32_t soa_ttl,
324 int owner_family,
325 const union in_addr_union *owner_address) {
326
7e8e0422
LP
327 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
328 int r;
329
330 assert(c);
331 assert(key);
a4076574 332 assert(owner_address);
7e8e0422
LP
333
334 dns_cache_remove(c, key);
335
ddf16339
LP
336 if (key->class == DNS_CLASS_ANY)
337 return 0;
338 if (key->type == DNS_TYPE_ANY)
339 return 0;
95dd6257
LP
340 if (soa_ttl <= 0)
341 return 0;
ddf16339 342
7e8e0422
LP
343 if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
344 return 0;
345
623a4c97 346 r = dns_cache_init(c);
322345fd
LP
347 if (r < 0)
348 return r;
349
350 dns_cache_make_space(c, 1);
351
352 i = new0(DnsCacheItem, 1);
353 if (!i)
354 return -ENOMEM;
355
7e8e0422
LP
356 i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN;
357 i->key = dns_resource_key_ref(key);
358 i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
359 i->prioq_idx = PRIOQ_IDX_NULL;
a4076574
LP
360 i->owner_family = owner_family;
361 i->owner_address = *owner_address;
322345fd 362
7e8e0422 363 r = dns_cache_link_item(c, i);
322345fd
LP
364 if (r < 0)
365 return r;
366
322345fd 367 i = NULL;
322345fd
LP
368 return 0;
369}
370
a4076574
LP
371int dns_cache_put(
372 DnsCache *c,
373 DnsQuestion *q,
374 int rcode,
375 DnsAnswer *answer,
376 unsigned max_rrs,
377 usec_t timestamp,
378 int owner_family,
379 const union in_addr_union *owner_address) {
380
7e8e0422 381 unsigned i;
322345fd
LP
382 int r;
383
384 assert(c);
0ec7c46e 385 assert(q);
322345fd 386
7e8e0422
LP
387 /* First, delete all matching old RRs, so that we only keep
388 * complete by_key in place. */
389 for (i = 0; i < q->n_keys; i++)
390 dns_cache_remove(c, q->keys[i]);
0ec7c46e
LP
391
392 if (!answer)
393 return 0;
394
faa133f3
LP
395 for (i = 0; i < answer->n_rrs; i++)
396 dns_cache_remove(c, answer->rrs[i]->key);
322345fd 397
7e8e0422
LP
398 /* We only care for positive replies and NXDOMAINs, on all
399 * other replies we will simply flush the respective entries,
400 * and that's it */
401
402 if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
403 return 0;
404
405 /* Make some space for our new entries */
406 dns_cache_make_space(c, answer->n_rrs + q->n_keys);
322345fd 407
7e8e0422 408 if (timestamp <= 0)
9a015429 409 timestamp = now(CLOCK_BOOTTIME);
322345fd 410
7e8e0422 411 /* Second, add in positive entries for all contained RRs */
d2f47562 412 for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) {
a4076574 413 r = dns_cache_put_positive(c, answer->rrs[i], timestamp, owner_family, owner_address);
7e8e0422
LP
414 if (r < 0)
415 goto fail;
416 }
417
418 /* Third, add in negative entries for all keys with no RR */
419 for (i = 0; i < q->n_keys; i++) {
420 DnsResourceRecord *soa = NULL;
421
422 r = dns_answer_contains(answer, q->keys[i]);
423 if (r < 0)
424 goto fail;
425 if (r > 0)
426 continue;
427
428 r = dns_answer_find_soa(answer, q->keys[i], &soa);
429 if (r < 0)
430 goto fail;
431 if (r == 0)
432 continue;
433
a4076574 434 r = dns_cache_put_negative(c, q->keys[i], rcode, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
322345fd
LP
435 if (r < 0)
436 goto fail;
322345fd
LP
437 }
438
439 return 0;
440
441fail:
442 /* Adding all RRs failed. Let's clean up what we already
443 * added, just in case */
444
7e8e0422
LP
445 for (i = 0; i < q->n_keys; i++)
446 dns_cache_remove(c, q->keys[i]);
447 for (i = 0; i < answer->n_rrs; i++)
faa133f3 448 dns_cache_remove(c, answer->rrs[i]->key);
322345fd
LP
449
450 return r;
451}
452
7e8e0422 453int dns_cache_lookup(DnsCache *c, DnsQuestion *q, int *rcode, DnsAnswer **ret) {
faa133f3
LP
454 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
455 unsigned i, n = 0;
322345fd 456 int r;
7e8e0422 457 bool nxdomain = false;
322345fd
LP
458
459 assert(c);
faa133f3 460 assert(q);
623a4c97 461 assert(rcode);
faa133f3 462 assert(ret);
322345fd 463
faa133f3
LP
464 if (q->n_keys <= 0) {
465 *ret = NULL;
7e8e0422 466 *rcode = 0;
322345fd
LP
467 return 0;
468 }
469
faa133f3 470 for (i = 0; i < q->n_keys; i++) {
322345fd
LP
471 DnsCacheItem *j;
472
ddf16339
LP
473 if (q->keys[i]->type == DNS_TYPE_ANY ||
474 q->keys[i]->class == DNS_CLASS_ANY) {
475 /* If we have ANY lookups we simply refresh */
476 *ret = NULL;
477 *rcode = 0;
478 return 0;
479 }
480
7e8e0422 481 j = hashmap_get(c->by_key, q->keys[i]);
322345fd 482 if (!j) {
faa133f3
LP
483 /* If one question cannot be answered we need to refresh */
484 *ret = NULL;
7e8e0422 485 *rcode = 0;
faa133f3 486 return 0;
322345fd
LP
487 }
488
7e8e0422
LP
489 LIST_FOREACH(by_key, j, j) {
490 if (j->rr)
491 n++;
492 else if (j->type == DNS_CACHE_NXDOMAIN)
493 nxdomain = true;
494 }
faa133f3
LP
495 }
496
7e8e0422
LP
497 if (n <= 0) {
498 *ret = NULL;
499 *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
500 return 1;
501 }
faa133f3
LP
502
503 answer = dns_answer_new(n);
504 if (!answer)
505 return -ENOMEM;
322345fd 506
faa133f3
LP
507 for (i = 0; i < q->n_keys; i++) {
508 DnsCacheItem *j;
322345fd 509
7e8e0422
LP
510 j = hashmap_get(c->by_key, q->keys[i]);
511 LIST_FOREACH(by_key, j, j) {
512 if (j->rr) {
513 r = dns_answer_add(answer, j->rr);
514 if (r < 0)
515 return r;
516 }
322345fd
LP
517 }
518 }
519
faa133f3 520 *ret = answer;
7e8e0422 521 *rcode = DNS_RCODE_SUCCESS;
faa133f3
LP
522 answer = NULL;
523
524 return n;
322345fd 525}
a4076574
LP
526
527int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) {
528 DnsCacheItem *i, *first;
529 bool same_owner = true;
530
531 assert(cache);
532 assert(rr);
533
534 dns_cache_prune(cache);
535
536 /* See if there's a cache entry for the same key. If there
537 * isn't there's no conflict */
538 first = hashmap_get(cache->by_key, rr->key);
539 if (!first)
540 return 0;
541
542 /* See if the RR key is owned by the same owner, if so, there
543 * isn't a conflict either */
544 LIST_FOREACH(by_key, i, first) {
545 if (i->owner_family != owner_family ||
546 !in_addr_equal(owner_family, &i->owner_address, owner_address)) {
547 same_owner = false;
548 break;
549 }
550 }
551 if (same_owner)
552 return 0;
553
554 /* See if there's the exact same RR in the cache. If yes, then
555 * there's no conflict. */
556 if (dns_cache_get(cache, rr))
557 return 0;
558
559 /* There's a conflict */
560 return 1;
561}