The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_redis.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 (at
5 * 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: b6c150b7886e838c6e3eb79dc11c9b5a7edcff67 $
19 * @file rlm_redis.c
20 * @brief Driver for the Redis noSQL key value store.
21 *
22 * @author Gabriel Blanchard
23 *
24 * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
25 * @copyright 2011 TekSavvy Solutions (gabe@teksavvy.com)
26 * @copyright 2000,2006,2015 The FreeRADIUS server project
27 */
28
29RCSID("$Id: b6c150b7886e838c6e3eb79dc11c9b5a7edcff67 $")
30
31#include <assert.h>
32#include <stdint.h>
33
34#include <freeradius-devel/redis/base.h>
35#include <freeradius-devel/redis/cluster.h>
36
37#include <freeradius-devel/server/modpriv.h>
38#include <freeradius-devel/server/module_rlm.h>
39
40#include <freeradius-devel/unlang/xlat_func.h>
41
42#include <freeradius-devel/util/base16.h>
43#include <freeradius-devel/util/debug.h>
44#include <freeradius-devel/util/types.h>
45
46/** A lua function or stored procedure we make available as an xlat
47 *
48 */
49typedef struct {
50 char const *name; //!< Friendly name for the function. Used to register the equivalent xlat.
51 char digest[(SHA1_DIGEST_LENGTH * 2) + 1]; //!< pre-computed hash of lua code.
52 char const *body; //!< the actual lua code.
53 bool read_only; //!< Function has no side effects
55
56/** Instance of a redis lua func xlat
57 *
58 */
59typedef struct {
60 redis_lua_func_t *func; //!< Function configuration.
62
63
64typedef struct {
65 redis_lua_func_t **funcs; //!< Array of functions to register.
66
68
69/** rlm_redis module instance
70 *
71 */
72typedef struct {
73 fr_redis_conf_t conf; //!< Connection parameters for the Redis server.
74 //!< Must be first field in this struct.
75
76 rlm_redis_lua_t lua; //!< Array of functions to register.
77
78 fr_redis_cluster_t *cluster; //!< Redis cluster.
80
81static int lua_func_body_parse(TALLOC_CTX *ctx, void *out, void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
82
88
95
101
102/** Do basic processing for a lua function body and compute its sha1 hash
103 *
104 */
105static int lua_func_body_parse(TALLOC_CTX *ctx, void *out, void *parent, CONF_ITEM *ci, conf_parser_t const *rule)
106{
107 int ret;
108 redis_lua_func_t *func = talloc_get_type_abort(parent, redis_lua_func_t);
109 char const *body;
110 fr_sha1_ctx sha1_ctx;
112
113 /*
114 * Get the function name from name2
115 * of the enclosing function section.
116 */
118 if (unlikely(!func->name)) {
119 cf_log_err(cf_parent(ci), "functions must be declared as \"function <name> {\"");
120 return -1;
121 }
122
123 /*
124 * Perform normal string parsing first
125 */
126 if ((ret = cf_pair_parse_value(ctx, out, parent, ci, rule)) < 0) return ret;
127 body = *((char **)out);
128
129 fr_sha1_init(&sha1_ctx);
130 fr_sha1_update(&sha1_ctx, (uint8_t const *)body, talloc_strlen(body));
131 fr_sha1_final(digest, &sha1_ctx);
132 fr_base16_encode(&FR_SBUFF_OUT(func->digest, sizeof(func->digest)), &FR_DBUFF_TMP(digest, sizeof(digest)));
133
134 if (DEBUG_ENABLED3) cf_log_debug(ci, "sha1 hash of function is %pV", fr_box_strvalue_len(func->digest, sizeof(func->digest) - 1));
135
136 return 0;
137}
138
139/** Issue a command against redis and get a response
140 *
141 * This is a convenience function for dealing with commands which made need to execute against an
142 * ldap replica. It temporarily places the connection in readonly mode to allow commands to be
143 * run against ldap replicas, then reverts back to readwrite mode.
144 *
145 * @param[out] status_out Where to write the status from the command.
146 * @param[out] reply_out Where to write the reply associated with the highest priority status.
147 * @param[in] request The current request.
148 * @param[in] conn to issue commands with.
149 * @param[in] read_only wrap command in READONLY/READWRITE.
150 * @param[in] argc Redis command argument count.
151 * @param[in] argv Redis command arguments.
152 * @param[in] arg_len Optional array of redis command argument length.
153 * @return
154 * - 0 success.
155 * - -1 normal failure.
156 * - -2 failure that may leave the connection in a READONLY state.
157 */
158static int redis_command(fr_redis_rcode_t *status_out, redisReply **reply_out,
159 request_t *request, fr_redis_conn_t *conn,
160 bool read_only,
161 int argc, char const **argv, size_t arg_len[])
162{
163 bool maybe_more = false;
164 redisReply *reply;
165 fr_redis_rcode_t status;
166
167 *reply_out = NULL;
168
169 if (read_only) redisAppendCommand(conn->handle, "READONLY");
170 redisAppendCommandArgv(conn->handle, argc, argv, arg_len);
171 if (read_only) {
172 redisAppendCommand(conn->handle, "READWRITE");
173 } else goto parse_reply;
174
175
176 /*
177 * Process the response for READONLY
178 */
179 reply = NULL; /* Doesn't set reply to NULL on error *sigh* */
180 if (redisGetReply(conn->handle, (void **)&reply) == REDIS_OK) maybe_more = true;
181 status = fr_redis_command_status(conn, reply);
182 if (status != REDIS_RCODE_SUCCESS) {
183 ROPTIONAL(REDEBUG, ERROR, "Setting READONLY failed");
184
185 *reply_out = reply;
186 *status_out = status;
187
188 if (maybe_more) {
189 if (redisGetReply(conn->handle, (void **)&reply) != REDIS_OK) return -1;
190 fr_redis_reply_free(&reply);
191 if (redisGetReply(conn->handle, (void **)&reply) != REDIS_OK) return -1;
192 fr_redis_reply_free(&reply);
193 }
194 return -1;
195 }
196
197 fr_redis_reply_free(&reply);
198
199parse_reply:
200 /*
201 * Process the response for the command
202 */
203 reply = NULL; /* Doesn't set reply to NULL on error *sigh* */
204 if ((redisGetReply(conn->handle, (void **)&reply) == REDIS_OK) && read_only) maybe_more = true;
205 status = fr_redis_command_status(conn, reply);
206 if (status != REDIS_RCODE_SUCCESS) {
207 *reply_out = reply;
208 *status_out = status;
209
210 if (maybe_more) {
211 if (redisGetReply(conn->handle, (void **)&reply) != REDIS_OK) return -1;
212 fr_redis_reply_free(&reply);
213 }
214 return -1;
215 }
216
217 *reply_out = reply;
218 reply = NULL;
219 *status_out = status;
220
221 if (!read_only) return 0; /* No more responses to deal with */
222
223 /*
224 * Process the response for READWRITE
225 */
226 reply = NULL; /* Doesn't set reply to NULL on error *sigh* */
227 if ((redisGetReply(conn->handle, (void **)&reply) != REDIS_OK) ||
229 ROPTIONAL(REDEBUG, ERROR, "Setting READWRITE failed");
230
231 fr_redis_reply_free(&reply); /* There could be a response we need to free */
232 fr_redis_reply_free(reply_out);
233 *reply_out = reply;
234 *status_out = status;
235
236 return -2;
237 }
238 fr_redis_reply_free(&reply); /* Free READWRITE response */
239
240 return 0;
241}
242
244 { .required = true, .concat = true, .type = FR_TYPE_STRING },
246};
247
248/** Force a redis cluster remap
249 *
250@verbatim
251%redis.remap(<redis server ip>:<redis server port>)
252@endverbatim
253 *
254 * @ingroup xlat_functions
255 */
257 xlat_ctx_t const *xctx,
258 request_t *request, fr_value_box_list_t *in)
259{
261
262 fr_socket_t node_addr;
263 fr_pool_t *pool;
264 fr_redis_conn_t *conn;
266 fr_value_box_t *vb;
267 fr_value_box_t *in_head = fr_value_box_list_head(in);
268
269 if (fr_inet_pton_port(&node_addr.inet.dst_ipaddr, &node_addr.inet.dst_port, in_head->vb_strvalue, in_head->vb_length,
270 AF_UNSPEC, true, true) < 0) {
271 RPEDEBUG("Failed parsing node address");
272 return XLAT_ACTION_FAIL;
273 }
274
275 if (fr_redis_cluster_pool_by_node_addr(&pool, inst->cluster, &node_addr, true) < 0) {
276 RPEDEBUG("Failed locating cluster node");
277 return XLAT_ACTION_FAIL;
278 }
279
280 conn = fr_pool_connection_get(pool, request);
281 if (!conn) {
282 REDEBUG("No connections available for cluster node");
283 return XLAT_ACTION_FAIL;
284 }
285
286 rcode = fr_redis_cluster_remap(request, inst->cluster, conn);
287 switch (rcode) {
289 fr_pool_connection_close(pool, request, conn);
290 break;
291
292 default:
293 fr_pool_connection_release(pool, request, conn);
294 break;
295 }
296
297 MEM(vb = fr_value_box_alloc_null(ctx));
298 fr_value_box_strdup(vb, vb, NULL, fr_table_str_by_value(fr_redis_cluster_rcodes_table, rcode, "<INVALID>"), false);
300
301 return XLAT_ACTION_DONE;
302}
303
305 { .required = true, .single = true, .type = FR_TYPE_STRING },
306 { .single = true, .type = FR_TYPE_UINT32 },
308};
309
310/** Return the node that is currently servicing a particular key
311 *
312@verbatim
313%redis.node(<key>[, <index>])
314@endverbatim
315 *
316 * @ingroup xlat_functions
317 */
319 xlat_ctx_t const *xctx,
320 request_t *request, fr_value_box_list_t *in)
321{
323
324 fr_redis_cluster_key_slot_t const *key_slot;
325 fr_redis_cluster_node_t const *node;
326 fr_ipaddr_t ipaddr;
327 uint16_t port;
328
329 unsigned long idx = 0;
330 fr_value_box_t *vb;
331 fr_value_box_t *key = fr_value_box_list_head(in);
332 fr_value_box_t *idx_vb = fr_value_box_list_next(in, key);
333
334 if (idx_vb) idx = idx_vb->vb_uint32;
335
336 key_slot = fr_redis_cluster_slot_by_key(inst->cluster, request, (uint8_t const *)key->vb_strvalue,
337 key->vb_length);
338 if (idx == 0) {
339 node = fr_redis_cluster_master(inst->cluster, key_slot);
340 } else {
341 node = fr_redis_cluster_slave(inst->cluster, key_slot, idx - 1);
342 }
343
344 if (!node) {
345 RDEBUG2("No node available for this key slot");
346 return XLAT_ACTION_DONE;
347 }
348
349 if ((fr_redis_cluster_ipaddr(&ipaddr, node) < 0) || (fr_redis_cluster_port(&port, node) < 0)) {
350 REDEBUG("Failed retrieving node information");
351 return XLAT_ACTION_FAIL;
352 }
353
354 MEM(vb = fr_value_box_alloc_null(ctx));
355 fr_value_box_asprintf(vb, vb, NULL, false, "%pV:%u", fr_box_ipaddr(ipaddr), port);
357
358 return XLAT_ACTION_DONE;
359}
360
362 { .required = true, .single = true, .type = FR_TYPE_UINT64 }, /* key count */
363 { .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .concat = true, .type = FR_TYPE_STRING }, /* keys and args */
365};
366
367/** Call a lua function on the redis server
368 *
369 * Lua functions either get uploaded when the module is instantiated or the first
370 * time they get executed.
371 */
373 xlat_ctx_t const *xctx,
374 request_t *request, fr_value_box_list_t *in)
375{
376 rlm_redis_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_t);
378 redis_lua_func_t *func = xlat_inst->func;
379
380 fr_redis_conn_t *conn;
382 fr_redis_rcode_t status;
383
384 redisReply *reply = NULL;
385 int s_ret;
386
387 char const *argv[MAX_REDIS_ARGS];
388 size_t arg_len[MAX_REDIS_ARGS];
389 int argc;
390 char key_count[sizeof("184467440737095551615")];
391 uint8_t const *key = NULL;
392 size_t key_len = 0;
393
395 fr_value_box_t *vb_out;
396
397 /*
398 * First argument is always the key count
399 */
400 if (unlikely(fr_value_box_print(&FR_SBUFF_OUT(key_count, sizeof(key_count)), fr_value_box_list_head(in), NULL) < 0)) {
401 RPERROR("Failed converting key count to string");
402 return XLAT_ACTION_FAIL;
403 }
404 fr_value_box_list_talloc_free_head(in);
405
406 /*
407 * Try EVALSHA first, and if that fails fall back to SCRIPT LOAD
408 */
409 argv[0] = "EVALSHA";
410 arg_len[0] = sizeof("EVALSHA") - 1;
411 argv[1] = func->digest;
412 arg_len[1] = sizeof(func->digest) - 1;
413 argv[2] = key_count;
414 arg_len[2] = strlen(key_count);
415 argc = 3;
416
418 if (argc == NUM_ELEMENTS(argv)) {
419 REDEBUG("Too many arguments (%i)", argc);
420 REXDENT();
421 return XLAT_ACTION_FAIL;
422 }
423
424 /*
425 * Fixup null or empty arguments to be
426 * zero length strings so that the position
427 * of subsequent arguments are maintained.
428 */
429 if (!fr_type_is_string(vb->type)) {
430 argv[argc] = "";
431 arg_len[argc++] = 0;
432 continue;
433 }
434
435 argv[argc] = vb->vb_strvalue;
436 arg_len[argc++] = vb->vb_length;
437 }
438
439 /*
440 * For eval commands all keys should hash to the same redis instance
441 * so we just use the first key (the arg after the key count).
442 */
443 if (argc > 3) {
444 key = (uint8_t const *)argv[3];
445 key_len = arg_len[3];
446 }
447
448 for (s_ret = fr_redis_cluster_state_init(&state, &conn, inst->cluster, request, key, key_len, func->read_only);
449 s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */
450 s_ret = fr_redis_cluster_state_next(&state, &conn, inst->cluster, request, status, &reply)) {
451 bool script_load_done = false;
452
453 again:
454 RDEBUG3("Calling script 0x%s", func->digest);
455 if (argc > 2) {
456 RDEBUG3("With arguments");
457 RINDENT();
458 for (int i = 2; i < argc; i++) RDEBUG3("[%i] %s", i, argv[i]);
459 REXDENT();
460 }
461 if (redis_command(&status, &reply, request, conn,
462 func->read_only, argc, argv, arg_len) == -2) {
463 state.close_conn = true;
464 }
465
466 if (status != REDIS_RCODE_NO_SCRIPT) continue;
467
468 /*
469 * Discard the error we received, and attempt load the function.
470 */
471 fr_redis_reply_free(&reply);
472
473 RDEBUG3("Loading lua function \"%s\" (0x%s)", func->name, func->digest);
474 {
475 char const *script_load_argv[] = {
476 "SCRIPT",
477 "LOAD",
478 func->body
479 };
480
481 size_t script_load_arg_len[] = {
482 (sizeof("SCRIPT") - 1),
483 (sizeof("LOAD") - 1),
484 (talloc_strlen(func->body))
485 };
486
487 /*
488 * Loading the script failed... fail the call.
489 */
490 if (script_load_done) {
491 script_load_failed:
492 status = REDIS_RCODE_ERROR;
493 fr_redis_reply_free(&reply);
494 continue;
495 }
496
497 /*
498 * Fixme: Really the script load and the eval call should be
499 * handled in a single MULTI/EXEC block, but the complexity
500 * in handling this properly is great, and most of this
501 * synchronous code will need to be rewritten, so for now
502 * we just load the script and try again.
503 */
504 if (redis_command(&status, &reply, request, conn, func->read_only,
505 NUM_ELEMENTS(script_load_argv),
506 script_load_argv, script_load_arg_len) == -2) {
507 state.close_conn = true;
508 }
509
510 if (status == REDIS_RCODE_SUCCESS) {
511 script_load_done = true;
512
513 /*
514 * Verify we got a sane response
515 */
516 if (reply->type != REDIS_REPLY_STRING) {
517 REDEBUG("Unexpected reply type after loading function");
518 fr_redis_reply_print(L_DBG_LVL_OFF, reply, request, 0, status);
519 goto script_load_failed;
520 }
521
522 if (strcmp(reply->str, func->digest) != 0) {
523 REDEBUG("Function digest %s, does not match calculated digest %s", reply->str, func->digest);
524 goto script_load_failed;
525 }
526 fr_redis_reply_free(&reply);
527 goto again;
528 }
529 }
530 }
531
532 if (s_ret != REDIS_RCODE_SUCCESS) {
533 action = XLAT_ACTION_FAIL;
534 goto finish;
535 }
536
537 if (!fr_cond_assert(reply)) {
538 action = XLAT_ACTION_FAIL;
539 goto finish;
540 }
541
542 MEM(vb_out = fr_value_box_alloc_null(ctx));
543 if (fr_redis_reply_to_value_box(ctx, vb_out, reply, FR_TYPE_VOID, NULL, false, false) < 0) {
544 RPERROR("Failed processing reply");
545 action = XLAT_ACTION_FAIL;
546 goto finish;
547 }
548
549 if (vb_out->type == FR_TYPE_GROUP) {
550 fr_value_box_t *child_vb = NULL;
551 while ((child_vb = fr_value_box_list_pop_head(&vb_out->vb_group))) fr_dcursor_append(out, child_vb);
552 talloc_free(vb_out);
553 } else {
554 fr_dcursor_append(out, vb_out);
555 }
556
557finish:
558 fr_redis_reply_free(&reply);
559
560 return action;
561}
562
563/** Copies the function configuration into xlat function instance data
564 *
565 */
567{
568 redis_lua_func_inst_t *inst = talloc_get_type_abort(xctx->inst, redis_lua_func_inst_t);
569
570 inst->func = talloc_get_type_abort(xctx->uctx, redis_lua_func_t);
571
572 return 0;
573}
574
576 { .required = true, .concat = true, .type = FR_TYPE_STRING },
577 { .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .concat = true, .type = FR_TYPE_STRING },
579};
580
581/** Xlat to make calls to redis
582 *
583@verbatim
584%redis(<redis command>)
585@endverbatim
586 *
587 * @ingroup xlat_functions
588 */
589static xlat_action_t redis_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out,
590 xlat_ctx_t const *xctx,
591 request_t *request, fr_value_box_list_t *in)
592{
595 fr_redis_conn_t *conn;
596
597 bool read_only = false;
598 uint8_t const *key = NULL;
599 size_t key_len = 0;
600
602 fr_redis_rcode_t status;
603
604 redisReply *reply = NULL;
605 int s_ret;
606
607 fr_value_box_t *first = fr_value_box_list_head(in);
608 fr_sbuff_t sbuff = FR_SBUFF_IN(first->vb_strvalue, first->vb_length);
609
610 int argc = 0;
611 char const *argv[MAX_REDIS_ARGS];
612 size_t arg_len[MAX_REDIS_ARGS];
613
614 fr_value_box_t *vb_out;
615
616 if (fr_sbuff_next_if_char(&sbuff, '-')) read_only = true;
617
618 /*
619 * Hack to allow querying against a specific node for testing
620 */
621 if (fr_sbuff_next_if_char(&sbuff, '@')) {
622 fr_socket_t node_addr;
623 fr_pool_t *pool;
624
625 RDEBUG3("Overriding node selection");
626
627 if (fr_inet_pton_port(&node_addr.inet.dst_ipaddr, &node_addr.inet.dst_port,
628 fr_sbuff_current(&sbuff), fr_sbuff_remaining(&sbuff),
629 AF_UNSPEC, true, true) < 0) {
630 RPEDEBUG("Failed parsing node address");
631 return XLAT_ACTION_FAIL;
632 }
633
634 if (fr_redis_cluster_pool_by_node_addr(&pool, inst->cluster, &node_addr, true) < 0) {
635 RPEDEBUG("Failed locating cluster node");
636 return XLAT_ACTION_FAIL;
637 }
638
639 conn = fr_pool_connection_get(pool, request);
640 if (!conn) {
641 REDEBUG("No connections available for cluster node");
642 return XLAT_ACTION_FAIL;
643 }
644
645 fr_value_box_list_talloc_free_head(in); /* Remove and free server arg */
646
648 if (argc == NUM_ELEMENTS(argv)) {
649 REDEBUG("Too many arguments (%i)", argc);
650 REXDENT();
651 goto fail;
652 }
653
654 /*
655 * Fixup null or empty arguments to be
656 * zero length strings so that the position
657 * of subsequent arguments are maintained.
658 */
659 if (!fr_type_is_string(vb->type)) {
660 argv[argc] = "";
661 arg_len[argc++] = 0;
662 continue;
663 }
664
665 argv[argc] = vb->vb_strvalue;
666 arg_len[argc++] = vb->vb_length;
667 }
668
669 RDEBUG2("Executing command: %pV", fr_value_box_list_head(in));
670 if (argc > 1) {
671 RDEBUG2("With arguments");
672 RINDENT();
673 for (int i = 1; i < argc; i++) RDEBUG2("[%i] %s", i, argv[i]);
674 REXDENT();
675 }
676
677 if (redis_command(&status, &reply, request, conn, read_only, argc, argv, arg_len) == -2) {
678 goto close_conn;
679 }
680
681 if (!reply) goto fail;
682
683 switch (status) {
684 case REDIS_RCODE_MOVE:
685 {
687
688 if (fr_redis_reply_to_value_box(NULL, &vb, reply, FR_TYPE_STRING, NULL, false, true) == 0) {
689 REDEBUG("Key served by a different node: %pV", &vb);
690 }
691 goto fail;
692 }
693
695 goto reply_parse;
696
698 close_conn:
699 fr_pool_connection_close(pool, request, conn);
700 action = XLAT_ACTION_FAIL;
701 goto finish;
702
703 default:
704 fail:
705 fr_pool_connection_release(pool, request, conn);
706 action = XLAT_ACTION_FAIL;
707 goto finish;
708 }
709 }
710
712 if (argc == NUM_ELEMENTS(argv)) {
713 REDEBUG("Too many arguments (%i)", argc);
714 REXDENT();
715 goto finish;
716 }
717
718 argv[argc] = vb->vb_strvalue;
719 arg_len[argc] = vb->vb_length;
720 argc++;
721 }
722
723 /*
724 * If we've got multiple arguments, the second one is usually the key.
725 * The Redis docs say commands should be analysed first to get key
726 * positions, but this involves sending them to the server, which is
727 * just as expensive as sending them to the wrong server and receiving
728 * a redirect.
729 */
730 if (argc > 1) {
731 key = (uint8_t const *)argv[1];
732 key_len = arg_len[1];
733 }
734
735 for (s_ret = fr_redis_cluster_state_init(&state, &conn, inst->cluster, request, key, key_len, read_only);
736 s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */
737 s_ret = fr_redis_cluster_state_next(&state, &conn, inst->cluster, request, status, &reply)) {
738 RDEBUG2("Executing command: %pV", fr_value_box_list_head(in));
739 if (argc > 1) {
740 RDEBUG2("With arguments");
741 RINDENT();
742 for (int i = 1; i < argc; i++) RDEBUG2("[%i] %s", i, argv[i]);
743 REXDENT();
744 }
745
746 if (redis_command(&status, &reply, request, conn, read_only, argc, argv, arg_len) == -2) {
747 state.close_conn = true;
748 }
749 }
750 if (s_ret != REDIS_RCODE_SUCCESS) {
751 action = XLAT_ACTION_FAIL;
752 goto finish;
753 }
754
755 if (!fr_cond_assert(reply)) {
756 action = XLAT_ACTION_FAIL;
757 goto finish;
758 }
759
760reply_parse:
761 MEM(vb_out = fr_value_box_alloc_null(ctx));
762 if (fr_redis_reply_to_value_box(ctx, vb_out, reply, FR_TYPE_VOID, NULL, false, false) < 0) {
763 RPERROR("Failed processing reply");
764 action = XLAT_ACTION_FAIL;
765 goto finish;
766 }
767
768 if (vb_out->type == FR_TYPE_GROUP) {
769 fr_value_box_t *child_vb = NULL;
770 while ((child_vb = fr_value_box_list_pop_head(&vb_out->vb_group))) fr_dcursor_append(out, child_vb);
771 talloc_free(vb_out);
772 } else {
773 fr_dcursor_append(out, vb_out);
774 }
775
776finish:
777 fr_redis_reply_free(&reply);
778
779 return action;
780}
781
782static int mod_instantiate(module_inst_ctx_t const *mctx)
783{
784 rlm_redis_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_t);
785 fr_socket_t *nodes;
786 int ret, i;
787
788 inst->cluster = fr_redis_cluster_alloc(inst, mctx->mi->conf, &inst->conf, NULL, NULL, NULL);
789 if (!inst->cluster) return -1;
790
791 /*
792 * Best effort - Try and load in scripts on startup
793 */
794 if (talloc_array_length(inst->lua.funcs) == 0) return 0;
795
796 ret = fr_redis_cluster_node_addr_by_role(NULL, &nodes, inst->cluster, true, true);
797 if (ret <= 0) return 0;
798
799 for (i = 0; i < ret; i++) {
800 fr_pool_t *pool;
801 fr_redis_conn_t *conn;
802
803 if (fr_redis_cluster_pool_by_node_addr(&pool, inst->cluster, &nodes[i], true) < 0) {
804 talloc_free(nodes);
805 return 0;
806 }
807
808 conn = fr_pool_connection_get(pool, 0);
809 if (!conn) continue;
810
811 talloc_foreach(inst->lua.funcs, func) {
812 char const *script_load_argv[] = {
813 "SCRIPT",
814 "LOAD",
815 func->body
816 };
817
818 size_t script_load_arg_len[] = {
819 (sizeof("SCRIPT") - 1),
820 (sizeof("LOAD") - 1),
821 (talloc_strlen(func->body))
822 };
823
824 fr_redis_rcode_t status;
825 redisReply *reply;
826
827 /*
828 * preload onto every node, even replicas.
829 */
830 if (redis_command(&status, &reply, NULL, conn, false,
831 NUM_ELEMENTS(script_load_argv), script_load_argv, script_load_arg_len) == -2) {
832 error:
833 fr_pool_connection_release(pool, NULL, conn);
834 talloc_free(nodes);
835 return -1;
836 }
837
838 fr_redis_reply_free(&reply);
839
840 /*
841 * Only error on explicit errors, not on connectivity issues
842 */
843 switch (status) {
845 PERROR("Loading lua function \"%s\" onto node failed", func->name);
846 goto error;
847
849 DEBUG2("Loaded lua function \"%s\" onto node", func->name);
850 break;
851
852 default:
853 PWARN("Loading lua function \"%s\" onto node failed", func->name);
854 continue;
855 }
856 }
857
858 fr_pool_connection_release(pool, NULL, conn);
859 }
860 talloc_free(nodes);
861
862 return 0;
863}
864
865static int mod_bootstrap(module_inst_ctx_t const *mctx)
866{
867 rlm_redis_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_t);
868 xlat_t *xlat;
869
870 xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, NULL, redis_xlat, FR_TYPE_VOID);
872
873 /*
874 * %redis.node(<key>[, idx])
875 */
876 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "node", redis_node_xlat, FR_TYPE_STRING)) == NULL)) return -1;
878
879 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "remap", redis_remap_xlat, FR_TYPE_STRING)) == NULL)) return -1;
881
882 /*
883 * Loop over the lua functions, registering an xlat
884 * that'll call that function specifically.
885 */
886 talloc_foreach(inst->lua.funcs, func) {
887 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, func->name, redis_lua_func_xlat, FR_TYPE_VOID)) == NULL)) return -1;
890 }
891
892 return 0;
893}
894
895static int mod_load(void)
896{
898
899 return 0;
900}
901
904 .common = {
905 .magic = MODULE_MAGIC_INIT,
906 .name = "redis",
907 .inst_size = sizeof(rlm_redis_t),
909 .onload = mod_load,
910 .bootstrap = mod_bootstrap,
911 .instantiate = mod_instantiate
912 }
913};
#define fr_base16_encode(_out, _in)
Definition base16.h:54
#define RCSID(id)
Definition build.h:506
#define unlikely(_x)
Definition build.h:402
#define NUM_ELEMENTS(_t)
Definition build.h:358
int cf_pair_parse_value(TALLOC_CTX *ctx, void *out, UNUSED void *base, CONF_ITEM *ci, conf_parser_t const *rule)
Parses a CONF_PAIR into a C data type.
Definition cf_parse.c:213
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:657
cf_parse_t func
Override default parsing behaviour for the specified type with a custom parsing function.
Definition cf_parse.h:611
#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_SUBSECTION_ALLOC(_name, _type, _flags, _struct, _field, _subcs)
A conf_parser_t multi-subsection.
Definition cf_parse.h:369
char const * name2
Second identifier for CONF_SECTION.
Definition cf_parse.h:596
#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
@ CONF_FLAG_MULTI
CONF_PAIR can have multiple copies.
Definition cf_parse.h:446
@ CONF_FLAG_OK_MISSING
OK if it's missing.
Definition cf_parse.h:454
@ CONF_FLAG_SUBSECTION
Instead of putting the information into a configuration structure, the configuration file routines MA...
Definition cf_parse.h:423
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:594
Common header for all CONF_* types.
Definition cf_priv.h:49
char const * cf_section_name2(CONF_SECTION const *cs)
Return the second identifier of a CONF_SECTION.
Definition cf_util.c:1184
CONF_SECTION * cf_item_to_section(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_SECTION.
Definition cf_util.c:683
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:285
#define cf_parent(_cf)
Definition cf_util.h:98
#define cf_log_debug(_cf, _fmt,...)
Definition cf_util.h:288
#define CF_IDENT_ANY
Definition cf_util.h:75
fr_redis_rcode_t fr_redis_cluster_state_next(fr_redis_cluster_state_t *state, fr_redis_conn_t **conn, fr_redis_cluster_t *cluster, request_t *request, fr_redis_rcode_t status, redisReply **reply)
Get the next connection to attempt a command against.
Definition cluster.c:1862
fr_redis_cluster_t * fr_redis_cluster_alloc(TALLOC_CTX *ctx, CONF_SECTION *module, fr_redis_conf_t *conf, char const *log_prefix, char const *trigger_prefix, fr_pair_list_t *trigger_args)
Allocate and initialise a new cluster structure.
Definition cluster.c:2259
fr_redis_cluster_node_t const * fr_redis_cluster_slave(fr_redis_cluster_t *cluster, fr_redis_cluster_key_slot_t const *key_slot, uint8_t slave_num)
Return the slave node that would be used for a particular key.
Definition cluster.c:1654
fr_redis_cluster_node_t const * fr_redis_cluster_master(fr_redis_cluster_t *cluster, fr_redis_cluster_key_slot_t const *key_slot)
Return the master node that would be used for a particular key.
Definition cluster.c:1638
int fr_redis_cluster_pool_by_node_addr(fr_pool_t **pool, fr_redis_cluster_t *cluster, fr_socket_t *node_addr, bool create)
Get the pool associated with a node in the cluster.
Definition cluster.c:2069
ssize_t fr_redis_cluster_node_addr_by_role(TALLOC_CTX *ctx, fr_socket_t *out[], fr_redis_cluster_t *cluster, bool is_master, bool is_slave)
Return an array of IP addresses belonging to masters or slaves.
Definition cluster.c:2137
int fr_redis_cluster_port(uint16_t *out, fr_redis_cluster_node_t const *node)
Return the port of a particular node.
Definition cluster.c:1688
fr_redis_cluster_key_slot_t const * fr_redis_cluster_slot_by_key(fr_redis_cluster_t *cluster, request_t *request, uint8_t const *key, size_t key_len)
Implements the key slot selection scheme used by freeradius.
Definition cluster.c:1602
fr_redis_cluster_rcode_t fr_redis_cluster_remap(request_t *request, fr_redis_cluster_t *cluster, fr_redis_conn_t *conn)
Perform a runtime remap of the cluster.
Definition cluster.c:1008
fr_redis_rcode_t fr_redis_cluster_state_init(fr_redis_cluster_state_t *state, fr_redis_conn_t **conn, fr_redis_cluster_t *cluster, request_t *request, uint8_t const *key, size_t key_len, bool read_only)
Resolve a key to a pool, and reserve a connection in that pool.
Definition cluster.c:1740
fr_table_num_sorted_t const fr_redis_cluster_rcodes_table[]
Definition cluster.c:280
int fr_redis_cluster_ipaddr(fr_ipaddr_t *out, fr_redis_cluster_node_t const *node)
Return the ipaddr of a particular node.
Definition cluster.c:1671
A redis cluster.
Definition cluster.c:250
Indexes in the fr_redis_cluster_node_t array for a single key slot.
Definition cluster.c:240
A Redis cluster node.
Definition cluster.c:212
bool close_conn
Set by caller of fr_redis_cluster_state_next, to indicate that connection must be closed,...
Definition cluster.h:50
fr_redis_cluster_rcode_t
Return values for internal functions.
Definition cluster.h:67
@ FR_REDIS_CLUSTER_RCODE_NO_CONNECTION
Operation failed because we couldn't find a live connection.
Definition cluster.h:71
Redis connection sequence state.
Definition cluster.h:49
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:522
static int fr_dcursor_append(fr_dcursor_t *cursor, void *v)
Insert a single item at the end of the list.
Definition dcursor.h:406
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:141
#define MEM(x)
Definition debug.h:46
#define ERROR(fmt,...)
Definition dhcpclient.c:40
static fr_slen_t in
Definition dict.h:882
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
static xlat_action_t redis_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
Xlat to make calls to redis.
Definition rlm_redis.c:589
static xlat_action_t redis_node_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
Return the node that is currently servicing a particular key.
Definition rlm_redis.c:318
static xlat_action_t redis_remap_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
Force a redis cluster remap.
Definition rlm_redis.c:256
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
IPv4/6 prefix.
#define PERROR(_fmt,...)
Definition log.h:228
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition log.h:455
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Definition log.h:540
#define RDEBUG3(fmt,...)
Definition log.h:355
#define PWARN(_fmt,...)
Definition log.h:227
#define RPERROR(fmt,...)
Definition log.h:314
#define RPEDEBUG(fmt,...)
Definition log.h:388
#define DEBUG_ENABLED3
True if global debug level 1-3 messages are enabled.
Definition log.h:259
#define RINDENT()
Indent R* messages by one level.
Definition log.h:442
@ L_DBG_LVL_OFF
No debug messages.
Definition log.h:66
unsigned short uint16_t
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_UINT64
64 Bit unsigned integer.
@ FR_TYPE_VOID
User data.
@ FR_TYPE_GROUP
A grouping of other attributes.
unsigned char uint8_t
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
xlat_t * module_rlm_xlat_register(TALLOC_CTX *ctx, module_inst_ctx_t const *mctx, char const *name, xlat_func_t func, fr_type_t return_type)
Definition module_rlm.c:247
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
void fr_pool_connection_release(fr_pool_t *pool, request_t *request, void *conn)
Release a connection.
Definition pool.c:1410
int fr_pool_connection_close(fr_pool_t *pool, request_t *request, void *conn)
Delete a connection from the connection pool.
Definition pool.c:1540
void * fr_pool_connection_get(fr_pool_t *pool, request_t *request)
Reserve a connection in the connection pool.
Definition pool.c:1395
A connection pool.
Definition pool.c:85
static const conf_parser_t config[]
Definition base.c:163
#define REDEBUG(fmt,...)
#define RDEBUG2(fmt,...)
#define DEBUG2(fmt,...)
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:142
int fr_redis_reply_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *out, redisReply *reply, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, bool box_error, bool shallow))
Convert a string or integer type to fr_value_box_t of specified type.
Definition redis.c:211
redisContext * handle
Hiredis context used when issuing commands.
Definition base.h:101
#define REDIS_COMMON_CONFIG
Definition base.h:135
#define MAX_REDIS_ARGS
Definition base.h:44
static void fr_redis_reply_free(redisReply **reply)
Wrap freeReplyObject so we consistently check for NULL pointers.
Definition base.h:64
void fr_redis_version_print(void)
Print the version of libhiredis the server was built against.
Definition redis.c:53
fr_redis_rcode_t fr_redis_command_status(fr_redis_conn_t *conn, redisReply *reply)
Check the reply for errors.
Definition redis.c:71
fr_redis_rcode_t
Codes are ordered inversely by priority.
Definition base.h:87
@ REDIS_RCODE_RECONNECT
Transitory error, caller should retry the operation with a new connection.
Definition base.h:91
@ REDIS_RCODE_SUCCESS
Operation was successful.
Definition base.h:88
@ REDIS_RCODE_MOVE
Attempt operation on an alternative node with remap.
Definition base.h:94
@ REDIS_RCODE_TRY_AGAIN
Try the operation again.
Definition base.h:90
@ REDIS_RCODE_NO_SCRIPT
Script doesn't exist.
Definition base.h:95
@ REDIS_RCODE_ERROR
Unrecoverable library/server error.
Definition base.h:89
Configuration parameters for a redis connection.
Definition base.h:109
Connection handle, holding a redis context.
Definition base.h:100
static int mod_load(void)
Definition rlm_redis.c:895
rlm_redis_lua_t lua
Array of functions to register.
Definition rlm_redis.c:76
static xlat_arg_parser_t const redis_remap_xlat_args[]
Definition rlm_redis.c:243
static conf_parser_t module_lua[]
Definition rlm_redis.c:89
module_rlm_t rlm_redis
Definition rlm_redis.c:903
fr_redis_conf_t conf
Connection parameters for the Redis server.
Definition rlm_redis.c:73
static int redis_lua_func_instantiate(xlat_inst_ctx_t const *xctx)
Copies the function configuration into xlat function instance data.
Definition rlm_redis.c:566
redis_lua_func_t ** funcs
Array of functions to register.
Definition rlm_redis.c:65
char digest[(SHA1_DIGEST_LENGTH *2)+1]
pre-computed hash of lua code.
Definition rlm_redis.c:51
static int mod_bootstrap(module_inst_ctx_t const *mctx)
Definition rlm_redis.c:865
static int redis_command(fr_redis_rcode_t *status_out, redisReply **reply_out, request_t *request, fr_redis_conn_t *conn, bool read_only, int argc, char const **argv, size_t arg_len[])
Issue a command against redis and get a response.
Definition rlm_redis.c:158
redis_lua_func_t * func
Function configuration.
Definition rlm_redis.c:60
static conf_parser_t module_lua_func[]
Definition rlm_redis.c:83
char const * body
the actual lua code.
Definition rlm_redis.c:52
char const * name
Friendly name for the function. Used to register the equivalent xlat.
Definition rlm_redis.c:50
static xlat_arg_parser_t const redis_node_xlat_args[]
Definition rlm_redis.c:304
fr_redis_cluster_t * cluster
Redis cluster.
Definition rlm_redis.c:78
static xlat_arg_parser_t const redis_args[]
Definition rlm_redis.c:575
static int lua_func_body_parse(TALLOC_CTX *ctx, void *out, void *parent, CONF_ITEM *ci, conf_parser_t const *rule)
Do basic processing for a lua function body and compute its sha1 hash.
Definition rlm_redis.c:105
static int mod_instantiate(module_inst_ctx_t const *mctx)
Definition rlm_redis.c:782
static xlat_action_t redis_lua_func_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
Call a lua function on the redis server.
Definition rlm_redis.c:372
static conf_parser_t module_config[]
Definition rlm_redis.c:96
static xlat_arg_parser_t const redis_lua_func_args[]
Definition rlm_redis.c:361
bool read_only
Function has no side effects.
Definition rlm_redis.c:53
Instance of a redis lua func xlat.
Definition rlm_redis.c:59
A lua function or stored procedure we make available as an xlat.
Definition rlm_redis.c:49
rlm_redis module instance
Definition rlm_redis.c:72
bool fr_sbuff_next_if_char(fr_sbuff_t *sbuff, char c)
Return true if the current char matches, and if it does, advance.
Definition sbuff.c:2129
#define FR_SBUFF_IN(_start, _len_or_end)
#define fr_sbuff_current(_sbuff_or_marker)
#define fr_sbuff_remaining(_sbuff_or_marker)
#define FR_SBUFF_OUT(_start, _len_or_end)
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:351
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:293
void * boot
Data allocated during the boostrap phase.
Definition module.h:296
void fr_sha1_init(fr_sha1_ctx *context)
Definition sha1.c:93
void fr_sha1_final(uint8_t digest[static SHA1_DIGEST_LENGTH], fr_sha1_ctx *context)
Definition sha1.c:141
void fr_sha1_update(fr_sha1_ctx *context, uint8_t const *in, size_t len)
Definition sha1.c:105
#define SHA1_DIGEST_LENGTH
Definition sha1.h:29
eap_aka_sim_process_conf_t * inst
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition table.h:772
#define talloc_get_type_abort_const
Definition talloc.h:110
#define talloc_foreach(_array, _iter)
Iterate over a talloced array of elements.
Definition talloc.h:72
static size_t talloc_strlen(char const *s)
Returns the length of a talloc array containing a string.
Definition talloc.h:136
@ XLAT_ARG_VARIADIC_EMPTY_KEEP
Empty argument groups are left alone, and either passed through as empty groups or null boxes.
Definition xlat.h:137
unsigned int required
Argument must be present, and non-empty.
Definition xlat.h:146
#define XLAT_ARG_PARSER_TERMINATOR
Definition xlat.h:170
xlat_action_t
Definition xlat.h:37
@ XLAT_ACTION_FAIL
An xlat function failed.
Definition xlat.h:44
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
Definition xlat.h:43
Definition for a single argument consumed by an xlat function.
Definition xlat.h:145
static fr_slen_t parent
Definition pair.h:858
Holds information necessary for binding or connecting to a socket.
Definition socket.h:60
#define fr_type_is_string(_x)
Definition types.h:348
ssize_t fr_value_box_print(fr_sbuff_t *out, fr_value_box_t const *data, fr_sbuff_escape_rules_t const *e_rules)
Print one boxed value to a string.
Definition value.c:6090
int fr_value_box_asprintf(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, bool tainted, char const *fmt,...)
Print a formatted string using our internal printf wrapper and assign it to a value box.
Definition value.c:4692
int fr_value_box_strdup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, bool tainted)
Copy a nul terminated string to a fr_value_box_t.
Definition value.c:4604
#define fr_box_ipaddr(_val)
Definition value.h:317
#define fr_box_strvalue_len(_val, _len)
Definition value.h:309
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
Definition value.h:655
#define fr_value_box_list_foreach(_list_head, _iter)
Definition value.h:224
static size_t char ** out
Definition value.h:1030
void const * inst
xlat instance data.
Definition xlat_ctx.h:50
void * uctx
Passed to the registration function.
Definition xlat_ctx.h:66
module_ctx_t const * mctx
Synthesised module calling ctx.
Definition xlat_ctx.h:52
void * inst
xlat instance data to populate.
Definition xlat_ctx.h:63
An xlat calling ctx.
Definition xlat_ctx.h:49
An xlat instantiation ctx.
Definition xlat_ctx.h:62
int xlat_func_args_set(xlat_t *x, xlat_arg_parser_t const args[])
Register the arguments of an xlat.
Definition xlat_func.c:363
#define xlat_func_instantiate_set(_xlat, _instantiate, _inst_struct, _detach, _uctx)
Set a callback for global instantiation of xlat functions.
Definition xlat_func.h:93