1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Lennart Poettering
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.
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.
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/>.
22 #include "resolved-dns-cache.h"
24 /* Never cache more than 1K entries */
25 #define CACHE_MAX 1024
27 /* We never keep any item longer than 10min in our cache */
28 #define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE)
30 static void dns_cache_item_free(DnsCacheItem *i) {
34 dns_resource_record_unref(i->rr);
38 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free);
40 static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) {
48 first = hashmap_get(c->rrsets, &i->rr->key);
49 LIST_REMOVE(rrsets, first, i);
52 assert_se(hashmap_replace(c->rrsets, &first->rr->key, first) >= 0);
54 hashmap_remove(c->rrsets, &i->rr->key);
56 prioq_remove(c->expire, i, &i->expire_prioq_idx);
58 dns_cache_item_free(i);
61 void dns_cache_flush(DnsCache *c) {
66 while ((i = hashmap_first(c->rrsets)))
67 dns_cache_item_remove_and_free(c, i);
69 assert(hashmap_size(c->rrsets) == 0);
70 assert(prioq_size(c->expire) == 0);
72 hashmap_free(c->rrsets);
75 prioq_free(c->expire);
79 void dns_cache_remove(DnsCache *c, DnsResourceKey *key) {
85 while ((i = hashmap_get(c->rrsets, key)))
86 dns_cache_item_remove_and_free(c, i);
89 static void dns_cache_make_space(DnsCache *c, unsigned add) {
95 /* Makes space for n new entries. Note that we actually allow
96 * the cache to grow beyond CACHE_MAX, but only when we shall
97 * add more RRs to the cache than CACHE_MAX at once. In that
98 * case the cache will be emptied completely otherwise. */
101 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
104 if (prioq_size(c->expire) <= 0)
107 if (prioq_size(c->expire) + add < CACHE_MAX)
110 i = prioq_peek(c->expire);
113 /* Take an extra reference to the RR so that the key
114 * doesn't go away in the middle of the remove call */
115 rr = dns_resource_record_ref(i->rr);
116 dns_cache_remove(c, &rr->key);
120 void dns_cache_prune(DnsCache *c) {
125 /* Remove all entries that are past their TTL */
128 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
132 i = prioq_peek(c->expire);
136 ttl = i->rr->ttl * USEC_PER_SEC;
137 if (ttl > CACHE_TTL_MAX_USEC)
138 ttl = CACHE_TTL_MAX_USEC;
141 t = now(CLOCK_MONOTONIC);
143 if (i->timestamp + ttl > t)
146 /* Take an extra reference to the RR so that the key
147 * doesn't go away in the middle of the remove call */
148 rr = dns_resource_record_ref(i->rr);
149 dns_cache_remove(c, &rr->key);
153 static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
155 const DnsCacheItem *x = a, *y = b;
157 t = x->timestamp + x->rr->ttl * USEC_PER_SEC;
158 z = y->timestamp + y->rr->ttl * USEC_PER_SEC;
167 static void dns_cache_item_update(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, usec_t timestamp) {
172 if (!i->rrsets_prev) {
173 /* We are the first item in the list, we need to
174 * update the key used in the hashmap */
176 assert_se(hashmap_replace(c->rrsets, &rr->key, i) >= 0);
179 dns_resource_record_ref(rr);
180 dns_resource_record_unref(i->rr);
183 i->timestamp = timestamp;
184 prioq_reshuffle(c->expire, i, &i->expire_prioq_idx);
187 int dns_cache_put(DnsCache *c, DnsResourceRecord *rr, usec_t timestamp) {
188 _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
189 DnsCacheItem *first = NULL, *existing;
195 /* New TTL is 0? Delete the entry... */
197 dns_cache_remove(c, &rr->key);
201 /* Entry exists already? Update TTL and timestamp */
202 existing = dns_cache_get(c, rr);
204 dns_cache_item_update(c, existing, rr, timestamp);
208 /* Otherwise, add the new RR */
209 r = prioq_ensure_allocated(&c->expire, dns_cache_item_prioq_compare_func);
213 r = hashmap_ensure_allocated(&c->rrsets, dns_resource_key_hash_func, dns_resource_key_compare_func);
217 dns_cache_make_space(c, 1);
219 i = new0(DnsCacheItem, 1);
223 i->rr = dns_resource_record_ref(rr);
224 i->timestamp = timestamp;
225 i->expire_prioq_idx = PRIOQ_IDX_NULL;
227 r = prioq_put(c->expire, i, &i->expire_prioq_idx);
231 first = hashmap_get(c->rrsets, &i->rr->key);
233 LIST_PREPEND(rrsets, first, i);
234 assert_se(hashmap_replace(c->rrsets, &first->rr->key, first) >= 0);
236 r = hashmap_put(c->rrsets, &i->rr->key, i);
238 prioq_remove(c->expire, i, &i->expire_prioq_idx);
248 int dns_cache_put_rrs(DnsCache *c, DnsResourceRecord **rrs, unsigned n_rrs, usec_t timestamp) {
249 unsigned i, added = 0;
259 /* First iteration, delete all matching old RRs, so that we
260 * only keep complete rrsets in place. */
261 for (i = 0; i < n_rrs; i++)
262 dns_cache_remove(c, &rrs[i]->key);
264 dns_cache_make_space(c, n_rrs);
266 /* Second iteration, add in new RRs */
267 for (added = 0; added < n_rrs; added++) {
269 timestamp = now(CLOCK_MONOTONIC);
271 r = dns_cache_put(c, rrs[added], timestamp);
280 /* Adding all RRs failed. Let's clean up what we already
281 * added, just in case */
283 for (i = 0; i < added; i++)
284 dns_cache_remove(c, &rrs[i]->key);
289 DnsCacheItem* dns_cache_lookup(DnsCache *c, DnsResourceKey *key) {
293 return hashmap_get(c->rrsets, key);
296 DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
302 LIST_FOREACH(rrsets, i, hashmap_get(c->rrsets, &rr->key))
303 if (dns_resource_record_equal(i->rr, rr))
309 int dns_cache_lookup_many(DnsCache *c, DnsResourceKey *keys, unsigned n_keys, DnsResourceRecord ***rrs) {
310 DnsResourceRecord **p = NULL;
311 size_t allocated = 0, used = 0;
325 for (i = 0; i < n_keys; i++) {
328 j = dns_cache_lookup(c, &keys[i]);
335 LIST_FOREACH(rrsets, j, j) {
337 if (!GREEDY_REALLOC(p, allocated, used+1)) {
342 p[used++] = dns_resource_record_ref(j->rr);
350 dns_resource_record_freev(p, used);