badcache.c revision 1.9 1 1.9 christos /* $NetBSD: badcache.c,v 1.9 2025/01/26 16:25:21 christos Exp $ */
2 1.1 christos
3 1.1 christos /*
4 1.1 christos * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 1.1 christos *
6 1.6 christos * SPDX-License-Identifier: MPL-2.0
7 1.6 christos *
8 1.1 christos * This Source Code Form is subject to the terms of the Mozilla Public
9 1.1 christos * License, v. 2.0. If a copy of the MPL was not distributed with this
10 1.5 christos * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 1.1 christos *
12 1.1 christos * See the COPYRIGHT file distributed with this work for additional
13 1.1 christos * information regarding copyright ownership.
14 1.1 christos */
15 1.1 christos
16 1.1 christos /*! \file */
17 1.1 christos
18 1.3 christos #include <inttypes.h>
19 1.3 christos #include <stdbool.h>
20 1.3 christos
21 1.9 christos #include <isc/async.h>
22 1.1 christos #include <isc/buffer.h>
23 1.4 christos #include <isc/hash.h>
24 1.1 christos #include <isc/log.h>
25 1.9 christos #include <isc/loop.h>
26 1.1 christos #include <isc/mem.h>
27 1.1 christos #include <isc/mutex.h>
28 1.4 christos #include <isc/rwlock.h>
29 1.9 christos #include <isc/spinlock.h>
30 1.9 christos #include <isc/stdtime.h>
31 1.1 christos #include <isc/string.h>
32 1.9 christos #include <isc/thread.h>
33 1.1 christos #include <isc/time.h>
34 1.9 christos #include <isc/urcu.h>
35 1.1 christos #include <isc/util.h>
36 1.1 christos
37 1.1 christos #include <dns/badcache.h>
38 1.8 christos #include <dns/fixedname.h>
39 1.1 christos #include <dns/name.h>
40 1.1 christos #include <dns/rdatatype.h>
41 1.1 christos #include <dns/types.h>
42 1.1 christos
43 1.1 christos typedef struct dns_bcentry dns_bcentry_t;
44 1.1 christos
45 1.9 christos typedef struct dns_bckey {
46 1.9 christos const dns_name_t *name;
47 1.9 christos dns_rdatatype_t type;
48 1.9 christos } dns__bckey_t;
49 1.9 christos
50 1.1 christos struct dns_badcache {
51 1.4 christos unsigned int magic;
52 1.4 christos isc_mem_t *mctx;
53 1.9 christos struct cds_lfht *ht;
54 1.9 christos struct cds_list_head *lru;
55 1.9 christos uint32_t nloops;
56 1.1 christos };
57 1.1 christos
58 1.4 christos #define BADCACHE_MAGIC ISC_MAGIC('B', 'd', 'C', 'a')
59 1.4 christos #define VALID_BADCACHE(m) ISC_MAGIC_VALID(m, BADCACHE_MAGIC)
60 1.1 christos
61 1.9 christos #define BADCACHE_INIT_SIZE (1 << 10) /* Must be power of 2 */
62 1.9 christos #define BADCACHE_MIN_SIZE (1 << 8) /* Must be power of 2 */
63 1.9 christos
64 1.1 christos struct dns_bcentry {
65 1.9 christos isc_loop_t *loop;
66 1.9 christos isc_stdtime_t expire;
67 1.9 christos uint32_t flags;
68 1.9 christos
69 1.9 christos struct cds_lfht_node ht_node;
70 1.9 christos struct rcu_head rcu_head;
71 1.9 christos struct cds_list_head lru_head;
72 1.9 christos
73 1.9 christos dns_name_t name;
74 1.4 christos dns_rdatatype_t type;
75 1.1 christos };
76 1.1 christos
77 1.4 christos static void
78 1.9 christos bcentry_print(dns_bcentry_t *bad, isc_stdtime_t now, FILE *fp);
79 1.1 christos
80 1.9 christos static void
81 1.9 christos bcentry_destroy(struct rcu_head *rcu_head);
82 1.1 christos
83 1.9 christos static bool
84 1.9 christos bcentry_alive(struct cds_lfht *ht, dns_bcentry_t *bad, isc_stdtime_t now);
85 1.1 christos
86 1.9 christos dns_badcache_t *
87 1.9 christos dns_badcache_new(isc_mem_t *mctx, isc_loopmgr_t *loopmgr) {
88 1.9 christos REQUIRE(loopmgr != NULL);
89 1.9 christos
90 1.9 christos uint32_t nloops = isc_loopmgr_nloops(loopmgr);
91 1.9 christos dns_badcache_t *bc = isc_mem_get(mctx, sizeof(*bc));
92 1.9 christos *bc = (dns_badcache_t){
93 1.9 christos .magic = BADCACHE_MAGIC,
94 1.9 christos .nloops = nloops,
95 1.9 christos };
96 1.9 christos
97 1.9 christos bc->ht = cds_lfht_new(BADCACHE_INIT_SIZE, BADCACHE_MIN_SIZE, 0,
98 1.9 christos CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL);
99 1.9 christos INSIST(bc->ht != NULL);
100 1.9 christos
101 1.9 christos bc->lru = isc_mem_cget(mctx, bc->nloops, sizeof(bc->lru[0]));
102 1.9 christos for (size_t i = 0; i < bc->nloops; i++) {
103 1.9 christos CDS_INIT_LIST_HEAD(&bc->lru[i]);
104 1.9 christos }
105 1.1 christos
106 1.1 christos isc_mem_attach(mctx, &bc->mctx);
107 1.1 christos
108 1.9 christos return bc;
109 1.1 christos }
110 1.1 christos
111 1.1 christos void
112 1.1 christos dns_badcache_destroy(dns_badcache_t **bcp) {
113 1.9 christos REQUIRE(bcp != NULL && *bcp != NULL);
114 1.9 christos REQUIRE(VALID_BADCACHE(*bcp));
115 1.1 christos
116 1.9 christos dns_badcache_t *bc = *bcp;
117 1.4 christos *bcp = NULL;
118 1.9 christos bc->magic = 0;
119 1.9 christos
120 1.9 christos dns_bcentry_t *bad = NULL;
121 1.9 christos struct cds_lfht_iter iter;
122 1.9 christos cds_lfht_for_each_entry(bc->ht, &iter, bad, ht_node) {
123 1.9 christos INSIST(!cds_lfht_del(bc->ht, &bad->ht_node));
124 1.9 christos bcentry_destroy(&bad->rcu_head);
125 1.9 christos }
126 1.9 christos RUNTIME_CHECK(!cds_lfht_destroy(bc->ht, NULL));
127 1.1 christos
128 1.9 christos isc_mem_cput(bc->mctx, bc->lru, bc->nloops, sizeof(bc->lru[0]));
129 1.1 christos
130 1.1 christos isc_mem_putanddetach(&bc->mctx, bc, sizeof(dns_badcache_t));
131 1.1 christos }
132 1.1 christos
133 1.9 christos static int
134 1.9 christos bcentry_match(struct cds_lfht_node *ht_node, const void *key0) {
135 1.9 christos const dns__bckey_t *key = key0;
136 1.9 christos dns_bcentry_t *bad = caa_container_of(ht_node, dns_bcentry_t, ht_node);
137 1.9 christos
138 1.9 christos return (bad->type == key->type) &&
139 1.9 christos dns_name_equal(&bad->name, key->name);
140 1.9 christos }
141 1.9 christos
142 1.9 christos static uint32_t
143 1.9 christos bcentry_hash(const dns__bckey_t *key) {
144 1.9 christos isc_hash32_t state;
145 1.9 christos isc_hash32_init(&state);
146 1.9 christos isc_hash32_hash(&state, key->name->ndata, key->name->length, false);
147 1.9 christos isc_hash32_hash(&state, &key->type, sizeof(key->type), true);
148 1.9 christos return isc_hash32_finalize(&state);
149 1.9 christos }
150 1.9 christos
151 1.9 christos static dns_bcentry_t *
152 1.9 christos bcentry_lookup(struct cds_lfht *ht, uint32_t hashval, dns__bckey_t *key) {
153 1.9 christos struct cds_lfht_iter iter;
154 1.9 christos
155 1.9 christos cds_lfht_lookup(ht, hashval, bcentry_match, key, &iter);
156 1.9 christos
157 1.9 christos return cds_lfht_entry(cds_lfht_iter_get_node(&iter), dns_bcentry_t,
158 1.9 christos ht_node);
159 1.9 christos }
160 1.9 christos
161 1.9 christos static dns_bcentry_t *
162 1.9 christos bcentry_new(isc_loop_t *loop, const dns_name_t *name,
163 1.9 christos const dns_rdatatype_t type, const uint32_t flags,
164 1.9 christos const isc_stdtime_t expire) {
165 1.9 christos isc_mem_t *mctx = isc_loop_getmctx(loop);
166 1.9 christos dns_bcentry_t *bad = isc_mem_get(mctx, sizeof(*bad));
167 1.9 christos *bad = (dns_bcentry_t){
168 1.9 christos .type = type,
169 1.9 christos .flags = flags,
170 1.9 christos .expire = expire,
171 1.9 christos .loop = isc_loop_ref(loop),
172 1.9 christos .lru_head = CDS_LIST_HEAD_INIT(bad->lru_head),
173 1.9 christos };
174 1.9 christos
175 1.9 christos dns_name_init(&bad->name, NULL);
176 1.9 christos dns_name_dup(name, mctx, &bad->name);
177 1.9 christos
178 1.9 christos return bad;
179 1.9 christos }
180 1.9 christos
181 1.4 christos static void
182 1.9 christos bcentry_destroy(struct rcu_head *rcu_head) {
183 1.9 christos dns_bcentry_t *bad = caa_container_of(rcu_head, dns_bcentry_t,
184 1.9 christos rcu_head);
185 1.9 christos isc_loop_t *loop = bad->loop;
186 1.9 christos isc_mem_t *mctx = isc_loop_getmctx(loop);
187 1.9 christos
188 1.9 christos dns_name_free(&bad->name, mctx);
189 1.9 christos isc_mem_put(mctx, bad, sizeof(*bad));
190 1.9 christos
191 1.9 christos isc_loop_unref(loop);
192 1.9 christos }
193 1.9 christos
194 1.9 christos static void
195 1.9 christos bcentry_evict_async(void *arg) {
196 1.9 christos dns_bcentry_t *bad = arg;
197 1.9 christos
198 1.9 christos RUNTIME_CHECK(bad->loop == isc_loop());
199 1.9 christos
200 1.9 christos cds_list_del(&bad->lru_head);
201 1.9 christos call_rcu(&bad->rcu_head, bcentry_destroy);
202 1.9 christos }
203 1.9 christos
204 1.9 christos static void
205 1.9 christos bcentry_evict(struct cds_lfht *ht, dns_bcentry_t *bad) {
206 1.9 christos if (!cds_lfht_del(ht, &bad->ht_node)) {
207 1.9 christos if (bad->loop == isc_loop()) {
208 1.9 christos bcentry_evict_async(bad);
209 1.9 christos return;
210 1.1 christos }
211 1.9 christos
212 1.9 christos isc_async_run(bad->loop, bcentry_evict_async, bad);
213 1.9 christos }
214 1.9 christos }
215 1.9 christos
216 1.9 christos static bool
217 1.9 christos bcentry_alive(struct cds_lfht *ht, dns_bcentry_t *bad, isc_stdtime_t now) {
218 1.9 christos if (cds_lfht_is_node_deleted(&bad->ht_node)) {
219 1.9 christos return false;
220 1.9 christos } else if (bad->expire < now) {
221 1.9 christos bcentry_evict(ht, bad);
222 1.9 christos return false;
223 1.1 christos }
224 1.1 christos
225 1.9 christos return true;
226 1.9 christos }
227 1.4 christos
228 1.9 christos #define cds_lfht_for_each_entry_next(ht, iter, pos, member) \
229 1.9 christos for (cds_lfht_next(ht, iter), \
230 1.9 christos pos = cds_lfht_entry(cds_lfht_iter_get_node(iter), \
231 1.9 christos __typeof__(*(pos)), member); \
232 1.9 christos pos != NULL; /**/ \
233 1.9 christos cds_lfht_next(ht, iter), \
234 1.9 christos pos = cds_lfht_entry(cds_lfht_iter_get_node(iter), \
235 1.9 christos __typeof__(*(pos)), member))
236 1.1 christos
237 1.9 christos static void
238 1.9 christos bcentry_purge(struct cds_lfht *ht, struct cds_list_head *lru,
239 1.9 christos isc_stdtime_t now) {
240 1.9 christos size_t count = 10;
241 1.9 christos dns_bcentry_t *bad;
242 1.9 christos cds_list_for_each_entry_rcu(bad, lru, lru_head) {
243 1.9 christos if (bcentry_alive(ht, bad, now)) {
244 1.9 christos break;
245 1.9 christos }
246 1.9 christos if (--count == 0) {
247 1.9 christos break;
248 1.9 christos }
249 1.9 christos }
250 1.1 christos }
251 1.1 christos
252 1.1 christos void
253 1.1 christos dns_badcache_add(dns_badcache_t *bc, const dns_name_t *name,
254 1.9 christos dns_rdatatype_t type, uint32_t flags, isc_stdtime_t expire) {
255 1.1 christos REQUIRE(VALID_BADCACHE(bc));
256 1.1 christos REQUIRE(name != NULL);
257 1.1 christos
258 1.9 christos isc_loop_t *loop = isc_loop();
259 1.9 christos uint32_t tid = isc_tid();
260 1.9 christos struct cds_list_head *lru = &bc->lru[tid];
261 1.9 christos
262 1.9 christos isc_stdtime_t now = isc_stdtime_now();
263 1.9 christos if (expire < now) {
264 1.9 christos expire = now;
265 1.9 christos }
266 1.9 christos
267 1.9 christos rcu_read_lock();
268 1.9 christos struct cds_lfht *ht = rcu_dereference(bc->ht);
269 1.9 christos INSIST(ht != NULL);
270 1.9 christos
271 1.9 christos dns__bckey_t key = {
272 1.9 christos .name = name,
273 1.9 christos .type = type,
274 1.9 christos };
275 1.9 christos uint32_t hashval = bcentry_hash(&key);
276 1.9 christos
277 1.9 christos /* struct cds_lfht_iter iter; */
278 1.9 christos dns_bcentry_t *bad = bcentry_new(loop, name, type, flags, expire);
279 1.9 christos struct cds_lfht_node *ht_node;
280 1.9 christos do {
281 1.9 christos ht_node = cds_lfht_add_unique(ht, hashval, bcentry_match, &key,
282 1.9 christos &bad->ht_node);
283 1.9 christos if (ht_node != &bad->ht_node) {
284 1.9 christos dns_bcentry_t *found = caa_container_of(
285 1.9 christos ht_node, dns_bcentry_t, ht_node);
286 1.9 christos bcentry_evict(ht, found);
287 1.9 christos }
288 1.9 christos } while (ht_node != &bad->ht_node);
289 1.1 christos
290 1.9 christos /* No locking, instead we are using per-thread lists */
291 1.9 christos cds_list_add_tail_rcu(&bad->lru_head, lru);
292 1.1 christos
293 1.9 christos bcentry_purge(ht, lru, now);
294 1.1 christos
295 1.9 christos rcu_read_unlock();
296 1.1 christos }
297 1.1 christos
298 1.9 christos isc_result_t
299 1.1 christos dns_badcache_find(dns_badcache_t *bc, const dns_name_t *name,
300 1.9 christos dns_rdatatype_t type, uint32_t *flagp, isc_stdtime_t now) {
301 1.1 christos REQUIRE(VALID_BADCACHE(bc));
302 1.1 christos REQUIRE(name != NULL);
303 1.1 christos
304 1.9 christos isc_result_t result = ISC_R_NOTFOUND;
305 1.9 christos
306 1.9 christos rcu_read_lock();
307 1.9 christos struct cds_lfht *ht = rcu_dereference(bc->ht);
308 1.9 christos INSIST(ht != NULL);
309 1.9 christos
310 1.9 christos dns__bckey_t key = {
311 1.9 christos .name = name,
312 1.9 christos .type = type,
313 1.9 christos };
314 1.9 christos uint32_t hashval = bcentry_hash(&key);
315 1.1 christos
316 1.9 christos dns_bcentry_t *found = bcentry_lookup(ht, hashval, &key);
317 1.1 christos
318 1.9 christos if (found != NULL && bcentry_alive(ht, found, now)) {
319 1.9 christos result = ISC_R_SUCCESS;
320 1.9 christos if (flagp != NULL) {
321 1.9 christos *flagp = found->flags;
322 1.1 christos }
323 1.1 christos }
324 1.1 christos
325 1.9 christos uint32_t tid = isc_tid();
326 1.9 christos struct cds_list_head *lru = &bc->lru[tid];
327 1.9 christos bcentry_purge(ht, lru, now);
328 1.9 christos
329 1.9 christos rcu_read_unlock();
330 1.1 christos
331 1.9 christos return result;
332 1.1 christos }
333 1.1 christos
334 1.1 christos void
335 1.1 christos dns_badcache_flush(dns_badcache_t *bc) {
336 1.1 christos REQUIRE(VALID_BADCACHE(bc));
337 1.1 christos
338 1.9 christos rcu_read_lock();
339 1.9 christos struct cds_lfht *ht = rcu_dereference(bc->ht);
340 1.9 christos INSIST(ht != NULL);
341 1.9 christos
342 1.9 christos /* Flush the hash table */
343 1.9 christos dns_bcentry_t *bad;
344 1.9 christos struct cds_lfht_iter iter;
345 1.9 christos cds_lfht_for_each_entry(ht, &iter, bad, ht_node) {
346 1.9 christos bcentry_evict(ht, bad);
347 1.1 christos }
348 1.9 christos
349 1.9 christos rcu_read_unlock();
350 1.1 christos }
351 1.1 christos
352 1.1 christos void
353 1.1 christos dns_badcache_flushname(dns_badcache_t *bc, const dns_name_t *name) {
354 1.1 christos REQUIRE(VALID_BADCACHE(bc));
355 1.1 christos REQUIRE(name != NULL);
356 1.1 christos
357 1.9 christos isc_stdtime_t now = isc_stdtime_now();
358 1.1 christos
359 1.9 christos rcu_read_lock();
360 1.9 christos struct cds_lfht *ht = rcu_dereference(bc->ht);
361 1.9 christos INSIST(ht != NULL);
362 1.9 christos
363 1.9 christos dns_bcentry_t *bad;
364 1.9 christos struct cds_lfht_iter iter;
365 1.9 christos cds_lfht_for_each_entry(ht, &iter, bad, ht_node) {
366 1.9 christos if (dns_name_equal(&bad->name, name)) {
367 1.9 christos bcentry_evict(ht, bad);
368 1.9 christos continue;
369 1.4 christos }
370 1.9 christos
371 1.9 christos /* Flush all the expired entries */
372 1.9 christos (void)bcentry_alive(ht, bad, now);
373 1.1 christos }
374 1.1 christos
375 1.9 christos rcu_read_unlock();
376 1.1 christos }
377 1.1 christos
378 1.1 christos void
379 1.1 christos dns_badcache_flushtree(dns_badcache_t *bc, const dns_name_t *name) {
380 1.1 christos REQUIRE(VALID_BADCACHE(bc));
381 1.1 christos REQUIRE(name != NULL);
382 1.1 christos
383 1.9 christos isc_stdtime_t now = isc_stdtime_now();
384 1.9 christos
385 1.9 christos rcu_read_lock();
386 1.9 christos struct cds_lfht *ht = rcu_dereference(bc->ht);
387 1.9 christos INSIST(ht != NULL);
388 1.9 christos
389 1.9 christos dns_bcentry_t *bad;
390 1.9 christos struct cds_lfht_iter iter;
391 1.9 christos cds_lfht_for_each_entry(ht, &iter, bad, ht_node) {
392 1.9 christos if (dns_name_issubdomain(&bad->name, name)) {
393 1.9 christos bcentry_evict(ht, bad);
394 1.9 christos continue;
395 1.1 christos }
396 1.9 christos
397 1.9 christos /* Flush all the expired entries */
398 1.9 christos (void)bcentry_alive(ht, bad, now);
399 1.1 christos }
400 1.1 christos
401 1.9 christos rcu_read_unlock();
402 1.9 christos }
403 1.9 christos
404 1.9 christos static void
405 1.9 christos bcentry_print(dns_bcentry_t *bad, isc_stdtime_t now, FILE *fp) {
406 1.9 christos char namebuf[DNS_NAME_FORMATSIZE];
407 1.9 christos char typebuf[DNS_RDATATYPE_FORMATSIZE];
408 1.9 christos
409 1.9 christos dns_name_format(&bad->name, namebuf, sizeof(namebuf));
410 1.9 christos dns_rdatatype_format(bad->type, typebuf, sizeof(typebuf));
411 1.9 christos fprintf(fp, "; %s/%s [ttl %" PRIu32 "]\n", namebuf, typebuf,
412 1.9 christos bad->expire - now);
413 1.1 christos }
414 1.1 christos
415 1.1 christos void
416 1.1 christos dns_badcache_print(dns_badcache_t *bc, const char *cachename, FILE *fp) {
417 1.9 christos dns_bcentry_t *bad;
418 1.9 christos isc_stdtime_t now = isc_stdtime_now();
419 1.1 christos
420 1.1 christos REQUIRE(VALID_BADCACHE(bc));
421 1.1 christos REQUIRE(fp != NULL);
422 1.1 christos
423 1.1 christos fprintf(fp, ";\n; %s\n;\n", cachename);
424 1.1 christos
425 1.9 christos rcu_read_lock();
426 1.9 christos struct cds_lfht *ht = rcu_dereference(bc->ht);
427 1.9 christos INSIST(ht != NULL);
428 1.9 christos
429 1.9 christos struct cds_lfht_iter iter;
430 1.9 christos cds_lfht_for_each_entry(ht, &iter, bad, ht_node) {
431 1.9 christos if (bcentry_alive(ht, bad, now)) {
432 1.9 christos bcentry_print(bad, now, fp);
433 1.1 christos }
434 1.1 christos }
435 1.9 christos
436 1.9 christos rcu_read_unlock();
437 1.1 christos }
438