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 #define CACHE_MAX 1024
25 #define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE)
27 static void dns_cache_item_free(DnsCacheItem *i) {
31 dns_resource_record_unref(i->rr);
35 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free);
37 static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) {
45 first = hashmap_get(c->rrsets, &i->rr->key);
46 LIST_REMOVE(rrsets, first, i);
49 assert_se(hashmap_replace(c->rrsets, &first->rr->key, first) >= 0);
51 hashmap_remove(c->rrsets, &i->rr->key);
53 prioq_remove(c->expire, i, &i->expire_prioq_idx);
55 dns_cache_item_free(i);
58 void dns_cache_flush(DnsCache *c) {
63 while ((i = hashmap_first(c->rrsets)))
64 dns_cache_item_remove_and_free(c, i);
66 assert(hashmap_size(c->rrsets) == 0);
67 assert(prioq_size(c->expire) == 0);
69 hashmap_free(c->rrsets);
72 prioq_free(c->expire);
76 void dns_cache_remove(DnsCache *c, DnsResourceKey *key) {
82 while ((i = hashmap_get(c->rrsets, &key)))
83 dns_cache_item_remove_and_free(c, i);
86 static void dns_cache_make_space(DnsCache *c, unsigned add) {
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. */
98 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
101 if (prioq_size(c->expire) <= 0)
104 if (prioq_size(c->expire) + add < CACHE_MAX)
107 i = prioq_peek(c->expire);
108 rr = dns_resource_record_ref(i->rr);
109 dns_cache_remove(c, &rr->key);
113 void dns_cache_prune(DnsCache *c) {
118 /* Remove all entries that are past their TTL */
124 i = prioq_peek(c->expire);
128 ttl = i->rr->ttl * USEC_PER_SEC;
129 if (ttl > CACHE_TTL_MAX_USEC)
130 ttl = CACHE_TTL_MAX_USEC;
133 t = now(CLOCK_MONOTONIC);
135 if (i->timestamp + ttl > t)
138 dns_cache_remove(c, &i->rr->key);
142 static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
144 const DnsCacheItem *x = a, *y = b;
146 t = x->timestamp + x->rr->ttl * USEC_PER_SEC;
147 z = y->timestamp + y->rr->ttl * USEC_PER_SEC;
156 static void dns_cache_item_update(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, usec_t timestamp) {
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 */
165 assert_se(hashmap_replace(c->rrsets, &rr->key, i) >= 0);
168 dns_resource_record_unref(i->rr);
169 i->rr = dns_resource_record_ref(rr);
171 i->timestamp = timestamp;
173 prioq_reshuffle(c->expire, i, &i->expire_prioq_idx);
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;
184 /* New TTL is 0? Delete the entry... */
186 dns_cache_remove(c, &rr->key);
190 /* Entry exists already? Update TTL and timestamp */
191 existing = dns_cache_get(c, rr);
193 dns_cache_item_update(c, existing, rr, timestamp);
197 /* Otherwise, add the new RR */
198 r = prioq_ensure_allocated(&c->expire, dns_cache_item_prioq_compare_func);
202 r = hashmap_ensure_allocated(&c->rrsets, dns_resource_key_hash_func, dns_resource_key_compare_func);
206 dns_cache_make_space(c, 1);
208 i = new0(DnsCacheItem, 1);
212 i->rr = dns_resource_record_ref(rr);
213 i->timestamp = timestamp;
214 i->expire_prioq_idx = PRIOQ_IDX_NULL;
216 r = prioq_put(c->expire, i, &i->expire_prioq_idx);
220 first = hashmap_get(c->rrsets, &i->rr->key);
222 LIST_PREPEND(rrsets, first, i);
223 assert_se(hashmap_replace(c->rrsets, &first->rr->key, first) >= 0);
225 r = hashmap_put(c->rrsets, &i->rr->key, i);
227 prioq_remove(c->expire, i, &i->expire_prioq_idx);
237 int dns_cache_put_rrs(DnsCache *c, DnsResourceRecord **rrs, unsigned n_rrs, usec_t timestamp) {
238 unsigned i, added = 0;
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);
253 dns_cache_make_space(c, n_rrs);
255 /* Second iteration, add in new RRs */
256 for (added = 0; added < n_rrs; added++) {
258 timestamp = now(CLOCK_MONOTONIC);
260 r = dns_cache_put(c, rrs[added], timestamp);
269 /* Adding all RRs failed. Let's clean up what we already
270 * added, just in case */
272 for (i = 0; i < added; i++)
273 dns_cache_remove(c, &rrs[i]->key);
278 DnsCacheItem* dns_cache_lookup(DnsCache *c, DnsResourceKey *key) {
282 return hashmap_get(c->rrsets, key);
285 DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
291 LIST_FOREACH(rrsets, i, hashmap_get(c->rrsets, &rr->key))
292 if (dns_resource_record_equal(i->rr, rr))
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;
314 for (i = 0; i < n_keys; i++) {
317 j = dns_cache_lookup(c, &keys[i]);
324 LIST_FOREACH(rrsets, j, j) {
326 if (!GREEDY_REALLOC(p, allocated, used+1)) {
331 p[used++] = dns_resource_record_ref(j->rr);
339 dns_resource_record_freev(p, used);