From: Arran Cudbard-Bell Date: Tue, 30 Jan 2018 21:52:10 +0000 (-0700) Subject: Add xlat for rlm_delay X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d8a777f184238dca7e73888d5d702f27bfd2fa63;p=thirdparty%2Ffreeradius-server.git Add xlat for rlm_delay ...it delays things. --- diff --git a/src/include/modules.h b/src/include/modules.h index a42ca553fc8..82cfcdfc49f 100644 --- a/src/include/modules.h +++ b/src/include/modules.h @@ -296,8 +296,8 @@ void fr_request_async_bootstrap(REQUEST *request, fr_event_list_t *el); /* for /* * module_unlang.c */ -int unlang_event_timeout_add(REQUEST *request, fr_unlang_module_timeout_t callback, - void const *ctx, struct timeval *timeout); +int unlang_event_module_timeout_add(REQUEST *request, fr_unlang_module_timeout_t callback, + void const *ctx, struct timeval *timeout); int unlang_event_fd_add(REQUEST *request, fr_unlang_module_fd_event_t read, diff --git a/src/main/unlang_interpret.c b/src/main/unlang_interpret.c index c6662e10a06..53848da07ce 100644 --- a/src/main/unlang_interpret.c +++ b/src/main/unlang_interpret.c @@ -974,8 +974,8 @@ static void unlang_event_fd_error_handler(UNUSED fr_event_list_t *el, int fd, * - 0 on success. * - <0 on error. */ -int unlang_event_timeout_add(REQUEST *request, fr_unlang_module_timeout_t callback, - void const *ctx, struct timeval *when) +int unlang_event_module_timeout_add(REQUEST *request, fr_unlang_module_timeout_t callback, + void const *ctx, struct timeval *when) { unlang_stack_t *stack = request->stack; unlang_stack_frame_t *frame = &stack->frame[stack->depth]; @@ -1015,8 +1015,11 @@ int unlang_event_timeout_add(REQUEST *request, fr_unlang_module_timeout_t callba /** Delete a previously set timeout callback * - * param[in] request the request - * param[in] ctx a local context for the callback + * @param[in] request The current request. + * @param[in] ctx a local context for the callback. + * @return + * - -1 on error. + * - 0 on success. */ int unlang_event_timeout_delete(REQUEST *request, void const *ctx) { @@ -1024,8 +1027,8 @@ int unlang_event_timeout_delete(REQUEST *request, void const *ctx) ev = request_data_get(request, ctx, -1); if (!ev) return -1; - talloc_free(ev); + return 0; } diff --git a/src/modules/rlm_delay/rlm_delay.c b/src/modules/rlm_delay/rlm_delay.c index b8ff6b18532..5ad40e43aea 100644 --- a/src/modules/rlm_delay/rlm_delay.c +++ b/src/modules/rlm_delay/rlm_delay.c @@ -31,6 +31,7 @@ RCSID("$Id$") #include typedef struct rlm_delay_t { + char const *xlat_name; //!< Name of our xlat function. vp_tmpl_t *delay; //!< How long we delay for. bool relative; //!< Whether the delay is relative to the start of request processing. bool force_reschedule; //!< Whether we should force rescheduling of the request. @@ -46,17 +47,9 @@ static const CONF_PARSER module_config[] = { CONF_PARSER_TERMINATOR }; -/** Called when the delay is complete, and we're running from the interpreter - * - */ -static rlm_rcode_t delay_return(UNUSED REQUEST *request, UNUSED void *instance, UNUSED void *thread, UNUSED void *ctx) -{ - return RLM_MODULE_OK; -} - /** Called when the timeout has expired * - * Marks the request as resumable, and prints the actual delay time. + * Marks the request as resumable, and prints the delayed delay time. * * @param[in] request The current request. * @param[in] instance This instance of the delay module. @@ -64,110 +57,239 @@ static rlm_rcode_t delay_return(UNUSED REQUEST *request, UNUSED void *instance, * @param[in] ctx Scheduled end of the delay. * @param[in] fired When request processing was resumed. */ -static void delay_done(REQUEST *request, UNUSED void *instance, UNUSED void *thread, void *ctx, struct timeval *fired) +static void _delay_done(REQUEST *request, UNUSED void *instance, UNUSED void *thread, void *ctx, struct timeval *fired) { - struct timeval *when = talloc_get_type_abort(ctx, struct timeval); + struct timeval *yielded = talloc_get_type_abort(ctx, struct timeval); + + RDEBUG2("Delay done"); /* - * Print how long the delay *actually* was. + * timeout should never be *before* the scheduled time, + * if it is, something is very broken. */ - if (RDEBUG_ENABLED3) { - struct timeval actual; - - /* - * timeout should never be *before* the scheduled time, - * if it is, something is very broken. - */ - rad_assert(fr_timeval_cmp(fired, when) <= 0); - - fr_timeval_subtract(&actual, fired, when); + rad_assert(fr_timeval_cmp(fired, yielded) >= 0); - RDEBUG3("Request delayed by %"PRIu64".%06"PRIu64"s", - (uint64_t)actual.tv_sec, (uint64_t)actual.tv_usec); - } - - talloc_free(when); unlang_resumable(request); } -static rlm_rcode_t delay_add(rlm_delay_t const *inst, REQUEST *request) +static int delay_add(REQUEST *request, struct timeval *resume_at, struct timeval *now, + struct timeval *delay, bool force_reschedule, bool relative) { - struct timeval delay; - struct timeval *now; - struct timeval when; - int cmp; + int cmp; - if (inst->delay) { - if (tmpl_aexpand(request, &delay, request, inst->delay, NULL, NULL) < 0) return RLM_MODULE_FAIL; - } else { - memset(&delay, 0, sizeof(delay)); - } /* * Delay is zero (and reschedule is not forced) */ - if (!inst->force_reschedule && (delay.tv_sec == 0) && (delay.tv_usec == 0)) return RLM_MODULE_NOOP; - - MEM(now = talloc(request, struct timeval)); - if (gettimeofday(now, NULL) < 0) { - REDEBUG("Failed getting current time: %s", fr_syserror(errno)); - return RLM_MODULE_FAIL; - } + if (!force_reschedule && (delay->tv_sec == 0) && (delay->tv_usec == 0)) return 1; /* * Process the delay relative to the start of packet processing */ - if (inst->relative) { - fr_timeval_add(&when, &request->packet->timestamp, &delay); + if (relative) { + fr_timeval_add(resume_at, &request->packet->timestamp, delay); } else { - fr_timeval_add(&when, now, &delay); + fr_timeval_add(resume_at, now, delay); } /* - * If when is in the past (and reschedule is not forced), just return noop + * If resume_at is in the past (and reschedule is not forced), just return noop */ - cmp = fr_timeval_cmp(now, &when); - if (!inst->force_reschedule && (cmp >= 0)) return RLM_MODULE_NOOP; + cmp = fr_timeval_cmp(now, resume_at); + if (!force_reschedule && (cmp >= 0)) return 1; if (cmp < 0) { - struct timeval actual; - fr_timeval_subtract(&actual, &when, now); + struct timeval delay_by; + + fr_timeval_subtract(&delay_by, resume_at, now); - RDEBUG2("Delaying request by ~%"PRIu64".%06"PRIu64"s", - (uint64_t)actual.tv_sec, (uint64_t)actual.tv_usec); + RDEBUG2("Delaying request by ~%pVs", fr_box_timeval(delay_by)); } else { RDEBUG2("Rescheduling request"); } - if (unlang_event_timeout_add(request, delay_done, now, &when) < 0) return RLM_MODULE_FAIL; + return 0; +} - return RLM_MODULE_YIELD; +/** Called resume_at the delay is complete, and we're running from the interpreter + * + */ +static rlm_rcode_t mod_delay_return(UNUSED REQUEST *request, + UNUSED void *instance, UNUSED void *thread, UNUSED void *ctx) +{ + struct timeval *yielded = talloc_get_type_abort(ctx, struct timeval); + + /* + * Print how long the delay *really* was. + */ + if (RDEBUG_ENABLED3) { + struct timeval delayed, now; + + gettimeofday(&now, NULL); + fr_timeval_subtract(&delayed, &now, yielded); + + RDEBUG3("Request delayed by %pV", fr_box_timeval(delayed)); + } + talloc_free(yielded); + + return RLM_MODULE_OK; } -static void delay_cancel(REQUEST *request, UNUSED void *instance, UNUSED void *thread, void *ctx, - fr_state_signal_t action) +static void mod_delay_cancel(REQUEST *request, UNUSED void *instance, UNUSED void *thread, void *ctx, + fr_state_signal_t action) { if (action != FR_SIGNAL_DONE) return; RDEBUG2("Cancelling delay"); - if (!fr_cond_assert(unlang_event_timeout_delete(request, ctx) < 0)) return; + if (!fr_cond_assert(unlang_event_timeout_delete(request, ctx) == 0)) return; } static rlm_rcode_t CC_HINT(nonnull) mod_delay(void *instance, UNUSED void *thread, REQUEST *request) { rlm_delay_t const *inst = instance; - rlm_rcode_t rcode; + struct timeval delay, resume_at, *yielded_at; + + if (inst->delay) { + if (tmpl_aexpand(request, &delay, request, inst->delay, NULL, NULL) < 0) return RLM_MODULE_FAIL; + } else { + memset(&delay, 0, sizeof(delay)); + } + + /* + * Record the time that we yielded the request + */ + MEM(yielded_at = talloc(request, struct timeval)); + if (gettimeofday(yielded_at, NULL) < 0) { + REDEBUG("Failed getting current time: %s", fr_syserror(errno)); + return RLM_MODULE_FAIL; + } /* * Setup the delay for this request */ - rcode = delay_add(inst, request); - if (rcode != RLM_MODULE_YIELD) return rcode; + if (delay_add(request, &resume_at, yielded_at, &delay, inst->force_reschedule, inst->delay) != 0) { + return RLM_MODULE_NOOP; + } + + RDEBUG3("Current time %pV, resume time %pV", fr_box_timeval(*yielded_at), fr_box_timeval(resume_at)); + + if (unlang_event_module_timeout_add(request, _delay_done, yielded_at, &resume_at) < 0) { + RPEDEBUG("Adding event failed"); + return RLM_MODULE_FAIL; + } + + return unlang_module_yield(request, mod_delay_return, mod_delay_cancel, yielded_at); +} + +static xlat_action_t xlat_delay_resume(TALLOC_CTX *ctx, fr_cursor_t *out, + UNUSED REQUEST *request, + UNUSED void const *xlat_inst, UNUSED void *xlat_thread_inst, + UNUSED fr_value_box_t **in, UNUSED void *rctx) +{ + struct timeval *yielded_at = talloc_get_type_abort(rctx, struct timeval); + struct timeval delayed, now; + fr_value_box_t *vb; + + gettimeofday(&now, NULL); + fr_timeval_subtract(&delayed, &now, yielded_at); + talloc_free(yielded_at); + + RDEBUG3("Request delayed by %pVs", fr_box_timeval(delayed)); + + MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_TIMEVAL, NULL, false)); + vb->vb_timeval = delayed; + + fr_cursor_insert(out, vb); + + return XLAT_ACTION_DONE; +} + +static void xlat_delay_cancel(REQUEST *request, UNUSED void *instance, UNUSED void *thread, + void *rctx, fr_state_signal_t action) +{ + if (action != FR_SIGNAL_DONE) return; + + RDEBUG2("Cancelling delay"); + + if (!fr_cond_assert(unlang_event_timeout_delete(request, rctx) == 0)) return; +} + +static xlat_action_t xlat_delay(TALLOC_CTX *ctx, UNUSED fr_cursor_t *out, + REQUEST *request, void const *xlat_inst, UNUSED void *xlat_thread_inst, + fr_value_box_t **in) +{ + rlm_delay_t const *inst = talloc_get_type_abort(*((void const * const *)xlat_inst), rlm_delay_t); + struct timeval resume_at, delay, *yielded_at; + + /* + * Record the time that we yielded the request + */ + MEM(yielded_at = talloc(request, struct timeval)); + if (gettimeofday(yielded_at, NULL) < 0) { + REDEBUG("Failed getting current time: %s", fr_syserror(errno)); + return XLAT_ACTION_FAIL; + } /* - * Yield, setting delay_return as the next state + * If there's no input delay, just yield and + * immediately re-enqueue the request. + * This is very useful for testing. */ - return unlang_module_yield(request, delay_return, delay_cancel, NULL); + if (!*in) { + memset(&delay, 0, sizeof(delay)); + if (!fr_cond_assert(delay_add(request, &resume_at, yielded_at, &delay, true, true) == 0)) { + return XLAT_ACTION_FAIL; + } + goto yield; + } + + if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_STRING, true) < 0) { + RPEDEBUG("Failed concatenating input"); + talloc_free(yielded_at); + return XLAT_ACTION_FAIL; + } + + if (fr_timeval_from_str(&delay, (*in)->vb_strvalue) < 0) { + RPEDEBUG("Failed parsing delay time"); + talloc_free(yielded_at); + return XLAT_ACTION_FAIL; + } + + if (delay_add(request, &resume_at, yielded_at, &delay, inst->force_reschedule, inst->relative) != 0) { + RDEBUG2("Not adding delay"); + talloc_free(yielded_at); + return XLAT_ACTION_DONE; + } + +yield: + RDEBUG3("Current time %pV, resume time %pV", fr_box_timeval(*yielded_at), fr_box_timeval(resume_at)); + + if (xlat_unlang_event_timeout_add(request, _delay_done, yielded_at, &resume_at) < 0) { + RPEDEBUG("Adding event failed"); + return XLAT_ACTION_FAIL; + } + + return xlat_unlang_yield(request, xlat_delay_resume, xlat_delay_cancel, yielded_at); +} + +static int mod_xlat_instantiate(void *xlat_inst, UNUSED xlat_exp_t const *exp, void *uctx) +{ + *((void **)xlat_inst) = talloc_get_type_abort(uctx, rlm_delay_t); + return 0; +} + +static int mod_bootstrap(void *instance, CONF_SECTION *conf) +{ + rlm_delay_t *inst = instance; + + inst->xlat_name = cf_section_name2(conf); + if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf); + + xlat_async_register(inst, inst->xlat_name, xlat_delay, + mod_xlat_instantiate, sizeof(void *), NULL, + NULL, 0, NULL, inst); + + return 0; } extern rad_module_t rlm_delay; @@ -177,6 +299,7 @@ rad_module_t rlm_delay = { .type = 0, .inst_size = sizeof(rlm_delay_t), .config = module_config, + .bootstrap = mod_bootstrap, .methods = { [MOD_PREACCT] = mod_delay, [MOD_AUTHORIZE] = mod_delay, diff --git a/src/tests/keywords/xlat-delay b/src/tests/keywords/xlat-delay new file mode 100644 index 00000000000..6d506d7e38f --- /dev/null +++ b/src/tests/keywords/xlat-delay @@ -0,0 +1,17 @@ +# +# PRE: update +# + +# This is mainly a smoke test... i.e. if it crashes there's smoke + +"%{delay_10s:}" # Should 'blip' the request + +update request { + Tmp-String-0 := "%{delay_10s:0.1}" +} + +if (!&Tmp-String-0) { + fail +} + +success