Coverage Report

Created: 2026-04-12 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/unlang/load_balance.c
Line
Count
Source
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: 22b5a6b8a1306b828ef151f947f3c975062179c4 $
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/util/hash.h>
27
#include <freeradius-devel/util/rand.h>
28
29
#include "unlang_priv.h"
30
#include "load_balance_priv.h"
31
32
0
#define unlang_redundant_load_balance unlang_load_balance
33
34
static unlang_action_t unlang_load_balance_next(unlang_result_t *p_result, request_t *request,
35
            unlang_stack_frame_t *frame)
36
0
{
37
0
  unlang_frame_state_redundant_t  *redundant = talloc_get_type_abort(frame->state, unlang_frame_state_redundant_t);
38
0
  unlang_group_t      *g = unlang_generic_to_group(frame->instruction);
39
40
  /*
41
   *  If the current child wasn't set, we just start there.  Otherwise we loop around to the next
42
   *  child.
43
   */
44
0
  if (!redundant->child) {
45
0
    redundant->child = redundant->start;
46
0
    goto push;
47
0
  }
48
49
  /*
50
   *  We are in a resumed frame.  Check if running the child resulted in a failure rcode which
51
   *  requires us to keep going.  If not, return to the caller.
52
   */
53
0
  switch (redundant->result.rcode) {
54
0
  case RLM_MODULE_FAIL:
55
0
  case RLM_MODULE_TIMEOUT:
56
0
  case RLM_MODULE_NOT_SET:
57
0
    break;
58
59
0
  default:
60
0
    if (p_result) {
61
0
      p_result->priority = MOD_PRIORITY_MIN;
62
0
      p_result->rcode = redundant->result.rcode;
63
0
    }
64
65
0
    return UNLANG_ACTION_CALCULATE_RESULT;
66
0
  }
67
68
  /*
69
   *  We finished the previous child, and it failed.  Go to the next one.  If we fall off of the
70
   *  end, loop around to the next one.
71
   */
72
0
  redundant->child = unlang_list_next(&g->children, redundant->child);
73
0
  if (!redundant->child) redundant->child = unlang_list_head(&g->children);
74
75
  /*
76
   *  We looped back to the start.  Return whatever results we had from the last child.
77
   */
78
0
  if (redundant->child == redundant->start) {
79
0
    if (p_result) *p_result = redundant->result;
80
0
    return UNLANG_ACTION_CALCULATE_RESULT;
81
0
  }
82
83
0
push:
84
  /*
85
   *  The child begins has no result set.  Resetting the results ensures that the failed code from
86
   *  one child doesn't affect the next child that we run.
87
   */
88
0
  redundant->result = UNLANG_RESULT_NOT_SET;
89
0
  repeatable_set(frame);
90
91
  /*
92
   *  Push the child. and run it.
93
   */
94
0
  if (unlang_interpret_push(&redundant->result, request, redundant->child,
95
0
          FRAME_CONF(RLM_MODULE_NOT_SET, UNLANG_SUB_FRAME), UNLANG_NEXT_STOP) < 0) {
96
0
    RETURN_UNLANG_ACTION_FATAL;
97
0
  }
98
99
0
  return UNLANG_ACTION_PUSHED_CHILD;
100
0
}
101
102
static unlang_action_t unlang_redundant(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
103
0
{
104
0
  unlang_frame_state_redundant_t  *redundant = talloc_get_type_abort(frame->state,
105
0
                     unlang_frame_state_redundant_t);
106
0
  unlang_group_t      *g = unlang_generic_to_group(frame->instruction);
107
108
  /*
109
   *  Start at the first child, and then continue from there.
110
   */
111
0
  redundant->start = unlang_list_head(&g->children);
112
113
0
  frame->process = unlang_load_balance_next;
114
0
  return unlang_load_balance_next(p_result, request, frame);
115
0
}
116
117
static unlang_action_t unlang_load_balance(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
118
0
{
119
0
  unlang_frame_state_redundant_t  *redundant;
120
0
  unlang_group_t      *g = unlang_generic_to_group(frame->instruction);
121
0
  unlang_load_balance_t   *gext = NULL;
122
123
0
  uint32_t      count = 0;
124
125
#ifdef STATIC_ANALYZER
126
  if (!g || unlang_list_empty(&g->children)) return UNLANG_ACTION_FAIL;
127
#else
128
0
  fr_assert(g != NULL);
129
0
  fr_assert(!unlang_list_empty(&g->children));
130
0
#endif
131
132
0
  gext = unlang_group_to_load_balance(g);
133
134
0
  redundant = talloc_get_type_abort(frame->state, unlang_frame_state_redundant_t);
135
136
0
  if (gext && gext->vpt) {
137
0
    uint32_t hash, start;
138
0
    ssize_t slen;
139
0
    char buffer[1024];
140
141
    /*
142
     *  Hash the attribute value to select the statement which will be used.
143
     */
144
0
    if (tmpl_is_attr(gext->vpt)) {
145
0
      fr_pair_t *vp;
146
147
0
      slen = tmpl_find_vp(&vp, request, gext->vpt);
148
0
      if (slen < 0) {
149
0
        REDEBUG("Failed finding attribute %s - choosing random statement", gext->vpt->name);
150
0
        goto randomly_choose;
151
0
      }
152
153
0
      fr_assert(fr_type_is_leaf(vp->vp_type));
154
155
0
      start = fr_value_box_hash(&vp->data) % unlang_list_num_elements(&g->children);
156
157
0
    } else {
158
0
      uint8_t *octets = NULL;
159
160
      /*
161
       *  Get the raw data, and then hash the data.
162
       */
163
0
      slen = tmpl_expand(&octets, buffer, sizeof(buffer), request, gext->vpt);
164
0
      if (slen <= 0) {
165
0
        REDEBUG("Failed expanding %s - choosing random statement", gext->vpt->name);
166
0
        goto randomly_choose;
167
0
      }
168
169
0
      hash = fr_hash(octets, slen);
170
171
0
      start = hash % unlang_list_num_elements(&g->children);
172
0
    }
173
174
0
    RDEBUG3("load-balance starting at child %d", (int) start);
175
176
0
    count = 0;
177
0
    unlang_list_foreach(&g->children, child) {
178
0
      if (count == start) {
179
0
        redundant->start = child;
180
0
        break;
181
0
      }
182
183
0
      count++;
184
0
    }
185
0
    fr_assert(redundant->start != NULL);
186
187
0
  } else {
188
0
  randomly_choose:
189
0
    count = 1;
190
191
    /*
192
     *  Choose a child at random.
193
     *
194
     *  @todo - leverage the "power of 2", as per
195
     *      lib/io/network.c.  This is good enough for
196
     *      most purposes.  However, in order to do this,
197
     *      we need to track active callers across
198
     *      *either* multiple modules in one thread, *or*
199
     *      across multiple threads.
200
     *
201
     *  We don't have thread-specific instance data
202
     *  for this load-balance section.  So for now,
203
     *  just pick a random child.
204
     */
205
0
    unlang_list_foreach(&g->children, child) {
206
0
      if ((count * (fr_rand() & 0xffffff)) < (uint32_t) 0x1000000) {
207
0
        redundant->start = child;
208
0
      }
209
0
      count++;
210
0
    }
211
212
0
    fr_assert(redundant->start != NULL);
213
214
0
  }
215
216
0
  fr_assert(redundant->start != NULL);
217
218
  /*
219
   *  Plain "load-balance".  Just do one child, and return the result directly back to the caller.
220
   */
221
0
  if (frame->instruction->type == UNLANG_TYPE_LOAD_BALANCE) {
222
0
    if (unlang_interpret_push(p_result, request, redundant->start,
223
0
            FRAME_CONF(RLM_MODULE_NOT_SET, UNLANG_SUB_FRAME), UNLANG_NEXT_STOP) < 0) {
224
0
      RETURN_UNLANG_ACTION_FATAL;
225
0
    }
226
0
    return UNLANG_ACTION_PUSHED_CHILD;
227
0
  }
228
229
0
  frame->process = unlang_load_balance_next;
230
0
  return unlang_load_balance_next(p_result, request, frame);
231
0
}
232
233
234
static unlang_t *compile_load_balance_subsection(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_SECTION *cs,
235
             unlang_type_t type)
236
0
{
237
0
  char const      *name2;
238
0
  fr_token_t      quote;
239
0
  unlang_t      *c;
240
0
  unlang_group_t      *g;
241
0
  unlang_load_balance_t   *gext;
242
243
0
  tmpl_rules_t      t_rules;
244
245
0
  if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
246
247
  /*
248
   *  We allow unknown attributes here.
249
   */
250
0
  t_rules = *(unlang_ctx->rules);
251
0
  t_rules.attr.allow_unknown = true;
252
0
  RULES_VERIFY(&t_rules);
253
254
0
  if (!unlang_compile_limit_subsection(cs, cf_section_name1(cs))) return NULL;
255
256
0
  c = unlang_compile_section(parent, unlang_ctx, cs, type);
257
0
  if (!c) return NULL;
258
259
0
  g = unlang_generic_to_group(c);
260
261
  /*
262
   *  Inside of the "modules" section, it's a virtual module.  The key is the third argument, and
263
   *  the "name2" is the module name, which we ignore here.
264
   */
265
0
  name2 = cf_section_name2(cs);
266
0
  quote = cf_section_name2_quote(cs);
267
268
0
  if (name2) {
269
0
    if (strcmp(cf_section_name1(cf_item_to_section(cf_parent(cs))), "modules") == 0) {
270
0
      char const *key;
271
272
      /*
273
       *  Key is optional.
274
       */
275
0
      key = cf_section_argv(cs, 0);
276
0
      if (key) {
277
0
        name2 = key;
278
0
        quote = cf_section_argv_quote(cs, 0);
279
0
      } else {
280
0
        name2 = NULL; /* no key */
281
0
      }
282
0
    }
283
0
  }
284
285
  /*
286
   *  Allow for keyed load-balance / redundant-load-balance sections.
287
   */
288
0
  if (name2) {
289
0
    ssize_t slen;
290
291
    /*
292
     *  Create the template.  All attributes and xlats are
293
     *  defined by now.
294
     */
295
0
    gext = unlang_group_to_load_balance(g);
296
0
    slen = tmpl_afrom_substr(gext, &gext->vpt,
297
0
           &FR_SBUFF_IN_STR(name2),
298
0
           quote,
299
0
           NULL,
300
0
           &t_rules);
301
0
    if (!gext->vpt) {
302
0
      cf_canonicalize_error(cs, slen, "Failed parsing argument", name2);
303
0
      talloc_free(g);
304
0
      return NULL;
305
0
    }
306
307
0
    fr_assert(gext->vpt != NULL);
308
309
    /*
310
     *  Fixup the templates
311
     */
312
0
    if (!pass2_fixup_tmpl(g, &gext->vpt, cf_section_to_item(cs), unlang_ctx->rules->attr.dict_def)) {
313
0
      talloc_free(g);
314
0
      return NULL;
315
0
    }
316
317
0
    switch (gext->vpt->type) {
318
0
    default:
319
0
      cf_log_err(cs, "Invalid type in '%s': data will not result in a load-balance key", name2);
320
0
      talloc_free(g);
321
0
      return NULL;
322
323
      /*
324
       *  Allow only these ones.
325
       */
326
0
    case TMPL_TYPE_ATTR:
327
0
      if (!fr_type_is_leaf(tmpl_attr_tail_da(gext->vpt)->type)) {
328
0
        cf_log_err(cs, "Invalid attribute reference in '%s': load-balancing can only be done on 'leaf' data types", name2);
329
0
        talloc_free(g);
330
0
        return NULL;
331
0
      }
332
0
      break;
333
334
0
    case TMPL_TYPE_XLAT:
335
0
    case TMPL_TYPE_EXEC:
336
0
      break;
337
0
    }
338
0
  }
339
340
0
  return c;
341
0
}
342
343
static unlang_t *unlang_compile_load_balance(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
344
0
{
345
0
  return compile_load_balance_subsection(parent, unlang_ctx, cf_item_to_section(ci), UNLANG_TYPE_LOAD_BALANCE);
346
0
}
347
348
static unlang_t *unlang_compile_redundant_load_balance(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
349
0
{
350
0
  return compile_load_balance_subsection(parent, unlang_ctx, cf_item_to_section(ci), UNLANG_TYPE_REDUNDANT_LOAD_BALANCE);
351
0
}
352
353
354
static unlang_t *unlang_compile_redundant(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
355
0
{
356
0
  CONF_SECTION      *cs = cf_item_to_section(ci);
357
358
0
  if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
359
360
0
  if (!unlang_compile_limit_subsection(cs, cf_section_name1(cs))) {
361
0
    return NULL;
362
0
  }
363
364
  /*
365
   *  "redundant foo" is allowed only inside of a "modules" section, where the name is the instance
366
   *  name.
367
   *
368
   *  @todo - static versus dynamic modules?
369
   */
370
371
0
  if (cf_section_name2(cs) &&
372
0
      (strcmp(cf_section_name1(cf_item_to_section(cf_parent(cs))), "modules") != 0)) {
373
0
    cf_log_err(cs, "Cannot specify a key for 'redundant'");
374
0
    return NULL;
375
0
  }
376
377
0
  return unlang_compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_REDUNDANT);
378
0
}
379
380
381
void unlang_load_balance_init(void)
382
0
{
383
0
  unlang_register(&(unlang_op_t){
384
0
      .name = "load-balance",
385
0
      .type = UNLANG_TYPE_LOAD_BALANCE,
386
0
      .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET,
387
388
0
      .compile = unlang_compile_load_balance,
389
0
      .interpret = unlang_load_balance,
390
391
0
      .unlang_size = sizeof(unlang_load_balance_t),
392
0
      .unlang_name = "unlang_load_balance_t",
393
394
0
      .frame_state_size = sizeof(unlang_frame_state_redundant_t),
395
0
      .frame_state_type = "unlang_frame_state_redundant_t",
396
0
    });
397
398
0
  unlang_register(&(unlang_op_t){
399
0
      .name = "redundant-load-balance",
400
0
      .type = UNLANG_TYPE_REDUNDANT_LOAD_BALANCE,
401
0
      .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET,
402
403
0
      .compile = unlang_compile_redundant_load_balance,
404
0
      .interpret = unlang_redundant_load_balance,
405
406
0
      .unlang_size = sizeof(unlang_load_balance_t),
407
0
      .unlang_name = "unlang_load_balance_t",
408
409
0
      .frame_state_size = sizeof(unlang_frame_state_redundant_t),
410
0
      .frame_state_type = "unlang_frame_state_redundant_t",
411
0
    });
412
413
0
  unlang_register(&(unlang_op_t){
414
0
      .name = "redundant",
415
0
      .type = UNLANG_TYPE_REDUNDANT,
416
0
      .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET,
417
418
0
      .compile = unlang_compile_redundant,
419
0
      .interpret = unlang_redundant,
420
421
0
      .unlang_size = sizeof(unlang_group_t),
422
0
      .unlang_name = "unlang_group_t",
423
424
0
      .frame_state_size = sizeof(unlang_frame_state_redundant_t),
425
0
      .frame_state_type = "unlang_frame_state_redundant_t",
426
0
    });
427
428
0
}