Skip to content

Commit d42e990

Browse files
committed
add function to get the current load-balance child
1 parent 8a70daf commit d42e990

5 files changed

Lines changed: 158 additions & 11 deletions

File tree

doc/antora/modules/reference/pages/unlang/load-balance.adoc

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@ into the `instantiate` section of `radiusd.conf. This configuration
7676
is no longer used, and the `sql_all` definition can just be placed as
7777
a module definition into the `mods-enabled/` directory.
7878

79-
== Load-Balance Expansions
79+
== Modules and Load-Balance Expansions
8080

81-
When the `sql_all` module is defined as above, it can also be used as
82-
in a xref:xlat/index.adoc[dynamic expansion]:
81+
When the `sql_all` module is defined as above, the server also
82+
registers an sql xref:xlat/index.adoc[dynamic expansion]. The
83+
expansion performs load-balancing for the SQL expansion.
8384

8485
.Example of Load-Balance SQL module
8586
[source,unlang]
@@ -91,5 +92,28 @@ Reply-Message := %sql_all("SELECT message FROM table WHERE name='%{User-Name}'")
9192
The expansion works exactly like a `load-balance` block. One of the
9293
modules is chosen to run the expansion, in load-balance fashion.
9394

95+
== Related Functions
96+
97+
The server also defines two related functions:
98+
99+
=== `%interpreter.load-balance.persist()`
100+
101+
This function persists the current load-balance choice across
102+
multiple packets/ It is mainly useful for RADIUS proxying, when EAP
103+
is used.
104+
105+
If the function is called from outside of a `load-balance` or
106+
`redundant-load-balance` section, then it does nothing.
107+
108+
The function takes no arguments, and returns no value.
109+
110+
=== `%interpreter.load-balance.child()`
111+
112+
This function returns a `uint8` from `0..N`, which indicates which
113+
child is currently running.
114+
115+
If the function is called from outside of a `load-balance` or
116+
`redundant-load-balance` section, then it does nothing.
117+
94118
// Copyright (C) 2026 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
95119
// This documentation was developed by Network RADIUS SAS.

src/lib/unlang/interpret.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2349,6 +2349,39 @@ static xlat_action_t unlang_interpret_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out,
23492349
return XLAT_ACTION_DONE;
23502350
}
23512351

2352+
/** Tell the load-balance keyword to persist this child selection
2353+
*
2354+
* @ingroup xlat_functions
2355+
*/
2356+
static xlat_action_t unlang_interpret_lb_persist_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out,
2357+
UNUSED xlat_ctx_t const *xctx,
2358+
request_t *request, UNUSED fr_value_box_list_t *in)
2359+
{
2360+
if (unlang_load_balance_persist(request) < 0) return XLAT_ACTION_FAIL;
2361+
2362+
return XLAT_ACTION_DONE;
2363+
}
2364+
2365+
2366+
/** Tell the load-balance keyword to persist this child selection
2367+
*
2368+
* @ingroup xlat_functions
2369+
*/
2370+
static xlat_action_t unlang_interpret_lb_child_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out,
2371+
UNUSED xlat_ctx_t const *xctx,
2372+
request_t *request, UNUSED fr_value_box_list_t *in)
2373+
{
2374+
fr_value_box_t *vb;
2375+
2376+
MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_UINT8, NULL));
2377+
2378+
vb->vb_uint8 = unlang_load_balance_child(request);
2379+
2380+
fr_dcursor_append(out, vb);
2381+
return XLAT_ACTION_DONE;
2382+
}
2383+
2384+
23522385
/** Return the current virtual server for this request
23532386
*
23542387
* @param[in] request To return virtual server for.
@@ -2497,6 +2530,9 @@ int unlang_interpret_init_global(TALLOC_CTX *ctx)
24972530
if (unlikely((xlat = xlat_func_register(ctx, "interpreter", unlang_interpret_xlat, FR_TYPE_VOID)) == NULL)) return -1;
24982531
xlat_func_args_set(xlat, unlang_interpret_xlat_args);
24992532

2533+
if (unlikely((xlat = xlat_func_register(ctx, "interpreter.load-balance.persist", unlang_interpret_lb_persist_xlat, FR_TYPE_VOID)) == NULL)) return -1;
2534+
if (unlikely((xlat = xlat_func_register(ctx, "interpreter.load-balance.child", unlang_interpret_lb_child_xlat, FR_TYPE_UINT8)) == NULL)) return -1;
2535+
25002536
if (unlikely((xlat = xlat_func_register(ctx, "cancel", unlang_cancel_xlat, FR_TYPE_VOID)) == NULL)) return -1;
25012537
xlat_func_args_set(xlat, unlang_cancel_xlat_args);
25022538

src/lib/unlang/load_balance.c

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,29 @@ int unlang_load_balance_persist(request_t *request)
5757
return request_data_add_const(request, frame->instruction, 0, child, true);
5858
}
5959

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+
*/
65+
uint8_t unlang_load_balance_child(request_t *request)
66+
{
67+
unlang_stack_t *stack = request->stack;
68+
unlang_stack_frame_t *frame = &stack->frame[stack->depth];
69+
unlang_frame_state_redundant_t *redundant;
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+
6083
#define unlang_redundant_load_balance unlang_load_balance
6184

6285
static unlang_action_t unlang_load_balance_next(unlang_result_t *p_result, request_t *request,
@@ -98,7 +121,12 @@ static unlang_action_t unlang_load_balance_next(unlang_result_t *p_result, reque
98121
* end, loop around to the next one.
99122
*/
100123
redundant->child = unlang_list_next(&g->children, redundant->child);
101-
if (!redundant->child) redundant->child = unlang_list_head(&g->children);
124+
if (!redundant->child) {
125+
redundant->child = unlang_list_head(&g->children);
126+
redundant->num = 0;
127+
} else {
128+
redundant->num++;
129+
}
102130

103131
/*
104132
* We looped back to the start. Return whatever results we had from the last child.
@@ -161,7 +189,24 @@ static unlang_action_t unlang_load_balance(unlang_result_t *p_result, request_t
161189
redundant = talloc_get_type_abort(frame->state, unlang_frame_state_redundant_t);
162190

163191
redundant->start = request_data_get(request, frame->instruction, 0);
164-
if (redundant->start) goto selected_child;
192+
if (redundant->start) {
193+
uint32_t i;
194+
195+
/*
196+
* This loop should be small, typically less than 16 items.
197+
*/
198+
for (i = 0; i < unlang_list_num_elements(&g->children); i++) {
199+
if (gext->children[i] != redundant->start) continue;
200+
201+
redundant->num = i;
202+
RDEBUG3("load-balance starting at child %u", redundant->num);
203+
goto selected_child;
204+
}
205+
206+
fr_assert(0);
207+
208+
goto selected_child;
209+
}
165210

166211
if (gext->vpt) {
167212
uint32_t start;
@@ -205,9 +250,10 @@ static unlang_action_t unlang_load_balance(unlang_result_t *p_result, request_t
205250
}
206251
talloc_free(to_free);
207252

208-
RDEBUG3("load-balance starting at child %d", (int) start);
253+
RDEBUG3("load-balance starting at child %u", start);
209254

210255
redundant->start = gext->children[start];
256+
redundant->num = start;
211257

212258
} else {
213259
uint32_t start, one, two;
@@ -231,8 +277,9 @@ static unlang_action_t unlang_load_balance(unlang_result_t *p_result, request_t
231277
start = two;
232278
}
233279

234-
RDEBUG3("load-balance starting at child %d", (int) start);
280+
RDEBUG3("load-balance starting at child %u", start);
235281
redundant->start = gext->children[start];
282+
redundant->num = start;
236283
}
237284

238285
selected_child:
@@ -282,6 +329,17 @@ static unlang_t *compile_load_balance_subsection(unlang_t *parent, unlang_compil
282329

283330
g = unlang_generic_to_group(c);
284331

332+
/*
333+
* The various State mangling functions need to limit the number of load-balance sections.
334+
*
335+
* Plus, it doesn't make a lot of sense to have 256 children of a load-balance section. Just
336+
* what the heck are they doing?
337+
*/
338+
if (unlang_list_num_elements(&g->children) > UINT8_MAX) {
339+
cf_log_err(cs, "Too many children for %s section", c->name);
340+
return NULL;
341+
}
342+
285343
/*
286344
* Inside of the "modules" section, it's a virtual module. The key is the third argument, and
287345
* the "name2" is the module name, which we ignore here.

src/lib/unlang/load_balance_priv.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ typedef struct {
4141
typedef struct {
4242
unlang_t *child; //!< the current child we're processing
4343
unlang_t *start; //!< the starting child
44+
uint32_t num; //!< the current child number
4445
unlang_result_t result; //!< for intermediate child results
4546
} unlang_frame_state_redundant_t;
4647

@@ -61,6 +62,7 @@ static inline unlang_group_t *unlang_load_balance_to_group(unlang_load_balance_t
6162
}
6263

6364
int unlang_load_balance_persist(request_t *request) CC_HINT(nonnull);
65+
uint8_t unlang_load_balance_child(request_t *request) CC_HINT(nonnull);
6466

6567
#ifdef __cplusplus
6668
}

src/tests/keywords/load-balance-uint8

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ uint8 key
1515
foreach i (%range(50)) {
1616
key := 0
1717
load-balance %{key} {
18-
ok
18+
group {
19+
if (%interpreter.load-balance.child() != key) {
20+
test_fail
21+
return
22+
}
23+
ok
24+
}
1925
test_fail
2026
test_fail
2127
test_fail
@@ -27,7 +33,14 @@ if (!ok) {
2733
key := 1
2834
load-balance %{key} {
2935
test_fail
30-
ok
36+
group {
37+
if (%interpreter.load-balance.child() != key) {
38+
test_fail
39+
return
40+
}
41+
ok
42+
}
43+
3144
test_fail
3245
test_fail
3346
}
@@ -39,7 +52,14 @@ key := 2
3952
load-balance %{key} {
4053
test_fail
4154
test_fail
42-
ok
55+
group {
56+
if (%interpreter.load-balance.child() != key) {
57+
test_fail
58+
return
59+
}
60+
ok
61+
}
62+
4363
test_fail
4464
}
4565
if (!ok) {
@@ -51,7 +71,14 @@ load-balance %{key} {
5171
test_fail
5272
test_fail
5373
test_fail
54-
ok
74+
group {
75+
if (%interpreter.load-balance.child() != key) {
76+
test_fail
77+
return
78+
}
79+
ok
80+
}
81+
5582
}
5683
if (!ok) {
5784
return

0 commit comments

Comments
 (0)