]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add retry and timeout signals to modules
authorAlan T. DeKok <aland@freeradius.org>
Mon, 30 Aug 2021 20:44:37 +0000 (16:44 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Mon, 30 Aug 2021 20:45:14 +0000 (16:45 -0400)
and add a "retry" method to the "test" module.

tests to follow

src/lib/server/signal.h
src/lib/unlang/module.c
src/lib/unlang/module_priv.h
src/modules/rlm_test/rlm_test.c

index b92f7be95c1d694dd35cb830ccf2b4b195aacb0c..bfc0dd2a8591dbbcc18135c7a0eed0971ec5c5e0 100644 (file)
@@ -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
index f14521c285ea57cde58071d8152c9baeedf3b49f..e31ad88ccff8e4101a1e2b29d1f3ef86d2e191c6 100644 (file)
@@ -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;
 
index af1aa3a5d8acb0304223e5eaef915367dc8a6787..6208d737cb36337826a831b749f329e0e6bebdc9 100644 (file)
@@ -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)
index 0f1c05ecb2a940912e2be035c69f216d4eb05635..7b6cc8106e7c3b7ec4bdbf81f21378a91e39fc8b 100644 (file)
@@ -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
        }