xref: /netbsd-src/external/mpl/bind/dist/bin/tests/system/hooks/driver/test-async.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: test-async.c,v 1.3 2025/01/26 16:24:50 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 /* aliases for the exported symbols */
19 
20 #include <inttypes.h>
21 #include <stdbool.h>
22 #include <string.h>
23 
24 #include <isc/async.h>
25 #include <isc/buffer.h>
26 #include <isc/hash.h>
27 #include <isc/ht.h>
28 #include <isc/log.h>
29 #include <isc/mem.h>
30 #include <isc/netaddr.h>
31 #include <isc/result.h>
32 #include <isc/types.h>
33 #include <isc/util.h>
34 
35 #include <ns/client.h>
36 #include <ns/hooks.h>
37 #include <ns/log.h>
38 #include <ns/query.h>
39 #include <ns/types.h>
40 
41 #define CHECK(op)                              \
42 	do {                                   \
43 		result = (op);                 \
44 		if (result != ISC_R_SUCCESS) { \
45 			goto cleanup;          \
46 		}                              \
47 	} while (0)
48 
49 /*
50  * Persistent data for use by this module. This will be associated
51  * with client object address in the hash table, and will remain
52  * accessible until the client object is detached.
53  */
54 typedef struct async_instance {
55 	ns_plugin_t *module;
56 	isc_mem_t *mctx;
57 	isc_ht_t *ht;
58 	isc_mutex_t hlock;
59 	isc_log_t *lctx;
60 } async_instance_t;
61 
62 typedef struct state {
63 	bool async;
64 	ns_hook_resume_t *rev;
65 	ns_hookpoint_t hookpoint;
66 	isc_result_t origresult;
67 } state_t;
68 
69 /*
70  * Forward declarations of functions referenced in install_hooks().
71  */
72 static ns_hookresult_t
73 async_qctx_initialize(void *arg, void *cbdata, isc_result_t *resp);
74 static ns_hookresult_t
75 async_query_done_begin(void *arg, void *cbdata, isc_result_t *resp);
76 static ns_hookresult_t
77 async_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp);
78 
79 /*%
80  * Register the functions to be called at each hook point in 'hooktable', using
81  * memory context 'mctx' for allocating copies of stack-allocated structures
82  * passed to ns_hook_add().  Make sure 'inst' will be passed as the 'cbdata'
83  * argument to every callback.
84  */
85 static void
86 install_hooks(ns_hooktable_t *hooktable, isc_mem_t *mctx,
87 	      async_instance_t *inst) {
88 	const ns_hook_t async_init = {
89 		.action = async_qctx_initialize,
90 		.action_data = inst,
91 	};
92 	const ns_hook_t async_donebegin = {
93 		.action = async_query_done_begin,
94 		.action_data = inst,
95 	};
96 	const ns_hook_t async_destroy = {
97 		.action = async_qctx_destroy,
98 		.action_data = inst,
99 	};
100 
101 	ns_hook_add(hooktable, mctx, NS_QUERY_QCTX_INITIALIZED, &async_init);
102 	ns_hook_add(hooktable, mctx, NS_QUERY_DONE_BEGIN, &async_donebegin);
103 	ns_hook_add(hooktable, mctx, NS_QUERY_QCTX_DESTROYED, &async_destroy);
104 }
105 
106 static void
107 logmsg(const char *fmt, ...) {
108 	va_list ap;
109 
110 	va_start(ap, fmt);
111 	isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
112 		      ISC_LOG_INFO, fmt, ap);
113 	va_end(ap);
114 }
115 
116 /**
117 ** Mandatory plugin API functions:
118 **
119 ** - plugin_destroy
120 ** - plugin_register
121 ** - plugin_version
122 ** - plugin_check
123 **/
124 
125 /*
126  * Called by ns_plugin_register() to initialize the plugin and
127  * register hook functions into the view hook table.
128  */
129 isc_result_t
130 plugin_register(const char *parameters, const void *cfg, const char *cfg_file,
131 		unsigned long cfg_line, isc_mem_t *mctx, isc_log_t *lctx,
132 		void *actx, ns_hooktable_t *hooktable, void **instp) {
133 	async_instance_t *inst = NULL;
134 
135 	UNUSED(parameters);
136 	UNUSED(cfg);
137 	UNUSED(actx);
138 
139 	isc_log_write(lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
140 		      ISC_LOG_INFO,
141 		      "registering 'test-async' module from %s:%lu", cfg_file,
142 		      cfg_line);
143 
144 	inst = isc_mem_get(mctx, sizeof(*inst));
145 	*inst = (async_instance_t){ .mctx = NULL };
146 	isc_mem_attach(mctx, &inst->mctx);
147 
148 	isc_ht_init(&inst->ht, mctx, 1, ISC_HT_CASE_SENSITIVE);
149 	isc_mutex_init(&inst->hlock);
150 
151 	/*
152 	 * Set hook points in the view's hooktable.
153 	 */
154 	install_hooks(hooktable, mctx, inst);
155 
156 	*instp = inst;
157 
158 	return ISC_R_SUCCESS;
159 }
160 
161 isc_result_t
162 plugin_check(const char *parameters, const void *cfg, const char *cfg_file,
163 	     unsigned long cfg_line, isc_mem_t *mctx, isc_log_t *lctx,
164 	     void *actx) {
165 	UNUSED(parameters);
166 	UNUSED(cfg);
167 	UNUSED(cfg_file);
168 	UNUSED(cfg_line);
169 	UNUSED(mctx);
170 	UNUSED(lctx);
171 	UNUSED(actx);
172 
173 	return ISC_R_SUCCESS;
174 }
175 
176 /*
177  * Called by ns_plugins_free(); frees memory allocated by
178  * the module when it was registered.
179  */
180 void
181 plugin_destroy(void **instp) {
182 	async_instance_t *inst = (async_instance_t *)*instp;
183 
184 	if (inst->ht != NULL) {
185 		isc_ht_destroy(&inst->ht);
186 		isc_mutex_destroy(&inst->hlock);
187 	}
188 
189 	isc_mem_putanddetach(&inst->mctx, inst, sizeof(*inst));
190 	*instp = NULL;
191 
192 	return;
193 }
194 
195 /*
196  * Returns plugin API version for compatibility checks.
197  */
198 int
199 plugin_version(void) {
200 	return NS_PLUGIN_VERSION;
201 }
202 
203 static state_t *
204 client_state_get(const query_ctx_t *qctx, async_instance_t *inst) {
205 	state_t *state = NULL;
206 	isc_result_t result;
207 
208 	LOCK(&inst->hlock);
209 	result = isc_ht_find(inst->ht, (const unsigned char *)&qctx->client,
210 			     sizeof(qctx->client), (void **)&state);
211 	UNLOCK(&inst->hlock);
212 
213 	return result == ISC_R_SUCCESS ? state : NULL;
214 }
215 
216 static void
217 client_state_create(const query_ctx_t *qctx, async_instance_t *inst) {
218 	state_t *state = NULL;
219 	isc_result_t result;
220 
221 	state = isc_mem_get(inst->mctx, sizeof(*state));
222 	*state = (state_t){ .async = false };
223 
224 	LOCK(&inst->hlock);
225 	result = isc_ht_add(inst->ht, (const unsigned char *)&qctx->client,
226 			    sizeof(qctx->client), state);
227 	UNLOCK(&inst->hlock);
228 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
229 }
230 
231 static void
232 client_state_destroy(const query_ctx_t *qctx, async_instance_t *inst) {
233 	state_t *state = client_state_get(qctx, inst);
234 	isc_result_t result;
235 
236 	if (state == NULL) {
237 		return;
238 	}
239 
240 	LOCK(&inst->hlock);
241 	result = isc_ht_delete(inst->ht, (const unsigned char *)&qctx->client,
242 			       sizeof(qctx->client));
243 	UNLOCK(&inst->hlock);
244 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
245 
246 	isc_mem_put(inst->mctx, state, sizeof(*state));
247 }
248 
249 static ns_hookresult_t
250 async_qctx_initialize(void *arg, void *cbdata, isc_result_t *resp) {
251 	query_ctx_t *qctx = (query_ctx_t *)arg;
252 	async_instance_t *inst = (async_instance_t *)cbdata;
253 	state_t *state = NULL;
254 
255 	logmsg("qctx init hook");
256 	*resp = ISC_R_UNSET;
257 
258 	state = client_state_get(qctx, inst);
259 	if (state == NULL) {
260 		client_state_create(qctx, inst);
261 	}
262 
263 	return NS_HOOK_CONTINUE;
264 }
265 
266 static void
267 cancelasync(ns_hookasync_t *hctx) {
268 	UNUSED(hctx);
269 	logmsg("cancelasync");
270 }
271 
272 static void
273 destroyasync(ns_hookasync_t **ctxp) {
274 	ns_hookasync_t *ctx = *ctxp;
275 
276 	logmsg("destroyasync");
277 	*ctxp = NULL;
278 	isc_mem_putanddetach(&ctx->mctx, ctx, sizeof(*ctx));
279 }
280 
281 static isc_result_t
282 doasync(query_ctx_t *qctx, isc_mem_t *mctx, void *arg, isc_loop_t *loop,
283 	isc_job_cb cb, void *evarg, ns_hookasync_t **ctxp) {
284 	ns_hook_resume_t *rev = isc_mem_get(mctx, sizeof(*rev));
285 	ns_hookasync_t *ctx = isc_mem_get(mctx, sizeof(*ctx));
286 	state_t *state = (state_t *)arg;
287 
288 	logmsg("doasync");
289 	*ctx = (ns_hookasync_t){
290 		.cancel = cancelasync,
291 		.destroy = destroyasync,
292 	};
293 	isc_mem_attach(mctx, &ctx->mctx);
294 
295 	qctx->result = DNS_R_NOTIMP;
296 	*rev = (ns_hook_resume_t){
297 		.hookpoint = state->hookpoint,
298 		.origresult = qctx->result,
299 		.saved_qctx = qctx,
300 		.ctx = ctx,
301 		.arg = evarg,
302 	};
303 
304 	state->rev = rev;
305 
306 	isc_async_run(loop, cb, rev);
307 
308 	*ctxp = ctx;
309 	return ISC_R_SUCCESS;
310 }
311 
312 static ns_hookresult_t
313 async_query_done_begin(void *arg, void *cbdata, isc_result_t *resp) {
314 	query_ctx_t *qctx = (query_ctx_t *)arg;
315 	async_instance_t *inst = (async_instance_t *)cbdata;
316 	state_t *state = client_state_get(qctx, inst);
317 
318 	UNUSED(qctx);
319 	UNUSED(cbdata);
320 	UNUSED(state);
321 
322 	logmsg("done begin hook");
323 	if (state->async) {
324 		/* resuming */
325 		state->async = false;
326 		return NS_HOOK_CONTINUE;
327 	}
328 
329 	/* initial call */
330 	state->async = true;
331 	state->hookpoint = NS_QUERY_DONE_BEGIN;
332 	state->origresult = *resp;
333 	ns_query_hookasync(qctx, doasync, state);
334 	return NS_HOOK_RETURN;
335 }
336 
337 static ns_hookresult_t
338 async_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp) {
339 	query_ctx_t *qctx = (query_ctx_t *)arg;
340 	async_instance_t *inst = (async_instance_t *)cbdata;
341 
342 	logmsg("qctx destroy hook");
343 	*resp = ISC_R_UNSET;
344 
345 	if (!qctx->detach_client) {
346 		return NS_HOOK_CONTINUE;
347 	}
348 
349 	client_state_destroy(qctx, inst);
350 
351 	return NS_HOOK_CONTINUE;
352 }
353