The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
rlm_delay.c
Go to the documentation of this file.
1 /*
2  * This program is is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or (at
5  * your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: 1025165c01b4bc477061ed609fcf6def0004e1a1 $
19  * @file rlm_delay.c
20  * @brief Add an artificial delay to requests.
21  *
22  * @copyright 2016 The FreeRADIUS server project
23  * @copyright 2016 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24  */
25 RCSID("$Id: 1025165c01b4bc477061ed609fcf6def0004e1a1 $")
26 
27 #include <freeradius-devel/server/base.h>
28 #include <freeradius-devel/server/module_rlm.h>
29 #include <freeradius-devel/util/debug.h>
30 #include <freeradius-devel/server/map_proc.h>
31 #include <freeradius-devel/util/time.h>
32 #include <freeradius-devel/unlang/xlat_func.h>
33 
34 typedef struct {
35  tmpl_t *delay; //!< How long we delay for.
36  bool relative; //!< Whether the delay is relative to the start of request processing.
37  bool force_reschedule; //!< Whether we should force rescheduling of the request.
38 } rlm_delay_t;
39 
40 /*
41  * A mapping of configuration file names to internal variables.
42  */
43 static const conf_parser_t module_config[] = {
44  { FR_CONF_OFFSET("delay", rlm_delay_t, delay) },
45  { FR_CONF_OFFSET("relative", rlm_delay_t, relative), .dflt = "no" },
46  { FR_CONF_OFFSET("force_reschedule", rlm_delay_t, force_reschedule), .dflt = "no" },
48 };
49 
50 /** Called when the timeout has expired
51  *
52  * Marks the request as resumable, and prints the delayed delay time.
53  */
54 static void _delay_done(module_ctx_t const *mctx, request_t *request, fr_time_t fired)
55 {
56  fr_time_t *yielded = talloc_get_type_abort(mctx->rctx, fr_time_t);
57 
58  RDEBUG2("Delay done");
59 
60  /*
61  * timeout should never be *before* the scheduled time,
62  * if it is, something is very broken.
63  */
64  if (!fr_cond_assert(fr_time_gteq(fired, *yielded))) REDEBUG("Unexpected resume time");
65 
67 }
68 
69 static void _xlat_delay_done(xlat_ctx_t const *xctx, request_t *request, fr_time_t fired)
70 {
71  fr_time_t *yielded = talloc_get_type_abort(xctx->rctx, fr_time_t);
72 
73  RDEBUG2("Delay done");
74 
75  /*
76  * timeout should never be *before* the scheduled time,
77  * if it is, something is very broken.
78  */
79  if (!fr_cond_assert(fr_time_gt(fired, *yielded))) REDEBUG("Unexpected resume time");
80 
82 }
83 
84 static int delay_add(request_t *request, fr_time_t *resume_at, fr_time_t now,
85  fr_time_delta_t delay, bool force_reschedule, bool relative)
86 {
87  /*
88  * Delay is zero (and reschedule is not forced)
89  */
90  if (!force_reschedule && !fr_time_delta_ispos(delay)) return 1;
91 
92  /*
93  * Process the delay relative to the start of packet processing
94  */
95  if (relative) {
96  *resume_at = fr_time_add(request->packet->timestamp, delay);
97  } else {
98  *resume_at = fr_time_add(now, delay);
99  }
100 
101  /*
102  * If resume_at is in the past (and reschedule is not forced), just return noop
103  */
104  if (!force_reschedule && fr_time_lteq(*resume_at, now)) return 1;
105 
106  if (fr_time_gt(*resume_at, now)) {
107  RDEBUG2("Delaying request by ~%pVs", fr_box_time_delta(fr_time_sub(*resume_at, now)));
108  } else {
109  RDEBUG2("Rescheduling request");
110  }
111 
112  return 0;
113 }
114 
115 /** Called resume_at the delay is complete, and we're running from the interpreter
116  *
117  */
118 static unlang_action_t mod_delay_return(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
119 {
120  fr_time_t *yielded = talloc_get_type_abort(mctx->rctx, fr_time_t);
121 
122  /*
123  * Print how long the delay *really* was.
124  */
125  RDEBUG3("Request delayed by %pV", fr_box_time_delta(fr_time_sub(fr_time(), *yielded)));
126  talloc_free(yielded);
127 
129 }
130 
131 static void mod_delay_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
132 {
133  RDEBUG2("Cancelling delay");
134 
135  (void) unlang_module_timeout_delete(request, mctx->rctx);
136 }
137 
138 static unlang_action_t CC_HINT(nonnull) mod_delay(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
139 {
141  fr_time_delta_t delay;
142  fr_time_t resume_at, *yielded_at;
143 
144  if (inst->delay) {
145  if (tmpl_aexpand_type(request, &delay, FR_TYPE_TIME_DELTA,
146  request, inst->delay, NULL, NULL) < 0) {
147  RPEDEBUG("Failed parsing %s as delay time", inst->delay->name);
149  }
150  } else {
151  delay = fr_time_delta_wrap(0);
152  }
153 
154  /*
155  * Record the time that we yielded the request
156  */
157  MEM(yielded_at = talloc(request, fr_time_t));
158  *yielded_at = fr_time();
159 
160  /*
161  * Setup the delay for this request
162  */
163  if (delay_add(request, &resume_at, *yielded_at, delay,
164  inst->force_reschedule, inst->relative) != 0) {
166  }
167 
168  RDEBUG3("Current time %pVs, resume time %pVs",
169  fr_box_time(*yielded_at), fr_box_time(resume_at));
170 
171  if (unlang_module_timeout_add(request, _delay_done, yielded_at, resume_at) < 0) {
172  RPEDEBUG("Adding event failed");
174  }
175 
176  return unlang_module_yield(request, mod_delay_return, mod_delay_cancel, ~FR_SIGNAL_CANCEL, yielded_at);
177 }
178 
180  xlat_ctx_t const *xctx,
181  request_t *request, UNUSED fr_value_box_list_t *in)
182 {
183  fr_time_t *yielded_at = talloc_get_type_abort(xctx->rctx, fr_time_t);
184  fr_time_delta_t delayed;
185  fr_value_box_t *vb;
186 
187  delayed = fr_time_sub(fr_time(), *yielded_at);
188  talloc_free(yielded_at);
189 
190  MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_TIME_DELTA, NULL));
191  vb->vb_time_delta = delayed;
192 
193  RDEBUG3("Request delayed by %pVs", vb);
194 
195  fr_dcursor_insert(out, vb);
196 
197  return XLAT_ACTION_DONE;
198 }
199 
200 static void xlat_delay_cancel(UNUSED xlat_ctx_t const *xctx, request_t *request, UNUSED fr_signal_t action)
201 {
202  RDEBUG2("Cancelling delay");
203 }
204 
206  { .single = true, .type = FR_TYPE_TIME_DELTA },
208 };
209 
210 /** Xlat to delay the request
211  *
212  * Example (delay 2 seconds):
213 @verbatim
214 %delay(2)
215 @endverbatim
216  *
217  * @ingroup xlat_functions
218  */
220  xlat_ctx_t const *xctx,
221  request_t *request, fr_value_box_list_t *in)
222 {
223  rlm_delay_t const *inst = talloc_get_type_abort(xctx->mctx->inst->data, rlm_delay_t);
224  fr_time_t resume_at, *yielded_at;
225  fr_value_box_t *delay = fr_value_box_list_head(in);
226 
227  /*
228  * Record the time that we yielded the request
229  */
230  MEM(yielded_at = talloc(request, fr_time_t));
231  *yielded_at = fr_time();
232 
233  /*
234  * If there's no input delay, just yield and
235  * immediately re-enqueue the request.
236  * This is very useful for testing.
237  */
238  if (!delay) {
239  if (!fr_cond_assert(delay_add(request, &resume_at, *yielded_at, fr_time_delta_wrap(0), true, true) == 0)) {
240  return XLAT_ACTION_FAIL;
241  }
242  goto yield;
243  }
244 
245  if (delay_add(request, &resume_at, *yielded_at, delay->vb_time_delta,
246  inst->force_reschedule, inst->relative) != 0) {
247  RDEBUG2("Not adding delay");
248  talloc_free(yielded_at);
249  return XLAT_ACTION_DONE;
250  }
251 
252 yield:
253  RDEBUG3("Current time %pVs, resume time %pVs", fr_box_time(*yielded_at), fr_box_time(resume_at));
254 
255  if (unlang_xlat_timeout_add(request, _xlat_delay_done, yielded_at, resume_at) < 0) {
256  RPEDEBUG("Adding event failed");
257  return XLAT_ACTION_FAIL;
258  }
259 
260  return unlang_xlat_yield(request, xlat_delay_resume, xlat_delay_cancel, ~FR_SIGNAL_CANCEL, yielded_at);
261 }
262 
263 static int mod_bootstrap(module_inst_ctx_t const *mctx)
264 {
265  rlm_delay_t *inst = talloc_get_type_abort(mctx->inst->data, rlm_delay_t);
266  xlat_t *xlat;
267 
270  return 0;
271 }
272 
273 extern module_rlm_t rlm_delay;
275  .common = {
276  .magic = MODULE_MAGIC_INIT,
277  .name = "delay",
278  .flags = 0,
279  .inst_size = sizeof(rlm_delay_t),
281  .bootstrap = mod_bootstrap
282  },
283  .method_names = (module_method_name_t[]){
284  { .name1 = CF_IDENT_ANY, .name2 = CF_IDENT_ANY, .method = mod_delay },
286  }
287 };
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition: action.h:35
#define RCSID(id)
Definition: build.h:444
#define UNUSED
Definition: build.h:313
#define CONF_PARSER_TERMINATOR
Definition: cf_parse.h:626
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition: cf_parse.h:268
Defines a CONF_PAIR to C data type mapping.
Definition: cf_parse.h:563
#define CF_IDENT_ANY
Definition: cf_util.h:78
static int fr_dcursor_insert(fr_dcursor_t *cursor, void *v)
Insert directly after the current item.
Definition: dcursor.h:434
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition: debug.h:137
static fr_slen_t in
Definition: dict.h:645
void *_CONST data
Module instance's parsed configuration.
Definition: dl_module.h:165
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition: dl_module.h:65
static xlat_action_t xlat_delay(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
Xlat to delay the request.
Definition: rlm_delay.c:219
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
Definition: interpret.c:1340
#define RDEBUG3(fmt,...)
Definition: log.h:343
#define RPEDEBUG(fmt,...)
Definition: log.h:376
talloc_free(reap)
@ FR_TYPE_TIME_DELTA
A period of time measured in nanoseconds.
Definition: merged_model.c:113
void * rctx
Resume ctx that a module previously set.
Definition: module_ctx.h:45
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:52
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:42
Temporary structure to hold arguments for module calls.
Definition: module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition: module_ctx.h:51
Specifies a module method identifier.
Definition: module_method.c:36
module_t common
Common fields presented by all modules.
Definition: module_rlm.h:37
static const conf_parser_t config[]
Definition: base.c:188
#define REDEBUG(fmt,...)
Definition: radclient.h:52
#define RDEBUG2(fmt,...)
Definition: radclient.h:54
#define RETURN_MODULE_NOOP
Definition: rcode.h:62
#define RETURN_MODULE_OK
Definition: rcode.h:57
rlm_rcode_t
Return codes indicating the result of the module call.
Definition: rcode.h:40
tmpl_t * delay
How long we delay for.
Definition: rlm_delay.c:35
static unlang_action_t mod_delay_return(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Called resume_at the delay is complete, and we're running from the interpreter.
Definition: rlm_delay.c:118
static void mod_delay_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
Definition: rlm_delay.c:131
bool relative
Whether the delay is relative to the start of request processing.
Definition: rlm_delay.c:36
static int delay_add(request_t *request, fr_time_t *resume_at, fr_time_t now, fr_time_delta_t delay, bool force_reschedule, bool relative)
Definition: rlm_delay.c:84
static int mod_bootstrap(module_inst_ctx_t const *mctx)
Definition: rlm_delay.c:263
bool force_reschedule
Whether we should force rescheduling of the request.
Definition: rlm_delay.c:37
static void _xlat_delay_done(xlat_ctx_t const *xctx, request_t *request, fr_time_t fired)
Definition: rlm_delay.c:69
static void _delay_done(module_ctx_t const *mctx, request_t *request, fr_time_t fired)
Called when the timeout has expired.
Definition: rlm_delay.c:54
module_rlm_t rlm_delay
Definition: rlm_delay.c:274
static void xlat_delay_cancel(UNUSED xlat_ctx_t const *xctx, request_t *request, UNUSED fr_signal_t action)
Definition: rlm_delay.c:200
static unlang_action_t mod_delay(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_delay.c:138
static xlat_action_t xlat_delay_resume(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, UNUSED fr_value_box_list_t *in)
Definition: rlm_delay.c:179
static xlat_arg_parser_t const xlat_delay_args[]
Definition: rlm_delay.c:205
static const conf_parser_t module_config[]
Definition: rlm_delay.c:43
#define MODULE_NAME_TERMINATOR
Definition: module.h:135
#define tmpl_aexpand_type(_ctx, _out, _type, _request, _vpt, _escape, _escape_ctx)
Expand a tmpl to a C type, allocing a new buffer to hold the string.
Definition: tmpl.h:1063
fr_signal_t
Definition: signal.h:48
int unlang_module_timeout_delete(request_t *request, void const *ctx)
Delete a previously set timeout callback.
Definition: module.c:176
unlang_action_t unlang_module_yield(request_t *request, module_method_t resume, unlang_module_signal_t signal, fr_signal_t sigmask, void *rctx)
Yield a request back to the interpreter from within a module.
Definition: module.c:575
int unlang_module_timeout_add(request_t *request, unlang_module_timeout_t callback, void const *rctx, fr_time_t when)
Set a timeout for the request.
Definition: module.c:128
RETURN_MODULE_FAIL
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
eap_aka_sim_process_conf_t * inst
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition: state_test.c:8
#define talloc_get_type_abort_const
Definition: talloc.h:270
#define fr_time_gteq(_a, _b)
Definition: time.h:238
#define fr_time_delta_wrap(_time)
Definition: time.h:152
#define fr_time_lteq(_a, _b)
Definition: time.h:240
#define fr_time_delta_ispos(_a)
Definition: time.h:288
#define fr_time_add(_a, _b)
Add a time/time delta together.
Definition: time.h:196
#define fr_time_gt(_a, _b)
Definition: time.h:237
#define fr_time_sub(_a, _b)
Subtract one time from another.
Definition: time.h:229
A time delta, a difference in time measured in nanoseconds.
Definition: time.h:80
"server local" time.
Definition: time.h:69
xlat_action_t unlang_xlat_yield(request_t *request, xlat_func_t resume, xlat_func_signal_t signal, fr_signal_t sigmask, void *rctx)
Yield a request back to the interpreter from within a module.
Definition: xlat.c:561
int unlang_xlat_timeout_add(request_t *request, fr_unlang_xlat_timeout_t callback, void const *rctx, fr_time_t when)
Add a timeout for an xlat handler.
Definition: xlat.c:140
bool single
Argument must only contain a single box.
Definition: xlat.h:148
#define XLAT_ARG_PARSER_TERMINATOR
Definition: xlat.h:166
xlat_action_t
Definition: xlat.h:35
@ XLAT_ACTION_FAIL
An xlat function failed.
Definition: xlat.h:42
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
Definition: xlat.h:41
Definition for a single argument consumend by an xlat function.
Definition: xlat.h:145
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition: value.h:608
#define fr_box_time_delta(_val)
Definition: value.h:336
int nonnull(2, 5))
#define fr_box_time(_val)
Definition: value.h:319
static size_t char ** out
Definition: value.h:984
void * rctx
Resume context.
Definition: xlat_ctx.h:47
module_ctx_t const * mctx
Synthesised module calling ctx.
Definition: xlat_ctx.h:45
An xlat calling ctx.
Definition: xlat_ctx.h:42
int xlat_func_args_set(xlat_t *x, xlat_arg_parser_t const args[])
Register the arguments of an xlat.
Definition: xlat_func.c:360
xlat_t * xlat_func_register_module(TALLOC_CTX *ctx, module_inst_ctx_t const *mctx, char const *name, xlat_func_t func, fr_type_t return_type)
Register an xlat function for a module.
Definition: xlat_func.c:274