The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
base.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: 13a53235e534252f784c11e6043d0b637256bab7 $
19 * @file src/process/redis/base.c
20 * @brief State machine for Redis cluster coordinator thread
21 *
22 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
23 */
24#include <freeradius-devel/redis/attrs.h>
25#include <freeradius-devel/redis/base.h>
26#include <freeradius-devel/redis/cluster_async.h>
27#include <freeradius-devel/io/coord_pair.h>
28#include <freeradius-devel/server/main_config.h>
29#include <freeradius-devel/unlang/function.h>
30#include <freeradius-devel/unlang/interpret.h>
31#include <freeradius-devel/util/debug.h>
32
33/* Unique number for each cluster. Starts at 1, so 0 missing data */
35
37
40 { .out = &dict_freeradius, .proto = "freeradius" },
42};
43
46
53
55
60
61/** Individual cluster node
62 */
63typedef struct {
64 fr_dlist_t entry; //!< Entry in the list of cluster nodes.
65 fr_redis_io_conf_t io_conf; //!< Connection config for this node.
66 fr_redis_trunk_t *trunk; //!< Trunk connection for this node.
67 bool in_cluster; //!< Has the node been found in the latest cluster map.
68 uint32_t version; //!< Redis version on this node.
69 uint64_t current_epoch; //!< Redis cluster epoch as reported by this node.
70 fr_pair_list_t trigger_args; //!< Pair list to pass to trigger.
72
73/** Coordinator representation of a Redis cluster
74 */
75typedef struct {
76 fr_redis_conf_t *conf; //!< Redis config for this cluster.
77 uint16_t cluster_id; //!< Numeric ID assigned by the coordinator
78 fr_ipaddr_t addr; //!< IP address of the first bootstrap server
79 uint16_t port; //!< Port of the first bootstrap server
80 fr_dlist_head_t nodes; //!< List of current nodes in the cluster
81 fr_rb_node_t cluster_by_server; //!< Entry in the tree of clusters by bootstrap server.
82 fr_rb_node_t cluster_by_id; //!< Entry in the tree of clusters by ID.
83 fr_redis_ct_t *rtcluster; //!< Cluster used to allocate redis trunk connections
84 fr_pair_list_t cluster_pairs; //!< Pairs built from the last fetch.
85 bool fetching; //!< The map is being fetched.
86 fr_time_t last_update; //!< When was the map last updated.
87 fr_rb_tree_t pending; //!< Requests waiting for custer map update.
88 fr_coord_pair_t *coord_pair; //!< The coord_pair which requested this cluster map.
89 bool failed; //!< Has the cluster failed.
90 fr_timer_t *ev; //!< Timer event for retry / refresh.
92
97
98static fr_cmp_ret_t cluster_server_cmp(void const *a, void const *b)
99{
100 process_redis_cluster_t const *cluster_a = (process_redis_cluster_t const *)a;
101 process_redis_cluster_t const *cluster_b = (process_redis_cluster_t const *)b;
102 fr_cmp_ret_t ret;
103
104 ret = fr_ipaddr_cmp(&cluster_a->addr, &cluster_b->addr);
105 if (ret != 0) return ret;
106
107 return CMP(cluster_a->port, cluster_b->port);
108}
109
110static fr_cmp_ret_t cluster_id_cmp(void const*a, void const *b)
111{
112 process_redis_cluster_t const *cluster_a = (process_redis_cluster_t const *)a;
113 process_redis_cluster_t const *cluster_b = (process_redis_cluster_t const *)b;
114
115 return CMP(cluster_a->cluster_id, cluster_b->cluster_id);
116}
117
118static fr_cmp_ret_t process_redis_pending_cmp(void const *a, void const *b)
119{
120 process_redis_pending_t const *pending_a = (process_redis_pending_t const *)a;
121 process_redis_pending_t const *pending_b = (process_redis_pending_t const *)b;
122
123 return CMP(pending_a->request, pending_b->request);
124}
125
135
136typedef struct {
138 fr_rb_tree_t cluster_by_server; //!< Tree of clusters by primary bootstrap server.
139 fr_rb_tree_t cluster_by_id; //!< Tree of clusters by ID.
141
142static const conf_parser_t config[] = {
143 { FR_CONF_OFFSET_SUBSECTION("pool", 0, process_redis_t, trunk_conf, trunk_config) },
144 { FR_CONF_OFFSET("timeout", process_redis_t, timeout), .dflt = "5s" },
145 { FR_CONF_OFFSET("retry_interval", process_redis_t, retry_interval), .dflt = "30s" },
146 { FR_CONF_OFFSET("refresh_interval", process_redis_t, refresh_interval) },
148};
149
150/** State of cluster map fetching from each node.
151 */
160
161/** Resume context for node specific calls
162 */
163typedef struct {
164 process_redis_node_t *node; //!< Node being queried.
165 fr_dlist_t entry; //!< In list of resume contexts.
166 map_get_status_t status; //!< Status of the node calls.
167 bool cluster_ok; //!< Does CLUSTER INFO say the cluster is OK.
168 fr_pair_list_t list; //!< To populate with parsed reply data.
169 fr_redis_command_set_t *cmds; //!< Command set for fetching cluster map.
170 fr_timer_t *ev; //!< Timeout event for this node.
172
173/** Resume context for Redis requests */
174typedef struct {
175 process_redis_t const *inst; //!< Module instance.
176 process_redis_thread_t *thread; //!< Thread instance.
177 unlang_result_t result; //!< Where results are written to
178 int32_t worker_id; //!< The worker which sent the data leading to this request.
179 process_redis_cluster_t *cluster; //!< Cluster which is being updated.
180 process_redis_node_t *current_node; //!< Node currently being queried.
181 fr_dlist_head_t rctx_list; //!< List of per-node resume contexts.
182 uint64_t cluster_epoch; //!< Largest epoch value returned by any node.
184
185#define FR_REDIS_PACKET_CODE_VALID(_code) (((_code) > 0) && ((_code) < FR_REDIS_CODE_MAX))
186#define FR_REDIS_PROCESS_CODE_VALID(_code) (FR_REDIS_PACKET_CODE_VALID(_code) || (_code == FR_REDIS_DO_NOT_RESPOND))
187
188#define PROCESS_PACKET_TYPE fr_redis_packet_code_t
189#define PROCESS_CODE_MAX FR_REDIS_CODE_MAX
190#define PROCESS_CODE_DO_NOT_RESPOND FR_REDIS_DO_NOT_RESPOND
191#define PROCESS_PACKET_CODE_VALID FR_REDIS_PROCESS_CODE_VALID
192#define PROCESS_INST process_redis_t
193#define PROCESS_RCTX process_redis_rctx_t
194
195#include <freeradius-devel/server/process.h>
196
197/** Convert the "slots" array in CLUSTER SHARDS replies into pairs
198 *
199 * @param reply Redis reply containing the "slots" array.
200 * @param shard_vp Pair representing shard to build slots pairs under.
201 * @param slots_covered Array recording which slots have been covered.
202 * @return
203 * - number of slot ranges found
204 * - -1 on error
205 */
206static int fr_redis_cluster_shards_slots_to_pairs(redisReply *reply, fr_pair_t *shard_vp, bool slots_covered[KEY_SLOTS])
207{
208 fr_pair_t *slot_vp, *vp;
209 size_t i;
210 uint16_t s;
211
212 /*
213 * The "slots" value must be an array with an even number
214 * of entries and all integers.
215 */
216 if (reply->type != REDIS_REPLY_ARRAY) return -1;
217 if (reply->elements == 0) return 0;
218 if ((reply->elements % 2) != 0) return -1;
219 for (i = 0; i < reply->elements; i++) if (reply->element[i]->type != REDIS_REPLY_INTEGER) return -1;
220
221 for (i = 0; i < (reply->elements - 1); i += 2) {
222 MEM(slot_vp = fr_pair_afrom_da(shard_vp, attr_redis_slot));
223 fr_pair_append(&shard_vp->vp_group, slot_vp);
224
226 if (reply->element[i]->type != REDIS_REPLY_INTEGER) return -1;
227 if (reply->element[i]->integer >= KEY_SLOTS) return -1;
228 vp->vp_uint16 = (uint16_t) reply->element[i]->integer;
229 fr_pair_append(&slot_vp->vp_group, vp);
230
232 if (reply->element[i + 1]->type != REDIS_REPLY_INTEGER) return -1;
233 vp->vp_uint16 = (uint16_t) reply->element[i + 1]->integer;
234 if (reply->element[i + 1]->integer >= KEY_SLOTS) return -1;
235 fr_pair_append(&slot_vp->vp_group, vp);
236
237 for(s = reply->element[i]->integer; s <= reply->element[i + 1]->integer; s++) slots_covered[s] = true;
238 }
239
240 return i / 2;
241}
242
243/** Convert the "nodes" array in CLUSTER SHARDS replies into pairs
244 *
245 * @param reply Redis reply containing the "nodes" array.
246 * @param shard_vp Pair representing shard to build ndoes pairs under.
247 * @return
248 * - number of nodes found
249 * - -1 on error
250 */
251static int fr_redis_cluster_shards_nodes_to_pairs(redisReply *reply, fr_pair_t *shard_vp)
252{
253 fr_pair_t *node_vp, *vp;
254 size_t i, j;
255 redisReply *node, *field, *value;
256
257 /*
258 * The "nodes" value must be an array of arrays.
259 */
260 if (reply->type != REDIS_REPLY_ARRAY) return -1;
261 for (i = 0; i < reply->elements; i++) if (reply->element[i]->type != REDIS_REPLY_ARRAY) return -1;
262
263 for (i = 0; i < reply->elements; i++) {
264 node = reply->element[i];
265
266 /*
267 * Every other entry must be a string - the field name.
268 */
269 for (j = 0; j < node->elements; j += 2) if (node->element[j]->type != REDIS_REPLY_STRING) return -1;
270
271 MEM(node_vp = fr_pair_afrom_da(shard_vp, attr_redis_node));
272
273 for (j = 0; j < (node->elements - 1); j +=2) {
274 field = node->element[j];
275 value = node->element[j + 1];
276 if (strcmp(field->str, "endpoint") == 0) {
277 if (value->type != REDIS_REPLY_STRING) return -1;
279 fr_pair_value_bstrndup(vp, value->str, value->len, true);
280
281 } else if (strcmp(field->str, "port") == 0) {
282 if (value->type != REDIS_REPLY_INTEGER) return -1;
284 vp->vp_uint16 = (uint16_t) value->integer;
285
286 } else if (strcmp(field->str, "role") == 0) {
287 if (value->type != REDIS_REPLY_STRING) return -1;
289 vp->vp_uint8 = (strcmp(value->str, "master") == 0) ? 1 : 2;
290
291 } else if (strcmp(field->str, "health") == 0) {
292 if (value->type != REDIS_REPLY_STRING) return -1;
293 if (strcmp(value->str, "failed") == 0) {
294 TALLOC_FREE(node_vp);
295 break;
296 }
297 continue;
298
299 } else {
300 continue;
301 }
302
303 fr_pair_append(&node_vp->vp_group, vp);
304 }
305
306 if (!node_vp) continue;
307
308 fr_pair_append(&shard_vp->vp_group, node_vp);
309 }
310
311 return i;
312}
313
314/** Convert the reply to CLUSTER SHARDS into pairs
315 *
316 * The CLUSTER SHARDS reply is designed as an extensible
317 * structure using arrays containing named fields.
318 * i.e. an element which is the field name, followed by
319 * the value in the next element.
320 *
321 * The fields for node entries are specifically described as
322 * being extensible.
323 *
324 * The CLUSTER SHARDS reply structure
325 @verbatim
326 [0] -> Shard 0
327 [0] -> "slots"
328 [1] -> Array of slot entries in pairs of start / end values.
329 [0] -> key_slot0_start
330 [1] -> key_slot0_end
331 [2] -> key_slot1_start
332 [3] -> key_slot1_end
333 [4 .. n] -> key_slot2_start .. key_slotm_end
334 [2] -> "nodes"
335 [3] -> Array of nodes which cover the slots in the "slots" array.
336 [0] -> Node 0
337 [0] -> "id"
338 [1] -> Node ID
339 [2] -> "port"
340 [3] -> (integer) port number
341 [4] -> "ip"
342 [5] -> IP address of node
343 [6] -> "endpoint"
344 [7] -> Preferred endpoint to connect to node
345 [8] -> "role"
346 [9] -> ("master"|"replica")
347 [10] -> "replication-offset"
348 [11] -> (integer) replication offset
349 [12] -> "health"
350 [13] -> ("online"|"failed"|"loading")
351 [1] -> Node 1
352 [0 .. n] -> Entries for Node 1
353 [1] -> Shard 1
354 [...]
355 @endverbatim
356 */
357 static int fr_redis_cluster_shards_to_pairs(TALLOC_CTX *ctx, request_t *request, fr_pair_list_t *list, redisReply *reply)
358{
359 size_t i;
360 fr_pair_t *shard_vp;
361 int ret;
362 bool slots_covered[KEY_SLOTS];
363 uint16_t s;
364
365 if(reply->type != REDIS_REPLY_ARRAY) return -1;
366
368
369 memset(slots_covered, 0, sizeof(slots_covered));
370
371 for (i = 0; i < reply->elements; i++) {
372 size_t j;
373 redisReply *shard = reply->element[i];
374
375 if (shard->type != REDIS_REPLY_ARRAY) {
376 error:
377 fr_pair_list_free(list);
378 return -1;
379 }
380 if (shard->elements < 4 || (shard->elements % 2 != 0)) goto error;
381
382 MEM(shard_vp = fr_pair_afrom_da(ctx, attr_redis_shard));
383 fr_pair_append(list, shard_vp);
384
385 for (j = 0; j < (shard->elements - 1); j += 2) {
386 redisReply *field = shard->element[j];
387 if (strcmp(field->str, "slots") == 0) {
388 ret = fr_redis_cluster_shards_slots_to_pairs(shard->element[j + 1], shard_vp,
389 slots_covered);
390 if (ret < 0) goto error;
391
392 /*
393 * Failed nodes can be reported with zero slots entries.
394 * Remove this shard from the list.
395 */
396 if (ret == 0) {
397 clean_up:
398 fr_pair_remove(list, shard_vp);
399 talloc_free(shard_vp);
400 break;
401 }
402
403 } else if (strcmp(field->str, "nodes") == 0) {
404 ret = fr_redis_cluster_shards_nodes_to_pairs(shard->element[j + 1], shard_vp);
405 if (ret < 0) goto error;
406 if (ret == 0) goto clean_up;
407
408 } else {
409 continue;
410 }
411 }
412 }
413
414 for (s = 0; s < KEY_SLOTS; s++) if (!slots_covered[s]) goto error;
415
416 return 0;
417}
418
419/** Convert the reply to CLUSER SLOTS into pairs
420 *
421 * The CLUSTER SLOTS reply structure
422 @verbatim
423 [0] -> key slot range 0
424 [0] -> key_slot_start
425 [1] -> key_slot_end
426 [2] -> master_node
427 [0] -> master 0 ip (string)
428 [1] -> master 0 port (number)
429 [3..n] -> replica_node(s)
430 [1] -> key slot range 1)
431 [0] -> key_slot_start
432 [1] -> key_slot_end
433 [2] -> master_node
434 [0] -> master 1 ip (string)
435 [1] -> master 1 port (number)
436 [3..n] -> replica_node(s)
437 [n] -> key slot range n
438 [0] -> key_slot_start
439 [1] -> key_slot_end
440 [2] -> master_node
441 [0] -> master n ip (string)
442 [1] -> master n port (number)
443 [3..n] -> replica_node(s)
444 @endverbatim
445 *
446 * @param[in] ctx to allocate pairs in.
447 * @param[in,out] list to populate with pairs.
448 * @param[in] reply from CLUSTER SLOTS
449 */
450static int fr_redis_cluster_slots_to_pairs(TALLOC_CTX *ctx, request_t *request, fr_pair_list_t *list, redisReply *reply)
451{
452 size_t i;
453 fr_pair_t *shard_vp, *slot_vp, *node_vp, *vp;
454 bool slots_covered[KEY_SLOTS];
455 uint16_t s;
456
457 if(reply->type != REDIS_REPLY_ARRAY) return -1;
458
460
461 memset(slots_covered, 0, sizeof(slots_covered));
462
463 /*
464 * A map consists of an array with the following indexes:
465 * [0] -> key_slot_start
466 * [1] -> key_slot_end
467 * [2] -> master_node
468 * [3..n] -> replica_node(s)
469 */
470 for (i = 0; i < reply->elements; i++) {
471 size_t j;
472 redisReply *map = reply->element[i];
473 redisReply *node;
474
475 MEM(shard_vp = fr_pair_afrom_da(ctx, attr_redis_shard));
476
477 MEM(slot_vp = fr_pair_afrom_da(shard_vp, attr_redis_slot));
478 fr_pair_append(&shard_vp->vp_group, slot_vp);
479
481 if (map->element[0]->type != REDIS_REPLY_INTEGER) {
482 error:
483 talloc_free(shard_vp);
484 list_free:
485 fr_pair_list_free(list);
486 return -1;
487 }
488 if (map->element[0]->integer >= KEY_SLOTS) goto error;
489 vp->vp_uint16 = (uint16_t) map->element[0]->integer;
490 fr_pair_append(&slot_vp->vp_group, vp);
491
493 if (map->element[1]->type != REDIS_REPLY_INTEGER) goto error;
494 if (map->element[0]->integer >= KEY_SLOTS) goto error;
495 vp->vp_uint16 = (uint16_t) map->element[1]->integer;
496 fr_pair_append(&slot_vp->vp_group, vp);
497
498 for (j = 2; j < map->elements; j++) {
499 node = map->element[j];
500 MEM(node_vp = fr_pair_afrom_da(shard_vp, attr_redis_node));
501 fr_pair_append(&shard_vp->vp_group, node_vp);
502
504 if (node->element[0]->type != REDIS_REPLY_STRING) goto error;
505 fr_pair_value_bstrndup(vp, node->element[0]->str, node->element[0]->len, true);
506 fr_pair_append(&node_vp->vp_group, vp);
507
509 if (node->element[1]->type != REDIS_REPLY_INTEGER) goto error;
510 vp->vp_uint16 = (uint16_t) node->element[1]->integer;
511 fr_pair_append(&node_vp->vp_group, vp);
512
514 vp->vp_uint8 = (j == 2) ? 1 : 2;
515 fr_pair_append(&node_vp->vp_group, vp);
516 }
517
518 for (s = map->element[0]->integer; s <= map->element[1]->integer; s++) slots_covered[s] = true;
519
520 fr_pair_append(list, shard_vp);
521 }
522
523 for (s = 0; s < KEY_SLOTS; s++) if (!slots_covered[s]) goto list_free;
524
525 return 0;
526}
527
529 fr_redis_conf_t *conf, fr_pair_t *host_vp, fr_pair_t *port_vp)
530{
532 fr_ipaddr_t ipaddr;
534
535 MEM(node = talloc_zero(ctx, process_redis_node_t));
536 node->io_conf = (fr_redis_io_conf_t) {
537 .password = conf->password,
538 .username = conf->username,
539 .use_tls = conf->use_tls,
540 };
541 if (fr_inet_pton_port(&ipaddr, &node->io_conf.port, host_vp->vp_strvalue,
542 host_vp->vp_length, AF_UNSPEC, true, true) < 0){
543 error:
544 talloc_free(node);
545 return -1;
546 }
547 node->io_conf.hostname = talloc_strdup(node, fr_inet_ntop(buff, sizeof(buff), &ipaddr));
548 if (node->io_conf.port == 0) {
549 if (!port_vp) goto error;
550 node->io_conf.port = port_vp->vp_uint16;
551 }
552 node->io_conf.log_prefix = talloc_asprintf(node, "Coord %s %s:%d", conf->log_prefix,
553 fr_inet_ntop(buff, sizeof(buff), &ipaddr),
554 node->io_conf.port);
555 node->in_cluster = true;
557 if (conf->trunk_conf.conn_triggers) {
558 module_trigger_args_build(node, &node->trigger_args, NULL,
560 .module = "process_redis",
561 .name = inst->inst_name, \
562 .server = buff, \
563 .port = node->io_conf.port \
564 }); \
565 }
566 fr_dlist_insert_tail(&cluster->nodes, node);
567 return 0;
568}
569
571 redisReply *reply, void *rctx)
572{
573 process_redis_node_t *node = talloc_get_type_abort(rctx, process_redis_node_t);
574 char buffer[20];
575
577 if (fr_redis_parse_version(buffer, sizeof(buffer), reply) != REDIS_RCODE_SUCCESS) return;
579 RDEBUG3("Cluster node %s:%d is running Redis version %s", node->io_conf.hostname, node->io_conf.port, buffer);
580}
581
582static void redis_cluster_info_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
583{
584 process_redis_node_rctx_t *nrctx = talloc_get_type_abort(rctx, process_redis_node_rctx_t);
585 fr_sbuff_t sbuff;
586
588
589 if (reply->type != REDIS_REPLY_STRING) {
590 RERROR("Bad value type, expected string, got %s",
591 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
592 error:
594 return;
595 }
596
597 fr_sbuff_init_in(&sbuff, reply->str, reply->len);
598 if (!fr_sbuff_adv_to_str_literal(&sbuff, SIZE_MAX, "cluster_state:")) {
599 RERROR("Response did not contain cluster_state");
600 goto error;
601 }
602 fr_sbuff_advance(&sbuff, sizeof("cluster_state:") - 1);
603
604 if (fr_sbuff_adv_past_str_literal(&sbuff, "ok\r\n")) {
605 nrctx->cluster_ok = true;
606 RDEBUG2("Node %s:%d reports Cluster OK", nrctx->node->io_conf.hostname, nrctx->node->io_conf.port);
607 } else {
608 RERROR("Node %s:%d reports Cluster Failed", nrctx->node->io_conf.hostname, nrctx->node->io_conf.port);
609 }
610
611 /*
612 * The sequence of entries in the CLUSTER INFO results is not guaranteed,
613 * so we start the search from the beginning again.
614 */
615 fr_sbuff_set_to_start(&sbuff);
616
617 if (!fr_sbuff_adv_to_str_literal(&sbuff, SIZE_MAX, "cluster_current_epoch:")) {
618 RERROR("Response did not contain cluster_current_epoch");
619 goto error;
620 }
621 fr_sbuff_advance(&sbuff, sizeof("cluster_current_epoch:") -1);
622
623 if (fr_sbuff_out_uint64(NULL, &nrctx->node->current_epoch, &sbuff, false) < 0) {
624 RERROR("Failed parsing current_cluster_epoch");
625 goto error;
626 }
627
628 RDEBUG3("Node %s:%d reported epoch %"PRIu64, nrctx->node->io_conf.hostname,
629 nrctx->node->io_conf.port, nrctx->node->current_epoch);
631 return;
632}
633
634static void redis_cluster_slots_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
635{
636 process_redis_node_rctx_t *nrctx = talloc_get_type_abort(rctx, process_redis_node_rctx_t);
637 int ret;
638
639 if (nrctx->node->version > redis_shards_version) {
640 ret = fr_redis_cluster_shards_to_pairs(nrctx, request, &nrctx->list, reply);
641 } else {
642 ret = fr_redis_cluster_slots_to_pairs(nrctx, request, &nrctx->list, reply);
643 }
644 if (RDEBUG_ENABLED2 && (ret == 0)){
645 RDEBUG2("Cluster map fetched:");
646 RINDENT();
647 fr_pair_list_foreach(&nrctx->list, vp) {
648 RDEBUG2("%pP", vp);
649 }
650 REXDENT();
651 }
653}
654
656{
657 process_redis_node_rctx_t *nrctx = talloc_get_type_abort(uctx, process_redis_node_rctx_t);
658
659 ERROR("Fetching map from %s:%d failed", nrctx->node->io_conf.hostname, nrctx->node->io_conf.port);
662}
663
665{
666 process_redis_cluster_t *cluster = talloc_get_type_abort(uctx, process_redis_cluster_t);
667 fr_pair_list_t list;
668 fr_pair_t *vp;
669 TALLOC_CTX *local = talloc_new(NULL);
670
671 if (cluster->failed) {
672 DEBUG2("Retrying fetch of cluster map");
673 } else {
674 DEBUG2("Refreshing cluster map");
675 }
676
677 fr_pair_list_init(&list);
679 if (!vp) goto free;
680
681 fr_pair_list_append_by_da(local, vp, &list, attr_redis_cluster_id, cluster->cluster_id, false);
682 if (!vp) goto free;
683
684 fr_coord_pair_coord_request_start(cluster->coord_pair, &list, now);
685
686free:
687 talloc_free(local);
688}
689
690/** Send a Cluster-Failed message to a worker
691 */
694{
695 fr_pair_t *vp;
696
697 fr_pair_prepend_by_da(request->reply_ctx, &vp, &request->reply_pairs, attr_redis_cluster_id);
698 vp->vp_uint16 = cluster->cluster_id;
699
700 fr_pair_prepend_by_da(request->reply_ctx, &vp, &request->reply_pairs, attr_redis_packet_type);
701 vp->vp_uint32 = FR_REDIS_CLUSTER_MAP_FAIL;
702
705}
706
708{
709 process_redis_rctx_t *rctx = talloc_get_type_abort(uctx, process_redis_rctx_t);
710 process_redis_cluster_t *cluster = rctx->cluster;
712
713 cluster->fetching = true;
715
717 MEM(nrctx = talloc_zero(rctx, process_redis_node_rctx_t));
718 nrctx->node = node;
719 fr_pair_list_init(&nrctx->list);
720
721 MEM(nrctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, nrctx, false));
722
723 RDEBUG2("Fetching cluster map %d from %s:%d", cluster->cluster_id, node->io_conf.hostname,
724 node->io_conf.port);
725 if (!node->trunk) {
726 node->trunk = fr_redis_trunk_alloc(cluster->rtcluster, &node->io_conf, &node->trigger_args,
727 NULL, NULL, false);
729 node) != FR_REDIS_PIPELINE_OK) {
730 fail:
731 fr_fatal_assert_fail("Failed adding command to Redis command set");
732 }
733 }
734
736 nrctx) != FR_REDIS_PIPELINE_OK) goto fail;
737
738 if (redis_command_set_enqueue(node->trunk, nrctx->cmds) != FR_REDIS_PIPELINE_OK) {
739 RERROR("Unable to enqueue request on node %s:%d", node->io_conf.hostname,
740 node->io_conf.port);
741 talloc_free(nrctx);
742 continue;
743 }
744
745 fr_timer_in(nrctx, rctx->thread->el->tl, &nrctx->ev, rctx->inst->timeout, true,
747 fr_dlist_insert_tail(&rctx->rctx_list, nrctx);
748 }
749
751
752 RERROR("Unable to query any cluster node");
753
754 cluster->failed = true;
755 if (fr_timer_in(cluster, rctx->thread->el->tl, &cluster->ev, rctx->inst->retry_interval,
756 false, redis_cluster_map_get_refetch, cluster) < 0) {
757 RERROR("Failed setting up retry event");
758 };
759 return process_redis_return_failed(request, cluster, rctx->worker_id);
760}
761
763{
764 process_redis_rctx_t *rctx = talloc_get_type_abort(uctx, process_redis_rctx_t);
765 process_redis_cluster_t *cluster = rctx->cluster;
766 fr_pair_t *vp;
769 size_t completed = 0;
770 fr_pair_list_t *list = NULL;
771
772 /*
773 * The request processing will resume when one or more nodes has
774 * replied.
775 * Check the current state of the rctx for each node.
776 */
778 switch (nrctx->status) {
781 break;
782
785 clean_up:
786 fr_dlist_remove(&rctx->rctx_list, nrctx);
787 talloc_free(nrctx);
788 break;
789
791 /*
792 * Check epoch returned by node. Anything lower than
793 * the highest value seen so far can be disregarded.
794 */
795 if (nrctx->node->current_epoch < rctx->cluster_epoch) {
796 RWARN("Node %s:%d returned lower epoch than other nodes - ignoring",
797 nrctx->node->io_conf.hostname, nrctx->node->io_conf.port);
798 goto clean_up;
799 }
800
801 rctx->cluster_epoch = nrctx->node->current_epoch;
802
803 fr_redis_command_set_clear(nrctx->cmds);
804 if (nrctx->node->version > redis_shards_version) {
805 if (fr_redis_command_literal_add(nrctx->cmds, "CLUSTER SHARDS",
807 nrctx) != FR_REDIS_PIPELINE_OK) goto clean_up;
808 } else {
809 if (fr_redis_command_literal_add(nrctx->cmds, "CLUSTER SLOTS",
811 nrctx) != FR_REDIS_PIPELINE_OK) goto clean_up;
812 }
813 nrctx->status = CLUSTER_MAP_GET_MAP;
814 if (redis_command_set_enqueue(nrctx->node->trunk, nrctx->cmds) != FR_REDIS_PIPELINE_OK) {
815 RERROR("Unable to enqueue request on node %s:%d",
816 nrctx->node->io_conf.hostname, nrctx->node->io_conf.port);
817 goto clean_up;
818 }
819 break;
820
822 if (fr_pair_list_num_elements(&nrctx->list) == 0) {
823 RWARN("Node %s:%d didn't return a cluster", nrctx->node->io_conf.hostname,
824 nrctx->node->io_conf.port);
825 goto clean_up;
826 }
827 completed++;
828 break;
829 }
830 }
831
832 /*
833 * If there are still nodes with outstanding rctx then yield.
834 */
835 if (completed < fr_dlist_num_elements(&rctx->rctx_list)) {
836 if (unlang_function_repeat_set(request, redis_cluster_map_get_resume) < 0) goto fail;
837 return UNLANG_ACTION_YIELD;
838 }
839
840 if (fr_dlist_num_elements(&rctx->rctx_list) < 1) {
841 RERROR("No node returned a valid cluster map");
842 fail:
843 cluster->failed = true;
844 if (fr_timer_in(cluster, rctx->thread->el->tl, &cluster->ev, rctx->inst->retry_interval,
845 false, redis_cluster_map_get_refetch, cluster) < 0) {
846 RERROR("Failed setting up retry event");
847 };
848 return process_redis_return_failed(request, cluster, rctx->worker_id);
849 }
850
851 /*
852 * Find the first node's rctx where the node epoch matches the
853 * highest seen value.
854 */
856 if (nrctx->node->current_epoch == rctx->cluster_epoch) {
857 list = &nrctx->list;
858 break;
859 }
860 }
861 if (unlikely(!list)) goto fail;
862
863 /*
864 * Verify the list of nodes, checking the cluster map matches
865 */
867 node->in_cluster = false;
868 }
869 fr_pair_list_foreach(list, shard) {
870 vp = NULL;
871 while ((vp = fr_pair_find_by_da(&shard->vp_group, vp, attr_redis_node))) {
872 fr_pair_t *endpoint, *port;
873 bool found = false;
874
875 endpoint = fr_pair_find_by_da(&vp->vp_group, NULL, attr_redis_node_endpoint);
876 port = fr_pair_find_by_da(&vp->vp_group, NULL, attr_redis_node_port);
877
879 if ((strcmp(node->io_conf.hostname, endpoint->vp_strvalue) == 0) &&
880 (node->io_conf.port == port->vp_uint16)) {
881 node->in_cluster = true;
882 found = true;
883 break;
884 }
885 }
886
887 if (found) continue;
888
889 if (process_redis_cluster_node_add(cluster, cluster, rctx->inst, cluster->conf, endpoint, port) < 0) {
890 RERROR("Failed adding new node to cluster");
891 }
892 }
893 }
894
895 /*
896 * Remove any nodes not in the returned cluster map.
897 */
899 if (node->in_cluster) continue;
900 fr_dlist_remove(&cluster->nodes, node);
901 if (node->trunk) talloc_free(node->trunk);
902 talloc_free(node);
903 }
904
905 /*
906 * Update the stored cluster definition
907 */
909 fr_pair_list_copy(cluster, &cluster->cluster_pairs, list);
910
911 fr_pair_prepend_by_da(request->reply_ctx, &vp, &request->reply_pairs, attr_redis_cluster_id);
912 vp->vp_uint16 = cluster->cluster_id;
913 cluster->fetching = false;
914 cluster->failed = false;
915 cluster->last_update = fr_time();
916
917 fr_pair_list_copy(request->reply_ctx, &request->reply_pairs, list);
918
919 fr_pair_prepend_by_da(request->reply_ctx, &vp, &request->reply_pairs, attr_redis_packet_type);
920 vp->vp_uint32 = FR_REDIS_CLUSTER_MAP_UPDATE;
921
923
925 (fr_timer_in(cluster, rctx->thread->el->tl, &cluster->ev, rctx->inst->refresh_interval,
926 false, redis_cluster_map_get_refetch, cluster) < 0)) {
927 RERROR("Failed setting up refresh event");
928 }
929
931
932 for (pending = fr_rb_iter_init_inorder(&cluster->pending, &iter);
933 pending;
934 pending = fr_rb_iter_next_inorder(&cluster->pending, &iter)) {
935 fr_rb_iter_delete_inorder(&cluster->pending, &iter);
937 talloc_free(pending);
938 }
939
941}
942
943static void redis_cluster_map_get_cancel(request_t *request, UNUSED fr_signal_t action, void *uctx)
944{
945 process_redis_rctx_t *rctx = talloc_get_type_abort(uctx, process_redis_rctx_t);
946
948 if (!nrctx->cmds) continue;
949 RWARN("Forcibly cancelling cluster map request on %s:%d",
950 nrctx->node->io_conf.hostname, nrctx->node->io_conf.port);
951 fr_redis_command_set_cancel(nrctx->cmds);
952 }
953}
954
956{
957 process_redis_node_t *node = NULL;
958
959 while ((node = fr_dlist_next(&cluster->nodes, node))) {
960 if (!node->trunk) continue;
961 talloc_free(node->trunk);
962 }
963 return 0;
964}
965
966static void process_redis_pending_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
967{
968 process_redis_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, process_redis_rctx_t);
969 process_redis_pending_t find, *pending;
970
971 find.request = request;
972 fr_rb_find((void **)&pending, &rctx->cluster->pending, &find);
973 if (!pending) return;
974
975 fr_rb_remove(NULL, &rctx->cluster->pending, pending);
976 talloc_free(pending);
977}
978
981{
982 fr_pair_t *vp;
983
984 fr_pair_prepend_by_da(request->reply_ctx, &vp, &request->reply_pairs, attr_redis_cluster_id);
985 vp->vp_uint16 = cluster->cluster_id;
986
987 fr_pair_list_copy(request->reply_ctx, &request->reply_pairs, &cluster->cluster_pairs);
988
989 fr_pair_prepend_by_da(request->reply_ctx, &vp, &request->reply_pairs, attr_redis_packet_type);
990 vp->vp_uint32 = FR_REDIS_CLUSTER_MAP_UPDATE;
991
994}
995
996RECV(cluster_map_bootstrap)
997{
998 process_redis_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, process_redis_rctx_t);
999 process_redis_t const *inst = rctx->inst;
1000 process_redis_thread_t *thread = rctx->thread;
1001 fr_pair_t *vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_worker_id);
1002 fr_pair_t *port_vp;
1003 process_redis_cluster_t find, *cluster;
1005 CONF_SECTION *tls_conf = NULL;
1006
1007 rctx->worker_id = vp ? vp->vp_int32 : 0;
1008
1009 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_redis_bootstrap_node);
1010 fr_fatal_assert_msg(vp, "Missing %s", attr_redis_bootstrap_node->name);
1011
1012 port_vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_redis_bootstrap_port);
1013
1014 if (fr_inet_pton_port(&find.addr, &find.port, vp->vp_strvalue, vp->vp_length,
1015 AF_UNSPEC, true, true) < 0) {
1016 fr_fatal_assert_fail("Unable to parse bootstrap node");
1017 }
1018
1019 if (find.port == 0) {
1020 fr_fatal_assert_msg(port_vp, "Missing %s", attr_redis_bootstrap_port->name);
1021 find.port = port_vp->vp_uint16;
1022 }
1023
1024 fr_rb_find((void **)&cluster, &thread->cluster_by_server, &find);
1025
1026 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_redis_max_nodes);
1027 fr_fatal_assert_msg(vp, "Missing %s", attr_redis_max_nodes->name);
1028
1029 if (cluster) {
1030 /*
1031 * If this is a bootstrap call using nodes matching an existing
1032 * cluster, check the max_nodes match or array sizes will get messy.
1033 */
1034 fr_fatal_assert_msg(vp->vp_uint8 == cluster->conf->max_nodes,
1035 "Max nodes (%d) mis-match with existing cluster configured with %d",
1036 vp->vp_uint8, cluster->conf->max_nodes);
1037
1038 /*
1039 * We already have data for this cluster, just return it.
1040 */
1041 if (fr_time_gt(cluster->last_update, fr_time_wrap(0))) {
1042 return process_redis_return_existing(request, cluster, rctx->worker_id);
1043 }
1044
1045 /*
1046 * If the cluster map is already being fetched, yield until
1047 * the result is in.
1048 * More than one module instance may be using the same
1049 * so we need to process the request rather than relying
1050 * on the broadcast to workers, as that will only update
1051 * a single module instance.
1052 */
1053 if (cluster->fetching) {
1054 process_redis_pending_t *pending;
1055
1056 RDEBUG2("Cluster map already being fetched");
1057 rctx->cluster = cluster;
1058 if (unlang_module_yield(request, recv_cluster_map_bootstrap, process_redis_pending_cancel,
1060
1061 MEM(pending = talloc(cluster, process_redis_pending_t));
1062 *pending = (process_redis_pending_t) {.request = request};
1063 fr_rb_insert(&cluster->pending, pending);
1064
1065 return UNLANG_ACTION_YIELD;
1066 }
1067
1068 /*
1069 * Cluster map fetching failed, and the retry timer is armed.
1070 * Tell the caller that the map has failed.
1071 */
1072 if (cluster->failed) {
1073 return process_redis_return_failed(request, cluster, rctx->worker_id);
1074 }
1075 }
1076
1077 MEM(cluster = talloc_zero(thread, process_redis_cluster_t));
1078 MEM(conf = talloc_zero(cluster, fr_redis_conf_t));
1079 conf->trunk_conf = inst->trunk_conf;
1080
1081 cluster->cluster_id = cluster_id++;
1082 cluster->conf = conf;
1083 cluster->addr = find.addr;
1084 cluster->port = find.port;
1085 cluster->coord_pair = fr_coord_pair_request_coord_pair(request);
1087
1090
1091 conf->max_nodes = vp->vp_uint8;
1092 conf->use_cluster_map = true;
1093
1094 fr_pair_list_foreach(&request->request_pairs, conf_vp) {
1095 if (conf_vp->da == attr_redis_username) {
1096 conf->username = talloc_strdup(conf, conf_vp->vp_strvalue);
1097 } else if (conf_vp->da == attr_redis_password) {
1098 conf->password = talloc_strdup(conf, conf_vp->vp_strvalue);
1099 } else if (conf_vp->da == attr_redis_log_prefix) {
1100 conf->log_prefix = talloc_strdup(conf, conf_vp->vp_strvalue);
1101 }
1102 }
1103
1104 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_redis_use_tls);
1105 if (vp) {
1106 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_redis_tls_conf);
1107 fr_fatal_assert_msg(vp, "Missing %s when TLS is enabled", attr_redis_tls_conf->name);
1108 conf->use_tls = true;
1109 tls_conf = (CONF_SECTION *)(uintptr_t)vp->vp_uint64;
1110 }
1111
1112 MEM(cluster->rtcluster = fr_redis_ct_alloc(cluster, tls_conf, thread->el, conf, NULL, NULL, false));
1113
1114 /*
1115 * Add all the bootstrap nodes to the cluster.
1116 */
1117 vp = NULL;
1118 while ((vp = fr_pair_find_by_da(&request->request_pairs, vp, attr_redis_bootstrap_node))) {
1119 if (process_redis_cluster_node_add(cluster, cluster, inst, conf, vp, port_vp) < 0) {
1120 talloc_free(cluster);
1121 fr_fatal_assert_fail("Failed adding cluster node to list");
1122 }
1123 }
1124 fr_rb_insert(&thread->cluster_by_server, cluster);
1125 fr_rb_insert(&thread->cluster_by_id, cluster);
1126 talloc_set_destructor(cluster, _process_redis_cluster_free);
1127
1128 rctx->cluster = cluster;
1131}
1132
1133RECV(cluster_map_get)
1134{
1135 process_redis_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, process_redis_rctx_t);
1136 fr_pair_t *vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_worker_id);
1138
1139 rctx->worker_id = vp ? vp->vp_int32 : 0;
1140
1141 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_redis_cluster_id);
1142 fr_fatal_assert_msg(vp, "Missing %s", attr_redis_cluster_id->name);
1143
1144 find.cluster_id = vp->vp_uint16;
1145 fr_rb_find((void **)&rctx->cluster, &rctx->thread->cluster_by_id, &find);
1146 fr_fatal_assert_msg(rctx->cluster, "Update requested for cluster %d which has not been bootstrapped",
1147 vp->vp_uint16);
1148
1149 /*
1150 * Cluster map fetching failed, and the retry timer is armed.
1151 * Tell the caller that the map has failed.
1152 */
1153 if (fr_timer_armed(rctx->cluster->ev)) {
1154 return process_redis_return_failed(request, rctx->cluster, rctx->worker_id);
1155 }
1156
1157 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_redis_force_update);
1158 if ((fr_time_to_sec(fr_time()) == fr_time_to_sec(rctx->cluster->last_update)) && (!vp || !vp->vp_bool)) {
1159 RWARN("Cluster was updated less than a second ago, returning last response");
1160 return process_redis_return_existing(request, rctx->cluster, rctx->worker_id);
1161 }
1162
1164 NULL, 0, UNLANG_SUB_FRAME, rctx);
1165}
1166
1167static unlang_action_t mod_process(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
1168{
1169 fr_process_state_t const *state;
1170 process_redis_t *inst = talloc_get_type_abort(mctx->mi->data, process_redis_t);
1171 process_redis_thread_t *thread = talloc_get_type_abort(mctx->thread, process_redis_thread_t);
1172 process_redis_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, process_redis_rctx_t);
1173
1175
1177 fr_assert(FR_REDIS_PACKET_CODE_VALID(request->packet->code));
1178
1179 request->component = "redis";
1180 request->module = NULL;
1181 fr_assert(request->proto_dict == dict_redis);
1182
1183 UPDATE_STATE(packet);
1184
1185 if (!state->recv) {
1186 REDEBUG("Invalid packet type (%u)", request->packet->code);
1188 }
1189
1190 rctx->inst = inst;
1191 rctx->thread = thread;
1192
1193 return state->recv(p_result, mctx, request);
1194}
1195
1197{
1198 process_redis_thread_t *t = talloc_get_type_abort(mctx->thread, process_redis_thread_t);
1199
1200 t->el = mctx->el;
1203 return 0;
1204}
1205
1206static int mod_instantiate(module_inst_ctx_t const *mctx)
1207{
1208 process_redis_t *inst = talloc_get_type_abort(mctx->mi->data, process_redis_t);
1209
1210 inst->inst_name = mctx->mi->name;
1211 return 0;
1212}
1213
1214static int mod_load(void)
1215{
1216 if (redis_dict_init() < 0) return -1;
1217
1219 return 0;
1220}
1221
1222static fr_process_state_t const process_state[] = {
1224 .default_reply = FR_REDIS_CLUSTER_MAP_UPDATE,
1225 .default_rcode = RLM_MODULE_NOOP,
1226 .recv = recv_cluster_map_bootstrap,
1227 },
1229 .default_reply = FR_REDIS_CLUSTER_MAP_UPDATE,
1230 .default_rcode = RLM_MODULE_NOOP,
1231 .recv = recv_cluster_map_get,
1232 }
1233};
1234
1237 .common = {
1238 .magic = MODULE_MAGIC_INIT,
1239 .name = "redis",
1240 .config = config,
1241 .onload = mod_load,
1242 .instantiate = mod_instantiate,
1246 .thread_instantiate = mod_thread_instantiate,
1247 },
1248 .process = mod_process,
1249 .dict = &dict_redis,
1250 .packet_type = &attr_redis_packet_type
1251};
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
@ UNLANG_ACTION_YIELD
Temporarily pause execution until an event occurs.
Definition action.h:41
static int const char char buffer[256]
Definition acutest.h:576
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition build.h:113
#define unlikely(_x)
Definition build.h:455
#define UNUSED
Definition build.h:384
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:669
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:280
#define FR_CONF_OFFSET_SUBSECTION(_name, _flags, _struct, _field, _subcs)
conf_parser_t which populates a sub-struct using a CONF_SECTION
Definition cf_parse.h:309
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:606
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:106
fr_redis_ct_t * fr_redis_ct_alloc(TALLOC_CTX *ctx, CONF_SECTION *tls_cs, fr_event_list_t *el, fr_redis_conf_t *conf, fr_redis_trunk_active_t active, void *active_uctx, bool active_oneshot)
Allocate per-thread, per-cluster instance.
Thread local state for a cluster.
#define KEY_SLOTS
Maximum number of keyslots (should not change).
int fr_coord_to_worker_reply_broadcast(request_t *request)
Send a reply list from a coordinator to all workers.
Definition coord_pair.c:788
int fr_coord_to_worker_reply_send(request_t *request, uint32_t worker_id)
Send a reply list from a coordinator to a worker.
Definition coord_pair.c:760
fr_coord_pair_t * fr_coord_pair_request_coord_pair(request_t *request)
Return the coord_pair associated with a coord_pair internal request.
Definition coord_pair.c:879
int fr_coord_pair_coord_request_start(fr_coord_pair_t *coord_pair, fr_pair_list_t *list, fr_time_t now)
Start a coordinator request to run through a coord_pair process module.
Definition coord_pair.c:894
#define fr_fatal_assert_fail(_msg,...)
Calls panic_action ifndef NDEBUG, else logs error and causes the server to exit immediately with code...
Definition debug.h:224
#define fr_fatal_assert_msg(_x, _fmt,...)
Calls panic_action ifndef NDEBUG, else logs error and causes the server to exit immediately with code...
Definition debug.h:217
#define MEM(x)
Definition debug.h:38
#define ERROR(fmt,...)
Definition dhcpclient.c:40
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:292
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:305
#define DICT_AUTOLOAD_TERMINATOR
Definition dict.h:311
Specifies an attribute which must be present for the module to function.
Definition dict.h:291
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:304
Test enumeration values.
Definition dict_test.h:92
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
#define fr_dlist_foreach(_list_head, _type, _iter)
Iterate over the contents of a list.
Definition dlist.h:98
static void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
Definition dlist.h:620
static unsigned int fr_dlist_num_elements(fr_dlist_head_t const *head)
Return the number of elements in the dlist.
Definition dlist.h:921
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
Definition dlist.h:360
#define fr_dlist_talloc_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition dlist.h:257
static void * fr_dlist_next(fr_dlist_head_t const *list_head, void const *ptr)
Get the next item in a list.
Definition dlist.h:537
Head of a doubly linked list.
Definition dlist.h:51
Entry in a doubly linked list.
Definition dlist.h:41
#define unlang_function_push_with_result(_result_p, _request, _func, _repeat, _signal, _sigmask, _top_frame, _uctx)
Push a generic function onto the unlang stack that produces a result.
Definition function.h:144
#define unlang_function_repeat_set(_request, _repeat)
Set a new repeat function for an existing function frame.
Definition function.h:108
free(array)
talloc_free(hp)
int fr_inet_pton_port(fr_ipaddr_t *out, uint16_t *port_out, char const *value, ssize_t inlen, int af, bool resolve, bool mask)
Parses IPv4/6 address + port, to fr_ipaddr_t and integer (port)
Definition inet.c:944
char * fr_inet_ntop(char out[static FR_IPADDR_STRLEN], size_t outlen, fr_ipaddr_t const *addr)
Print the address portion of a fr_ipaddr_t.
Definition inet.c:1025
fr_cmp_ret_t fr_ipaddr_cmp(fr_ipaddr_t const *a, fr_ipaddr_t const *b)
Compare two ip addresses.
Definition inet.c:1353
#define FR_IPADDR_STRLEN
Like INET6_ADDRSTRLEN but includes space for the textual Zone ID.
Definition inet.h:89
IPv4/6 prefix.
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
Definition interpret.c:2008
#define UNLANG_SUB_FRAME
Definition interpret.h:37
static fr_dict_t const * dict_freeradius
Definition base.c:37
fr_dict_attr_t const * attr_packet_type
Definition base.c:91
fr_dict_attr_t const * attr_redis_node_role
Definition redis.c:74
fr_dict_attr_t const * attr_redis_bootstrap_node
Definition redis.c:62
fr_dict_attr_t const * attr_redis_use_tls
Definition redis.c:76
fr_dict_attr_t const * attr_redis_slot_end
Definition redis.c:70
fr_dict_attr_t const * attr_redis_force_update
Definition redis.c:75
fr_dict_attr_t const * attr_redis_packet_type
Definition redis.c:59
fr_dict_attr_t const * attr_redis_log_prefix
Definition redis.c:60
fr_dict_attr_t const * attr_redis_node
Definition redis.c:71
fr_dict_attr_t const * attr_redis_node_port
Definition redis.c:73
fr_dict_attr_t const * attr_redis_slot
Definition redis.c:68
fr_dict_t const * dict_redis
Definition redis.c:51
fr_dict_attr_t const * attr_redis_slot_start
Definition redis.c:69
fr_dict_attr_t const * attr_redis_max_nodes
Definition redis.c:61
fr_dict_attr_t const * attr_redis_password
Definition redis.c:65
fr_dict_attr_t const * attr_redis_bootstrap_port
Definition redis.c:63
fr_dict_attr_t const * attr_redis_cluster_id
Definition redis.c:66
fr_dict_attr_t const * attr_redis_tls_conf
Definition redis.c:77
fr_dict_attr_t const * attr_redis_username
Definition redis.c:64
fr_dict_attr_t const * attr_redis_node_endpoint
Definition redis.c:72
fr_dict_attr_t const * attr_redis_shard
Definition redis.c:67
char const * password
to authenticate to Redis.
Definition io.h:56
char const * hostname
Definition io.h:51
char const * log_prefix
Definition io.h:57
uint16_t port
Definition io.h:52
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition log.h:460
#define RDEBUG3(fmt,...)
Definition log.h:360
#define RWARN(fmt,...)
Definition log.h:314
#define RERROR(fmt,...)
Definition log.h:315
#define RINDENT()
Indent R* messages by one level.
Definition log.h:447
#define fr_time()
Definition event.c:60
Stores all information relating to an event list.
Definition event.c:377
@ L_DBG_LVL_3
3rd highest priority debug messages (-xxx | -Xx).
Definition log.h:69
unsigned short uint16_t
@ FR_TYPE_INT32
32 Bit signed integer.
unsigned int uint32_t
fr_cmp_ret_t
Result of an ordering comparison.
Definition misc.h:50
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
void * thread
Thread specific instance data.
Definition module_ctx.h:43
void * rctx
Resume ctx that a module previously set.
Definition module_ctx.h:45
fr_event_list_t * el
Event list to register any IO handlers and timers against.
Definition module_ctx.h:68
void * thread
Thread instance data.
Definition module_ctx.h:67
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
Temporary structure to hold arguments for thread_instantiation calls.
Definition module_ctx.h:63
int fr_pair_list_copy(TALLOC_CTX *ctx, fr_pair_list_t *to, fr_pair_list_t const *from)
Duplicate a list of pairs.
Definition pair.c:2326
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:707
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1352
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:290
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:46
int fr_pair_value_bstrndup(fr_pair_t *vp, char const *src, size_t len, bool tainted)
Copy data into a "string" type value pair.
Definition pair.c:2812
int fr_pair_prepend_by_da(TALLOC_CTX *ctx, fr_pair_t **out, fr_pair_list_t *list, fr_dict_attr_t const *da)
Alloc a new fr_pair_t (and prepend)
Definition pair.c:1498
fr_redis_pipeline_status_t redis_command_set_enqueue(fr_redis_trunk_t *rtrunk, fr_redis_command_set_t *cmds)
Enqueue a command set on a specific trunk.
Definition pipeline.c:532
fr_redis_command_set_t * fr_redis_command_set_alloc(TALLOC_CTX *ctx, request_t *request, fr_redis_command_set_complete_t complete, fr_redis_command_set_fail_t fail, void *rctx, bool autofree)
Allocate a new command set.
Definition pipeline.c:249
void fr_redis_command_set_cancel(fr_redis_command_set_t *cmds)
Cancel a command set.
Definition pipeline.c:557
fr_redis_pipeline_status_t fr_redis_command_literal_add(fr_redis_command_set_t *cmds, char const *cmd_str, fr_redis_command_complete_t complete, void *rctx)
Add a literal command to the command set.
Definition pipeline.c:401
fr_redis_trunk_t * fr_redis_trunk_alloc(fr_redis_ct_t *rtcluster, fr_redis_io_conf_t const *io_conf, fr_pair_list_t *trigger_args, fr_redis_trunk_active_t active, void *active_uctx, bool active_oneshot)
Allocate a new trunk.
Definition pipeline.c:941
int fr_redis_command_set_clear(fr_redis_command_set_t *cmds)
Definition pipeline.c:1032
Represents a single command.
Definition pipeline.c:62
Represents a collection of pipelined commands.
Definition pipeline.c:94
@ FR_REDIS_PIPELINE_OK
No failure.
Definition pipeline.h:44
static unlang_action_t mod_process(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition base.c:168
static fr_process_state_t const process_state[]
Definition base.c:68
RECV(for_any_server)
Validate a solicit/rebind/confirm message.
Definition base.c:348
static int mod_load(void)
Definition base.c:228
static int mod_instantiate(module_inst_ctx_t const *mctx)
Definition base.c:213
static const conf_parser_t config[]
Definition base.c:162
int32_t worker_id
The worker which sent the data leading to this request.
Definition base.c:178
bool in_cluster
Has the node been found in the latest cluster map.
Definition base.c:67
static unlang_action_t process_redis_return_existing(request_t *request, process_redis_cluster_t *cluster, uint32_t worker_id)
Definition base.c:979
fr_rb_node_t node
Definition base.c:95
uint64_t current_epoch
Redis cluster epoch as reported by this node.
Definition base.c:69
static void redis_cluster_map_get_timeout(UNUSED fr_timer_list_t *el, UNUSED fr_time_t now, void *uctx)
Definition base.c:655
bool cluster_ok
Does CLUSTER INFO say the cluster is OK.
Definition base.c:167
static void redis_cluster_info_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Definition base.c:582
uint16_t port
Port of the first bootstrap server.
Definition base.c:79
bool fetching
The map is being fetched.
Definition base.c:85
static const uint32_t redis_shards_version
Definition base.c:54
static uint16_t cluster_id
Definition base.c:34
static int _process_redis_cluster_free(process_redis_cluster_t *cluster)
Definition base.c:955
map_get_status_t status
Status of the node calls.
Definition base.c:166
fr_rb_node_t cluster_by_id
Entry in the tree of clusters by ID.
Definition base.c:82
uint64_t cluster_epoch
Largest epoch value returned by any node.
Definition base.c:182
process_redis_node_t * current_node
Node currently being queried.
Definition base.c:180
process_redis_t const * inst
Module instance.
Definition base.c:175
static int fr_redis_cluster_shards_nodes_to_pairs(redisReply *reply, fr_pair_t *shard_vp)
Convert the "nodes" array in CLUSTER SHARDS replies into pairs.
Definition base.c:251
fr_coord_pair_t * coord_pair
The coord_pair which requested this cluster map.
Definition base.c:88
static void redis_cluster_slots_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Definition base.c:634
fr_dlist_head_t nodes
List of current nodes in the cluster.
Definition base.c:80
request_t * request
Definition base.c:94
fr_redis_trunk_t * trunk
Trunk connection for this node.
Definition base.c:66
static unlang_action_t process_redis_return_failed(request_t *request, process_redis_cluster_t *cluster, uint32_t worker_id)
Send a Cluster-Failed message to a worker.
Definition base.c:692
static void process_redis_pending_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
Definition base.c:966
#define FR_REDIS_PACKET_CODE_VALID(_code)
Definition base.c:185
uint32_t version
Redis version on this node.
Definition base.c:68
fr_dlist_t entry
In list of resume contexts.
Definition base.c:165
fr_timer_t * ev
Timeout event for this node.
Definition base.c:170
fr_redis_ct_t * rtcluster
Cluster used to allocate redis trunk connections.
Definition base.c:83
fr_dlist_t entry
Entry in the list of cluster nodes.
Definition base.c:64
static fr_cmp_ret_t process_redis_pending_cmp(void const *a, void const *b)
Definition base.c:118
fr_event_list_t * el
Definition base.c:137
char const * inst_name
Definition base.c:133
fr_rb_tree_t pending
Requests waiting for custer map update.
Definition base.c:87
fr_redis_command_set_t * cmds
Command set for fetching cluster map.
Definition base.c:169
static fr_dict_attr_t const * attr_worker_id
Definition base.c:45
static fr_cmp_ret_t cluster_server_cmp(void const *a, void const *b)
Definition base.c:98
fr_process_module_t process_redis
Definition base.c:1236
process_redis_cluster_t * cluster
Cluster which is being updated.
Definition base.c:179
bool failed
Has the cluster failed.
Definition base.c:89
static void redis_cluster_map_get_cancel(request_t *request, UNUSED fr_signal_t action, void *uctx)
Definition base.c:943
fr_time_t last_update
When was the map last updated.
Definition base.c:86
fr_time_delta_t retry_interval
Definition base.c:131
fr_redis_io_conf_t io_conf
Connection config for this node.
Definition base.c:65
static unlang_action_t redis_cluster_map_get(UNUSED unlang_result_t *p_result, request_t *request, void *uctx)
Definition base.c:707
uint16_t cluster_id
Numeric ID assigned by the coordinator.
Definition base.c:77
fr_time_delta_t refresh_interval
Definition base.c:132
fr_redis_conf_t * conf
Redis config for this cluster.
Definition base.c:76
static fr_cmp_ret_t cluster_id_cmp(void const *a, void const *b)
Definition base.c:110
static int process_redis_cluster_node_add(TALLOC_CTX *ctx, process_redis_cluster_t *cluster, process_redis_t const *inst, fr_redis_conf_t *conf, fr_pair_t *host_vp, fr_pair_t *port_vp)
Definition base.c:528
static void redis_cluster_info_server_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Definition base.c:570
fr_rb_node_t cluster_by_server
Entry in the tree of clusters by bootstrap server.
Definition base.c:81
fr_dict_attr_autoload_t process_redis_dict_attr[]
Definition base.c:48
fr_time_delta_t timeout
Definition base.c:130
fr_pair_list_t cluster_pairs
Pairs built from the last fetch.
Definition base.c:84
process_redis_sections_t sections
Definition base.c:127
module_method_t const * method
Definition base.c:128
process_redis_node_t * node
Node being queried.
Definition base.c:164
fr_timer_t * ev
Timer event for retry / refresh.
Definition base.c:90
static unlang_action_t redis_cluster_map_get_resume(UNUSED unlang_result_t *p_result, request_t *request, void *uctx)
Definition base.c:762
static int fr_redis_cluster_shards_to_pairs(TALLOC_CTX *ctx, request_t *request, fr_pair_list_t *list, redisReply *reply)
Convert the reply to CLUSTER SHARDS into pairs.
Definition base.c:357
map_get_status_t
State of cluster map fetching from each node.
Definition base.c:152
@ CLUSTER_MAP_GET_MAP
Definition base.c:155
@ CLUSTER_MAP_GET_INFO
Definition base.c:153
@ CLUSTER_MAP_GET_FAILED
Definition base.c:157
@ CLUSTER_MAP_GOT_MAP
Definition base.c:156
@ CLUSTER_MAP_GET_TIMEOUT
Definition base.c:158
@ CLUSTER_MAP_GOT_INFO
Definition base.c:154
fr_pair_list_t list
To populate with parsed reply data.
Definition base.c:168
unlang_result_t result
Where results are written to.
Definition base.c:177
process_redis_thread_t * thread
Thread instance.
Definition base.c:176
fr_rb_tree_t cluster_by_id
Tree of clusters by ID.
Definition base.c:139
fr_ipaddr_t addr
IP address of the first bootstrap server.
Definition base.c:78
fr_pair_list_t trigger_args
Pair list to pass to trigger.
Definition base.c:70
trunk_conf_t trunk_conf
Definition base.c:129
fr_dict_autoload_t process_redis_dict[]
Definition base.c:39
fr_dlist_head_t rctx_list
List of per-node resume contexts.
Definition base.c:181
CONF_SECTION * cluster_map_get
Definition base.c:58
fr_rb_tree_t cluster_by_server
Tree of clusters by primary bootstrap server.
Definition base.c:138
static void redis_cluster_map_get_refetch(UNUSED fr_timer_list_t *tl, fr_time_t now, void *uctx)
Definition base.c:664
static int fr_redis_cluster_slots_to_pairs(TALLOC_CTX *ctx, request_t *request, fr_pair_list_t *list, redisReply *reply)
Convert the reply to CLUSER SLOTS into pairs.
Definition base.c:450
static int fr_redis_cluster_shards_slots_to_pairs(redisReply *reply, fr_pair_t *shard_vp, bool slots_covered[KEY_SLOTS])
Convert the "slots" array in CLUSTER SHARDS replies into pairs.
Definition base.c:206
Coordinator representation of a Redis cluster.
Definition base.c:75
Resume context for node specific calls.
Definition base.c:163
Individual cluster node.
Definition base.c:63
Resume context for Redis requests.
Definition base.c:174
#define PROCESS_TRACE
Trace each state function as it's entered.
Definition process.h:55
module_t common
Common fields for all loadable modules.
Common public symbol definition for all process modules.
#define fr_assert(_expr)
Definition rad_assert.h:37
#define REDEBUG(fmt,...)
#define RDEBUG_ENABLED2()
#define RDEBUG2(fmt,...)
#define DEBUG2(fmt,...)
static rs_t * conf
Definition radsniff.c:52
uint32_t fr_rb_num_elements(fr_rb_tree_t *tree)
Return how many nodes there are in a tree.
Definition rb.c:807
int fr_rb_remove(void **removed, fr_rb_tree_t *tree, void const *data)
Remove an entry from the tree, without freeing the data.
Definition rb.c:718
int fr_rb_find(void **found, fr_rb_tree_t const *tree, void const *data)
Find an element in the tree, returning the data, not the node.
Definition rb.c:586
void * fr_rb_iter_init_inorder(fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Initialise an in-order iterator.
Definition rb.c:850
void fr_rb_iter_delete_inorder(fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Remove the current node from the tree.
Definition rb.c:925
int fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
Definition rb.c:637
void * fr_rb_iter_next_inorder(UNUSED fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Return the next node.
Definition rb.c:876
#define fr_rb_inline_init(_tree, _type, _field, _data_cmp, _data_free)
Initialises a red black tree.
Definition rb.h:178
Iterator structure for in-order traversal of an rbtree.
Definition rb.h:319
The main red black tree structure.
Definition rb.h:71
#define RETURN_UNLANG_FAIL
Definition rcode.h:63
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
Definition rcode.h:54
void fr_redis_reply_print(fr_log_lvl_t lvl, redisReply *reply, request_t *request, int idx, fr_redis_rcode_t status)
Print the response data in a useful treelike form.
Definition redis.c:229
#define REDIS_VERSION(_max, _min, _patch)
Definition base.h:53
uint8_t max_nodes
Maximum number of cluster nodes to connect to.
Definition base.h:124
fr_redis_rcode_t fr_redis_parse_version(char *out, size_t out_len, redisReply *reply)
Parse the reply from the Redis command INFO SERVER to extract the version.
Definition redis.c:611
int redis_dict_init(void)
Load the Redis dictionaries.
Definition redis.c:136
uint32_t fr_redis_version_num(char const *version)
Convert version string into a 32bit unsigned integer for comparisons.
Definition redis.c:643
@ FR_REDIS_CLUSTER_MAP_BOOTSTRAP
Definition base.h:102
@ FR_REDIS_CLUSTER_MAP_GET
Definition base.h:103
@ FR_REDIS_CLUSTER_MAP_UPDATE
Definition base.h:104
@ FR_REDIS_CLUSTER_MAP_FAIL
Definition base.h:105
fr_table_num_sorted_t const redis_reply_types[]
Definition redis.c:31
@ REDIS_RCODE_SUCCESS
Operation was successful.
Definition base.h:70
Configuration parameters for a redis connection.
Definition base.h:114
static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
#define fr_sbuff_adv_past_str_literal(_sbuff, _needle)
#define fr_sbuff_adv_to_str_literal(_sbuff, _len, _needle)
#define fr_sbuff_advance(_sbuff_or_marker, _len)
#define fr_sbuff_init_in(_out, _start, _len_or_end)
static _Thread_local int worker_id
Internal ID of the current worker thread.
Definition schedule.c:104
#define MODULE_THREAD_INST(_ctype)
Definition module.h:258
char const * name
Instance name e.g. user_database.
Definition module.h:357
void * data
Module's instance data.
Definition module.h:293
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 MODULE_RCTX(_ctype)
Definition module.h:259
#define MODULE_INST(_ctype)
Definition module.h:257
conf_parser_t const * config
How to convert a CONF_SECTION to a module instance.
Definition module.h:206
fr_signal_t
Signals that can be generated/processed by request signal handlers.
Definition signal.h:38
@ FR_SIGNAL_CANCEL
Request has been cancelled.
Definition signal.h:40
static char buff[sizeof("18446744073709551615")+3]
Definition size_tests.c:37
unlang_action_t unlang_module_yield(request_t *request, module_method_t resume, unlang_module_signal_t signal, fr_signal_t sigmask, void *rctx)
Yield a request back to the interpreter from within a module.
Definition module.c:431
eap_aka_sim_process_conf_t * inst
fr_pair_t * vp
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:804
#define talloc_get_type_abort_const
Definition talloc.h:117
#define talloc_asprintf
Definition talloc.h:151
#define talloc_strdup(_ctx, _str)
Definition talloc.h:149
void * state
Definition testlib.c:46
static int64_t fr_time_to_sec(fr_time_t when)
Convert an fr_time_t (internal time) to number of sec since the unix epoch (wallclock time)
Definition time.h:731
#define fr_time_wrap(_time)
Definition time.h:145
#define fr_time_delta_ispos(_a)
Definition time.h:290
#define fr_time_gt(_a, _b)
Definition time.h:237
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
"server local" time.
Definition time.h:69
An event timer list.
Definition timer.c:49
A timer event.
Definition timer.c:83
#define fr_timer_in(...)
Definition timer.h:87
static bool fr_timer_armed(fr_timer_t *ev)
Definition timer.h:120
int module_trigger_args_build(TALLOC_CTX *ctx, fr_pair_list_t *list, CONF_SECTION const *cs, module_trigger_args_t *args)
Build trigger args pair list for modules.
Definition trigger.c:504
Common values used by modules when building trigger args.
Definition trigger.h:42
conf_parser_t const trunk_config[]
Config parser definitions to populate a trunk_conf_t.
Definition trunk.c:345
Common configuration parameters for a trunk.
Definition trunk.h:234
static fr_event_list_t * el
fr_pair_t * fr_pair_remove(fr_pair_list_t *list, fr_pair_t *vp)
Remove fr_pair_t from a list without freeing.
Definition pair_inline.c:93
#define fr_pair_list_foreach(_list_head, _iter)
Iterate over the contents of a fr_pair_list_t.
Definition pair.h:279
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
#define fr_pair_list_append_by_da(_ctx, _vp, _list, _attr, _val, _tainted)
Append a pair to a list, assigning its value.
Definition pair.h:304
size_t fr_pair_list_num_elements(fr_pair_list_t const *list)
Get the length of a list of fr_pair_t.