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