]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add xlat for rlm_delay
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Tue, 30 Jan 2018 21:52:10 +0000 (14:52 -0700)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Tue, 30 Jan 2018 23:46:02 +0000 (16:46 -0700)
...it delays things.

src/include/modules.h
src/main/unlang_interpret.c
src/modules/rlm_delay/rlm_delay.c
src/tests/keywords/xlat-delay [new file with mode: 0644]

index a42ca553fc8a8eaaaa966aacd08895fea5c64ed9..82cfcdfc49fd1862227a3459e20036b4068b66fe 100644 (file)
@@ -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,
index c6662e10a061f92595e16aa016616441ea2ce59d..53848da07ce4df1449c0d2ea0341afedaaa014b8 100644 (file)
@@ -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;
 }
 
index b8ff6b185323aa8e2f72fa40e96c85f01f618aef..5ad40e43aea8528658c059de221e977172c87a6c 100644 (file)
@@ -31,6 +31,7 @@ RCSID("$Id$")
 #include <freeradius-devel/map_proc.h>
 
 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 (file)
index 0000000..6d506d7
--- /dev/null
@@ -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