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