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