Home | History | Annotate | Line # | Download | only in dns
      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