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