/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 | } |