chiark / gitweb /
resolved: add DNS cache
[elogind.git] / src / resolve / resolved-dns-cache.c
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"
23
24 #define CACHE_MAX 1024
25 #define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE)
26
27 static void dns_cache_item_free(DnsCacheItem *i) {
28         if (!i)
29                 return;
30
31         dns_resource_record_unref(i->rr);
32         free(i);
33 }
34
35 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free);
36
37 static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) {
38         DnsCacheItem *first;
39
40         assert(c);
41
42         if (!i)
43                 return;
44
45         first = hashmap_get(c->rrsets, &i->rr->key);
46         LIST_REMOVE(rrsets, first, i);
47
48         if (first)
49                 assert_se(hashmap_replace(c->rrsets, &first->rr->key, first) >= 0);
50         else
51                 hashmap_remove(c->rrsets, &i->rr->key);
52
53         prioq_remove(c->expire, i, &i->expire_prioq_idx);
54
55         dns_cache_item_free(i);
56 }
57
58 void dns_cache_flush(DnsCache *c) {
59         DnsCacheItem *i;
60
61         assert(c);
62
63         while ((i = hashmap_first(c->rrsets)))
64                 dns_cache_item_remove_and_free(c, i);
65
66         assert(hashmap_size(c->rrsets) == 0);
67         assert(prioq_size(c->expire) == 0);
68
69         hashmap_free(c->rrsets);
70         c->rrsets = NULL;
71
72         prioq_free(c->expire);
73         c->expire = NULL;
74 }
75
76 void dns_cache_remove(DnsCache *c, DnsResourceKey *key) {
77         DnsCacheItem *i;
78
79         assert(c);
80         assert(key);
81
82         while ((i = hashmap_get(c->rrsets, &key)))
83                 dns_cache_item_remove_and_free(c, i);
84 }
85
86 static void dns_cache_make_space(DnsCache *c, unsigned add) {
87         assert(c);
88
89         if (add <= 0)
90                 return;
91
92         /* Makes space for n new entries. Note that we actually allow
93          * the cache to grow beyond CACHE_MAX, but only when we shall
94          * add more RRs to the cache than CACHE_MAX at once. In that
95          * case the cache will be emptied completely otherwise. */
96
97         for (;;) {
98                 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
99                 DnsCacheItem *i;
100
101                 if (prioq_size(c->expire) <= 0)
102                         break;
103
104                 if (prioq_size(c->expire) + add < CACHE_MAX)
105                         break;
106
107                 i = prioq_peek(c->expire);
108                 rr = dns_resource_record_ref(i->rr);
109                 dns_cache_remove(c, &rr->key);
110         }
111 }
112
113 void dns_cache_prune(DnsCache *c) {
114         usec_t t = 0;
115
116         assert(c);
117
118         /* Remove all entries that are past their TTL */
119
120         for (;;) {
121                 DnsCacheItem *i;
122                 usec_t ttl;
123
124                 i = prioq_peek(c->expire);
125                 if (!i)
126                         break;
127
128                 ttl = i->rr->ttl * USEC_PER_SEC;
129                 if (ttl > CACHE_TTL_MAX_USEC)
130                         ttl = CACHE_TTL_MAX_USEC;
131
132                 if (t <= 0)
133                         t = now(CLOCK_MONOTONIC);
134
135                 if (i->timestamp + ttl > t)
136                         break;
137
138                 dns_cache_remove(c, &i->rr->key);
139         }
140 }
141
142 static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
143         usec_t t, z;
144         const DnsCacheItem *x = a, *y = b;
145
146         t = x->timestamp + x->rr->ttl * USEC_PER_SEC;
147         z = y->timestamp + y->rr->ttl * USEC_PER_SEC;
148
149         if (t < z)
150                 return -1;
151         if (t > z)
152                 return 1;
153         return 0;
154 }
155
156 static void dns_cache_item_update(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, usec_t timestamp) {
157         assert(c);
158         assert(i);
159         assert(rr);
160
161         if (!i->rrsets_prev) {
162                 /* We are the first item in the list, we need to
163                  * update the key used in the hashmap */
164
165                 assert_se(hashmap_replace(c->rrsets, &rr->key, i) >= 0);
166         }
167
168         dns_resource_record_unref(i->rr);
169         i->rr = dns_resource_record_ref(rr);
170
171         i->timestamp = timestamp;
172
173         prioq_reshuffle(c->expire, i, &i->expire_prioq_idx);
174 }
175
176 int dns_cache_put(DnsCache *c, DnsResourceRecord *rr, usec_t timestamp) {
177         _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
178         DnsCacheItem *first = NULL, *existing;
179         int r;
180
181         assert(c);
182         assert(rr);
183
184         /* New TTL is 0? Delete the entry... */
185         if (rr->ttl <= 0) {
186                 dns_cache_remove(c, &rr->key);
187                 return 0;
188         }
189
190         /* Entry exists already? Update TTL and timestamp */
191         existing = dns_cache_get(c, rr);
192         if (existing) {
193                 dns_cache_item_update(c, existing, rr, timestamp);
194                 return 0;
195         }
196
197         /* Otherwise, add the new RR */
198         r = prioq_ensure_allocated(&c->expire, dns_cache_item_prioq_compare_func);
199         if (r < 0)
200                 return r;
201
202         r = hashmap_ensure_allocated(&c->rrsets, dns_resource_key_hash_func, dns_resource_key_compare_func);
203         if (r < 0)
204                 return r;
205
206         dns_cache_make_space(c, 1);
207
208         i = new0(DnsCacheItem, 1);
209         if (!i)
210                 return -ENOMEM;
211
212         i->rr = dns_resource_record_ref(rr);
213         i->timestamp = timestamp;
214         i->expire_prioq_idx = PRIOQ_IDX_NULL;
215
216         r = prioq_put(c->expire, i, &i->expire_prioq_idx);
217         if (r < 0)
218                 return r;
219
220         first = hashmap_get(c->rrsets, &i->rr->key);
221         if (first) {
222                 LIST_PREPEND(rrsets, first, i);
223                 assert_se(hashmap_replace(c->rrsets, &first->rr->key, first) >= 0);
224         } else {
225                 r = hashmap_put(c->rrsets, &i->rr->key, i);
226                 if (r < 0) {
227                         prioq_remove(c->expire, i, &i->expire_prioq_idx);
228                         return r;
229                 }
230         }
231
232         i = NULL;
233
234         return 0;
235 }
236
237 int dns_cache_put_rrs(DnsCache *c, DnsResourceRecord **rrs, unsigned n_rrs, usec_t timestamp) {
238         unsigned i, added = 0;
239         int r;
240
241         assert(c);
242
243         if (n_rrs <= 0)
244                 return 0;
245
246         assert(rrs);
247
248         /* First iteration, delete all matching old RRs, so that we
249          * only keep complete rrsets in place. */
250         for (i = 0; i < n_rrs; i++)
251                 dns_cache_remove(c, &rrs[i]->key);
252
253         dns_cache_make_space(c, n_rrs);
254
255         /* Second iteration, add in new RRs */
256         for (added = 0; added < n_rrs; added++) {
257                 if (timestamp <= 0)
258                         timestamp = now(CLOCK_MONOTONIC);
259
260                 r = dns_cache_put(c, rrs[added], timestamp);
261                 if (r < 0)
262                         goto fail;
263
264         }
265
266         return 0;
267
268 fail:
269         /* Adding all RRs failed. Let's clean up what we already
270          * added, just in case */
271
272         for (i = 0; i < added; i++)
273                 dns_cache_remove(c, &rrs[i]->key);
274
275         return r;
276 }
277
278 DnsCacheItem* dns_cache_lookup(DnsCache *c, DnsResourceKey *key) {
279         assert(c);
280         assert(key);
281
282         return hashmap_get(c->rrsets, key);
283 }
284
285 DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
286         DnsCacheItem *i;
287
288         assert(c);
289         assert(rr);
290
291         LIST_FOREACH(rrsets, i, hashmap_get(c->rrsets, &rr->key))
292                 if (dns_resource_record_equal(i->rr, rr))
293                         return i;
294
295         return NULL;
296 }
297
298 int dns_cache_lookup_many(DnsCache *c, DnsResourceKey *keys, unsigned n_keys, DnsResourceRecord ***rrs) {
299         DnsResourceRecord **p = NULL;
300         size_t allocated = 0, used = 0;
301         unsigned i;
302         int r;
303
304         assert(c);
305         assert(rrs);
306
307         if (n_keys <= 0) {
308                 *rrs = NULL;
309                 return 0;
310         }
311
312         assert(keys);
313
314         for (i = 0; i < n_keys; i++) {
315                 DnsCacheItem *j;
316
317                 j = dns_cache_lookup(c, &keys[i]);
318                 if (!j) {
319                         *rrs = NULL;
320                         r = 0;
321                         goto fail;
322                 }
323
324                 LIST_FOREACH(rrsets, j, j) {
325
326                         if (!GREEDY_REALLOC(p, allocated, used+1)) {
327                                 r = -ENOMEM;
328                                 goto fail;
329                         }
330
331                         p[used++] = dns_resource_record_ref(j->rr);
332                 }
333         }
334
335         *rrs = p;
336         return (int) used;
337
338 fail:
339         dns_resource_record_freev(p, used);
340         return r;
341 }