The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
load_balance.c
Go to the documentation of this file.
1/*
2 * This program 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
5 * (at 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: d0d81beb575e3bcb0f9b8279ee57a0ddbab1de47 $
19 *
20 * @file unlang/load_balance.c
21 * @brief Implementation of the unlang "load-balance" keyword.
22 *
23 * @copyright 2006-2019 The FreeRADIUS server project
24 */
25#include <freeradius-devel/server/rcode.h>
26#include <freeradius-devel/server/request_data.h>
27#include <freeradius-devel/util/hash.h>
28#include <freeradius-devel/util/rand.h>
29
30#include "unlang_priv.h"
31#include "load_balance_priv.h"
32#include "xlat_priv.h"
33
34/** Persist the current load-balance selection
35 *
36 * If the frame is UNLANG_TYPE_LOAD_BALANCE or
37 * UNLANG_TYPE_REDUNDANT_LOAD_BALANCE, then remember which child was
38 * chosen, and choose that again the next time around.
39 */
41{
42 unlang_stack_t *stack = request->stack;
43 unlang_stack_frame_t *frame = &stack->frame[stack->depth];
45 unlang_t *child;
46
47 if (!frame->prev.frame_load_balance) return 0;
48
49 fr_assert(frame->prev.frame_load_balance < stack->depth);
50
51 frame = &stack->frame[frame->prev.frame_load_balance];
52 redundant = talloc_get_type_abort(frame->state, unlang_frame_state_redundant_t);
53
54 child = redundant->child;
55 if (!child) child = redundant->start;
56
57 return request_data_add_const(request, frame->instruction, 0, child, true);
58}
59
60/** Returns the current child of the load balance section
61 *
62 * If the frame is UNLANG_TYPE_LOAD_BALANCE or
63 * UNLANG_TYPE_REDUNDANT_LOAD_BALANCE, then return the child number.
64 */
66{
67 unlang_stack_t *stack = request->stack;
68 unlang_stack_frame_t *frame = &stack->frame[stack->depth];
70
71 if (!frame->prev.frame_load_balance) return 0;
72
73 fr_assert(frame->prev.frame_load_balance < stack->depth);
74
75 frame = &stack->frame[frame->prev.frame_load_balance];
76 redundant = talloc_get_type_abort(frame->state, unlang_frame_state_redundant_t);
77
78 fr_assert(redundant->num <= UINT8_MAX);
79
80 return redundant->num;
81}
82
83#define unlang_redundant_load_balance unlang_load_balance
84
87{
88 unlang_frame_state_redundant_t *redundant = talloc_get_type_abort(frame->state, unlang_frame_state_redundant_t);
90
91 /*
92 * If the current child wasn't set, we just start there. Otherwise we loop around to the next
93 * child.
94 */
95 if (!redundant->child) {
96 redundant->child = redundant->start;
97 goto push;
98 }
99
100 /*
101 * We are in a resumed frame. Check if running the child resulted in a failure rcode which
102 * requires us to keep going. If not, return to the caller.
103 */
104 switch (redundant->result.rcode) {
105 case RLM_MODULE_FAIL:
108 break;
109
110 default:
111 if (p_result) {
112 p_result->priority = MOD_PRIORITY_MIN;
113 p_result->rcode = redundant->result.rcode;
114 }
115
117 }
118
119 /*
120 * We finished the previous child, and it failed. Go to the next one. If we fall off of the
121 * end, loop around to the next one.
122 */
123 redundant->child = unlang_list_next(&g->children, redundant->child);
124 if (!redundant->child) {
125 redundant->child = unlang_list_head(&g->children);
126 redundant->num = 0;
127 } else {
128 redundant->num++;
129 }
130
131 /*
132 * We looped back to the start. Return whatever results we had from the last child.
133 */
134 if (redundant->child == redundant->start) {
135 if (p_result) *p_result = redundant->result;
137 }
138
139push:
140 /*
141 * The child begins has no result set. Resetting the results ensures that the failed code from
142 * one child doesn't affect the next child that we run.
143 */
144 redundant->result = UNLANG_RESULT_NOT_SET;
145 repeatable_set(frame);
146
147 /*
148 * Push the child. and run it.
149 */
150 if (unlang_interpret_push(&redundant->result, request, redundant->child,
153 }
154
156}
157
159{
160 unlang_frame_state_redundant_t *redundant = talloc_get_type_abort(frame->state,
163
164 if (unlang_list_empty(&g->children)) {
165 REDEBUG("redundant section must not be empty");
166 return UNLANG_ACTION_FAIL;
167 }
168
169 /*
170 * Start at the first child, and then continue from there.
171 */
172 redundant->start = unlang_list_head(&g->children);
173
175 return unlang_redundant_next(p_result, request, frame);
176}
177
179{
182 unlang_load_balance_t *gext = NULL;
183
184#ifdef STATIC_ANALYZER
185 if (!g || unlang_list_empty(&g->children)) return UNLANG_ACTION_FAIL;
186#else
187 fr_assert(g != NULL);
188#endif
189
190 if (unlang_list_empty(&g->children)) {
191 REDEBUG("load-balance section must not be empty");
192 return UNLANG_ACTION_FAIL;
193 }
194
196 fr_assert(gext != NULL);
197
198 redundant = talloc_get_type_abort(frame->state, unlang_frame_state_redundant_t);
199
200 redundant->start = request_data_get(request, frame->instruction, 0);
201 if (redundant->start) {
202 uint32_t i;
203
204 /*
205 * This loop should be small, typically less than 16 items.
206 */
207 for (i = 0; i < unlang_list_num_elements(&g->children); i++) {
208 if (gext->children[i] != redundant->start) continue;
209
210 redundant->num = i;
211 RDEBUG3("load-balance starting at child %u", redundant->num);
212 goto selected_child;
213 }
214
215 fr_assert(0);
216
217 goto selected_child;
218 }
219
220 /*
221 * Short circuit if there's only a single child element.
222 */
223 if (unlang_list_num_elements(&g->children) == 1) {
224 RWDEBUG3("load-balance section only has single element");
225
226 redundant->start = gext->children[0];
227 redundant->num = 0;
228
229 goto selected_child;
230 }
231
232 if (gext->vpt) {
233 uint32_t start;
234 size_t num;
235 ssize_t slen;
236 fr_value_box_t *box, *to_free = NULL;
237
238 num = unlang_list_num_elements(&g->children);
239
240 /*
241 * Use the attribute value to select the statement which will be used.
242 */
243 if (tmpl_is_attr(gext->vpt)) {
244 fr_pair_t *vp;
245
246 slen = tmpl_find_vp(&vp, request, gext->vpt);
247 if (slen < 0) {
248 REDEBUG("Failed finding attribute %s - choosing random statement", gext->vpt->name);
249 goto randomly_choose;
250 }
251
252 fr_assert(fr_type_is_leaf(vp->vp_type));
253 box = &vp->data;
254
255 } else {
257 request, gext->vpt);
258 if (slen <= 0) {
259 REDEBUG("Failed expanding %s - choosing random statement", gext->vpt->name);
260 goto randomly_choose;
261 }
262
263 to_free = box;
264 }
265
266
267 if ((box->type == FR_TYPE_UINT8) && (box->vb_uint8 <= num)) {
268 start = box->vb_uint8;
269 } else {
270 start = fr_value_box_hash(box) % num;
271 }
272 talloc_free(to_free);
273
274 RDEBUG3("load-balance starting at child %u", start);
275
276 redundant->start = gext->children[start];
277 redundant->num = start;
278
279 } else {
280 uint32_t start, one, two;
281 unlang_thread_t const *t1, *t2;
282
283 randomly_choose:
284 /*
285 * Leverage the "power of two". See src/lib/io/network.c for more information.
286 */
287 fr_assert(unlang_list_num_elements(&g->children) > 1);
288
289 one = fr_rand() % unlang_list_num_elements(&g->children);
290 do {
291 two = fr_rand() % unlang_list_num_elements(&g->children);
292 } while (two == one);
293
294 t1 = unlang_thread_stats(gext->children[one]);
295 t2 = unlang_thread_stats(gext->children[two]);
296
297 if (t1->active <= t2->active) {
298 start = one;
299 } else {
300 start = two;
301 }
302
303 RDEBUG3("load-balance starting at child %u", start);
304 redundant->start = gext->children[start];
305 redundant->num = start;
306 }
307
308selected_child:
309 fr_assert(redundant->start != NULL);
310
311 /*
312 * Plain "load-balance". Just do one child, and return the result directly back to the caller.
313 */
315 if (unlang_interpret_push(p_result, request, redundant->start,
318 }
320 }
321
323 return unlang_redundant_next(p_result, request, frame);
324}
325
326
329{
330 char const *name2;
331 int i;
332 fr_token_t quote;
333 unlang_t *c;
336
337 tmpl_rules_t t_rules;
338
339 if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
340
341 /*
342 * We allow unknown attributes here.
343 */
344 t_rules = *(unlang_ctx->rules);
345 t_rules.attr.allow_unknown = true;
346 RULES_VERIFY(&t_rules);
347
348 if (!unlang_compile_limit_subsection(cs, cf_section_name1(cs))) return NULL;
349
351 if (!c) return NULL;
352
354
355 /*
356 * The various State mangling functions need to limit the number of load-balance sections.
357 *
358 * Plus, it doesn't make a lot of sense to have 256 children of a load-balance section. Just
359 * what the heck are they doing?
360 */
361 if (unlang_list_num_elements(&g->children) > UINT8_MAX) {
362 cf_log_err(cs, "Too many children for %s section", c->name);
363 return NULL;
364 }
365
366 /*
367 * Inside of the "modules" section, it's a virtual module. The key is the third argument, and
368 * the "name2" is the module name, which we ignore here.
369 */
370 name2 = cf_section_name2(cs);
371 quote = cf_section_name2_quote(cs);
372
373 if (name2) {
374 if (strcmp(cf_section_name1(cf_item_to_section(cf_parent(cs))), "modules") == 0) {
375 char const *key;
376
377 /*
378 * Key is optional.
379 */
380 key = cf_section_argv(cs, 0);
381 if (key) {
382 name2 = key;
383 quote = cf_section_argv_quote(cs, 0);
384 } else {
385 name2 = NULL; /* no key */
386 }
387 }
388 }
389
391
392 /*
393 * Allow for keyed load-balance / redundant-load-balance sections.
394 */
395 if (name2) {
396 fr_slen_t slen;
397 xlat_exp_head_t const *xlat;
398
399 /*
400 * Create the template. All attributes and xlats are
401 * defined by now.
402 */
403 slen = tmpl_afrom_substr(gext, &gext->vpt,
404 &FR_SBUFF_IN_STR(name2),
405 quote,
406 NULL,
407 &t_rules);
408 if (!gext->vpt) {
409 cf_canonicalize_error(cs, slen, "Failed parsing argument", name2);
410 error:
411 talloc_free(g);
412 return NULL;
413 }
414
415 fr_assert(gext->vpt != NULL);
416
417 /*
418 * Fixup the templates
419 */
420 if (!pass2_fixup_tmpl(g, &gext->vpt, cf_section_to_item(cs), unlang_ctx->rules->attr.dict_def)) {
421 goto error;
422 }
423
424 switch (gext->vpt->type) {
425 default:
426 cf_log_err(cs, "Invalid type in '%s': data will not result in a load-balance key", name2);
427 goto error;
428
429 /*
430 * Allow only these ones.
431 */
432 case TMPL_TYPE_ATTR:
433 if (!fr_type_is_leaf(tmpl_attr_tail_da(gext->vpt)->type)) {
434 cf_log_err(cs, "Invalid attribute reference in '%s': load-balancing can only be done on 'leaf' data types", name2);
435 goto error;
436 }
437 break;
438
439 /*
440 * Allow xlat, but disallow exec. If the admin really wants exec, then they can
441 * use `%exec(...)`
442 */
443 case TMPL_TYPE_XLAT:
444 xlat = tmpl_xlat(gext->vpt);
445 fr_assert(xlat != NULL);
446
447 if (xlat->flags.constant) {
448 cf_log_err(cs, "Cannot use constant data for 'load-balance' statement");
449 goto error;
450 }
451 break;
452 }
453 }
454
455 /*
456 * Cache the children, so we can do O(1) lookups.
457 */
458 MEM(gext->children = talloc_array(gext, unlang_t *, unlang_list_num_elements(&g->children)));
459
460 i = 0;
461 unlang_list_foreach(&g->children, child) {
462 gext->children[i++] = child;
463 }
464
465 return c;
466}
467
472
477
478
480{
482
483 if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
484
486 return NULL;
487 }
488
489 /*
490 * "redundant foo" is allowed only inside of a "modules" section, where the name is the instance
491 * name.
492 *
493 * @todo - static versus dynamic modules?
494 */
495
496 if (cf_section_name2(cs) &&
497 (strcmp(cf_section_name1(cf_item_to_section(cf_parent(cs))), "modules") != 0)) {
498 cf_log_err(cs, "Cannot specify a key for 'redundant'");
499 return NULL;
500 }
501
503}
504
505
507{
509 .name = "load-balance",
512
514 .interpret = unlang_load_balance,
515
516 .unlang_size = sizeof(unlang_load_balance_t),
517 .unlang_name = "unlang_load_balance_t",
518
519 .frame_state_size = sizeof(unlang_frame_state_redundant_t),
520 .frame_state_type = "unlang_frame_state_redundant_t",
521 });
522
524 .name = "redundant-load-balance",
527
530
531 .unlang_size = sizeof(unlang_load_balance_t),
532 .unlang_name = "unlang_load_balance_t",
533
534 .frame_state_size = sizeof(unlang_frame_state_redundant_t),
535 .frame_state_type = "unlang_frame_state_redundant_t",
536 });
537
539 .name = "redundant",
540 .type = UNLANG_TYPE_REDUNDANT,
542
543 .compile = unlang_compile_redundant,
544 .interpret = unlang_redundant,
545
546 .unlang_size = sizeof(unlang_group_t),
547 .unlang_name = "unlang_group_t",
548
549 .frame_state_size = sizeof(unlang_frame_state_redundant_t),
550 .frame_state_type = "unlang_frame_state_redundant_t",
551 });
552
553}
#define RETURN_UNLANG_ACTION_FATAL
Definition action.h:44
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ UNLANG_ACTION_PUSHED_CHILD
unlang_t pushed a new child onto the stack, execute it instead of continuing.
Definition action.h:39
@ UNLANG_ACTION_FAIL
Encountered an unexpected error.
Definition action.h:36
@ UNLANG_ACTION_CALCULATE_RESULT
Calculate a new section rlm_rcode_t value.
Definition action.h:37
Common header for all CONF_* types.
Definition cf_priv.h:54
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:106
fr_token_t cf_section_argv_quote(CONF_SECTION const *cs, int argc)
Return the quoting for one of the variadic arguments.
Definition cf_util.c:1426
char const * cf_section_name2(CONF_SECTION const *cs)
Return the second identifier of a CONF_SECTION.
Definition cf_util.c:1363
CONF_ITEM * cf_section_to_item(CONF_SECTION const *cs)
Cast a CONF_SECTION to a CONF_ITEM.
Definition cf_util.c:750
char const * cf_section_name1(CONF_SECTION const *cs)
Return the first identifier of a CONF_SECTION.
Definition cf_util.c:1349
CONF_SECTION * cf_item_to_section(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_SECTION.
Definition cf_util.c:696
char const * cf_section_argv(CONF_SECTION const *cs, int argc)
Return variadic argument at the specified index.
Definition cf_util.c:1391
fr_token_t cf_section_name2_quote(CONF_SECTION const *cs)
Return the quoting of the name2 identifier.
Definition cf_util.c:1408
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:343
#define cf_parent(_cf)
Definition cf_util.h:118
#define cf_canonicalize_error(_ci, _slen, _msg, _str)
Definition cf_util.h:421
#define cf_item_next(_parent, _curr)
Definition cf_util.h:94
unlang_t * unlang_compile_section(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_SECTION *cs, unlang_type_t type)
Definition compile.c:1518
bool pass2_fixup_tmpl(UNUSED TALLOC_CTX *ctx, tmpl_t **vpt_p, CONF_ITEM const *ci, fr_dict_t const *dict)
Definition compile.c:87
bool unlang_compile_limit_subsection(CONF_SECTION *cs, char const *name)
Definition compile.c:1596
#define MEM(x)
Definition debug.h:38
talloc_free(hp)
TALLOC_CTX * unlang_interpret_frame_talloc_ctx(request_t *request)
Get a talloc_ctx which is valid only for this frame.
Definition interpret.c:2053
unlang_thread_t const * unlang_thread_stats(unlang_t const *instruction)
Definition interpret.c:61
int unlang_interpret_push(unlang_result_t *p_result, request_t *request, unlang_t const *instruction, unlang_frame_conf_t const *conf, bool do_next_sibling)
Push a new frame onto the stack.
Definition interpret.c:622
unlang_mod_action_t priority
The priority or action for that rcode.
Definition interpret.h:142
#define UNLANG_RESULT_NOT_SET
Definition interpret.h:145
#define FRAME_CONF(_default_rcode, _top_frame)
Definition interpret.h:158
#define UNLANG_SUB_FRAME
Definition interpret.h:37
rlm_rcode_t rcode
The current rcode, from executing the instruction or merging the result from a frame.
Definition interpret.h:140
#define RDEBUG3(fmt,...)
Definition log.h:360
#define RWDEBUG3(fmt,...)
Definition log.h:380
static TALLOC_CTX * unlang_ctx
Definition base.c:71
void unlang_register(unlang_op_t *op)
Register an operation with the interpreter.
Definition base.c:56
static char * stack[MAX_STACK]
Definition radmin.c:158
static unlang_t * unlang_compile_redundant(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
static unlang_t * compile_load_balance_subsection(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_SECTION *cs, unlang_type_t type)
uint8_t unlang_load_balance_child(request_t *request)
Returns the current child of the load balance section.
void unlang_load_balance_init(void)
static unlang_action_t unlang_redundant(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
static unlang_t * unlang_compile_redundant_load_balance(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
static unlang_t * unlang_compile_load_balance(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
int unlang_load_balance_persist(request_t *request)
Persist the current load-balance selection.
static unlang_action_t unlang_load_balance(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
static unlang_action_t unlang_redundant_next(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
#define unlang_redundant_load_balance
tmpl_t * vpt
template to expand for keyed balancing
static unlang_load_balance_t * unlang_group_to_load_balance(unlang_group_t *g)
Cast a group structure to the load_balance keyword extension.
unlang_result_t result
for intermediate child results
uint32_t num
the current child number
unlang_t * child
the current child we're processing
unlang_t * start
the starting child
unlang_t ** children
array of child instructions.
State of a redundant operation.
@ FR_TYPE_VALUE_BOX
A boxed value.
@ FR_TYPE_UINT8
8 Bit unsigned integer.
unsigned int uint32_t
long int ssize_t
unsigned char uint8_t
ssize_t fr_slen_t
#define UINT8_MAX
#define MOD_PRIORITY_MIN
Definition mod_action.h:64
#define fr_assert(_expr)
Definition rad_assert.h:37
#define REDEBUG(fmt,...)
uint32_t fr_rand(void)
Return a 32-bit random number.
Definition rand.c:104
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition rcode.h:48
@ RLM_MODULE_TIMEOUT
Module (or section) timed out.
Definition rcode.h:56
@ RLM_MODULE_NOT_SET
Error resolving rcode (should not be returned by modules).
Definition rcode.h:45
void * request_data_get(request_t *request, void const *unique_ptr, int unique_int)
Get opaque data from a request.
#define request_data_add_const(_request, _unique_ptr, _unique_int, _opaque, _persist)
Add const opaque data to a request_t.
#define FR_SBUFF_IN_STR(_start)
int tmpl_find_vp(fr_pair_t **out, request_t *request, tmpl_t const *vpt))
Returns the first VP matching a tmpl_t.
Definition tmpl_eval.c:777
#define tmpl_is_attr(vpt)
Definition tmpl.h:215
#define tmpl_xlat(_tmpl)
Definition tmpl.h:953
@ TMPL_TYPE_ATTR
Reference to one or more attributes.
Definition tmpl.h:149
@ TMPL_TYPE_XLAT
Pre-parsed xlat expansion.
Definition tmpl.h:153
ssize_t tmpl_afrom_substr(TALLOC_CTX *ctx, tmpl_t **out, fr_sbuff_t *in, fr_token_t quote, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules))
Convert an arbitrary string into a tmpl_t.
#define tmpl_aexpand_type(_ctx, _out, _type, _request, _vpt)
Expand a tmpl to a C type, allocing a new buffer to hold the string.
Definition tmpl.h:1096
tmpl_attr_rules_t attr
Rules/data for parsing attribute references.
Definition tmpl.h:346
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition tmpl.h:824
Optional arguments passed to vp_tmpl functions.
Definition tmpl.h:343
fr_aka_sim_id_type_t type
fr_pair_t * vp
unsigned int allow_unknown
Allow unknown attributes i.e.
Definition tmpl.h:310
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
enum fr_token fr_token_t
unsigned int constant
xlat is just tmpl_attr_tail_data, or XLAT_BOX
Definition xlat.h:114
Private interpreter structures and functions.
#define RULES_VERIFY(_rules)
void * state
Stack frame specialisations.
#define UNLANG_NEXT_STOP
Definition unlang_priv.h:98
#define unlang_list_foreach(_list_head, _iter)
#define UNLANG_IGNORE
static unlang_group_t * unlang_generic_to_group(unlang_t const *p)
unlang_list_t children
char const * name
Unknown...
unlang_type_t
Types of unlang_t nodes.
Definition unlang_priv.h:47
@ UNLANG_TYPE_LOAD_BALANCE
Load balance section.
Definition unlang_priv.h:53
@ UNLANG_TYPE_REDUNDANT
exactly like group, but with different default return codes
Definition unlang_priv.h:52
@ UNLANG_TYPE_REDUNDANT_LOAD_BALANCE
Redundant load balance section.
Definition unlang_priv.h:54
unlang_t const * instruction
The unlang node we're evaluating.
struct unlang_stack_frame_s::@111 prev
@ UNLANG_OP_FLAG_DEBUG_BRACES
Print debug braces.
@ UNLANG_OP_FLAG_RCODE_SET
Set request->rcode to the result of this operation.
static void repeatable_set(unlang_stack_frame_t *frame)
unlang_process_t process
function to call for interpreting this stack frame
unlang_type_t type
The specialisation of this node.
Generic representation of a grouping.
An unlang operation.
A node in a graph of unlang_op_t (s) that we execute.
Our interpreter stack, as distinct from the C stack.
An unlang stack associated with a request.
static fr_slen_t parent
Definition pair.h:860
#define fr_type_is_leaf(_x)
Definition types.h:393
uint32_t fr_value_box_hash(fr_value_box_t const *vb)
Hash the contents of a value box.
Definition value.c:7205
String expansion ("translation").
xlat_flags_t flags
Flags that control resolution and evaluation.
Definition xlat_priv.h:192