1/*	$NetBSD: zt.c,v 1.1 2024/02/18 20:57:34 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/atomic.h>
22#include <isc/file.h>
23#include <isc/magic.h>
24#include <isc/mem.h>
25#include <isc/string.h>
26#include <isc/task.h>
27#include <isc/util.h>
28
29#include <dns/log.h>
30#include <dns/name.h>
31#include <dns/rbt.h>
32#include <dns/rdataclass.h>
33#include <dns/result.h>
34#include <dns/view.h>
35#include <dns/zone.h>
36#include <dns/zt.h>
37
38struct zt_load_params {
39	dns_zt_zoneloaded_t dl;
40	bool newonly;
41};
42
43struct dns_zt {
44	/* Unlocked. */
45	unsigned int magic;
46	isc_mem_t *mctx;
47	dns_rdataclass_t rdclass;
48	isc_rwlock_t rwlock;
49	dns_zt_allloaded_t loaddone;
50	void *loaddone_arg;
51	struct zt_load_params *loadparams;
52
53	/* Atomic */
54	atomic_bool flush;
55	isc_refcount_t references;
56	isc_refcount_t loads_pending;
57
58	/* Locked by lock. */
59	dns_rbt_t *table;
60};
61
62struct zt_freeze_params {
63	dns_view_t *view;
64	bool freeze;
65};
66
67#define ZTMAGIC	     ISC_MAGIC('Z', 'T', 'b', 'l')
68#define VALID_ZT(zt) ISC_MAGIC_VALID(zt, ZTMAGIC)
69
70static void
71auto_detach(void *, void *);
72
73static isc_result_t
74load(dns_zone_t *zone, void *uap);
75
76static isc_result_t
77asyncload(dns_zone_t *zone, void *callback);
78
79static isc_result_t
80freezezones(dns_zone_t *zone, void *uap);
81
82static isc_result_t
83doneloading(dns_zt_t *zt, dns_zone_t *zone, isc_task_t *task);
84
85isc_result_t
86dns_zt_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, dns_zt_t **ztp) {
87	dns_zt_t *zt;
88	isc_result_t result;
89
90	REQUIRE(ztp != NULL && *ztp == NULL);
91
92	zt = isc_mem_get(mctx, sizeof(*zt));
93
94	zt->table = NULL;
95	result = dns_rbt_create(mctx, auto_detach, zt, &zt->table);
96	if (result != ISC_R_SUCCESS) {
97		goto cleanup_zt;
98	}
99
100	isc_rwlock_init(&zt->rwlock, 0, 0);
101	zt->mctx = NULL;
102	isc_mem_attach(mctx, &zt->mctx);
103	isc_refcount_init(&zt->references, 1);
104	atomic_init(&zt->flush, false);
105	zt->rdclass = rdclass;
106	zt->magic = ZTMAGIC;
107	zt->loaddone = NULL;
108	zt->loaddone_arg = NULL;
109	zt->loadparams = NULL;
110	isc_refcount_init(&zt->loads_pending, 0);
111	*ztp = zt;
112
113	return (ISC_R_SUCCESS);
114
115cleanup_zt:
116	isc_mem_put(mctx, zt, sizeof(*zt));
117
118	return (result);
119}
120
121isc_result_t
122dns_zt_mount(dns_zt_t *zt, dns_zone_t *zone) {
123	isc_result_t result;
124	dns_zone_t *dummy = NULL;
125	dns_name_t *name;
126
127	REQUIRE(VALID_ZT(zt));
128
129	name = dns_zone_getorigin(zone);
130
131	RWLOCK(&zt->rwlock, isc_rwlocktype_write);
132
133	result = dns_rbt_addname(zt->table, name, zone);
134	if (result == ISC_R_SUCCESS) {
135		dns_zone_attach(zone, &dummy);
136	}
137
138	RWUNLOCK(&zt->rwlock, isc_rwlocktype_write);
139
140	return (result);
141}
142
143isc_result_t
144dns_zt_unmount(dns_zt_t *zt, dns_zone_t *zone) {
145	isc_result_t result;
146	dns_name_t *name;
147
148	REQUIRE(VALID_ZT(zt));
149
150	name = dns_zone_getorigin(zone);
151
152	RWLOCK(&zt->rwlock, isc_rwlocktype_write);
153
154	result = dns_rbt_deletename(zt->table, name, false);
155
156	RWUNLOCK(&zt->rwlock, isc_rwlocktype_write);
157
158	return (result);
159}
160
161isc_result_t
162dns_zt_find(dns_zt_t *zt, const dns_name_t *name, unsigned int options,
163	    dns_name_t *foundname, dns_zone_t **zonep) {
164	isc_result_t result;
165	dns_zone_t *dummy = NULL;
166	unsigned int rbtoptions = 0;
167
168	REQUIRE(VALID_ZT(zt));
169
170	if ((options & DNS_ZTFIND_NOEXACT) != 0) {
171		rbtoptions |= DNS_RBTFIND_NOEXACT;
172	}
173
174	RWLOCK(&zt->rwlock, isc_rwlocktype_read);
175
176	result = dns_rbt_findname(zt->table, name, rbtoptions, foundname,
177				  (void **)(void *)&dummy);
178	if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
179		/*
180		 * If DNS_ZTFIND_MIRROR is set and the zone which was
181		 * determined to be the deepest match for the supplied name is
182		 * a mirror zone which is expired or not yet loaded, treat it
183		 * as non-existent.  This will trigger a fallback to recursion
184		 * instead of returning a SERVFAIL.
185		 *
186		 * Note that currently only the deepest match in the zone table
187		 * is checked.  Consider a server configured with two mirror
188		 * zones: "bar" and its child, "foo.bar".  If zone data is
189		 * available for "bar" but not for "foo.bar", a query with
190		 * QNAME equal to or below "foo.bar" will cause ISC_R_NOTFOUND
191		 * to be returned, not DNS_R_PARTIALMATCH, despite zone data
192		 * being available for "bar".  This is considered to be an edge
193		 * case, handling which more appropriately is possible, but
194		 * arguably not worth the added complexity.
195		 */
196		if ((options & DNS_ZTFIND_MIRROR) != 0 &&
197		    dns_zone_gettype(dummy) == dns_zone_mirror &&
198		    !dns_zone_isloaded(dummy))
199		{
200			result = ISC_R_NOTFOUND;
201		} else {
202			dns_zone_attach(dummy, zonep);
203		}
204	}
205
206	RWUNLOCK(&zt->rwlock, isc_rwlocktype_read);
207
208	return (result);
209}
210
211void
212dns_zt_attach(dns_zt_t *zt, dns_zt_t **ztp) {
213	REQUIRE(VALID_ZT(zt));
214	REQUIRE(ztp != NULL && *ztp == NULL);
215
216	isc_refcount_increment(&zt->references);
217
218	*ztp = zt;
219}
220
221static isc_result_t
222flush(dns_zone_t *zone, void *uap) {
223	UNUSED(uap);
224	return (dns_zone_flush(zone));
225}
226
227static void
228zt_destroy(dns_zt_t *zt) {
229	if (atomic_load_acquire(&zt->flush)) {
230		(void)dns_zt_apply(zt, isc_rwlocktype_none, false, NULL, flush,
231				   NULL);
232	}
233	dns_rbt_destroy(&zt->table);
234	isc_rwlock_destroy(&zt->rwlock);
235	zt->magic = 0;
236	isc_mem_putanddetach(&zt->mctx, zt, sizeof(*zt));
237}
238
239static void
240zt_flushanddetach(dns_zt_t **ztp, bool need_flush) {
241	dns_zt_t *zt;
242
243	REQUIRE(ztp != NULL && VALID_ZT(*ztp));
244
245	zt = *ztp;
246	*ztp = NULL;
247
248	if (need_flush) {
249		atomic_store_release(&zt->flush, true);
250	}
251
252	if (isc_refcount_decrement(&zt->references) == 1) {
253		zt_destroy(zt);
254	}
255}
256
257void
258dns_zt_flushanddetach(dns_zt_t **ztp) {
259	zt_flushanddetach(ztp, true);
260}
261
262void
263dns_zt_detach(dns_zt_t **ztp) {
264	zt_flushanddetach(ztp, false);
265}
266
267isc_result_t
268dns_zt_load(dns_zt_t *zt, bool stop, bool newonly) {
269	isc_result_t result;
270	struct zt_load_params params;
271	REQUIRE(VALID_ZT(zt));
272	params.newonly = newonly;
273	result = dns_zt_apply(zt, isc_rwlocktype_read, stop, NULL, load,
274			      &params);
275	return (result);
276}
277
278static isc_result_t
279load(dns_zone_t *zone, void *paramsv) {
280	isc_result_t result;
281	struct zt_load_params *params = (struct zt_load_params *)paramsv;
282	result = dns_zone_load(zone, params->newonly);
283	if (result == DNS_R_CONTINUE || result == DNS_R_UPTODATE ||
284	    result == DNS_R_DYNAMIC)
285	{
286		result = ISC_R_SUCCESS;
287	}
288	return (result);
289}
290
291static void
292call_loaddone(dns_zt_t *zt) {
293	dns_zt_allloaded_t loaddone = zt->loaddone;
294	void *loaddone_arg = zt->loaddone_arg;
295
296	/*
297	 * Set zt->loaddone, zt->loaddone_arg and zt->loadparams to NULL
298	 * before calling loaddone.
299	 */
300	zt->loaddone = NULL;
301	zt->loaddone_arg = NULL;
302
303	isc_mem_put(zt->mctx, zt->loadparams, sizeof(struct zt_load_params));
304	zt->loadparams = NULL;
305
306	/*
307	 * Call the callback last.
308	 */
309	if (loaddone != NULL) {
310		loaddone(loaddone_arg);
311	}
312}
313
314isc_result_t
315dns_zt_asyncload(dns_zt_t *zt, bool newonly, dns_zt_allloaded_t alldone,
316		 void *arg) {
317	isc_result_t result;
318	uint_fast32_t loads_pending;
319
320	REQUIRE(VALID_ZT(zt));
321
322	/*
323	 * Obtain a reference to zt->loads_pending so that asyncload can
324	 * safely decrement both zt->references and zt->loads_pending
325	 * without going to zero.
326	 */
327	loads_pending = isc_refcount_increment0(&zt->loads_pending);
328	INSIST(loads_pending == 0);
329
330	/*
331	 * Only one dns_zt_asyncload call at a time should be active so
332	 * these pointers should be NULL.  They are set back to NULL
333	 * before the zt->loaddone (alldone) is called in call_loaddone.
334	 */
335	INSIST(zt->loadparams == NULL);
336	INSIST(zt->loaddone == NULL);
337	INSIST(zt->loaddone_arg == NULL);
338
339	zt->loadparams = isc_mem_get(zt->mctx, sizeof(struct zt_load_params));
340	zt->loadparams->dl = doneloading;
341	zt->loadparams->newonly = newonly;
342	zt->loaddone = alldone;
343	zt->loaddone_arg = arg;
344
345	result = dns_zt_apply(zt, isc_rwlocktype_read, false, NULL, asyncload,
346			      zt);
347
348	/*
349	 * Have all the loads completed?
350	 */
351	if (isc_refcount_decrement(&zt->loads_pending) == 1) {
352		call_loaddone(zt);
353	}
354
355	return (result);
356}
357
358/*
359 * Initiates asynchronous loading of zone 'zone'.  'callback' is a
360 * pointer to a function which will be used to inform the caller when
361 * the zone loading is complete.
362 */
363static isc_result_t
364asyncload(dns_zone_t *zone, void *zt_) {
365	isc_result_t result;
366	struct dns_zt *zt = (dns_zt_t *)zt_;
367	REQUIRE(zone != NULL);
368
369	isc_refcount_increment(&zt->references);
370	isc_refcount_increment(&zt->loads_pending);
371
372	result = dns_zone_asyncload(zone, zt->loadparams->newonly,
373				    *zt->loadparams->dl, zt);
374	if (result != ISC_R_SUCCESS) {
375		/*
376		 * Caller is holding a reference to zt->loads_pending
377		 * and zt->references so these can't decrement to zero.
378		 */
379		isc_refcount_decrement1(&zt->references);
380		isc_refcount_decrement1(&zt->loads_pending);
381	}
382	return (ISC_R_SUCCESS);
383}
384
385isc_result_t
386dns_zt_freezezones(dns_zt_t *zt, dns_view_t *view, bool freeze) {
387	isc_result_t result, tresult;
388	struct zt_freeze_params params = { view, freeze };
389
390	REQUIRE(VALID_ZT(zt));
391
392	result = dns_zt_apply(zt, isc_rwlocktype_read, false, &tresult,
393			      freezezones, &params);
394	if (tresult == ISC_R_NOTFOUND) {
395		tresult = ISC_R_SUCCESS;
396	}
397	return ((result == ISC_R_SUCCESS) ? tresult : result);
398}
399
400static isc_result_t
401freezezones(dns_zone_t *zone, void *uap) {
402	struct zt_freeze_params *params = uap;
403	bool frozen;
404	isc_result_t result = ISC_R_SUCCESS;
405	char classstr[DNS_RDATACLASS_FORMATSIZE];
406	char zonename[DNS_NAME_FORMATSIZE];
407	dns_zone_t *raw = NULL;
408	dns_view_t *view;
409	const char *vname;
410	const char *sep;
411	int level;
412
413	dns_zone_getraw(zone, &raw);
414	if (raw != NULL) {
415		zone = raw;
416	}
417	if (params->view != dns_zone_getview(zone)) {
418		if (raw != NULL) {
419			dns_zone_detach(&raw);
420		}
421		return (ISC_R_SUCCESS);
422	}
423	if (dns_zone_gettype(zone) != dns_zone_primary) {
424		if (raw != NULL) {
425			dns_zone_detach(&raw);
426		}
427		return (ISC_R_SUCCESS);
428	}
429	if (!dns_zone_isdynamic(zone, true)) {
430		if (raw != NULL) {
431			dns_zone_detach(&raw);
432		}
433		return (ISC_R_SUCCESS);
434	}
435
436	frozen = dns_zone_getupdatedisabled(zone);
437	if (params->freeze) {
438		if (frozen) {
439			result = DNS_R_FROZEN;
440		}
441		if (result == ISC_R_SUCCESS) {
442			result = dns_zone_flush(zone);
443		}
444		if (result == ISC_R_SUCCESS) {
445			dns_zone_setupdatedisabled(zone, params->freeze);
446		}
447	} else {
448		if (frozen) {
449			result = dns_zone_loadandthaw(zone);
450			if (result == DNS_R_CONTINUE ||
451			    result == DNS_R_UPTODATE)
452			{
453				result = ISC_R_SUCCESS;
454			}
455		}
456	}
457	view = dns_zone_getview(zone);
458	if (strcmp(view->name, "_bind") == 0 || strcmp(view->name, "_defaul"
459								   "t") == 0)
460	{
461		vname = "";
462		sep = "";
463	} else {
464		vname = view->name;
465		sep = " ";
466	}
467	dns_rdataclass_format(dns_zone_getclass(zone), classstr,
468			      sizeof(classstr));
469	dns_name_format(dns_zone_getorigin(zone), zonename, sizeof(zonename));
470	level = (result != ISC_R_SUCCESS) ? ISC_LOG_ERROR : ISC_LOG_DEBUG(1);
471	isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE,
472		      level, "%s zone '%s/%s'%s%s: %s",
473		      params->freeze ? "freezing" : "thawing", zonename,
474		      classstr, sep, vname, isc_result_totext(result));
475	if (raw != NULL) {
476		dns_zone_detach(&raw);
477	}
478	return (result);
479}
480
481void
482dns_zt_setviewcommit(dns_zt_t *zt) {
483	dns_rbtnode_t *node;
484	dns_rbtnodechain_t chain;
485	isc_result_t result;
486
487	REQUIRE(VALID_ZT(zt));
488
489	dns_rbtnodechain_init(&chain);
490
491	result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL);
492	while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
493		result = dns_rbtnodechain_current(&chain, NULL, NULL, &node);
494		if (result == ISC_R_SUCCESS && node->data != NULL) {
495			dns_zone_setviewcommit(node->data);
496		}
497
498		result = dns_rbtnodechain_next(&chain, NULL, NULL);
499	}
500
501	dns_rbtnodechain_invalidate(&chain);
502}
503
504void
505dns_zt_setviewrevert(dns_zt_t *zt) {
506	dns_rbtnode_t *node;
507	dns_rbtnodechain_t chain;
508	isc_result_t result;
509
510	REQUIRE(VALID_ZT(zt));
511
512	dns_rbtnodechain_init(&chain);
513
514	result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL);
515	while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
516		result = dns_rbtnodechain_current(&chain, NULL, NULL, &node);
517		if (result == ISC_R_SUCCESS && node->data != NULL) {
518			dns_zone_setviewrevert(node->data);
519		}
520
521		result = dns_rbtnodechain_next(&chain, NULL, NULL);
522	}
523
524	dns_rbtnodechain_invalidate(&chain);
525}
526
527isc_result_t
528dns_zt_apply(dns_zt_t *zt, isc_rwlocktype_t lock, bool stop, isc_result_t *sub,
529	     isc_result_t (*action)(dns_zone_t *, void *), void *uap) {
530	dns_rbtnode_t *node;
531	dns_rbtnodechain_t chain;
532	isc_result_t result, tresult = ISC_R_SUCCESS;
533	dns_zone_t *zone;
534
535	REQUIRE(VALID_ZT(zt));
536	REQUIRE(action != NULL);
537
538	if (lock != isc_rwlocktype_none) {
539		RWLOCK(&zt->rwlock, lock);
540	}
541
542	dns_rbtnodechain_init(&chain);
543	result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL);
544	if (result == ISC_R_NOTFOUND) {
545		/*
546		 * The tree is empty.
547		 */
548		tresult = result;
549		result = ISC_R_NOMORE;
550	}
551	while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
552		result = dns_rbtnodechain_current(&chain, NULL, NULL, &node);
553		if (result == ISC_R_SUCCESS) {
554			zone = node->data;
555			if (zone != NULL) {
556				result = (action)(zone, uap);
557			}
558			if (result != ISC_R_SUCCESS && stop) {
559				tresult = result;
560				goto cleanup; /* don't break */
561			} else if (result != ISC_R_SUCCESS &&
562				   tresult == ISC_R_SUCCESS)
563			{
564				tresult = result;
565			}
566		}
567		result = dns_rbtnodechain_next(&chain, NULL, NULL);
568	}
569	if (result == ISC_R_NOMORE) {
570		result = ISC_R_SUCCESS;
571	}
572
573cleanup:
574	dns_rbtnodechain_invalidate(&chain);
575	if (sub != NULL) {
576		*sub = tresult;
577	}
578
579	if (lock != isc_rwlocktype_none) {
580		RWUNLOCK(&zt->rwlock, lock);
581	}
582
583	return (result);
584}
585
586/*
587 * Decrement the loads_pending counter; when counter reaches
588 * zero, call the loaddone callback that was initially set by
589 * dns_zt_asyncload().
590 */
591static isc_result_t
592doneloading(dns_zt_t *zt, dns_zone_t *zone, isc_task_t *task) {
593	UNUSED(zone);
594	UNUSED(task);
595
596	REQUIRE(VALID_ZT(zt));
597
598	if (isc_refcount_decrement(&zt->loads_pending) == 1) {
599		call_loaddone(zt);
600	}
601
602	if (isc_refcount_decrement(&zt->references) == 1) {
603		zt_destroy(zt);
604	}
605
606	return (ISC_R_SUCCESS);
607}
608
609/***
610 *** Private
611 ***/
612
613static void
614auto_detach(void *data, void *arg) {
615	dns_zone_t *zone = data;
616
617	UNUSED(arg);
618	dns_zone_detach(&zone);
619}
620