# The default is `no`, which means that.
#
# relative = no
+
+ #
+ # rcode:: Which rcode the delay module should return when it resumes
+ # this can be useful for detecting whether a `timeout { ... }` section
+ # expired, whilst the delay module was waiting.
+ #
+ # The default is `notset`, which means that the delay module will be
+ # transparent and not alter the section rcode.
+ #
+ # [NOTE]
+ # ====
+ # The default section `rcode` in many places is `fail`. Care should be
+ # taken to set the `rcode` for the section a delay module appears in, if
+ # this configuration item is not set, and no other modules are present
+ # in the section.
+ # ====
+ #
+# rcode = 'notset'
}
#
{ L("invalid"), RLM_MODULE_INVALID },
{ L("noop"), RLM_MODULE_NOOP },
{ L("notfound"), RLM_MODULE_NOTFOUND },
+ { L("notset"), RLM_MODULE_NOT_SET },
{ L("ok"), RLM_MODULE_OK },
{ L("reject"), RLM_MODULE_REJECT },
{ L("timeout"), RLM_MODULE_TIMEOUT },
- { L("updated"), RLM_MODULE_UPDATED },
+ { L("updated"), RLM_MODULE_UPDATED }
};
size_t rcode_table_len = NUM_ELEMENTS(rcode_table);
request->module = state->previous_module;
break;
- case UNLANG_ACTION_EXECUTE_NEXT: /* Not valid */
- fr_assert(0);
- *p_result = RLM_MODULE_FAIL;
+ /*
+ * Module indicates we shouldn't process its rcode
+ */
+ case UNLANG_ACTION_EXECUTE_NEXT:
break;
}
*p_result = RLM_MODULE_FAIL;
break;
+ /*
+ * Module indicates we shouldn't process its rcode
+ */
case UNLANG_ACTION_EXECUTE_NEXT:
- fr_assert(0);
- *p_result = RLM_MODULE_FAIL;
break;
}
*
* Unless the next instruction is a "catch timeout", in which case we skip it.
*/
- if (!state->fired) return UNLANG_ACTION_CALCULATE_RESULT;
+ if (!state->fired) return UNLANG_ACTION_EXECUTE_NEXT; /* Don't modify the return code*/
RETURN_MODULE_TIMEOUT;
}
frame_cleanup(frame);
frame->instruction = frame->next;
- if (!frame->instruction) return;
+ if (!frame->instruction) return; /* No siblings, need to pop instead */
frame->next = frame->instruction->next;
#include <freeradius-devel/server/base.h>
#include <freeradius-devel/server/module_rlm.h>
+#include <freeradius-devel/server/rcode.h>
#include <freeradius-devel/util/debug.h>
#include <freeradius-devel/server/map_proc.h>
#include <freeradius-devel/util/time.h>
#include <freeradius-devel/unlang/xlat_func.h>
+#include <freeradius-devel/unlang/action.h>
typedef struct {
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.
+ rlm_rcode_t rcode; //!< The return code to use when the delay is complete.
} rlm_delay_t;
/*
{ FR_CONF_OFFSET_HINT_TYPE("delay", FR_TYPE_TIME_DELTA, rlm_delay_t, delay) },
{ FR_CONF_OFFSET("relative", rlm_delay_t, relative), .dflt = "no" },
{ FR_CONF_OFFSET("force_reschedule", rlm_delay_t, force_reschedule), .dflt = "no" },
+ { FR_CONF_OFFSET("rcode", rlm_delay_t, rcode),
+ .func = cf_table_parse_int, .uctx = &(cf_table_parse_ctx_t){ .table = rcode_table, .len = &rcode_table_len }, .dflt = "notset" },
CONF_PARSER_TERMINATOR
};
static unlang_action_t mod_delay_return(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
{
rlm_delay_retry_t *yielded = talloc_get_type_abort(mctx->rctx, rlm_delay_retry_t);
+ rlm_delay_t *inst = talloc_get_type_abort_const(mctx->mi->data, rlm_delay_t);
/*
* Print how long the delay *really* was.
RDEBUG3("Request delayed by %pV", fr_box_time_delta(fr_time_sub(fr_time(), yielded->when)));
talloc_free(yielded);
- RETURN_MODULE_OK;
+ /*
+ * Be transparent, don't alter the rcode
+ */
+ if (inst->rcode == RLM_MODULE_NOT_SET) return UNLANG_ACTION_EXECUTE_NEXT;
+ RETURN_MODULE_RCODE(inst->rcode);
}
static unlang_action_t CC_HINT(nonnull) mod_delay(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
--- /dev/null
+# PRE: if if-else
+
+# This is a regression test. We saw the request->rcode being lost after the first condition
+
+noop
+if (ok || notfound) {
+ test_fail
+} elsif (!noop) {
+ test_fail
+} elsif (noop) {
+ # Check nothing weird happens
+ reschedule_notfound
+}
+
+if (!notfound) {
+ test_fail
+}
+
+notfound
+if (%expr.rcode('ok') || %expr.rcode('updated')) {
+ test_fail
+} elsif (!%expr.rcode('notfound')) {
+ test_fail
+}
+
+success
--- /dev/null
+noop {
+ # 0 gives greatest chance of overwrite
+ noop = 1
+}
+
+# Verify that checking the rcode doesn't _alter_ the rcode
+if (fail) {
+ test_fail
+}
+elsif (ok || updated) {
+ test_fail
+}
+# rcode should still be noop
+elsif (noop) {
+ success
+} else {
+ test_fail
+}
# Ensure if one module yields, the rest execute
parallel {
- reschedule
+ group {
+ ok # Set the rcode for the section, else it defaults to fail
+ reschedule
+ }
group {
parent.request.Filter-Id := 'foo'
}
force_reschedule = yes
}
+ #
+ # Reschedule the request, then return notfound
+ #
+ delay reschedule_notfound {
+ force_reschedule = yes
+ rcode = "notfound"
+ }
+
delay delay_10s {
delay = 10
}
--- /dev/null
+#
+# PRE: timeout
+#
+
+#
+# If the timeout doesn't fire, the rcode should not change, and the
+# timeout block should be transparent.
+#
+timeout 1s {
+ if (ok) {
+ test_fail
+ } elsif (reject) {
+ notfound
+ }
+}
+if (!notfound) {
+ test_fail
+}
+
+success