The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
process.h
Go to the documentation of this file.
1#pragma once
2/*
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
16 */
17
18/**
19 * $Id: 5614e98f8ed13489563bf44dc43da8a246e4a884 $
20 *
21 * @file src/lib/server/process.h
22 * @brief Declarations for functions which process packet state machines
23 *
24 * This is a convenience header to simplify defining packet processing state machines.
25 *
26 * The following macros must be defined before this header is included:
27 *
28 * - PROCESS_INST the type of structure that holds instance data for the process module.
29 * - PROCESS_PACKET_TYPE an enum, or generic type (uint32) that can hold
30 * all valid packet types.
31 * - PROCESS_PACKET_CODE_VALID the name of a macro or function which accepts one argument
32 * and evaluates to true if the packet code is valid.
33 *
34 * The following macros may (optionally) be defined before this header is included:
35 *
36 * - PROCESS_CODE_MAX the highest valid protocol packet code + 1.
37 * - PROCESS_CODE_DO_NOT_RESPOND The packet code that's used to indicate that no response
38 * should be sent.
39 * - PROCESS_STATE_EXTRA_FIELDS extra fields to add to the fr_process_state_t structure.
40 *
41 * @copyright 2021 The FreeRADIUS server project
42 * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
43 */
44#ifdef __cplusplus
45extern "C" {
46#endif
47
48#include <freeradius-devel/server/virtual_servers.h>
49#include <freeradius-devel/server/pair.h>
50#include <freeradius-devel/server/process_types.h>
51
52/** Trace each state function as it's entered
53 */
54#ifndef NDEBUG
55# define PROCESS_TRACE RDEBUG3("Entered state %s", __FUNCTION__)
56#else
57# define PROCESS_TRACE
58#endif
59
60/** Convenience macro for providing CONF_SECTION offsets in section compilation arrays
61 *
62 */
63#ifndef PROCESS_INST
64# error PROCESS_INST must be defined to the C type of the process instance e.g. process_bfd_t
65#endif
66
67#ifndef PROCESS_RCTX
68# define PROCESS_RCTX process_rctx_t
69#endif
70
71#if defined(PROCESS_RCTX) && defined(PROCESS_RCTX_EXTRA_FIELDS)
72# error Only one of PROCESS_RCTX (the type of the rctx struct) OR PROCESS_RCTX_EXTRA_FIELDS (extra fields for the common rctx struct) can be defined.
73#endif
74
75#ifndef PROCESS_RCTX_RESULT
76# define PROCESS_RCTX_RESULT result
77#endif
78
79#define PROCESS_CONF_OFFSET(_x) offsetof(PROCESS_INST, sections._x)
80#define RESULT_UNUSED UNUSED
81
82#if defined(PROCESS_INST) && defined(PROCESS_PACKET_TYPE) && defined(PROCESS_PACKET_CODE_VALID)
83typedef PROCESS_PACKET_TYPE fr_process_rcode_t[RLM_MODULE_NUMCODES];
84
85#ifndef PROCESS_STATE_EXTRA_FIELDS
86# define PROCESS_STATE_EXTRA_FIELDS
87#endif
88
89#ifndef PROCESS_RCTX_EXTRA_FIELDS
90# define PROCESS_RCTX_EXTRA_FIELDS
91#endif
92/*
93 * Process state machine tables for rcode to packet.
94 */
95typedef struct {
96 PROCESS_PACKET_TYPE packet_type[RLM_MODULE_NUMCODES]; //!< rcode to packet type mapping.
97 PROCESS_PACKET_TYPE default_reply; //!< if not otherwise set
98 size_t section_offset; //!< Where to look in the process instance for
99 ///< a pointer to the section we should execute.
100 rlm_rcode_t default_rcode; //!< Default rcode that's set in the frame we used to
101 ///< evaluate child sections.
102 rlm_rcode_t result_rcode; //!< Result rcode we return if the virtual server is
103 ///< being called using the `call` keyword.
104 module_method_t resume; //!< Function to call after running a recv section.
105
106 /*
107 * Each state has only one "recv" or "send".
108 */
109 union {
110 module_method_t recv; //!< Method to call when receiving this type of packet.
111 module_method_t send; //!< Method to call when sending this type of packet.
112 };
114} fr_process_state_t;
115
116typedef struct {
117 unlang_result_t result; //!< Result of the last section executed.
118 PROCESS_RCTX_EXTRA_FIELDS
119} process_rctx_t;
120
121/*
122 * C doesn't technically support forward declaration of static variables. Until such time as we
123 * rearrange all of the process code, disabling the warnings will have to do.
124 *
125 * A real fix is to provide a header file which contains only the macro definitions for the process state
126 * machine. The process files can include that, then define the function prototypes. Then define their
127 * own process_state[] state machine, then define the functions.
128 */
129#ifdef __clang__
130DIAG_OFF(tentative-definition-compat)
131DIAG_OFF(tentative-definition-incomplete-type)
132#endif
133
134/*
135 * Some protocols have the same packet codes for requests and replies.
136 */
137#ifndef PROCESS_SEND_RECV
138#define process_state_packet process_state
139#define process_state_reply process_state
140static fr_process_state_t const process_state[];
141#else
142static fr_process_state_t const process_state_packet[];
143static fr_process_state_t const process_state_reply[];
144#endif
145
146/*
147 * Process state machine functions
148 */
149#define UPDATE_STATE_CS(_x) \
150do { \
151 state = &process_state_ ## _x[request->_x->code]; \
152 memcpy(&cs, (CONF_SECTION * const *) (((uint8_t const *) &inst->sections) + state->section_offset), sizeof(cs)); \
153} while (0)
154
155#define UPDATE_STATE(_x) state = &process_state_ ## _x [request->_x->code]
156
157#define RECV(_x) static inline unlang_action_t recv_ ## _x(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
158#define SEND(_x) static inline unlang_action_t send_ ## _x(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
159#define SEND_NO_RESULT(_x) static inline unlang_action_t send_ ## _x(UNUSED unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
160#define RESUME(_x) static inline unlang_action_t resume_ ## _x(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
161#define RESUME_FLAG(_x, _p_result_flag, _mctx_flag) static inline unlang_action_t resume_ ## _x(_p_result_flag unlang_result_t *p_result, _mctx_flag module_ctx_t const *mctx, request_t *request)
162
163/** Returns the current rcode then resets it for the next module call
164 *
165 */
166static inline CC_HINT(always_inline) unlang_result_t *process_result_reset(unlang_result_t *p_result, fr_process_state_t const *state)
167{
168 *p_result = UNLANG_RESULT_RCODE(state->default_rcode);
169 return p_result;
170}
171
172#define RESULT_RCODE (((PROCESS_RCTX *)mctx->rctx)->result.rcode)
173#define RESULT_P process_result_reset(&(((PROCESS_RCTX *)mctx->rctx)->result), state)
174
175/** Call a module method with a new rctx
176 *
177 * @note This should be used to add a rctxs when calling the initial recv section.
178 *
179 * @param[out] p_result Pointer to the result code.
180 * @param[in] mctx Module context.
181 * @param[in] request Request.
182 * @param[in] method Method to call.
183 * @param[in] rctx Resume context to use to override the one in the mctx.
184 * @return Result of the method call.
185 */
186static inline CC_HINT(always_inline)
187unlang_action_t process_with_rctx(unlang_result_t *p_result, module_ctx_t const *mctx,
188 request_t *request, module_method_t method, void *rctx)
189{
190 module_ctx_t our_mctx = *mctx;
191 our_mctx.rctx = rctx;
192
193 return method(p_result, &our_mctx, request);
194}
195
196/** Call a named recv function directly
197 */
198#define CALL_RECV(_x) recv_ ## _x(p_result, mctx, request)
199
200/** Call a named recv function directly with a new rctx
201 */
202#define CALL_RECV_RCTX(_x, _rctx) process_with_rctx(p_result, mctx, request, recv_ ## _x, _rctx);
203
204/** Call a named send function directly
205 */
206#define CALL_SEND(_x) send_ ## _x(p_result, mctx, request)
207
208/** Call a named resume function directly
209 */
210#define CALL_RESUME(_x) resume_ ## _x(p_result, mctx, request)
211
212/** Call the send function for the current state
213 */
214#define CALL_SEND_STATE(_state) state->send(p_result, mctx, request)
215
216/** Set the current reply code, and call the send function for that state
217 */
218#define CALL_SEND_TYPE(_x) call_send_type(process_state_reply[(request->reply->code = _x)].send, p_result, mctx, request)
219
220static inline unlang_action_t call_send_type(module_method_t send, \
221 unlang_result_t *p_result, module_ctx_t const *mctx,
222 request_t *request)
223{
224 /*
225 * Stupid hack to stop this being honoured
226 * by send_generic.
227 */
229 return send(p_result, mctx, request);
230}
231
232RECV(generic)
233{
234 CONF_SECTION *cs;
235 fr_process_state_t const *state;
236 PROCESS_INST *inst = mctx->mi->data;
237
239
240 UPDATE_STATE_CS(packet);
241
242 if (!state->recv) {
243 char const *name;
244
246 if (name) {
247 REDEBUG("Invalid packet type (%s)", name);
248 } else {
249 REDEBUG("Invalid packet type (%u)", request->packet->code);
250 }
252 }
253
254
255 if (cs) RDEBUG("Running '%s %s' from file %s", cf_section_name1(cs), cf_section_name2(cs), cf_filename(cs));
256 return unlang_module_yield_to_section(RESULT_P, request,
257 cs, state->default_rcode, state->resume,
258 NULL, 0, mctx->rctx);
259}
260
261RESUME(recv_generic)
262{
263 rlm_rcode_t rcode = RESULT_RCODE;
264 fr_process_state_t const *state;
265
267
269
270 UPDATE_STATE(packet);
271
272 request->reply->code = state->packet_type[rcode];
273 if (!request->reply->code) request->reply->code = state->default_reply;
274#ifdef PROCESS_CODE_DO_NOT_RESPOND
275 if (!request->reply->code) request->reply->code = PROCESS_CODE_DO_NOT_RESPOND;
276
277#endif
278 fr_assert(PROCESS_PACKET_CODE_VALID(request->reply->code));
279
280 UPDATE_STATE(reply);
281 fr_assert(state->send != NULL);
282 return state->send(p_result, mctx, request);
283}
284
285RESUME_FLAG(recv_no_send,UNUSED,UNUSED)
286{
287 rlm_rcode_t rcode = RESULT_RCODE;
288 fr_process_state_t const *state;
289
291
293
294 UPDATE_STATE(packet);
295
296 request->reply->code = state->packet_type[rcode];
297 if (!request->reply->code) request->reply->code = state->default_reply;
298#ifdef PROCESS_CODE_DO_NOT_RESPOND
299 if (!request->reply->code) request->reply->code = PROCESS_CODE_DO_NOT_RESPOND;
300
301#endif
302 fr_assert(request->reply->code != 0);
303
305}
306
307SEND_NO_RESULT(generic)
308{
309 fr_pair_t *vp;
310 CONF_SECTION *cs;
311 fr_process_state_t const *state;
312 PROCESS_INST *inst = mctx->mi->data;
313
315
316#ifndef NDEBUG
317 if (!(
319 (request->reply->code == PROCESS_CODE_DO_NOT_RESPOND) ||
320# endif
321 PROCESS_PACKET_CODE_VALID(request->reply->code))) fr_assert(0);
322#endif
323
324 UPDATE_STATE_CS(reply);
325
326 /*
327 * Allow for over-ride of reply code, IF it's
328 * within range, AND we've pre-compiled the
329 * unlang.
330 *
331 * Add reply->packet-type in case we're
332 * being called via the `call {}` keyword.
333 *
334 * @todo - enforce that this is an allowed reply for the
335 * request.
336 */
338 case 0: /* Does not exist */
339 update_packet_type:
340 vp->vp_uint32 = request->reply->code;
341 break;
342
343 case 1: /* Exists */
344 if (
345#ifdef PROCESS_CODE_MAX
346 (vp->vp_uint32 != PROCESS_CODE_MAX) &&
347#endif
348 PROCESS_PACKET_CODE_VALID(vp->vp_uint32) &&
349 process_state_reply[vp->vp_uint32].send) {
350 request->reply->code = vp->vp_uint32;
351 UPDATE_STATE_CS(reply);
352 break;
353 }
354
355 RWDEBUG("Ignoring invalid packet-type reply.%pP", vp);
356 goto update_packet_type;
357
358 default:
359 MEM(0);
360 }
361
362 if (cs) {
363 RDEBUG("Running '%s %s' from file %s", cf_section_name1(cs), cf_section_name2(cs), cf_filename(cs));
364 } else {
365 char const *name;
366
368 if (name) {
369 RWDEBUG("No 'send %s { ... } section was found.", name);
370 } else {
371 RWDEBUG("No 'send %u { ... } section was found.", request->reply->code);
372 }
373 }
374
375 return unlang_module_yield_to_section(RESULT_P, request,
376 cs, state->default_rcode, state->resume,
377 NULL, 0, mctx->rctx);
378}
379
380RESUME(send_generic)
381{
382 rlm_rcode_t rcode = RESULT_RCODE;
383 CONF_SECTION *cs;
384 fr_process_state_t const *state;
385 PROCESS_INST *inst = mctx->mi->data;
386
388
389#ifndef NDEBUG
390 if (!(
392 (request->reply->code == PROCESS_CODE_DO_NOT_RESPOND) ||
393# endif
394 PROCESS_PACKET_CODE_VALID(request->reply->code))) fr_assert(0);
395#endif
396 /*
397 * If they delete &reply.Packet-Type, tough for them.
398 */
399 UPDATE_STATE_CS(reply);
400
402 switch (state->packet_type[rcode]) {
403 case 0: /* don't change the reply */
404 p_result->rcode = state->result_rcode;
405 break;
406
407 default:
408 /*
409 * If we're in the "do not respond" situation,
410 * then don't change the packet code to something
411 * else. However, if we're in (say) Accept, and
412 * the code says Reject, then go do reject.
413 *
414 * The author of the state machine MUST ensure
415 * that there isn't a loop in the state machine
416 * definitions.
417 */
418 if (
420 (request->reply->code != PROCESS_CODE_DO_NOT_RESPOND) &&
421#endif
422 (state->packet_type[rcode] != request->reply->code)) {
423 char const *old = cf_section_name2(cs);
424
425 request->reply->code = state->packet_type[rcode];
426 UPDATE_STATE_CS(reply);
427
428 RWDEBUG("Failed running 'send %s', changing reply to %s", old, cf_section_name2(cs));
429
430 return unlang_module_yield_to_section(RESULT_P, request,
431 cs, state->default_rcode, state->send,
432 NULL, 0, mctx->rctx);
433 }
434 p_result->rcode = state->result_rcode;
435
436 fr_assert(!state->packet_type[rcode] || (state->packet_type[rcode] == request->reply->code));
437 break;
438
439#ifdef PROCESS_CODE_DO_NOT_RESPOND
441 /*
442 * There might not be send section defined
443 */
444 if (cs) {
445 RDEBUG("The 'send %s' section returned %s - not sending a response",
447 fr_table_str_by_value(rcode_table, rcode, "<INVALID>"));
448 }
449 request->reply->code = PROCESS_CODE_DO_NOT_RESPOND;
450 p_result->rcode = state->result_rcode;
451 break;
452#endif
453 }
454
455
456 request->reply->timestamp = fr_time();
457
458#ifdef PROCESS_CODE_DO_NOT_RESPOND
459 /*
460 * Check for "do not respond".
461 */
462 if (request->reply->code == PROCESS_CODE_DO_NOT_RESPOND) {
463 RDEBUG("Not sending reply to client");
464 p_result->rcode = RLM_MODULE_HANDLED;
466 }
467#endif
468
470}
471
472#ifdef PROCESS_CODE_DYNAMIC_CLIENT
473RESUME_FLAG(new_client_done,,UNUSED)
474{
475 p_result->rcode = RLM_MODULE_OK;
476
477 request->reply->timestamp = fr_time();
478
480}
481
482RESUME(new_client)
483{
484 rlm_rcode_t rcode = RESULT_RCODE;
485 CONF_SECTION *cs;
486 PROCESS_INST const *inst = mctx->mi->data;
487 fr_process_state_t const *state;
488
489 UPDATE_STATE(reply);
490
491 switch (rcode) {
492 case RLM_MODULE_OK:
494 RDEBUG("new client was successful.");
495 cs = inst->sections.add_client;
496 request->reply->code = PROCESS_CODE_DYNAMIC_CLIENT;
497 break;
498
499 default:
500 RDEBUG("new client was denied.");
501 cs = inst->sections.deny_client;
502 request->reply->code = PROCESS_CODE_DO_NOT_RESPOND;
503 break;
504 }
505
506 request->component = NULL;
507 request->module = NULL;
508
509 if (!cs) {
510 p_result->rcode = RLM_MODULE_OK;
511 request->reply->timestamp = fr_time();
513 }
514
515 RDEBUG("Running '%s %s' from file %s", cf_section_name1(cs), cf_section_name2(cs), cf_filename(cs));
516 return unlang_module_yield_to_section(RESULT_P, request,
517 cs, RLM_MODULE_FAIL, resume_new_client_done,
518 NULL, 0, mctx->rctx);
519}
520
521static inline unlang_action_t new_client(UNUSED unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
522{
523 CONF_SECTION *cs;
524 PROCESS_INST const *inst = mctx->mi->data;
525 fr_process_state_t const *state;
526
527 UPDATE_STATE(packet);
528
530 fr_assert(inst->sections.new_client != NULL);
531 cs = inst->sections.new_client;
532
533 RDEBUG("Running '%s %s' from file %s", cf_section_name1(cs), cf_section_name2(cs), cf_filename(cs));
534 return unlang_module_yield_to_section(RESULT_P, request,
535 cs, RLM_MODULE_FAIL, resume_new_client,
536 NULL, 0, mctx->rctx);
537}
538
539#define DYNAMIC_CLIENT_SECTIONS \
540 { \
541 .section = SECTION_NAME("new", "client"), \
542 .actions = &mod_actions_authorize, \
543 .offset = PROCESS_CONF_OFFSET(new_client), \
544 }, \
545 { \
546 .section = SECTION_NAME("add", "client"), \
547 .actions = &mod_actions_authorize, \
548 .offset = PROCESS_CONF_OFFSET(add_client), \
549 }, \
550 { \
551 .section = SECTION_NAME("deny", "client"), \
552 .actions = &mod_actions_authorize, \
553 .offset = PROCESS_CONF_OFFSET(deny_client), \
554 }
555
556#endif /* PROCESS_DYNAMIC_CLIENT */
557
558#endif /* defined(PROCESS_INST) && defined(PROCESS_PACKET_TYPE) && defined(PROCESS_PACKET_CODE_VALID) */
559
560#ifdef __cplusplus
561}
562#endif
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ UNLANG_ACTION_CALCULATE_RESULT
Calculate a new section rlm_rcode_t value.
Definition action.h:37
#define UNUSED
Definition build.h:317
#define DIAG_OFF(_x)
Definition build.h:461
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:101
char const * cf_section_name2(CONF_SECTION const *cs)
Return the second identifier of a CONF_SECTION.
Definition cf_util.c:1184
char const * cf_section_name1(CONF_SECTION const *cs)
Return the second identifier of a CONF_SECTION.
Definition cf_util.c:1170
#define cf_filename(_cf)
Definition cf_util.h:107
#define MEM(x)
Definition debug.h:36
static fr_dict_attr_t const * attr_packet_type
Definition dhcpclient.c:89
char const * fr_dict_enum_name_by_value(fr_dict_attr_t const *da, fr_value_box_t const *value)
Lookup the name of an enum value in a fr_dict_attr_t.
Definition dict_util.c:3692
#define UNLANG_RESULT_RCODE(_x)
Definition interpret.h:140
#define RWDEBUG(fmt,...)
Definition log.h:361
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
void * rctx
Resume ctx that a module previously set.
Definition module_ctx.h:45
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
#define PROCESS_PACKET_TYPE
Definition base.c:62
#define PROCESS_CODE_MAX
Definition base.c:63
static fr_process_state_t const process_state[]
Definition base.c:69
#define PROCESS_PACKET_CODE_VALID
Definition base.c:65
#define PROCESS_INST
Definition base.c:66
#define PROCESS_CODE_DO_NOT_RESPOND
Definition base.c:64
static fr_process_state_t const process_state_reply[]
Definition base.c:195
RESUME_FLAG(recv_bfd, UNUSED,)
Definition base.c:119
static fr_process_state_t const process_state_packet[]
Definition base.c:158
#define PROCESS_CODE_DYNAMIC_CLIENT
Definition base.c:132
#define PROCESS_STATE_EXTRA_FIELDS
Definition base.c:188
RECV(for_any_server)
Validate a solicit/rebind/confirm message.
Definition base.c:402
#define PROCESS_TRACE
Trace each state function as it's entered.
Definition process.h:55
#define fr_assert(_expr)
Definition rad_assert.h:38
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RDEBUG(fmt,...)
Definition radclient.h:53
fr_table_num_sorted_t const rcode_table[]
Definition rcode.c:35
#define RETURN_UNLANG_FAIL
Definition rcode.h:59
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:40
@ RLM_MODULE_OK
The module is OK, continue.
Definition rcode.h:45
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition rcode.h:44
@ RLM_MODULE_UPDATED
OK (pairs modified).
Definition rcode.h:51
@ RLM_MODULE_NUMCODES
How many valid return codes there are.
Definition rcode.h:53
@ RLM_MODULE_HANDLED
The module handled the request, so stop.
Definition rcode.h:46
static char const * name
void * data
Module's instance data.
Definition module.h:291
unlang_action_t(* module_method_t)(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
Module section callback.
Definition module.h:69
#define pair_update_reply(_attr, _da)
Return or allocate a fr_pair_t in the reply list.
Definition pair.h:129
#define pair_delete_reply(_pair_or_da)
Delete a fr_pair_t in the reply list.
Definition pair.h:181
unlang_action_t unlang_module_yield_to_section(unlang_result_t *p_result, request_t *request, CONF_SECTION *subcs, rlm_rcode_t default_rcode, module_method_t resume, unlang_module_signal_t signal, fr_signal_t sigmask, void *rctx)
Definition module.c:236
eap_aka_sim_process_conf_t * inst
#define RESUME(_x)
fr_aka_sim_id_type_t type
fr_pair_t * vp
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition state_test.c:8
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition table.h:772
#define fr_box_uint32(_val)
Definition value.h:335