From: Arran Cudbard-Bell Date: Wed, 24 Mar 2021 22:30:40 +0000 (+0000) Subject: Rework subrequests to behave more like parallel child requests X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=75d7daa17099bb05652c402f2ef30394f7d608b3;p=thirdparty%2Ffreeradius-server.git Rework subrequests to behave more like parallel child requests --- diff --git a/src/lib/unlang/all.mk b/src/lib/unlang/all.mk index b4abc0a4d42..3b9eaa06d66 100644 --- a/src/lib/unlang/all.mk +++ b/src/lib/unlang/all.mk @@ -5,6 +5,7 @@ SOURCES := base.c \ caller.c \ compile.c \ condition.c \ + detach.c \ foreach.c \ function.c \ group.c \ @@ -17,6 +18,7 @@ SOURCES := base.c \ parallel.c \ return.c \ subrequest.c \ + subrequest_child.c \ switch.c \ tmpl.c \ xlat.c \ diff --git a/src/lib/unlang/base.c b/src/lib/unlang/base.c index 3bb5240c334..77b458a8d42 100644 --- a/src/lib/unlang/base.c +++ b/src/lib/unlang/base.c @@ -30,6 +30,10 @@ RCSID("$Id$") static uint32_t instance_count; +/** Different operations the interpreter can execute + */ +unlang_op_t unlang_ops[UNLANG_TYPE_MAX]; + /** Return whether a section has unlang data associated with it * * @param[in] cs to check. @@ -92,6 +96,7 @@ int unlang_init_global(void) unlang_parallel_init(); unlang_return_init(); if (unlang_subrequest_op_init() < 0) return -1; + unlang_detach_init(); unlang_switch_init(); unlang_call_init(); unlang_caller_init(); diff --git a/src/lib/unlang/call.c b/src/lib/unlang/call.c index d917683941b..d45238c2541 100644 --- a/src/lib/unlang/call.c +++ b/src/lib/unlang/call.c @@ -49,10 +49,6 @@ static unlang_action_t unlang_call_frame_init(rlm_rcode_t *p_result, request_t * g = unlang_generic_to_group(frame->instruction); gext = unlang_group_to_call(g); - /* - * Push OUR subsection onto the childs stack frame. - */ - /* * Work out the current request type. */ diff --git a/src/lib/unlang/detach.c b/src/lib/unlang/detach.c new file mode 100644 index 00000000000..e2d472283f1 --- /dev/null +++ b/src/lib/unlang/detach.c @@ -0,0 +1,61 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file unlang/detach.c + * @brief Unlang detach keyword + * + * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) + */ +RCSID("$Id$") + +#include "unlang_priv.h" + +/** Signal a child to detach + * + */ +static unlang_action_t unlang_detach(rlm_rcode_t *p_result, request_t *request, UNUSED unlang_stack_frame_t *frame) +{ + /* + * Signal all frames in the child's stack + * that it's time to detach. + */ + unlang_interpret_signal(request, FR_SIGNAL_DETACH); + + /* + * Detach failed... + */ + if (unlikely(request->parent)) { + *p_result = RLM_MODULE_FAIL; + return UNLANG_ACTION_CALCULATE_RESULT; + } + + return UNLANG_ACTION_CALCULATE_RESULT; +} + +/** Initialise subrequest ops + * + */ +void unlang_detach_init(void) +{ + unlang_register(UNLANG_TYPE_DETACH, + &(unlang_op_t){ + .name = "detach", + .interpret = unlang_detach, + }); +} diff --git a/src/lib/unlang/interpret_priv.h b/src/lib/unlang/interpret_priv.h index 0ba456b094c..e504fd93aa0 100644 --- a/src/lib/unlang/interpret_priv.h +++ b/src/lib/unlang/interpret_priv.h @@ -18,14 +18,15 @@ /** * $Id$ * - * @file unlang/interpret.h - * @brief Declarations for the unlang interpreter. + * @file unlang/interpret_priv.h + * @brief Private declarations for the unlang interpreter. * - * @copyright 2019 The FreeRADIUS server project + * @copyright 2021 The FreeRADIUS server project */ #include #include +#include "interpret_priv.h" #ifdef __cplusplus extern "C" { @@ -37,7 +38,7 @@ struct unlang_interpret_s { void *uctx; }; -static inline void unlang_interpret_child_init(request_t *request) +static inline void interpret_child_init(request_t *request) { unlang_interpret_t *intp = unlang_interpret_get(request); diff --git a/src/lib/unlang/module.c b/src/lib/unlang/module.c index ebb277f91b5..50550902cda 100644 --- a/src/lib/unlang/module.c +++ b/src/lib/unlang/module.c @@ -418,38 +418,6 @@ request_t *unlang_module_subrequest_alloc(request_t *parent, fr_dict_t const *na return unlang_io_subrequest_alloc(parent, namespace, UNLANG_NORMAL_CHILD); } -/** Yield, spawning a child request, and resuming once the child request is complete - * - * @param[in] p_result Final rcode from when evaluation of the child request finishes. - * @param[out] child to yield to. The child knows about the parent, - * which is why the parent isn't passed explicitly. - * @param[in] resume function to call when the child has finished executing. - * @param[in] signal function to call if a signal is received. - * @param[in] session control values. Whether we restore/store session info. - * @param[in] rctx to pass to the resume() and signal() callbacks. - * @return - * - UNLANG_ACTION_YIELD. - */ -unlang_action_t unlang_module_yield_to_subrequest(rlm_rcode_t *p_result, request_t *child, - unlang_module_resume_t resume, - unlang_module_signal_t signal, - unlang_subrequest_session_t const *session, - void *rctx) -{ - /* - * Push the resumption point BEFORE adding the subrequest - * to the parents stack. - */ - (void) unlang_module_yield(child->parent, resume, signal, rctx); - - /* - * Push the subrequest and immediately run it. - */ - if (unlang_subrequest_push(p_result, child, session, UNLANG_SUB_FRAME) < 0) return UNLANG_ACTION_STOP_PROCESSING; - - return UNLANG_ACTION_YIELD; -} - /** Push a pre-compiled xlat and resumption state onto the stack for evaluation * * In order to use the async unlang processor the calling module needs to establish diff --git a/src/lib/unlang/parallel.c b/src/lib/unlang/parallel.c index 5a292756ec4..814f41259f9 100644 --- a/src/lib/unlang/parallel.c +++ b/src/lib/unlang/parallel.c @@ -354,7 +354,7 @@ static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t * Detach the child, and insert * it into the backlog. */ - if (unlang_detached_child_init(child) < 0) { + if (unlang_subrequest_child_detach(child) < 0) { talloc_free(child); *p_result = RLM_MODULE_FAIL; @@ -382,8 +382,9 @@ static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t state->children[i].name = talloc_bstrdup(state, child->name); state->children[i].request = child; state->children[i].state = CHILD_RUNNABLE; - unlang_interpret_child_init(child); + } + interpret_child_init(child); /* * Push the first instruction for diff --git a/src/lib/unlang/subrequest.c b/src/lib/unlang/subrequest.c index b174f446428..d39ada461e3 100644 --- a/src/lib/unlang/subrequest.c +++ b/src/lib/unlang/subrequest.c @@ -28,197 +28,101 @@ RCSID("$Id$") #include "unlang_priv.h" #include "interpret_priv.h" #include "subrequest_priv.h" - -/** Parameters for initialising the subrequest - * - * State of one level of nesting within an xlat expansion. - */ -typedef struct { - rlm_rcode_t *p_result; //!< Where to store the result. - request_t *child; //!< Pre-allocated child request. - bool free_child; //!< Whether we should free the child after - ///< it completes. - bool detachable; //!< Whether the request can be detached. - unlang_subrequest_session_t session; //!< Session configuration. -} unlang_frame_state_subrequest_t; - -static fr_dict_t const *dict_freeradius; - -extern fr_dict_autoload_t subrequest_dict[]; -fr_dict_autoload_t subrequest_dict[] = { - { .out = &dict_freeradius, .proto = "freeradius" }, - { NULL } -}; - -static fr_dict_attr_t const *request_attr_request_lifetime; - -extern fr_dict_attr_autoload_t subrequest_dict_attr[]; -fr_dict_attr_autoload_t subrequest_dict_attr[] = { - { .out = &request_attr_request_lifetime, .name = "Request-Lifetime", .type = FR_TYPE_UINT32, .dict = &dict_freeradius }, - { NULL } -}; - -typedef struct { - rlm_rcode_t rcode; //!< frame->result from before detach was called -} unlang_frame_state_detach_t; - -static void unlang_max_request_time(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx) -{ - request_t *request = talloc_get_type_abort(uctx, request_t); - - RDEBUG("Reached Request-Lifetime. Forcibly stopping request"); - - /* - * The request is scheduled and isn't running. Remove it - * from the backlog. - */ - if (unlang_request_is_scheduled(request)) { - fr_assert(request->backlog != NULL); - (void) fr_heap_extract(request->backlog, request); - } - - talloc_free(request); -} +#include "subrequest_child_priv.h" /** Send a signal from parent request to subrequest * */ -static void unlang_subrequest_signal(UNUSED request_t *request, unlang_stack_frame_t *frame, fr_state_signal_t action) +static void unlang_subrequest_parent_signal(UNUSED request_t *request, unlang_stack_frame_t *frame, + fr_state_signal_t action) { unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t); request_t *child = talloc_get_type_abort(state->child, request_t); + /* + * Parent should never receive a detach + * signal whilst the child is running. + * + * Only the child receives a detach + * signal when the detach keyword is used. + */ + fr_assert(action != FR_SIGNAL_DETACH); + + /* + * Forward other signals to the child + */ unlang_interpret_signal(child, action); } - -/** Process a subrequest until it either detaches, or is done. +/** Parent being resumed after a child completes * */ -static unlang_action_t unlang_subrequest_process(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame) +static unlang_action_t unlang_subrequest_parent_resume(rlm_rcode_t *p_result, request_t *request, + unlang_stack_frame_t *frame) { - unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t); - request_t *child = talloc_get_type_abort(state->child, request_t); - unlang_group_t *g = unlang_generic_to_group(frame->instruction); - rlm_rcode_t rcode; - - fr_assert(child != NULL); - - rcode = unlang_interpret(child); - if (rcode != RLM_MODULE_YIELD) { - if (!fr_cond_assert(rcode < NUM_ELEMENTS(frame->instruction->actions))) return UNLANG_ACTION_STOP_PROCESSING; - - if (state->session.enable) fr_state_store_in_parent(child, - state->session.unique_ptr, - state->session.unique_int); - - /* - * Copy the subrequest's reply list into - * the parent, creating any n - */ - - calculate_result: - if (child->reply) { - unlang_subrequest_t *gext; - - /* - * Copy reply attributes into the specified - * destination. - */ - gext = unlang_group_to_subrequest(g); - if (gext->dst) { - tmpl_attr_extent_t *extent = NULL; - fr_dlist_head_t leaf; - fr_dlist_head_t interior; - - fr_dlist_talloc_init(&leaf, tmpl_attr_extent_t, entry); - fr_dlist_talloc_init(&interior, tmpl_attr_extent_t, entry); - - /* - * Find out what we need to build and build it - */ - if ((tmpl_extents_find(state, &leaf, &interior, request, gext->dst) < 0) || - (tmpl_extents_build_to_leaf(&leaf, &interior, gext->dst) < 0)) { - RPDEBUG("Discarding subrequest attributes - Failed allocating groups"); - fr_dlist_talloc_free(&leaf); - fr_dlist_talloc_free(&interior); - goto done; - } - while ((extent = fr_dlist_tail(&leaf))) { - fr_pair_list_copy(extent->list_ctx, extent->list, &child->reply_pairs); - fr_dlist_talloc_free_tail(&leaf); - } - } - } - - done: - if (state->free_child) { - unlang_subrequest_free(&child); - state->child = NULL; - frame->signal = NULL; - } - - /* - * Pass the result back to the module - * that created the subrequest, or - * use it to modify the current section - * rcode. - */ - if (state->p_result) *state->p_result = rcode; + unlang_group_t *g = unlang_generic_to_group(frame->instruction); + unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, + unlang_frame_state_subrequest_t); + request_t *child = state->child; + unlang_subrequest_t *gext; - *p_result = rcode; - - return UNLANG_ACTION_CALCULATE_RESULT; - } + RDEBUG3("Subrequest complete"); /* - * The child has yielded, BUT has also detached itself. - * We know this because it has reached into our state, - * and removed itself as a child. We therefore just keep - * running, and don't return yield. + * Child detached */ if (!state->child) { - rcode = RLM_MODULE_NOOP; - goto calculate_result; + RDEBUG3("Child has detached"); + return UNLANG_ACTION_CALCULATE_RESULT; } /* - * Else the child yielded, so we have to yield. Set up - * the resume frame and continue. + * If there's a no destination tmpl, we're done. */ - return UNLANG_ACTION_YIELD; -} - - -static unlang_action_t unlang_subrequest_start(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame) -{ - unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t); - request_t *child = state->child; + if (!child->reply) { + unlang_subrequest_free(&child); + return UNLANG_ACTION_CALCULATE_RESULT; + } /* - * Restore state from the parent to the - * subrequest. + * Otherwise... copy reply attributes into the + * specified destination. */ - if (state->session.enable) fr_state_restore_to_child(child, - state->session.unique_ptr, - state->session.unique_int); + gext = unlang_group_to_subrequest(g); + if (gext->dst) { + tmpl_attr_extent_t *extent = NULL; + fr_dlist_head_t leaf; + fr_dlist_head_t interior; - REQUEST_VERIFY(child); + fr_dlist_talloc_init(&leaf, tmpl_attr_extent_t, entry); + fr_dlist_talloc_init(&interior, tmpl_attr_extent_t, entry); - RDEBUG2("Creating subrequest (%s)", child->name); - log_request_pair_list(L_DBG_LVL_1, request, NULL, &child->request_pairs, NULL); + /* + * Find out what we need to build and build it + */ + if ((tmpl_extents_find(state, &leaf, &interior, request, gext->dst) < 0) || + (tmpl_extents_build_to_leaf(&leaf, &interior, gext->dst) < 0)) { + RPDEBUG("Discarding subrequest attributes - Failed allocating groups"); + fr_dlist_talloc_free(&leaf); + fr_dlist_talloc_free(&interior); + *p_result = RLM_MODULE_FAIL; + return UNLANG_ACTION_CALCULATE_RESULT; + } + while ((extent = fr_dlist_tail(&leaf))) { + fr_pair_list_copy(extent->list_ctx, extent->list, &child->reply_pairs); + fr_dlist_talloc_free_tail(&leaf); + } + } - frame->process = unlang_subrequest_process; - return unlang_subrequest_process(p_result, request, frame); + unlang_subrequest_free(&child); + return UNLANG_ACTION_CALCULATE_RESULT; } - -static unlang_action_t unlang_subrequest_state_init(rlm_rcode_t *p_result, request_t *request, - unlang_stack_frame_t *frame) +static unlang_action_t unlang_subrequest_parent_init(rlm_rcode_t *p_result, request_t *request, + unlang_stack_frame_t *frame) { unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t); request_t *child; - rlm_rcode_t rcode; fr_pair_t *vp; @@ -255,7 +159,6 @@ static unlang_action_t unlang_subrequest_state_init(rlm_rcode_t *p_result, reque * Set the packet type. */ MEM(vp = fr_pair_afrom_da(child->request_ctx, gext->attr_packet_type)); - if (gext->type_enum) { child->packet->code = vp->vp_uint32 = gext->type_enum->value->vb_uint32; } else { @@ -317,25 +220,20 @@ static unlang_action_t unlang_subrequest_state_init(rlm_rcode_t *p_result, reque } } + /* + * Setup the child so it'll inform us when + * it resumes, or if it detaches. + */ + if (unlang_subrequest_child_push_resume(child, state) < 0) goto fail; + /* * Push the first instruction the child's * going to run. */ if (unlang_interpret_push(child, g->children, frame->result, - UNLANG_NEXT_SIBLING, UNLANG_TOP_FRAME) < 0) goto fail; + UNLANG_NEXT_SIBLING, UNLANG_SUB_FRAME) < 0) goto fail; - /* - * Probably not a great idea to set state->p_result to - * p_result, as it could be a pointer to an rlm_rcode_t - * somewhere on the stack which could be invalidated - * between unlang_subrequest being called and - * unlang_subrequest_resume being called. - * - * ...so we just set it to NULL and interpret - * that as use the p_result that was passed - * in to the currently executing function. - */ - state->p_result = NULL; + state->p_result = p_result; state->free_child = true; state->detachable = true; @@ -347,123 +245,9 @@ static unlang_action_t unlang_subrequest_state_init(rlm_rcode_t *p_result, reque state->session.unique_ptr = frame->instruction; state->session.unique_int = 0; - frame->process = unlang_subrequest_start; - return unlang_subrequest_start(p_result, request, frame); -} + frame->process = unlang_subrequest_parent_resume; -/** Initialize a detached child - * - * Detach it from the parent, set up it's lifetime, and mark it as - * runnable. - */ -int unlang_detached_child_init(request_t *request) -{ - fr_pair_t *vp; - - if (request_detach(request) < 0) { - ERROR("Failed detaching child"); - return -1; - } - - /* - * Set Request Lifetime - */ - vp = fr_pair_find_by_da(&request->control_pairs, request_attr_request_lifetime); - if (!vp || (vp->vp_uint32 > 0)) { - fr_time_delta_t when = 0; - const fr_event_timer_t **ev_p; - - if (!vp) { - when += fr_time_delta_from_sec(30); /* default to 30s if not set */ - - } else if (vp->vp_uint32 > 3600) { - RWDEBUG("Request-Timeout can be no more than 3600 seconds"); - when += fr_time_delta_from_sec(3600); - - } else if (vp->vp_uint32 < 5) { - RWDEBUG("Request-Timeout can be no less than 5 seconds"); - when += fr_time_delta_from_sec(5); - - } else { - when += fr_time_delta_from_sec(vp->vp_uint32); - } - - ev_p = talloc_size(request, sizeof(*ev_p)); - memset(ev_p, 0, sizeof(*ev_p)); - - (void) fr_event_timer_in(request, request->el, ev_p, when, unlang_max_request_time, request); - } - - /* - * Mark the child as runnable. - */ - fr_assert(request->parent == NULL); - unlang_interpret_child_init(request); - - return 0; -} - -static unlang_action_t unlang_detach(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame) -{ - unlang_frame_state_detach_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_detach_t); - unlang_stack_frame_t *parent_frame; - unlang_frame_state_subrequest_t *parent_state; - - /* - * We've detached, yielded, and now are continuing - * processing. There's nothing more to do, so just - * continue. - */ - if (!request->parent) { - *p_result = state->rcode; - return UNLANG_ACTION_CALCULATE_RESULT; - } - - /* - * First time through, detach from the parent. - */ - RDEBUG2("detach"); - - fr_assert(request->parent != NULL); - - /* - * Get the PARENT's stack. - */ - parent_frame = frame_current(request->parent); - parent_state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t); - - if (!parent_state->detachable) { - RWDEBUG("Ignoring 'detach' as the request is not detachable"); - return UNLANG_ACTION_CALCULATE_RESULT; - } - - /* - * If we can't detach the child OR we can't insert it - * into the backlog, stop processing it. - */ - if (unlang_detached_child_init(request) < 0) { - return UNLANG_ACTION_STOP_PROCESSING; - } - - /* - * The parent frame no longer has a child, and therefore - * can't be signaled. - */ - fr_assert(parent_state->child == request); - parent_state->child = NULL; - parent_frame->signal = NULL; - - /* - * Pass through whatever the previous instruction had as - * the result. - */ - state->rcode = *p_result; - - /* - * Yield to the parent, who will discover that there's no - * child, and return. - */ - return UNLANG_ACTION_YIELD; + return unlang_subrequest_child_run(child); /* returns UNLANG_ACTION_YIELD */ } /** Free a child request, detaching it from its parent and freeing allocated memory @@ -477,126 +261,27 @@ void unlang_subrequest_free(request_t **child) *child = NULL; } -static unlang_group_t *subrequest_instruction; - -/** Push a pre-existing child back onto the stack as a subrequest - * - * The child *MUST* have been allocated with unlang_io_subrequest_alloc, or something - * that calls it. - * Instruction *MUST* belong to the same virtual server as is set in the child. - * - * After the child is no longer required it *MUST* be freed with #unlang_subrequest_free. - * It's not enough to free it with talloc_free. +/** Initialise subrequest ops * - * @param[in] out Where to write the result of the subrequest. - * @param[in] child to push. - * @param[in] session control values. Whether we restore/store session info. - * @param[in] top_frame Set to UNLANG_TOP_FRAME if the interpreter should return. - * Set to UNLANG_SUB_FRAME if the interprer should continue. - * @return - * - 0 on success. - * - -1 on failure. */ -int unlang_subrequest_push(rlm_rcode_t *out, request_t *child, - unlang_subrequest_session_t const *session, bool top_frame) -{ - unlang_stack_t *stack = child->parent->stack; - unlang_stack_frame_t *frame; - unlang_frame_state_subrequest_t *state; - - /* - * Push a new subrequest frame onto the stack - */ - if (unlang_interpret_push(child->parent, &subrequest_instruction->self, - RLM_MODULE_UNKNOWN, UNLANG_NEXT_STOP, top_frame) < 0) return -1; - - frame = &stack->frame[stack->depth]; - - /* - * Allocate a state for the subrequest - * This lets us override the normal request - * the subrequest instruction would alloc. - */ - state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t); - state->p_result = out; - state->child = child; - state->free_child = false; - state->detachable = false; - if (session) state->session = *session; - - frame->process = unlang_subrequest_start; - - return 0; -} - int unlang_subrequest_op_init(void) { - unlang_subrequest_t *gctx; - - if (fr_dict_autoload(subrequest_dict) < 0) { - PERROR("%s", __FUNCTION__); - return -1; - } - if (fr_dict_attr_autoload(subrequest_dict_attr) < 0) { - PERROR("%s", __FUNCTION__); - return -1; - } - - /* - * Needs to be dynamically allocated - * so that talloc_get_type works - * correctly. - */ - gctx = talloc(NULL, unlang_subrequest_t); - if (!gctx) { - ERROR("%s: Out of memory", __FUNCTION__); - return -1; - } - *gctx = (unlang_subrequest_t){ - .group = { - .self = { - .type = UNLANG_TYPE_SUBREQUEST, - .name = "subrequest", - .debug_name = "subrequest", - .actions = { - [RLM_MODULE_REJECT] = 0, - [RLM_MODULE_FAIL] = MOD_ACTION_RETURN, /* Exit out of nested levels */ - [RLM_MODULE_OK] = 0, - [RLM_MODULE_HANDLED] = 0, - [RLM_MODULE_INVALID] = 0, - [RLM_MODULE_DISALLOW] = 0, - [RLM_MODULE_NOTFOUND] = 0, - [RLM_MODULE_NOOP] = 0, - [RLM_MODULE_UPDATED] = 0 - } - } - } - }; - - subrequest_instruction = unlang_subrequest_to_group(gctx); unlang_register(UNLANG_TYPE_SUBREQUEST, - &(unlang_op_t){ + &(unlang_op_t){ .name = "subrequest", - .interpret = unlang_subrequest_state_init, - .signal = unlang_subrequest_signal, + .interpret = unlang_subrequest_parent_init, + .signal = unlang_subrequest_parent_signal, .debug_braces = true, .frame_state_size = sizeof(unlang_frame_state_subrequest_t), .frame_state_name = "unlang_frame_state_subrequest_t", - }); + }); - unlang_register(UNLANG_TYPE_DETACH, - &(unlang_op_t){ - .name = "detach", - .interpret = unlang_detach, - .frame_state_size = sizeof(unlang_frame_state_detach_t), - .frame_state_name = "unlang_frame_state_detach_t", - }); + if (unlang_subrequest_child_op_init() < 0) return -1; return 0; } void unlang_subrequest_op_free(void) { - talloc_free(subrequest_instruction); - fr_dict_autofree(subrequest_dict); + unlang_subrequest_child_op_free(); } diff --git a/src/lib/unlang/subrequest.h b/src/lib/unlang/subrequest.h index 51b8016e73a..c530286663d 100644 --- a/src/lib/unlang/subrequest.h +++ b/src/lib/unlang/subrequest.h @@ -21,17 +21,29 @@ * @file unlang/subrequest.h * * @copyright 2019 The FreeRADIUS server project + * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) */ #ifdef __cplusplus extern "C" { #endif +#include +#include +#include + typedef struct { bool enable; //!< Whether we should store/restore sessions. void const *unique_ptr; //!< Session unique ptr identifier. int unique_int; //!< Session unique int identifier. } unlang_subrequest_session_t; +unlang_action_t unlang_subrequest_child_run(request_t *child); + +int unlang_subrequest_child_detach(request_t *request); + +int unlang_subrequest_child_push(rlm_rcode_t *out, request_t *child, + unlang_subrequest_session_t const *session, + bool top_frame); #ifdef __cplusplus } #endif diff --git a/src/lib/unlang/subrequest_child.c b/src/lib/unlang/subrequest_child.c new file mode 100644 index 00000000000..2c8dce5b422 --- /dev/null +++ b/src/lib/unlang/subrequest_child.c @@ -0,0 +1,360 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file unlang/subrequest.c + * @brief Unlang "subrequest" and "detach" keyword evaluation. + * + * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) + */ +#include +#include "interpret_priv.h" +#include "subrequest_child_priv.h" +#include "subrequest_priv.h" +#include "unlang_priv.h" + +/** Holds a synthesised instruction that we insert into the parent request + * + */ +static unlang_subrequest_t *subrequest_instruction; + +static fr_dict_t const *dict_freeradius; + +extern fr_dict_autoload_t subrequest_dict[]; +fr_dict_autoload_t subrequest_dict[] = { + { .out = &dict_freeradius, .proto = "freeradius" }, + { NULL } +}; + +static fr_dict_attr_t const *request_attr_request_lifetime; + +extern fr_dict_attr_autoload_t subrequest_dict_attr[]; +fr_dict_attr_autoload_t subrequest_dict_attr[] = { + { .out = &request_attr_request_lifetime, .name = "Request-Lifetime", .type = FR_TYPE_UINT32, .dict = &dict_freeradius }, + { NULL } +}; + +/** Event handler to free a detached child + * + */ +static void unlang_detached_max_request_time(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx) +{ + request_t *request = talloc_get_type_abort(uctx, request_t); + + RDEBUG("Reached Request-Lifetime. Forcibly stopping request"); + + /* + * The request is scheduled and isn't running. Remove it + * from the backlog. + */ + if (unlang_request_is_scheduled(request)) { + fr_assert(request->backlog != NULL); + (void) fr_heap_extract(request->backlog, request); + } + + talloc_free(request); +} + +/** Initialize a detached child + * + * Detach it from the parent, set up it's lifetime, and mark it as + * runnable. + */ +int unlang_subrequest_child_detach(request_t *request) +{ + fr_pair_t *vp; + + if (request_detach(request) < 0) { + ERROR("Failed detaching child"); + return -1; + } + + /* + * Set Request Lifetime + */ + vp = fr_pair_find_by_da(&request->control_pairs, request_attr_request_lifetime); + if (!vp || (vp->vp_uint32 > 0)) { + fr_time_delta_t when = 0; + const fr_event_timer_t **ev_p; + + if (!vp) { + when += fr_time_delta_from_sec(30); /* default to 30s if not set */ + + } else if (vp->vp_uint32 > 3600) { + RWDEBUG("Request-Timeout can be no more than 3600 seconds"); + when += fr_time_delta_from_sec(3600); + + } else if (vp->vp_uint32 < 5) { + RWDEBUG("Request-Timeout can be no less than 5 seconds"); + when += fr_time_delta_from_sec(5); + + } else { + when += fr_time_delta_from_sec(vp->vp_uint32); + } + + ev_p = talloc_size(request, sizeof(*ev_p)); + memset(ev_p, 0, sizeof(*ev_p)); + + if (fr_event_timer_in(request, request->el, ev_p, when, + unlang_detached_max_request_time, request) < 0) { + talloc_free(ev_p); + return -1; + } + } + + return 0; +} + +/** Process a detach signal in the child + * + * This processes any detach signals the child receives + * The child doesn't actually do the detaching + */ +static void unlang_subrequest_child_signal(request_t *request, fr_state_signal_t action, UNUSED void *uctx) +{ + unlang_frame_state_subrequest_t *state; + + /* + * Ignore signals which aren't detach, + * and ignore the signal if we have no parent. + */ + if ((action != FR_SIGNAL_DETACH) || !request->parent) return; + + state = talloc_get_type_abort(frame_current(request->parent), unlang_frame_state_subrequest_t); + + /* + * Ignore signals if the parent didn't want the child + * to be detached. + */ + if (!state->detachable) return; + + /* + * Place child's state back inside the parent + */ + if (state->session.enable) fr_state_store_in_parent(request, + state->session.unique_ptr, + state->session.unique_int); + + if (!fr_cond_assert(unlang_subrequest_child_detach(request) == 0)) { + REDEBUG("Child could not be detached"); + return; + } + + /* + * Indicate to the parent there's no longer a child + */ + state->child = NULL; + + /* + * Tell the parent to resume + */ + unlang_interpret_mark_runnable(request->parent); +} + +/** When the child is done, tell the parent that we've exited. + * + * This is pushed as a frame at the top of the child's stack, so when + * the child is done executing, it runs this to inform the parent + * that its done. + */ +static unlang_action_t unlang_subrequest_child_done(rlm_rcode_t *p_result, + UNUSED int *p_priority, request_t *request, void *uctx) +{ + unlang_frame_state_subrequest_t *state = talloc_get_type_abort(uctx, unlang_frame_state_subrequest_t); + + /* + * Child was detached, nothing more to do. + */ + if (!request->parent) return UNLANG_ACTION_CALCULATE_RESULT; + + /* + * Place child state back inside the parent + */ + if (state->session.enable) fr_state_store_in_parent(request, + state->session.unique_ptr, + state->session.unique_int); + /* + * Record the child's result + */ + if (state->p_result) *state->p_result = *p_result; + + /* + * Resume the parent + */ + unlang_interpret_mark_runnable(request->parent); + + return UNLANG_ACTION_CALCULATE_RESULT; +} + +/** Push a resumption frame onto a child's stack + * + * This is necessary so that the child informs its parent when it's done/detached + * and so that the child responds to detach signals. + */ +int unlang_subrequest_child_push_resume(request_t *child, unlang_frame_state_subrequest_t *state) +{ + /* + * Push a resume frame into the child + */ + if (unlang_interpret_push_function(child, NULL, + unlang_subrequest_child_done, + unlang_subrequest_child_signal, UNLANG_TOP_FRAME, state) < 0) return -1; + + return_point_set(frame_current(child)); /* Stop return going through the resumption frame */ + + return 0; +} + +/** Function to call when the parent is ready to execute the child + * + * @param[in] child to begin executing. + */ +unlang_action_t unlang_subrequest_child_run(request_t *child) +{ + unlang_frame_state_subrequest_t *state; + + /* + * No parent means this is a pre-detached child + * so the parent should continue executing. + */ + if (!child->parent) return UNLANG_ACTION_CALCULATE_RESULT; + + state = talloc_get_type_abort(frame_current(child->parent)->state, unlang_frame_state_subrequest_t); + + /* + * Ensure we restore the session state information + * into the child. + */ + if (state->session.enable) fr_state_restore_to_child(child, + state->session.unique_ptr, + state->session.unique_int); + /* + * Ensures the child is setup correctly and adds + * it into the runnable queue of whatever owns + * the interpreter. + */ + interpret_child_init(child); + + return UNLANG_ACTION_YIELD; +} + +/** Push a pre-existing child back onto the stack as a subrequest + * + * The child *MUST* have been allocated with unlang_io_subrequest_alloc, or something + * that calls it. + * + * After the child is no longer required it *MUST* be freed with #unlang_subrequest_free. + * It's not enough to free it with talloc_free. + * + * This function should be called _before_ pushing any additional frames onto the child's + * stack for it to execute. + * + * @param[in] out Where to write the result of the subrequest. + * @param[in] child to push. + * @param[in] session control values. Whether we restore/store session info. + * @param[in] top_frame Set to UNLANG_TOP_FRAME if the interpreter should return. + * Set to UNLANG_SUB_FRAME if the interprer should continue. + * @return + * - 0 on success. + * - -1 on failure. + */ +int unlang_subrequest_child_push(rlm_rcode_t *out, request_t *child, + unlang_subrequest_session_t const *session, + bool top_frame) +{ + unlang_frame_state_subrequest_t *state; + + /* + * Push a new subrequest frame onto the stack + * + * This allocates memory for the frame state + * which we fill in below. + */ + if (unlang_interpret_push(child->parent, &subrequest_instruction->group.self, + RLM_MODULE_UNKNOWN, UNLANG_NEXT_STOP, top_frame) < 0) { + return -1; + } + + /* + * Setup the state for the subrequest + */ + state = talloc_get_type_abort(frame_current(child->parent)->state, unlang_frame_state_subrequest_t); + state->p_result = out; + state->child = child; + state->session = *session; + + /* + * Push a resumption frame onto the stack + * so the child calls its parent when it's + * complete. + */ + if (unlang_subrequest_child_push_resume(child, state) < 0) return -1; + + return 0; +} + +int unlang_subrequest_child_op_init(void) +{ + if (fr_dict_autoload(subrequest_dict) < 0) { + PERROR("%s", __FUNCTION__); + return -1; + } + if (fr_dict_attr_autoload(subrequest_dict_attr) < 0) { + PERROR("%s", __FUNCTION__); + return -1; + } + + /* + * Needs to be dynamically allocated + * so that talloc_get_type works + * correctly. + */ + subrequest_instruction = talloc(NULL, unlang_subrequest_t); + if (!subrequest_instruction) { + ERROR("%s: Out of memory", __FUNCTION__); + return -1; + } + *subrequest_instruction = (unlang_subrequest_t){ + .group = { + .self = { + .type = UNLANG_TYPE_SUBREQUEST, + .name = "subrequest", + .debug_name = "subrequest", + .actions = { + [RLM_MODULE_REJECT] = 0, + [RLM_MODULE_FAIL] = 0, + [RLM_MODULE_OK] = 0, + [RLM_MODULE_HANDLED] = 0, + [RLM_MODULE_INVALID] = 0, + [RLM_MODULE_DISALLOW] = 0, + [RLM_MODULE_NOTFOUND] = 0, + [RLM_MODULE_NOOP] = 0, + [RLM_MODULE_UPDATED] = 0 + } + } + } + }; + + return 0; +} + +void unlang_subrequest_child_op_free(void) +{ + fr_dict_autofree(subrequest_dict); + talloc_free(subrequest_instruction); +} diff --git a/src/lib/unlang/subrequest_child_priv.h b/src/lib/unlang/subrequest_child_priv.h new file mode 100644 index 00000000000..602383337f7 --- /dev/null +++ b/src/lib/unlang/subrequest_child_priv.h @@ -0,0 +1,40 @@ +#pragma once +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * $Id$ + * + * @file unlang/subrequest_child_priv.h + * + * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) + */ +#include +#include "subrequest_priv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int unlang_subrequest_child_push_resume(request_t *child, unlang_frame_state_subrequest_t *state); + +int unlang_subrequest_child_op_init(void); + +void unlang_subrequest_child_op_free(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/lib/unlang/subrequest_priv.h b/src/lib/unlang/subrequest_priv.h index 9bc62ff0670..ebc16fd8b76 100644 --- a/src/lib/unlang/subrequest_priv.h +++ b/src/lib/unlang/subrequest_priv.h @@ -28,6 +28,7 @@ extern "C" { #include #include +#include "unlang_priv.h" typedef struct { unlang_group_t group; @@ -44,6 +45,18 @@ typedef struct { ///< if the packet-type is static. } unlang_subrequest_t; +/** Parameters for initialising the subrequest (parent's frame state) + * + */ +typedef struct { + rlm_rcode_t *p_result; //!< Where to store the result. + request_t *child; //!< Pre-allocated child request. + bool free_child; //!< Whether we should free the child after + ///< it completes. + bool detachable; //!< Whether the request can be detached. + unlang_subrequest_session_t session; //!< Session configuration. +} unlang_frame_state_subrequest_t; + /** Cast a group structure to the subrequest keyword extension * */ @@ -62,11 +75,11 @@ static inline unlang_group_t *unlang_subrequest_to_group(unlang_subrequest_t *su void unlang_subrequest_free(request_t **child); -int unlang_subrequest_push(rlm_rcode_t *out, request_t *child, +int unlang_subrequest_child_push(rlm_rcode_t *out, request_t *child, unlang_subrequest_session_t const *session, bool top_frame) CC_HINT(warn_unused_result); -int unlang_detached_child_init(request_t *request); +int unlang_subrequest_detach_child(request_t *request); #ifdef __cplusplus } diff --git a/src/lib/unlang/unlang_priv.h b/src/lib/unlang/unlang_priv.h index a4818f98fa5..376c31128bf 100644 --- a/src/lib/unlang/unlang_priv.h +++ b/src/lib/unlang/unlang_priv.h @@ -512,6 +512,8 @@ int unlang_subrequest_op_init(void); void unlang_subrequest_op_free(void); +void unlang_detach_init(void); + void unlang_switch_init(void); void unlang_tmpl_init(void); diff --git a/src/modules/rlm_eap/rlm_eap.c b/src/modules/rlm_eap/rlm_eap.c index b3b3f32331b..d4d110d64a8 100644 --- a/src/modules/rlm_eap/rlm_eap.c +++ b/src/modules/rlm_eap/rlm_eap.c @@ -351,7 +351,6 @@ static void mod_authenticate_cancel(UNUSED module_ctx_t const *mctx, request_t * eap_session = talloc_get_type_abort(rctx, eap_session_t); - (void)fr_cond_assert(request_detach(eap_session->subrequest) == 0); TALLOC_FREE(eap_session->subrequest); /* @@ -382,7 +381,6 @@ static unlang_action_t mod_authenticate_result(rlm_rcode_t *p_result, UNUSED mod /* * Cleanup the subrequest */ - (void)fr_cond_assert(request_detach(eap_session->subrequest) == 0); TALLOC_FREE(eap_session->subrequest); /* @@ -625,13 +623,36 @@ static unlang_action_t eap_method_select(rlm_rcode_t *p_result, module_ctx_t con } /* - * Push the submodule into the child's stack + * Push a resumption frame for the parent + * This will get executed when the child is + * done (after the subrequest frame in the + * parent gets popped). */ - if (unlang_module_push(NULL, /* rcode should bubble up and be returned by yield_to_subrequest */ - eap_session->subrequest, method->submodule_inst, eap_session->process, true) < 0) { + (void)unlang_module_yield(request, mod_authenticate_result_async, mod_authenticate_cancel, eap_session); + + /* + * This sets up a subrequest frame in the parent + * and a resumption frame in the child. + * + * This must be done before pushing frames onto + * the child's stack. + */ + if (unlang_subrequest_child_push(&eap_session->submodule_rcode, eap_session->subrequest, + &(unlang_subrequest_session_t){ .enable = true, .unique_ptr = eap_session }, + UNLANG_SUB_FRAME) < 0) { + child_fail: + unlang_interpet_frame_discard(request); /* Ensure the yield frame doesn't stick around */ goto fail; } + /* + * Push the EAP submodule into the child's stack + */ + if (unlang_module_push(NULL, /* rcode should bubble up and be set in eap_session->submodule_rcode */ + eap_session->subrequest, method->submodule_inst, eap_session->process, true) < 0) { + goto child_fail; + } + if (eap_session->identity) { fr_pair_t *identity; @@ -653,13 +674,7 @@ static unlang_action_t eap_method_select(rlm_rcode_t *p_result, module_ctx_t con type_vp->vp_uint32 = eap_session->type; } - /* - * Yield to the subrequest, and start executing it - */ - return unlang_module_yield_to_subrequest(&eap_session->submodule_rcode, eap_session->subrequest, - mod_authenticate_result_async, mod_authenticate_cancel, - &(unlang_subrequest_session_t){ .enable = true, .unique_ptr = eap_session }, - eap_session); + return unlang_subrequest_child_run(eap_session->subrequest); } static unlang_action_t mod_authenticate(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)