From: Alan T. DeKok Date: Mon, 30 Aug 2021 20:44:37 +0000 (-0400) Subject: add retry and timeout signals to modules X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=19071502263f84017d53b0aa18a409ccafdf1b61;p=thirdparty%2Ffreeradius-server.git add retry and timeout signals to modules and add a "retry" method to the "test" module. tests to follow --- diff --git a/src/lib/server/signal.h b/src/lib/server/signal.h index b92f7be95c1..bfc0dd2a859 100644 --- a/src/lib/server/signal.h +++ b/src/lib/server/signal.h @@ -40,7 +40,9 @@ typedef enum fr_state_signal_t { /* server action */ ///< with this, the module should stop processing ///< the request and cleanup anything it's done. FR_SIGNAL_DUP, //!< A duplicate request was received. - FR_SIGNAL_DETACH //!< Request is being detached from its parent. + FR_SIGNAL_DETACH, //!< Request is being detached from its parent. + FR_SIGNAL_RETRY, //!< a retry timer has hit + FR_SIGNAL_TIMEOUT //!< a retry timeout or max count has hit } fr_state_signal_t; #ifdef __cplusplus diff --git a/src/lib/unlang/module.c b/src/lib/unlang/module.c index f14521c285e..e31ad88ccff 100644 --- a/src/lib/unlang/module.c +++ b/src/lib/unlang/module.c @@ -834,12 +834,75 @@ static unlang_action_t unlang_module_resume(rlm_rcode_t *p_result, request_t *re return ua; } +/** Call the callback registered for a retry event + * + * @param[in] el the event timer was inserted into. + * @param[in] now The current time, as held by the event_list. + * @param[in] ctx the stack frame + * + */ +static void unlang_module_event_retry_handler(UNUSED fr_event_list_t *el, fr_time_t now, void *ctx) +{ + request_t *request = talloc_get_type_abort(ctx, request_t); + unlang_stack_t *stack = request->stack; + unlang_stack_frame_t *frame = &stack->frame[stack->depth]; + unlang_frame_state_module_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_module_t); + + /* + * The module will get either a RETRY signal, or a + * TIMEOUT signal (also for max count). + * + * The signal handler should generally change the resume + * function, and mark the request as runnable. We + * probably don't want the module to do tons of work in + * the signal handler, as it's called from the event + * loop. And doing so could affect the other event + * timers. + * + * Note also that we call frame->signal(), and not + * unlang_interpret_signal(). That is because we want to + * signal only the module. We know that the other frames + * on the stack can't handle this particular signal. So + * there's no point in calling them. Or, if sections + * have their own retry handlers, then we don't want to + * signal those _other_ retry handles with _our_ signal. + */ + switch (fr_retry_next(&state->retry, now)) { + case FR_RETRY_CONTINUE: + frame->signal(request, frame, FR_SIGNAL_RETRY); + + /* + * Reset the timer. + */ + if (fr_event_timer_at(request, request->el, &state->ev, state->retry.next, + unlang_module_event_retry_handler, request) < 0) { + RPEDEBUG("Failed inserting event"); + unlang_interpret_mark_runnable(request); /* and let the caller figure out what's up */ + } + return; + + case FR_RETRY_MRD: + REDEBUG("Reached max_rtx_duration (%pVs > %pVs)", + fr_box_time_delta(now - state->retry.start), fr_box_time_delta(state->retry.config->mrd)); + break; + + case FR_RETRY_MRC: + REDEBUG("Reached max_rtx_count (%u > %u)", + state->retry.count, state->retry.config->mrc); + break; + } + + frame->signal(request, frame, FR_SIGNAL_TIMEOUT); +} + + static unlang_action_t unlang_module(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame) { unlang_module_t *mc; unlang_frame_state_module_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_module_t); char const *caller; unlang_action_t ua; + fr_time_t now; *p_result = state->rcode = RLM_MODULE_NOOP; state->set_rcode = true; @@ -883,6 +946,12 @@ static unlang_action_t unlang_module(rlm_rcode_t *p_result, request_t *request, */ state->thread->total_calls++; + /* + * If we're doing retries, remember when we started + * running the module. + */ + if (frame->instruction->actions.retry.irt) now = fr_time(); + caller = request->module; request->module = mc->instance->name; safe_lock(mc->instance); /* Noop unless instance->mutex set */ @@ -922,6 +991,20 @@ static unlang_action_t unlang_module(rlm_rcode_t *p_result, request_t *request, } else { frame_repeat(frame, unlang_module_resume); } + + /* + * If we have retry timers, then start the retries. + */ + if (frame->instruction->actions.retry.irt) { + (void) fr_retry_init(&state->retry, now, &frame->instruction->actions.retry); /* can't fail */ + + if (fr_event_timer_at(request, request->el, &state->ev, state->retry.next, + unlang_module_event_retry_handler, request) < 0) { + RPEDEBUG("Failed inserting event"); + goto fail; + } + } + return UNLANG_ACTION_YIELD; /* @@ -966,6 +1049,7 @@ static unlang_action_t unlang_module(rlm_rcode_t *p_result, request_t *request, break; case UNLANG_ACTION_FAIL: + fail: *p_result = RLM_MODULE_FAIL; break; diff --git a/src/lib/unlang/module_priv.h b/src/lib/unlang/module_priv.h index af1aa3a5d8a..6208d737cb3 100644 --- a/src/lib/unlang/module_priv.h +++ b/src/lib/unlang/module_priv.h @@ -68,7 +68,17 @@ typedef struct { void *rctx; //!< for resume / signal unlang_module_resume_t resume; //!< resumption handler unlang_module_signal_t signal; //!< for signal handlers + + /** @} */ + + /** @name Retry handlers. + * @{ + */ + fr_event_timer_t const *ev; //!< retry timer just for this module. + fr_retry_t retry; //!< retry timers, etc. + /** @} */ + } unlang_frame_state_module_t; static inline unlang_module_t *unlang_generic_to_module(unlang_t const *p) diff --git a/src/modules/rlm_test/rlm_test.c b/src/modules/rlm_test/rlm_test.c index 0f1c05ecb2a..7b6cc8106e7 100644 --- a/src/modules/rlm_test/rlm_test.c +++ b/src/modules/rlm_test/rlm_test.c @@ -350,6 +350,71 @@ static unlang_action_t CC_HINT(nonnull) mod_return(rlm_rcode_t *p_result, UNUSED RETURN_MODULE_OK; } +static void mod_retry_signal(module_ctx_t const *mctx, request_t *request, void *rctx, fr_state_signal_t action); + +/** Continue after marked runnable + * + */ +static unlang_action_t mod_retry_resume(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request, UNUSED void *ctx) +{ + RDEBUG("Test called main retry handler - that's a failure"); + + RETURN_MODULE_FAIL; +} + +/** Continue after FR_SIGNAL_RETRY + * + */ +static unlang_action_t mod_retry_resume_retry(UNUSED rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request, UNUSED void *ctx) +{ + RDEBUG("Test retry"); + + return unlang_module_yield(request, mod_retry_resume, mod_retry_signal, NULL); +} + +/** Continue after FR_SIGNAL_TIMEOUT + * + */ +static unlang_action_t mod_retry_resume_timeout(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request, UNUSED void *ctx) +{ + RDEBUG("Test timed out as expected"); + + RETURN_MODULE_OK; +} + +static void mod_retry_signal(UNUSED module_ctx_t const *mctx, request_t *request, UNUSED void *rctx, fr_state_signal_t action) +{ + switch (action) { + case FR_SIGNAL_RETRY: + RDEBUG("Test retry"); + unlang_module_set_resume(request, mod_retry_resume_retry); + unlang_interpret_mark_runnable(request); + break; + + case FR_SIGNAL_TIMEOUT: + RDEBUG("Test timeout"); + unlang_module_set_resume(request, mod_retry_resume_timeout); + unlang_interpret_mark_runnable(request); + break; + + /* + * Ignore all other signals. + */ + default: + break; + } + +} + + +/* + * Test retries + */ +static unlang_action_t CC_HINT(nonnull) mod_retry(UNUSED rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, UNUSED request_t *request) +{ + return unlang_module_yield(request, mod_retry_resume, mod_retry_signal, NULL); +} + static int mod_detach(UNUSED void *instance) { /* free things here */ @@ -387,6 +452,7 @@ module_t rlm_test = { { .name1 = "recv", .name2 = "Access-Challenge", .method = mod_return }, { .name1 = "name1_null", .name2 = NULL, .method = mod_return }, { .name1 = "send", .name2 = CF_IDENT_ANY, .method = mod_return }, + { .name1 = "retry", .name2 = NULL, .method = mod_retry }, MODULE_NAME_TERMINATOR }