xref: /netbsd-src/external/mpl/bind/dist/lib/dns/transport.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
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