1 /* $NetBSD: test-async.c,v 1.2 2024/02/21 22:51:27 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/buffer.h> 25 #include <isc/hash.h> 26 #include <isc/ht.h> 27 #include <isc/log.h> 28 #include <isc/mem.h> 29 #include <isc/netaddr.h> 30 #include <isc/result.h> 31 #include <isc/types.h> 32 #include <isc/util.h> 33 34 #include <ns/client.h> 35 #include <ns/events.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_resevent_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, 16, 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_task_t *task, 283 isc_taskaction_t action, void *evarg, ns_hookasync_t **ctxp) { 284 ns_hook_resevent_t *rev = (ns_hook_resevent_t *)isc_event_allocate( 285 mctx, task, NS_EVENT_HOOKASYNCDONE, action, evarg, 286 sizeof(*rev)); 287 ns_hookasync_t *ctx = isc_mem_get(mctx, sizeof(*ctx)); 288 state_t *state = (state_t *)arg; 289 290 logmsg("doasync"); 291 *ctx = (ns_hookasync_t){ .mctx = NULL }; 292 isc_mem_attach(mctx, &ctx->mctx); 293 ctx->cancel = cancelasync; 294 ctx->destroy = destroyasync; 295 296 rev->hookpoint = state->hookpoint; 297 rev->origresult = state->origresult; 298 qctx->result = DNS_R_NOTIMP; 299 rev->saved_qctx = qctx; 300 rev->ctx = ctx; 301 302 state->rev = rev; 303 304 isc_task_send(task, (isc_event_t **)&rev); 305 306 *ctxp = ctx; 307 return (ISC_R_SUCCESS); 308 } 309 310 static ns_hookresult_t 311 async_query_done_begin(void *arg, void *cbdata, isc_result_t *resp) { 312 query_ctx_t *qctx = (query_ctx_t *)arg; 313 async_instance_t *inst = (async_instance_t *)cbdata; 314 state_t *state = client_state_get(qctx, inst); 315 316 UNUSED(qctx); 317 UNUSED(cbdata); 318 UNUSED(state); 319 320 logmsg("done begin hook"); 321 if (state->async) { 322 /* resuming */ 323 state->async = false; 324 return (NS_HOOK_CONTINUE); 325 } 326 327 /* initial call */ 328 state->async = true; 329 state->hookpoint = NS_QUERY_DONE_BEGIN; 330 state->origresult = *resp; 331 ns_query_hookasync(qctx, doasync, state); 332 return (NS_HOOK_RETURN); 333 } 334 335 static ns_hookresult_t 336 async_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp) { 337 query_ctx_t *qctx = (query_ctx_t *)arg; 338 async_instance_t *inst = (async_instance_t *)cbdata; 339 340 logmsg("qctx destroy hook"); 341 *resp = ISC_R_UNSET; 342 343 if (!qctx->detach_client) { 344 return (NS_HOOK_CONTINUE); 345 } 346 347 client_state_destroy(qctx, inst); 348 349 return (NS_HOOK_CONTINUE); 350 } 351