Home | History | Annotate | Line # | Download | only in dns
      1 /*	$NetBSD: acl.c,v 1.11 2026/01/29 18:37:48 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/mem.h>
     22 #include <isc/once.h>
     23 #include <isc/string.h>
     24 #include <isc/urcu.h>
     25 #include <isc/util.h>
     26 
     27 #include <dns/acl.h>
     28 #include <dns/iptable.h>
     29 
     30 #define DNS_ACLENV_MAGIC ISC_MAGIC('a', 'c', 'n', 'v')
     31 #define VALID_ACLENV(a)	 ISC_MAGIC_VALID(a, DNS_ACLENV_MAGIC)
     32 
     33 /*
     34  * Create a new ACL, including an IP table and an array with room
     35  * for 'n' ACL elements.  The elements are uninitialized and the
     36  * length is 0.
     37  */
     38 void
     39 dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target) {
     40 	REQUIRE(target != NULL && *target == NULL);
     41 
     42 	dns_acl_t *acl = isc_mem_get(mctx, sizeof(*acl));
     43 	*acl = (dns_acl_t){
     44 		.references = ISC_REFCOUNT_INITIALIZER(1),
     45 		.nextincache = ISC_LINK_INITIALIZER,
     46 		.elements = isc_mem_cget(mctx, n, sizeof(acl->elements[0])),
     47 		.alloc = n,
     48 		.ports_and_transports = ISC_LIST_INITIALIZER,
     49 		.magic = DNS_ACL_MAGIC,
     50 	};
     51 
     52 	isc_mem_attach(mctx, &acl->mctx);
     53 	dns_iptable_create(acl->mctx, &acl->iptable);
     54 
     55 	*target = acl;
     56 }
     57 
     58 /*
     59  * Create a new ACL and initialize it with the value "any" or "none",
     60  * depending on the value of the "neg" parameter.
     61  * "any" is a positive iptable entry with bit length 0.
     62  * "none" is the same as "!any".
     63  */
     64 static isc_result_t
     65 dns_acl_anyornone(isc_mem_t *mctx, bool neg, dns_acl_t **target) {
     66 	isc_result_t result;
     67 	dns_acl_t *acl = NULL;
     68 
     69 	dns_acl_create(mctx, 0, &acl);
     70 
     71 	result = dns_iptable_addprefix(acl->iptable, NULL, 0, !neg);
     72 	if (result != ISC_R_SUCCESS) {
     73 		dns_acl_detach(&acl);
     74 		return result;
     75 	}
     76 
     77 	*target = acl;
     78 	return result;
     79 }
     80 
     81 /*
     82  * Create a new ACL that matches everything.
     83  */
     84 isc_result_t
     85 dns_acl_any(isc_mem_t *mctx, dns_acl_t **target) {
     86 	return dns_acl_anyornone(mctx, false, target);
     87 }
     88 
     89 /*
     90  * Create a new ACL that matches nothing.
     91  */
     92 isc_result_t
     93 dns_acl_none(isc_mem_t *mctx, dns_acl_t **target) {
     94 	return dns_acl_anyornone(mctx, true, target);
     95 }
     96 
     97 /*
     98  * If pos is true, test whether acl is set to "{ any; }"
     99  * If pos is false, test whether acl is set to "{ none; }"
    100  */
    101 static bool
    102 dns_acl_isanyornone(dns_acl_t *acl, bool pos) {
    103 	/* Should never happen but let's be safe */
    104 	if (acl == NULL || acl->iptable == NULL ||
    105 	    acl->iptable->radix == NULL || acl->iptable->radix->head == NULL ||
    106 	    acl->iptable->radix->head->prefix == NULL)
    107 	{
    108 		return false;
    109 	}
    110 
    111 	if (acl->length != 0 || dns_acl_node_count(acl) != 1) {
    112 		return false;
    113 	}
    114 
    115 	if (acl->iptable->radix->head->prefix->bitlen == 0 &&
    116 	    acl->iptable->radix->head->data[0] != NULL &&
    117 	    acl->iptable->radix->head->data[0] ==
    118 		    acl->iptable->radix->head->data[1] &&
    119 	    *(bool *)(acl->iptable->radix->head->data[0]) == pos)
    120 	{
    121 		return true;
    122 	}
    123 
    124 	return false; /* All others */
    125 }
    126 
    127 /*
    128  * Test whether acl is set to "{ any; }"
    129  */
    130 bool
    131 dns_acl_isany(dns_acl_t *acl) {
    132 	return dns_acl_isanyornone(acl, true);
    133 }
    134 
    135 /*
    136  * Test whether acl is set to "{ none; }"
    137  */
    138 bool
    139 dns_acl_isnone(dns_acl_t *acl) {
    140 	return dns_acl_isanyornone(acl, false);
    141 }
    142 
    143 /*
    144  * Determine whether a given address or signer matches a given ACL.
    145  * For a match with a positive ACL element or iptable radix entry,
    146  * return with a positive value in match; for a match with a negated ACL
    147  * element or radix entry, return with a negative value in match.
    148  */
    149 
    150 isc_result_t
    151 dns_acl_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
    152 	      const dns_acl_t *acl, dns_aclenv_t *env, int *match,
    153 	      const dns_aclelement_t **matchelt) {
    154 	uint16_t bitlen;
    155 	isc_prefix_t pfx;
    156 	isc_radix_node_t *node = NULL;
    157 	const isc_netaddr_t *addr = reqaddr;
    158 	isc_netaddr_t v4addr;
    159 	isc_result_t result;
    160 	int match_num = -1;
    161 	unsigned int i;
    162 
    163 	REQUIRE(reqaddr != NULL);
    164 	REQUIRE(matchelt == NULL || *matchelt == NULL);
    165 
    166 	if (env != NULL && env->match_mapped && addr->family == AF_INET6 &&
    167 	    IN6_IS_ADDR_V4MAPPED(&addr->type.in6))
    168 	{
    169 		isc_netaddr_fromv4mapped(&v4addr, addr);
    170 		addr = &v4addr;
    171 	}
    172 
    173 	/* Always match with host addresses. */
    174 	bitlen = (addr->family == AF_INET6) ? 128 : 32;
    175 	NETADDR_TO_PREFIX_T(addr, pfx, bitlen);
    176 
    177 	/* Assume no match. */
    178 	*match = 0;
    179 
    180 	/* Search radix. */
    181 	result = isc_radix_search(acl->iptable->radix, &node, &pfx);
    182 
    183 	/* Found a match. */
    184 	if (result == ISC_R_SUCCESS && node != NULL) {
    185 		int fam = ISC_RADIX_FAMILY(&pfx);
    186 		match_num = node->node_num[fam];
    187 		if (*(bool *)node->data[fam]) {
    188 			*match = match_num;
    189 		} else {
    190 			*match = -match_num;
    191 		}
    192 	}
    193 
    194 	isc_refcount_destroy(&pfx.refcount);
    195 
    196 	/* Now search non-radix elements for a match with a lower node_num. */
    197 	for (i = 0; i < acl->length; i++) {
    198 		dns_aclelement_t *e = &acl->elements[i];
    199 
    200 		/* Already found a better match? */
    201 		if (match_num != -1 && match_num < e->node_num) {
    202 			break;
    203 		}
    204 
    205 		if (dns_aclelement_match(reqaddr, reqsigner, e, env, matchelt))
    206 		{
    207 			if (match_num == -1 || e->node_num < match_num) {
    208 				if (e->negative) {
    209 					*match = -e->node_num;
    210 				} else {
    211 					*match = e->node_num;
    212 				}
    213 			}
    214 			break;
    215 		}
    216 	}
    217 
    218 	return ISC_R_SUCCESS;
    219 }
    220 
    221 isc_result_t
    222 dns_acl_match_port_transport(const isc_netaddr_t *reqaddr,
    223 			     const in_port_t local_port,
    224 			     const isc_nmsocket_type_t transport,
    225 			     const bool encrypted, const dns_name_t *reqsigner,
    226 			     const dns_acl_t *acl, dns_aclenv_t *env,
    227 			     int *match, const dns_aclelement_t **matchelt) {
    228 	isc_result_t result = ISC_R_SUCCESS;
    229 	dns_acl_port_transports_t *next;
    230 
    231 	REQUIRE(reqaddr != NULL);
    232 	REQUIRE(DNS_ACL_VALID(acl));
    233 
    234 	if (!ISC_LIST_EMPTY(acl->ports_and_transports)) {
    235 		result = ISC_R_FAILURE;
    236 		for (next = ISC_LIST_HEAD(acl->ports_and_transports);
    237 		     next != NULL; next = ISC_LIST_NEXT(next, link))
    238 		{
    239 			bool match_port = true;
    240 			bool match_transport = true;
    241 
    242 			if (next->port != 0) {
    243 				/* Port is specified. */
    244 				match_port = (local_port == next->port);
    245 			}
    246 			if (next->transports != 0) {
    247 				/* Transport protocol is specified. */
    248 				match_transport =
    249 					((transport & next->transports) ==
    250 						 transport &&
    251 					 next->encrypted == encrypted);
    252 			}
    253 
    254 			if (match_port && match_transport) {
    255 				result = next->negative ? ISC_R_FAILURE
    256 							: ISC_R_SUCCESS;
    257 				break;
    258 			}
    259 		}
    260 	}
    261 
    262 	if (result != ISC_R_SUCCESS) {
    263 		return result;
    264 	}
    265 
    266 	return dns_acl_match(reqaddr, reqsigner, acl, env, match, matchelt);
    267 }
    268 
    269 /*
    270  * Merge the contents of one ACL into another.  Call dns_iptable_merge()
    271  * for the IP tables, then concatenate the element arrays.
    272  *
    273  * If pos is set to false, then the nested ACL is to be negated.  This
    274  * means reverse the sense of each *positive* element or IP table node,
    275  * but leave negatives alone, so as to prevent a double-negative causing
    276  * an unexpected positive match in the parent ACL.
    277  */
    278 isc_result_t
    279 dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, bool pos) {
    280 	isc_result_t result;
    281 	unsigned int nelem, i;
    282 	int max_node = 0, nodes;
    283 
    284 	/* Resize the element array if needed. */
    285 	if (dest->length + source->length > dest->alloc) {
    286 		size_t newalloc = dest->alloc + source->alloc;
    287 		if (newalloc < 4) {
    288 			newalloc = 4;
    289 		}
    290 
    291 		dest->elements = isc_mem_creget(dest->mctx, dest->elements,
    292 						dest->alloc, newalloc,
    293 						sizeof(dest->elements[0]));
    294 		dest->alloc = newalloc;
    295 	}
    296 
    297 	/*
    298 	 * Now copy in the new elements, increasing their node_num
    299 	 * values so as to keep the new ACL consistent.  If we're
    300 	 * negating, then negate positive elements, but keep negative
    301 	 * elements the same for security reasons.
    302 	 */
    303 	nelem = dest->length;
    304 	dest->length += source->length;
    305 	for (i = 0; i < source->length; i++) {
    306 		if (source->elements[i].node_num > max_node) {
    307 			max_node = source->elements[i].node_num;
    308 		}
    309 
    310 		/* Copy type. */
    311 		dest->elements[nelem + i].type = source->elements[i].type;
    312 
    313 		/* Adjust node numbering. */
    314 		dest->elements[nelem + i].node_num =
    315 			source->elements[i].node_num + dns_acl_node_count(dest);
    316 
    317 		/* Duplicate nested acl. */
    318 		if (source->elements[i].type == dns_aclelementtype_nestedacl &&
    319 		    source->elements[i].nestedacl != NULL)
    320 		{
    321 			dns_acl_attach(source->elements[i].nestedacl,
    322 				       &dest->elements[nelem + i].nestedacl);
    323 		}
    324 
    325 		/* Duplicate key name. */
    326 		if (source->elements[i].type == dns_aclelementtype_keyname) {
    327 			dns_name_init(&dest->elements[nelem + i].keyname, NULL);
    328 			dns_name_dup(&source->elements[i].keyname, dest->mctx,
    329 				     &dest->elements[nelem + i].keyname);
    330 		}
    331 
    332 #if defined(HAVE_GEOIP2)
    333 		/* Duplicate GeoIP data */
    334 		if (source->elements[i].type == dns_aclelementtype_geoip) {
    335 			dest->elements[nelem + i].geoip_elem =
    336 				source->elements[i].geoip_elem;
    337 		}
    338 #endif /* if defined(HAVE_GEOIP2) */
    339 
    340 		/* reverse sense of positives if this is a negative acl */
    341 		if (!pos && !source->elements[i].negative) {
    342 			dest->elements[nelem + i].negative = true;
    343 		} else {
    344 			dest->elements[nelem + i].negative =
    345 				source->elements[i].negative;
    346 		}
    347 	}
    348 
    349 	/*
    350 	 * Merge the iptables.  Make sure the destination ACL's
    351 	 * node_count value is set correctly afterward.
    352 	 */
    353 	nodes = max_node + dns_acl_node_count(dest);
    354 	result = dns_iptable_merge(dest->iptable, source->iptable, pos);
    355 	if (result != ISC_R_SUCCESS) {
    356 		return result;
    357 	}
    358 	if (nodes > dns_acl_node_count(dest)) {
    359 		dns_acl_node_count(dest) = nodes;
    360 	}
    361 
    362 	/*
    363 	 * Merge ports and transports
    364 	 */
    365 	dns_acl_merge_ports_transports(dest, source, pos);
    366 
    367 	return ISC_R_SUCCESS;
    368 }
    369 
    370 /*
    371  * Like dns_acl_match, but matches against the single ACL element 'e'
    372  * rather than a complete ACL, and returns true iff it matched.
    373  *
    374  * To determine whether the match was positive or negative, the
    375  * caller should examine e->negative.  Since the element 'e' may be
    376  * a reference to a named ACL or a nested ACL, a matching element
    377  * returned through 'matchelt' is not necessarily 'e' itself.
    378  */
    379 
    380 bool
    381 dns_aclelement_match(const isc_netaddr_t *reqaddr, const dns_name_t *reqsigner,
    382 		     const dns_aclelement_t *e, dns_aclenv_t *env,
    383 		     const dns_aclelement_t **matchelt) {
    384 	dns_acl_t *inner = NULL;
    385 	int indirectmatch;
    386 	isc_result_t result;
    387 
    388 	switch (e->type) {
    389 	case dns_aclelementtype_keyname:
    390 		if (reqsigner != NULL && dns_name_equal(reqsigner, &e->keyname))
    391 		{
    392 			if (matchelt != NULL) {
    393 				*matchelt = e;
    394 			}
    395 			return true;
    396 		} else {
    397 			return false;
    398 		}
    399 
    400 	case dns_aclelementtype_nestedacl:
    401 		dns_acl_attach(e->nestedacl, &inner);
    402 		break;
    403 
    404 	case dns_aclelementtype_localhost:
    405 		if (env == NULL) {
    406 			return false;
    407 		}
    408 		rcu_read_lock();
    409 		dns_acl_attach(rcu_dereference(env->localhost), &inner);
    410 		rcu_read_unlock();
    411 		break;
    412 
    413 	case dns_aclelementtype_localnets:
    414 		if (env == NULL) {
    415 			return false;
    416 		}
    417 		rcu_read_lock();
    418 		dns_acl_attach(rcu_dereference(env->localnets), &inner);
    419 		rcu_read_unlock();
    420 		break;
    421 
    422 #if defined(HAVE_GEOIP2)
    423 	case dns_aclelementtype_geoip:
    424 		if (env == NULL || env->geoip == NULL) {
    425 			return false;
    426 		}
    427 		return dns_geoip_match(reqaddr, env->geoip, &e->geoip_elem);
    428 #endif /* if defined(HAVE_GEOIP2) */
    429 	default:
    430 		UNREACHABLE();
    431 	}
    432 
    433 	result = dns_acl_match(reqaddr, reqsigner, inner, env, &indirectmatch,
    434 			       matchelt);
    435 	INSIST(result == ISC_R_SUCCESS);
    436 
    437 	dns_acl_detach(&inner);
    438 
    439 	/*
    440 	 * Treat negative matches in indirect ACLs as "no match".
    441 	 * That way, a negated indirect ACL will never become a
    442 	 * surprise positive match through double negation.
    443 	 * XXXDCL this should be documented.
    444 	 */
    445 	if (indirectmatch > 0) {
    446 		if (matchelt != NULL) {
    447 			*matchelt = e;
    448 		}
    449 		return true;
    450 	}
    451 
    452 	/*
    453 	 * A negative indirect match may have set *matchelt, but we don't
    454 	 * want it set when we return.
    455 	 */
    456 	if (matchelt != NULL) {
    457 		*matchelt = NULL;
    458 	}
    459 
    460 	return false;
    461 }
    462 
    463 static void
    464 dns__acl_destroy_port_transports(dns_acl_t *acl) {
    465 	dns_acl_port_transports_t *port_proto = NULL;
    466 	dns_acl_port_transports_t *next = NULL;
    467 	ISC_LIST_FOREACH_SAFE(acl->ports_and_transports, port_proto, link, next)
    468 	{
    469 		ISC_LIST_DEQUEUE(acl->ports_and_transports, port_proto, link);
    470 		isc_mem_put(acl->mctx, port_proto, sizeof(*port_proto));
    471 	}
    472 }
    473 
    474 static void
    475 dns__acl_destroy(dns_acl_t *dacl) {
    476 	INSIST(!ISC_LINK_LINKED(dacl, nextincache));
    477 
    478 	isc_refcount_destroy(&dacl->references);
    479 	dacl->magic = 0;
    480 
    481 	for (size_t i = 0; i < dacl->length; i++) {
    482 		dns_aclelement_t *de = &dacl->elements[i];
    483 		if (de->type == dns_aclelementtype_keyname) {
    484 			dns_name_free(&de->keyname, dacl->mctx);
    485 		} else if (de->type == dns_aclelementtype_nestedacl) {
    486 			dns_acl_detach(&de->nestedacl);
    487 		}
    488 	}
    489 	if (dacl->elements != NULL) {
    490 		isc_mem_cput(dacl->mctx, dacl->elements, dacl->alloc,
    491 			     sizeof(dacl->elements[0]));
    492 	}
    493 	if (dacl->name != NULL) {
    494 		isc_mem_free(dacl->mctx, dacl->name);
    495 	}
    496 	if (dacl->iptable != NULL) {
    497 		dns_iptable_detach(&dacl->iptable);
    498 	}
    499 
    500 	dns__acl_destroy_port_transports(dacl);
    501 
    502 	isc_mem_putanddetach(&dacl->mctx, dacl, sizeof(*dacl));
    503 }
    504 
    505 #if DNS_ACL_TRACE
    506 ISC_REFCOUNT_TRACE_IMPL(dns_acl, dns__acl_destroy);
    507 #else
    508 ISC_REFCOUNT_IMPL(dns_acl, dns__acl_destroy);
    509 #endif
    510 
    511 static isc_once_t insecure_prefix_once = ISC_ONCE_INIT;
    512 static isc_mutex_t insecure_prefix_lock;
    513 static bool insecure_prefix_found;
    514 
    515 static void
    516 initialize_action(void) {
    517 	isc_mutex_init(&insecure_prefix_lock);
    518 }
    519 
    520 /*
    521  * Called via isc_radix_process() to find IP table nodes that are
    522  * insecure.
    523  */
    524 static void
    525 is_insecure(isc_prefix_t *prefix, void **data) {
    526 	/*
    527 	 * If all nonexistent or negative then this node is secure.
    528 	 */
    529 	if ((data[0] == NULL || !*(bool *)data[0]) &&
    530 	    (data[1] == NULL || !*(bool *)data[1]))
    531 	{
    532 		return;
    533 	}
    534 
    535 	/*
    536 	 * If a loopback address found and the other family
    537 	 * entry doesn't exist or is negative, return.
    538 	 */
    539 	if (prefix->bitlen == 32 &&
    540 	    htonl(prefix->add.sin.s_addr) == INADDR_LOOPBACK &&
    541 	    (data[1] == NULL || !*(bool *)data[1]))
    542 	{
    543 		return;
    544 	}
    545 
    546 	if (prefix->bitlen == 128 && IN6_IS_ADDR_LOOPBACK(&prefix->add.sin6) &&
    547 	    (data[0] == NULL || !*(bool *)data[0]))
    548 	{
    549 		return;
    550 	}
    551 
    552 	/* Non-negated, non-loopback */
    553 	insecure_prefix_found = true; /* LOCKED */
    554 	return;
    555 }
    556 
    557 /*
    558  * Return true iff the acl 'a' is considered insecure, that is,
    559  * if it contains IP addresses other than those of the local host.
    560  * This is intended for applications such as printing warning
    561  * messages for suspect ACLs; it is not intended for making access
    562  * control decisions.  We make no guarantee that an ACL for which
    563  * this function returns false is safe.
    564  */
    565 bool
    566 dns_acl_isinsecure(const dns_acl_t *a) {
    567 	unsigned int i;
    568 	bool insecure;
    569 
    570 	isc_once_do(&insecure_prefix_once, initialize_action);
    571 
    572 	/*
    573 	 * Walk radix tree to find out if there are any non-negated,
    574 	 * non-loopback prefixes.
    575 	 */
    576 	LOCK(&insecure_prefix_lock);
    577 	insecure_prefix_found = false;
    578 	isc_radix_process(a->iptable->radix, is_insecure);
    579 	insecure = insecure_prefix_found;
    580 	UNLOCK(&insecure_prefix_lock);
    581 	if (insecure) {
    582 		return true;
    583 	}
    584 
    585 	/* Now check non-radix elements */
    586 	for (i = 0; i < a->length; i++) {
    587 		dns_aclelement_t *e = &a->elements[i];
    588 
    589 		/* A negated match can never be insecure. */
    590 		if (e->negative) {
    591 			continue;
    592 		}
    593 
    594 		switch (e->type) {
    595 		case dns_aclelementtype_keyname:
    596 		case dns_aclelementtype_localhost:
    597 			continue;
    598 
    599 		case dns_aclelementtype_nestedacl:
    600 			if (dns_acl_isinsecure(e->nestedacl)) {
    601 				return true;
    602 			}
    603 			continue;
    604 
    605 #if defined(HAVE_GEOIP2)
    606 		case dns_aclelementtype_geoip:
    607 #endif /* if defined(HAVE_GEOIP2) */
    608 		case dns_aclelementtype_localnets:
    609 			return true;
    610 
    611 		default:
    612 			UNREACHABLE();
    613 		}
    614 	}
    615 
    616 	/* No insecure elements were found. */
    617 	return false;
    618 }
    619 
    620 /*%
    621  * Check whether an address/signer is allowed by a given acl/aclenv.
    622  */
    623 bool
    624 dns_acl_allowed(isc_netaddr_t *addr, const dns_name_t *signer, dns_acl_t *acl,
    625 		dns_aclenv_t *aclenv) {
    626 	int match;
    627 	isc_result_t result;
    628 
    629 	if (acl == NULL) {
    630 		return true;
    631 	}
    632 	result = dns_acl_match(addr, signer, acl, aclenv, &match, NULL);
    633 	if (result == ISC_R_SUCCESS && match > 0) {
    634 		return true;
    635 	}
    636 	return false;
    637 }
    638 
    639 /*
    640  * Initialize ACL environment, setting up localhost and localnets ACLs
    641  */
    642 void
    643 dns_aclenv_create(isc_mem_t *mctx, dns_aclenv_t **envp) {
    644 	dns_aclenv_t *env = isc_mem_get(mctx, sizeof(*env));
    645 	*env = (dns_aclenv_t){
    646 		.references = ISC_REFCOUNT_INITIALIZER(1),
    647 		.magic = DNS_ACLENV_MAGIC,
    648 	};
    649 
    650 	isc_mem_attach(mctx, &env->mctx);
    651 	isc_refcount_init(&env->references, 1);
    652 
    653 	dns_acl_create(mctx, 0, &env->localhost);
    654 	dns_acl_create(mctx, 0, &env->localnets);
    655 
    656 	*envp = env;
    657 }
    658 
    659 void
    660 dns_aclenv_set(dns_aclenv_t *env, dns_acl_t *localhost, dns_acl_t *localnets) {
    661 	REQUIRE(VALID_ACLENV(env));
    662 	REQUIRE(DNS_ACL_VALID(localhost));
    663 	REQUIRE(DNS_ACL_VALID(localnets));
    664 
    665 	localhost = rcu_xchg_pointer(&env->localhost, dns_acl_ref(localhost));
    666 	localnets = rcu_xchg_pointer(&env->localnets, dns_acl_ref(localnets));
    667 
    668 	/*
    669 	 * This function is called only during interface scanning, so blocking
    670 	 * a bit is acceptable. Wait until all ongoing attachments to old
    671 	 * 'localhost' and 'localnets' are finished before we can detach and
    672 	 * possibly destroy them.
    673 	 *
    674 	 * The problem here isn't the memory reclamation per se, but
    675 	 * the reference counting race - we need to wait for the
    676 	 * critical section to end before we decrement the value and
    677 	 * possibly destroy the acl objects.
    678 	 */
    679 	synchronize_rcu();
    680 
    681 	dns_acl_detach(&localhost);
    682 	dns_acl_detach(&localnets);
    683 }
    684 
    685 void
    686 dns_aclenv_copy(dns_aclenv_t *target, dns_aclenv_t *source) {
    687 	REQUIRE(VALID_ACLENV(source));
    688 	REQUIRE(VALID_ACLENV(target));
    689 
    690 	rcu_read_lock();
    691 
    692 	/*
    693 	 * We need to acquire the reference inside the critical section.
    694 	 */
    695 
    696 	dns_acl_t *localhost = dns_acl_ref(rcu_dereference(source->localhost));
    697 	INSIST(DNS_ACL_VALID(localhost));
    698 
    699 	dns_acl_t *localnets = dns_acl_ref(rcu_dereference(source->localnets));
    700 	INSIST(DNS_ACL_VALID(localnets));
    701 
    702 	rcu_read_unlock();
    703 
    704 	localhost = rcu_xchg_pointer(&target->localhost, localhost);
    705 	localnets = rcu_xchg_pointer(&target->localnets, localnets);
    706 
    707 	/*
    708 	 * This function is called only during (re)configuration, so blocking
    709 	 * a bit is acceptable.
    710 	 *
    711 	 * See the comment above in dns_aclenv_set() for more detail.
    712 	 */
    713 	synchronize_rcu();
    714 
    715 	target->match_mapped = source->match_mapped;
    716 #if defined(HAVE_GEOIP2)
    717 	target->geoip = source->geoip;
    718 #endif /* if defined(HAVE_GEOIP2) */
    719 
    720 	dns_acl_detach(&localhost);
    721 	dns_acl_detach(&localnets);
    722 }
    723 
    724 static void
    725 dns__aclenv_destroy(dns_aclenv_t *aclenv) {
    726 	REQUIRE(VALID_ACLENV(aclenv));
    727 
    728 	aclenv->magic = 0;
    729 
    730 	/*
    731 	 * The last reference to the aclenv has been detached, so nobody should
    732 	 * be reading from this aclenv.  We can destroy the localhost and
    733 	 * localnet directly without swapping the pointers.
    734 	 */
    735 
    736 	dns_acl_detach(&aclenv->localhost);
    737 	dns_acl_detach(&aclenv->localnets);
    738 
    739 	isc_mem_putanddetach(&aclenv->mctx, aclenv, sizeof(*aclenv));
    740 }
    741 
    742 #if DNS_ACL_TRACE
    743 ISC_REFCOUNT_TRACE_IMPL(dns_aclenv, dns__aclenv_destroy);
    744 #else
    745 ISC_REFCOUNT_IMPL(dns_aclenv, dns__aclenv_destroy);
    746 #endif
    747 
    748 void
    749 dns_acl_add_port_transports(dns_acl_t *acl, const in_port_t port,
    750 			    const uint32_t transports, const bool encrypted,
    751 			    const bool negative) {
    752 	dns_acl_port_transports_t *port_proto;
    753 	REQUIRE(DNS_ACL_VALID(acl));
    754 	REQUIRE(port != 0 || transports != 0);
    755 
    756 	port_proto = isc_mem_get(acl->mctx, sizeof(*port_proto));
    757 	*port_proto = (dns_acl_port_transports_t){ .port = port,
    758 						   .transports = transports,
    759 						   .encrypted = encrypted,
    760 						   .negative = negative };
    761 
    762 	ISC_LINK_INIT(port_proto, link);
    763 
    764 	ISC_LIST_APPEND(acl->ports_and_transports, port_proto, link);
    765 	acl->port_proto_entries++;
    766 }
    767 
    768 void
    769 dns_acl_merge_ports_transports(dns_acl_t *dest, dns_acl_t *source, bool pos) {
    770 	dns_acl_port_transports_t *next;
    771 
    772 	REQUIRE(DNS_ACL_VALID(dest));
    773 	REQUIRE(DNS_ACL_VALID(source));
    774 
    775 	const bool negative = !pos;
    776 
    777 	/*
    778 	 * Merge ports and transports
    779 	 */
    780 	for (next = ISC_LIST_HEAD(source->ports_and_transports); next != NULL;
    781 	     next = ISC_LIST_NEXT(next, link))
    782 	{
    783 		const bool next_positive = !next->negative;
    784 		bool add_negative;
    785 
    786 		/*
    787 		 * Reverse sense of positives if this is a negative acl.  The
    788 		 * logic is used (and, thus, enforced) by dns_acl_merge(),
    789 		 * from which dns_acl_merge_ports_transports() is called.
    790 		 */
    791 		if (negative && next_positive) {
    792 			add_negative = true;
    793 		} else {
    794 			add_negative = next->negative;
    795 		}
    796 
    797 		dns_acl_add_port_transports(dest, next->port, next->transports,
    798 					    next->encrypted, add_negative);
    799 	}
    800 }
    801