Home | History | Annotate | Line # | Download | only in dns
      1 /*	$NetBSD: transport.c,v 1.3 2025/01/26 16:25:25 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 #include <inttypes.h>
     17 
     18 #include <isc/hashmap.h>
     19 #include <isc/list.h>
     20 #include <isc/mem.h>
     21 #include <isc/netaddr.h>
     22 #include <isc/refcount.h>
     23 #include <isc/result.h>
     24 #include <isc/rwlock.h>
     25 #include <isc/sockaddr.h>
     26 #include <isc/util.h>
     27 
     28 #include <dns/fixedname.h>
     29 #include <dns/name.h>
     30 #include <dns/transport.h>
     31 
     32 #define TRANSPORT_MAGIC	     ISC_MAGIC('T', 'r', 'n', 's')
     33 #define VALID_TRANSPORT(ptr) ISC_MAGIC_VALID(ptr, TRANSPORT_MAGIC)
     34 
     35 #define TRANSPORT_LIST_MAGIC	  ISC_MAGIC('T', 'r', 'L', 's')
     36 #define VALID_TRANSPORT_LIST(ptr) ISC_MAGIC_VALID(ptr, TRANSPORT_LIST_MAGIC)
     37 
     38 struct dns_transport_list {
     39 	unsigned int magic;
     40 	isc_refcount_t references;
     41 	isc_mem_t *mctx;
     42 	isc_rwlock_t lock;
     43 	isc_hashmap_t *transports[DNS_TRANSPORT_COUNT];
     44 };
     45 
     46 typedef enum ternary { ter_none = 0, ter_true = 1, ter_false = 2 } ternary_t;
     47 
     48 struct dns_transport {
     49 	unsigned int magic;
     50 	isc_refcount_t references;
     51 	isc_mem_t *mctx;
     52 	dns_transport_type_t type;
     53 	dns_fixedname_t fn;
     54 	dns_name_t *name;
     55 	struct {
     56 		char *tlsname;
     57 		char *certfile;
     58 		char *keyfile;
     59 		char *cafile;
     60 		char *remote_hostname;
     61 		char *ciphers;
     62 		char *cipher_suites;
     63 		uint32_t protocol_versions;
     64 		ternary_t prefer_server_ciphers;
     65 		bool always_verify_remote;
     66 	} tls;
     67 	struct {
     68 		char *endpoint;
     69 		dns_http_mode_t mode;
     70 	} doh;
     71 };
     72 
     73 static bool
     74 transport_match(void *node, const void *key) {
     75 	dns_transport_t *transport = node;
     76 
     77 	return dns_name_equal(transport->name, key);
     78 }
     79 
     80 static isc_result_t
     81 list_add(dns_transport_list_t *list, const dns_name_t *name,
     82 	 const dns_transport_type_t type, dns_transport_t *transport) {
     83 	isc_result_t result;
     84 	isc_hashmap_t *hm = NULL;
     85 
     86 	RWLOCK(&list->lock, isc_rwlocktype_write);
     87 	hm = list->transports[type];
     88 	INSIST(hm != NULL);
     89 
     90 	transport->name = dns_fixedname_initname(&transport->fn);
     91 	dns_name_copy(name, transport->name);
     92 	result = isc_hashmap_add(hm, dns_name_hash(name), transport_match, name,
     93 				 transport, NULL);
     94 	RWUNLOCK(&list->lock, isc_rwlocktype_write);
     95 
     96 	return result;
     97 }
     98 
     99 dns_transport_type_t
    100 dns_transport_get_type(const dns_transport_t *transport) {
    101 	REQUIRE(VALID_TRANSPORT(transport));
    102 
    103 	return transport->type;
    104 }
    105 
    106 char *
    107 dns_transport_get_certfile(const dns_transport_t *transport) {
    108 	REQUIRE(VALID_TRANSPORT(transport));
    109 
    110 	return transport->tls.certfile;
    111 }
    112 
    113 char *
    114 dns_transport_get_keyfile(const dns_transport_t *transport) {
    115 	REQUIRE(VALID_TRANSPORT(transport));
    116 
    117 	return transport->tls.keyfile;
    118 }
    119 
    120 char *
    121 dns_transport_get_cafile(const dns_transport_t *transport) {
    122 	REQUIRE(VALID_TRANSPORT(transport));
    123 
    124 	return transport->tls.cafile;
    125 }
    126 
    127 char *
    128 dns_transport_get_remote_hostname(const dns_transport_t *transport) {
    129 	REQUIRE(VALID_TRANSPORT(transport));
    130 
    131 	return transport->tls.remote_hostname;
    132 }
    133 
    134 char *
    135 dns_transport_get_endpoint(const dns_transport_t *transport) {
    136 	REQUIRE(VALID_TRANSPORT(transport));
    137 
    138 	return transport->doh.endpoint;
    139 }
    140 
    141 dns_http_mode_t
    142 dns_transport_get_mode(const dns_transport_t *transport) {
    143 	REQUIRE(VALID_TRANSPORT(transport));
    144 
    145 	return transport->doh.mode;
    146 }
    147 
    148 dns_transport_t *
    149 dns_transport_new(const dns_name_t *name, dns_transport_type_t type,
    150 		  dns_transport_list_t *list) {
    151 	dns_transport_t *transport = isc_mem_get(list->mctx,
    152 						 sizeof(*transport));
    153 	*transport = (dns_transport_t){ .type = type };
    154 	isc_refcount_init(&transport->references, 1);
    155 	isc_mem_attach(list->mctx, &transport->mctx);
    156 	transport->magic = TRANSPORT_MAGIC;
    157 
    158 	list_add(list, name, type, transport);
    159 
    160 	return transport;
    161 }
    162 
    163 void
    164 dns_transport_set_certfile(dns_transport_t *transport, const char *certfile) {
    165 	REQUIRE(VALID_TRANSPORT(transport));
    166 	REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
    167 		transport->type == DNS_TRANSPORT_HTTP);
    168 
    169 	if (transport->tls.certfile != NULL) {
    170 		isc_mem_free(transport->mctx, transport->tls.certfile);
    171 	}
    172 
    173 	if (certfile != NULL) {
    174 		transport->tls.certfile = isc_mem_strdup(transport->mctx,
    175 							 certfile);
    176 	}
    177 }
    178 
    179 void
    180 dns_transport_set_keyfile(dns_transport_t *transport, const char *keyfile) {
    181 	REQUIRE(VALID_TRANSPORT(transport));
    182 	REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
    183 		transport->type == DNS_TRANSPORT_HTTP);
    184 
    185 	if (transport->tls.keyfile != NULL) {
    186 		isc_mem_free(transport->mctx, transport->tls.keyfile);
    187 	}
    188 
    189 	if (keyfile != NULL) {
    190 		transport->tls.keyfile = isc_mem_strdup(transport->mctx,
    191 							keyfile);
    192 	}
    193 }
    194 
    195 void
    196 dns_transport_set_cafile(dns_transport_t *transport, const char *cafile) {
    197 	REQUIRE(VALID_TRANSPORT(transport));
    198 	REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
    199 		transport->type == DNS_TRANSPORT_HTTP);
    200 
    201 	if (transport->tls.cafile != NULL) {
    202 		isc_mem_free(transport->mctx, transport->tls.cafile);
    203 	}
    204 
    205 	if (cafile != NULL) {
    206 		transport->tls.cafile = isc_mem_strdup(transport->mctx, cafile);
    207 	}
    208 }
    209 
    210 void
    211 dns_transport_set_remote_hostname(dns_transport_t *transport,
    212 				  const char *hostname) {
    213 	REQUIRE(VALID_TRANSPORT(transport));
    214 	REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
    215 		transport->type == DNS_TRANSPORT_HTTP);
    216 
    217 	if (transport->tls.remote_hostname != NULL) {
    218 		isc_mem_free(transport->mctx, transport->tls.remote_hostname);
    219 	}
    220 
    221 	if (hostname != NULL) {
    222 		transport->tls.remote_hostname = isc_mem_strdup(transport->mctx,
    223 								hostname);
    224 	}
    225 }
    226 
    227 void
    228 dns_transport_set_endpoint(dns_transport_t *transport, const char *endpoint) {
    229 	REQUIRE(VALID_TRANSPORT(transport));
    230 	REQUIRE(transport->type == DNS_TRANSPORT_HTTP);
    231 
    232 	if (transport->doh.endpoint != NULL) {
    233 		isc_mem_free(transport->mctx, transport->doh.endpoint);
    234 	}
    235 
    236 	if (endpoint != NULL) {
    237 		transport->doh.endpoint = isc_mem_strdup(transport->mctx,
    238 							 endpoint);
    239 	}
    240 }
    241 
    242 void
    243 dns_transport_set_mode(dns_transport_t *transport, dns_http_mode_t mode) {
    244 	REQUIRE(VALID_TRANSPORT(transport));
    245 	REQUIRE(transport->type == DNS_TRANSPORT_HTTP);
    246 
    247 	transport->doh.mode = mode;
    248 }
    249 
    250 void
    251 dns_transport_set_tls_versions(dns_transport_t *transport,
    252 			       const uint32_t tls_versions) {
    253 	REQUIRE(VALID_TRANSPORT(transport));
    254 	REQUIRE(transport->type == DNS_TRANSPORT_HTTP ||
    255 		transport->type == DNS_TRANSPORT_TLS);
    256 
    257 	transport->tls.protocol_versions = tls_versions;
    258 }
    259 
    260 uint32_t
    261 dns_transport_get_tls_versions(const dns_transport_t *transport) {
    262 	REQUIRE(VALID_TRANSPORT(transport));
    263 
    264 	return transport->tls.protocol_versions;
    265 }
    266 
    267 void
    268 dns_transport_set_ciphers(dns_transport_t *transport, const char *ciphers) {
    269 	REQUIRE(VALID_TRANSPORT(transport));
    270 	REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
    271 		transport->type == DNS_TRANSPORT_HTTP);
    272 
    273 	if (transport->tls.ciphers != NULL) {
    274 		isc_mem_free(transport->mctx, transport->tls.ciphers);
    275 	}
    276 
    277 	if (ciphers != NULL) {
    278 		transport->tls.ciphers = isc_mem_strdup(transport->mctx,
    279 							ciphers);
    280 	}
    281 }
    282 
    283 void
    284 dns_transport_set_tlsname(dns_transport_t *transport, const char *tlsname) {
    285 	REQUIRE(VALID_TRANSPORT(transport));
    286 	REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
    287 		transport->type == DNS_TRANSPORT_HTTP);
    288 
    289 	if (transport->tls.tlsname != NULL) {
    290 		isc_mem_free(transport->mctx, transport->tls.tlsname);
    291 	}
    292 
    293 	if (tlsname != NULL) {
    294 		transport->tls.tlsname = isc_mem_strdup(transport->mctx,
    295 							tlsname);
    296 	}
    297 }
    298 
    299 char *
    300 dns_transport_get_ciphers(const dns_transport_t *transport) {
    301 	REQUIRE(VALID_TRANSPORT(transport));
    302 
    303 	return transport->tls.ciphers;
    304 }
    305 
    306 void
    307 dns_transport_set_cipher_suites(dns_transport_t *transport,
    308 				const char *cipher_suites) {
    309 	REQUIRE(VALID_TRANSPORT(transport));
    310 	REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
    311 		transport->type == DNS_TRANSPORT_HTTP);
    312 
    313 	if (transport->tls.cipher_suites != NULL) {
    314 		isc_mem_free(transport->mctx, transport->tls.cipher_suites);
    315 	}
    316 
    317 	if (cipher_suites != NULL) {
    318 		transport->tls.cipher_suites = isc_mem_strdup(transport->mctx,
    319 							      cipher_suites);
    320 	}
    321 }
    322 
    323 char *
    324 dns_transport_get_cipher_suites(const dns_transport_t *transport) {
    325 	REQUIRE(VALID_TRANSPORT(transport));
    326 
    327 	return transport->tls.cipher_suites;
    328 }
    329 
    330 char *
    331 dns_transport_get_tlsname(const dns_transport_t *transport) {
    332 	REQUIRE(VALID_TRANSPORT(transport));
    333 
    334 	return transport->tls.tlsname;
    335 }
    336 
    337 void
    338 dns_transport_set_prefer_server_ciphers(dns_transport_t *transport,
    339 					const bool prefer) {
    340 	REQUIRE(VALID_TRANSPORT(transport));
    341 	REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
    342 		transport->type == DNS_TRANSPORT_HTTP);
    343 
    344 	transport->tls.prefer_server_ciphers = prefer ? ter_true : ter_false;
    345 }
    346 
    347 bool
    348 dns_transport_get_prefer_server_ciphers(const dns_transport_t *transport,
    349 					bool *preferp) {
    350 	REQUIRE(VALID_TRANSPORT(transport));
    351 	REQUIRE(preferp != NULL);
    352 	if (transport->tls.prefer_server_ciphers == ter_none) {
    353 		return false;
    354 	} else if (transport->tls.prefer_server_ciphers == ter_true) {
    355 		*preferp = true;
    356 		return true;
    357 	} else if (transport->tls.prefer_server_ciphers == ter_false) {
    358 		*preferp = false;
    359 		return true;
    360 	}
    361 
    362 	UNREACHABLE();
    363 	return false;
    364 }
    365 
    366 void
    367 dns_transport_set_always_verify_remote(dns_transport_t *transport,
    368 				       const bool always_verify_remote) {
    369 	REQUIRE(VALID_TRANSPORT(transport));
    370 	REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
    371 		transport->type == DNS_TRANSPORT_HTTP);
    372 
    373 	transport->tls.always_verify_remote = always_verify_remote;
    374 }
    375 
    376 bool
    377 dns_transport_get_always_verify_remote(dns_transport_t *transport) {
    378 	REQUIRE(VALID_TRANSPORT(transport));
    379 	REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
    380 		transport->type == DNS_TRANSPORT_HTTP);
    381 
    382 	return transport->tls.always_verify_remote;
    383 }
    384 
    385 isc_result_t
    386 dns_transport_get_tlsctx(dns_transport_t *transport, const isc_sockaddr_t *peer,
    387 			 isc_tlsctx_cache_t *tlsctx_cache, isc_mem_t *mctx,
    388 			 isc_tlsctx_t **pctx,
    389 			 isc_tlsctx_client_session_cache_t **psess_cache) {
    390 	isc_result_t result = ISC_R_FAILURE;
    391 	isc_tlsctx_t *tlsctx = NULL, *found = NULL;
    392 	isc_tls_cert_store_t *store = NULL, *found_store = NULL;
    393 	isc_tlsctx_client_session_cache_t *sess_cache = NULL;
    394 	isc_tlsctx_client_session_cache_t *found_sess_cache = NULL;
    395 	uint32_t tls_versions;
    396 	const char *ciphers = NULL;
    397 	const char *cipher_suites = NULL;
    398 	bool prefer_server_ciphers;
    399 	uint16_t family;
    400 	const char *tlsname = NULL;
    401 
    402 	REQUIRE(VALID_TRANSPORT(transport));
    403 	REQUIRE(transport->type == DNS_TRANSPORT_TLS);
    404 	REQUIRE(peer != NULL);
    405 	REQUIRE(tlsctx_cache != NULL);
    406 	REQUIRE(mctx != NULL);
    407 	REQUIRE(pctx != NULL && *pctx == NULL);
    408 	REQUIRE(psess_cache != NULL && *psess_cache == NULL);
    409 
    410 	family = (isc_sockaddr_pf(peer) == PF_INET6) ? AF_INET6 : AF_INET;
    411 
    412 	tlsname = dns_transport_get_tlsname(transport);
    413 	INSIST(tlsname != NULL && *tlsname != '\0');
    414 
    415 	/*
    416 	 * Let's try to re-use the already created context. This way
    417 	 * we have a chance to resume the TLS session, bypassing the
    418 	 * full TLS handshake procedure, making establishing
    419 	 * subsequent TLS connections faster.
    420 	 */
    421 	result = isc_tlsctx_cache_find(tlsctx_cache, tlsname,
    422 				       isc_tlsctx_cache_tls, family, &found,
    423 				       &found_store, &found_sess_cache);
    424 	if (result != ISC_R_SUCCESS) {
    425 		const char *hostname =
    426 			dns_transport_get_remote_hostname(transport);
    427 		const char *ca_file = dns_transport_get_cafile(transport);
    428 		const char *cert_file = dns_transport_get_certfile(transport);
    429 		const char *key_file = dns_transport_get_keyfile(transport);
    430 		const bool always_verify_remote =
    431 			dns_transport_get_always_verify_remote(transport);
    432 		char peer_addr_str[INET6_ADDRSTRLEN] = { 0 };
    433 		isc_netaddr_t peer_netaddr = { 0 };
    434 		bool hostname_ignore_subject;
    435 
    436 		/*
    437 		 * So, no context exists. Let's create one using the
    438 		 * parameters from the configuration file and try to
    439 		 * store it for further reuse.
    440 		 */
    441 		result = isc_tlsctx_createclient(&tlsctx);
    442 		if (result != ISC_R_SUCCESS) {
    443 			goto failure;
    444 		}
    445 		tls_versions = dns_transport_get_tls_versions(transport);
    446 		if (tls_versions != 0) {
    447 			isc_tlsctx_set_protocols(tlsctx, tls_versions);
    448 		}
    449 		ciphers = dns_transport_get_ciphers(transport);
    450 		if (ciphers != NULL) {
    451 			isc_tlsctx_set_cipherlist(tlsctx, ciphers);
    452 		}
    453 		cipher_suites = dns_transport_get_cipher_suites(transport);
    454 		if (cipher_suites != NULL) {
    455 			isc_tlsctx_set_cipher_suites(tlsctx, cipher_suites);
    456 		}
    457 
    458 		if (dns_transport_get_prefer_server_ciphers(
    459 			    transport, &prefer_server_ciphers))
    460 		{
    461 			isc_tlsctx_prefer_server_ciphers(tlsctx,
    462 							 prefer_server_ciphers);
    463 		}
    464 
    465 		if (always_verify_remote || hostname != NULL || ca_file != NULL)
    466 		{
    467 			/*
    468 			 * The situation when 'found_store != NULL' while
    469 			 * 'found == NULL' may occur as there is a one-to-many
    470 			 * relation between cert stores and per-transport TLS
    471 			 * contexts. That is, there could be one store
    472 			 * shared between multiple contexts.
    473 			 */
    474 			if (found_store == NULL) {
    475 				/*
    476 				 * 'ca_file' can equal 'NULL' here, in
    477 				 * which case the store with system-wide
    478 				 * CA certificates will be created.
    479 				 */
    480 				result = isc_tls_cert_store_create(ca_file,
    481 								   &store);
    482 
    483 				if (result != ISC_R_SUCCESS) {
    484 					goto failure;
    485 				}
    486 			} else {
    487 				store = found_store;
    488 			}
    489 
    490 			INSIST(store != NULL);
    491 			if (hostname == NULL) {
    492 				/*
    493 				 * If hostname is not specified, then use the
    494 				 * peer IP address for validation.
    495 				 */
    496 				isc_netaddr_fromsockaddr(&peer_netaddr, peer);
    497 				isc_netaddr_format(&peer_netaddr, peer_addr_str,
    498 						   sizeof(peer_addr_str));
    499 				hostname = peer_addr_str;
    500 			}
    501 
    502 			/*
    503 			 * According to RFC 8310, Subject field MUST NOT
    504 			 * be inspected when verifying hostname for DoT.
    505 			 * Only SubjectAltName must be checked.
    506 			 */
    507 			hostname_ignore_subject = true;
    508 			result = isc_tlsctx_enable_peer_verification(
    509 				tlsctx, false, store, hostname,
    510 				hostname_ignore_subject);
    511 			if (result != ISC_R_SUCCESS) {
    512 				goto failure;
    513 			}
    514 
    515 			/*
    516 			 * Let's load client certificate and enable
    517 			 * Mutual TLS. We do that only in the case when
    518 			 * Strict TLS is enabled, because Mutual TLS is
    519 			 * an extension of it.
    520 			 */
    521 			if (cert_file != NULL) {
    522 				INSIST(key_file != NULL);
    523 
    524 				result = isc_tlsctx_load_certificate(
    525 					tlsctx, key_file, cert_file);
    526 				if (result != ISC_R_SUCCESS) {
    527 					goto failure;
    528 				}
    529 			}
    530 		}
    531 
    532 		isc_tlsctx_enable_dot_client_alpn(tlsctx);
    533 
    534 		isc_tlsctx_client_session_cache_create(
    535 			mctx, tlsctx,
    536 			ISC_TLSCTX_CLIENT_SESSION_CACHE_DEFAULT_SIZE,
    537 			&sess_cache);
    538 
    539 		found_store = NULL;
    540 		result = isc_tlsctx_cache_add(tlsctx_cache, tlsname,
    541 					      isc_tlsctx_cache_tls, family,
    542 					      tlsctx, store, sess_cache, &found,
    543 					      &found_store, &found_sess_cache);
    544 		if (result == ISC_R_EXISTS) {
    545 			/*
    546 			 * It seems the entry has just been created from
    547 			 * within another thread while we were initialising
    548 			 * ours. Although this is unlikely, it could happen
    549 			 * after startup/re-initialisation. In such a case,
    550 			 * discard the new context and associated data and use
    551 			 * the already established one from now on.
    552 			 *
    553 			 * Such situation will not occur after the
    554 			 * initial 'warm-up', so it is not critical
    555 			 * performance-wise.
    556 			 */
    557 			INSIST(found != NULL);
    558 			isc_tlsctx_free(&tlsctx);
    559 			/*
    560 			 * The 'store' variable can be 'NULL' when remote server
    561 			 * verification is not enabled (that is, when Strict or
    562 			 * Mutual TLS are not used).
    563 			 *
    564 			 * The 'found_store' might be equal to 'store' as there
    565 			 * is one-to-many relation between a store and
    566 			 * per-transport TLS contexts. In that case, the call to
    567 			 * 'isc_tlsctx_cache_find()' above could have returned a
    568 			 * store via the 'found_store' variable, whose value we
    569 			 * can assign to 'store' later. In that case,
    570 			 * 'isc_tlsctx_cache_add()' will return the same value.
    571 			 * When that happens, we should not free the store
    572 			 * object, as it is managed by the TLS context cache.
    573 			 */
    574 			if (store != NULL && store != found_store) {
    575 				isc_tls_cert_store_free(&store);
    576 			}
    577 			isc_tlsctx_client_session_cache_detach(&sess_cache);
    578 			/* Let's return the data from the cache. */
    579 			*psess_cache = found_sess_cache;
    580 			*pctx = found;
    581 		} else {
    582 			/*
    583 			 * Adding the fresh values into the cache has been
    584 			 * successful, let's return them
    585 			 */
    586 			INSIST(result == ISC_R_SUCCESS);
    587 			*psess_cache = sess_cache;
    588 			*pctx = tlsctx;
    589 		}
    590 	} else {
    591 		/*
    592 		 * The cache lookup has been successful, let's return the
    593 		 * results.
    594 		 */
    595 		INSIST(result == ISC_R_SUCCESS);
    596 		*psess_cache = found_sess_cache;
    597 		*pctx = found;
    598 	}
    599 
    600 	return ISC_R_SUCCESS;
    601 
    602 failure:
    603 	if (tlsctx != NULL) {
    604 		isc_tlsctx_free(&tlsctx);
    605 	}
    606 
    607 	/*
    608 	 * The 'found_store' is being managed by the TLS context
    609 	 * cache. Thus, we should keep it as it is, as it will get
    610 	 * destroyed alongside the cache. As there is one store per
    611 	 * multiple TLS contexts, we need to handle store deletion in a
    612 	 * special way.
    613 	 */
    614 	if (store != NULL && store != found_store) {
    615 		isc_tls_cert_store_free(&store);
    616 	}
    617 
    618 	return result;
    619 }
    620 
    621 static void
    622 transport_destroy(dns_transport_t *transport) {
    623 	isc_refcount_destroy(&transport->references);
    624 	transport->magic = 0;
    625 
    626 	if (transport->doh.endpoint != NULL) {
    627 		isc_mem_free(transport->mctx, transport->doh.endpoint);
    628 	}
    629 	if (transport->tls.remote_hostname != NULL) {
    630 		isc_mem_free(transport->mctx, transport->tls.remote_hostname);
    631 	}
    632 	if (transport->tls.cafile != NULL) {
    633 		isc_mem_free(transport->mctx, transport->tls.cafile);
    634 	}
    635 	if (transport->tls.keyfile != NULL) {
    636 		isc_mem_free(transport->mctx, transport->tls.keyfile);
    637 	}
    638 	if (transport->tls.certfile != NULL) {
    639 		isc_mem_free(transport->mctx, transport->tls.certfile);
    640 	}
    641 	if (transport->tls.ciphers != NULL) {
    642 		isc_mem_free(transport->mctx, transport->tls.ciphers);
    643 	}
    644 	if (transport->tls.cipher_suites != NULL) {
    645 		isc_mem_free(transport->mctx, transport->tls.cipher_suites);
    646 	}
    647 
    648 	if (transport->tls.tlsname != NULL) {
    649 		isc_mem_free(transport->mctx, transport->tls.tlsname);
    650 	}
    651 
    652 	isc_mem_putanddetach(&transport->mctx, transport, sizeof(*transport));
    653 }
    654 
    655 void
    656 dns_transport_attach(dns_transport_t *source, dns_transport_t **targetp) {
    657 	REQUIRE(source != NULL);
    658 	REQUIRE(targetp != NULL && *targetp == NULL);
    659 
    660 	isc_refcount_increment(&source->references);
    661 
    662 	*targetp = source;
    663 }
    664 
    665 void
    666 dns_transport_detach(dns_transport_t **transportp) {
    667 	dns_transport_t *transport = NULL;
    668 
    669 	REQUIRE(transportp != NULL);
    670 	REQUIRE(VALID_TRANSPORT(*transportp));
    671 
    672 	transport = *transportp;
    673 	*transportp = NULL;
    674 
    675 	if (isc_refcount_decrement(&transport->references) == 1) {
    676 		transport_destroy(transport);
    677 	}
    678 }
    679 
    680 dns_transport_t *
    681 dns_transport_find(const dns_transport_type_t type, const dns_name_t *name,
    682 		   dns_transport_list_t *list) {
    683 	isc_result_t result;
    684 	dns_transport_t *transport = NULL;
    685 	isc_hashmap_t *hm = NULL;
    686 
    687 	REQUIRE(VALID_TRANSPORT_LIST(list));
    688 	REQUIRE(list->transports[type] != NULL);
    689 
    690 	hm = list->transports[type];
    691 
    692 	RWLOCK(&list->lock, isc_rwlocktype_read);
    693 	result = isc_hashmap_find(hm, dns_name_hash(name), transport_match,
    694 				  name, (void **)&transport);
    695 	if (result == ISC_R_SUCCESS) {
    696 		isc_refcount_increment(&transport->references);
    697 	}
    698 	RWUNLOCK(&list->lock, isc_rwlocktype_read);
    699 
    700 	return transport;
    701 }
    702 
    703 dns_transport_list_t *
    704 dns_transport_list_new(isc_mem_t *mctx) {
    705 	dns_transport_list_t *list = isc_mem_get(mctx, sizeof(*list));
    706 
    707 	*list = (dns_transport_list_t){ 0 };
    708 
    709 	isc_rwlock_init(&list->lock);
    710 
    711 	isc_mem_attach(mctx, &list->mctx);
    712 	isc_refcount_init(&list->references, 1);
    713 
    714 	list->magic = TRANSPORT_LIST_MAGIC;
    715 
    716 	for (size_t type = 0; type < DNS_TRANSPORT_COUNT; type++) {
    717 		isc_hashmap_create(list->mctx, 10, &list->transports[type]);
    718 	}
    719 
    720 	return list;
    721 }
    722 
    723 void
    724 dns_transport_list_attach(dns_transport_list_t *source,
    725 			  dns_transport_list_t **targetp) {
    726 	REQUIRE(VALID_TRANSPORT_LIST(source));
    727 	REQUIRE(targetp != NULL && *targetp == NULL);
    728 
    729 	isc_refcount_increment(&source->references);
    730 
    731 	*targetp = source;
    732 }
    733 
    734 static void
    735 transport_list_destroy(dns_transport_list_t *list) {
    736 	isc_refcount_destroy(&list->references);
    737 	list->magic = 0;
    738 
    739 	for (size_t type = 0; type < DNS_TRANSPORT_COUNT; type++) {
    740 		isc_result_t result;
    741 		isc_hashmap_iter_t *it = NULL;
    742 
    743 		if (list->transports[type] == NULL) {
    744 			continue;
    745 		}
    746 
    747 		isc_hashmap_iter_create(list->transports[type], &it);
    748 		for (result = isc_hashmap_iter_first(it);
    749 		     result == ISC_R_SUCCESS;
    750 		     result = isc_hashmap_iter_delcurrent_next(it))
    751 		{
    752 			dns_transport_t *transport = NULL;
    753 			isc_hashmap_iter_current(it, (void **)&transport);
    754 			dns_transport_detach(&transport);
    755 		}
    756 		isc_hashmap_iter_destroy(&it);
    757 		isc_hashmap_destroy(&list->transports[type]);
    758 	}
    759 	isc_rwlock_destroy(&list->lock);
    760 	isc_mem_putanddetach(&list->mctx, list, sizeof(*list));
    761 }
    762 
    763 void
    764 dns_transport_list_detach(dns_transport_list_t **listp) {
    765 	dns_transport_list_t *list = NULL;
    766 
    767 	REQUIRE(listp != NULL);
    768 	REQUIRE(VALID_TRANSPORT_LIST(*listp));
    769 
    770 	list = *listp;
    771 	*listp = NULL;
    772 
    773 	if (isc_refcount_decrement(&list->references) == 1) {
    774 		transport_list_destroy(list);
    775 	}
    776 }
    777