The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
rlm_cache_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: 04341bcab49f6206b00a491175ea925d155f6d89 $
19 * @file rlm_cache_redis.c
20 * @brief redis based cache.
21 *
22 * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
23 */
24#define LOG_PREFIX "cache - redis"
25
26#include <freeradius-devel/server/base.h>
27#include <freeradius-devel/util/debug.h>
28
29#include "../../rlm_cache.h"
30#include <freeradius-devel/redis/base.h>
31#include <freeradius-devel/redis/cluster_async.h>
32#include <freeradius-devel/io/coord_pair.h>
33
38
39typedef struct {
40 fr_redis_conf_t conf; //!< Connection parameters for the Redis server.
41 //!< Must be first field in this struct.
42
43 CONF_SECTION *tls_conf; //!< TLS CONF_SECTION
44
45 tmpl_t *created_attr; //!< LHS of the Cache-Created map.
46 tmpl_t *expires_attr; //!< LHS of the Cache-Expires map.
47
48 module_instance_t const *mi; //!< Module instance.
49
50 fr_coord_reg_t *coord_reg; //!< Coordinator registration.
51 fr_coord_pair_reg_t *coord_pair_reg; //!< Coord pair registration.
53
54typedef struct {
55 rlm_cache_redis_t const *inst; //!< Module instance.
56 fr_redis_ct_t *rtcluster; //!< Per thread Redis cluster.
57 fr_coord_worker_t *cw; //!< Coord-worker for fetching cluster map.
59
68
70static fr_dict_t const *dict_redis;
71
74 { .out = &dict_freeradius, .proto = "freeradius" },
75 { .out = &dict_redis, .proto = "redis" },
77};
78
81
84 { .out = &attr_cache_created, .name = "Cache-Created", .type = FR_TYPE_DATE, .dict = &dict_freeradius },
85 { .out = &attr_cache_expires, .name = "Cache-Expires", .type = FR_TYPE_DATE, .dict = &dict_freeradius },
87};
88
90
91/** Create a new rlm_cache_redis instance
92 *
93 * @param[in] mctx Data required for instantiation.
94 * @return
95 * - 0 on success.
96 * - -1 on failure.
97 */
98static int mod_instantiate(module_inst_ctx_t const *mctx)
99{
100 rlm_cache_redis_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_cache_redis_t);
101 char buffer[256];
102
103 snprintf(buffer, sizeof(buffer), "rlm_cache (%s)", mctx->mi->parent->name);
104
105 inst->mi = mctx->mi;
106 inst->conf.log_prefix = talloc_asprintf(inst, "rlm_cache (%s)", mctx->mi->parent->name);
107 inst->conf.module_name = mctx->mi->parent->module->name;
108 inst->conf.inst_name = mctx->mi->parent->name;
109
110 if (inst->conf.use_tls) {
111 inst->tls_conf = cf_section_find(mctx->mi->conf, "tls", CF_IDENT_ANY);
112
113 if (!inst->tls_conf) {
114 cf_log_err(mctx->mi->conf, "Missing tls section");
115 return -1;
116 }
117 }
118
119 /*
120 * These never change, so do it once on instantiation
121 */
122 if (tmpl_afrom_attr_str(inst, NULL, &inst->created_attr, "Cache-Created", NULL) <= 0) {
123 ERROR("Cache-Created attribute not defined");
124 return -1;
125 }
126
127 if (tmpl_afrom_attr_str(inst, NULL, &inst->expires_attr, "Cache-Expires", NULL) <= 0) {
128 ERROR("Cache-Expires attribute not defined");
129 return -1;
130 }
131
132 if (!inst->conf.use_cluster_map) return 0;
133
134 if (inst->conf.database) {
135 cf_log_err(mctx->mi->conf, "Cannot set Redis database number when cluster in use");
136 return -1;
137 }
138
140 .name = mctx->mi->name,
141 .worker_cb = worker_pair_callbacks,
142 .cb_id = REDIS_COORD_PAIR_CALLBACK_ID,
143 .root = fr_dict_root(dict_redis),
144 .cs = mctx->mi->conf,
145 }
146 );
147 if (!inst->coord_pair_reg) return -1;
148
149 FR_COORD_PAIR_CB_CTX_SET(coord_callbacks, worker_callbacks, inst->coord_pair_reg);
150
152 .name = mctx->mi->name,
153 .coord_cb = coord_callbacks,
154 .worker_cb = worker_callbacks,
155 .mi = mctx->mi
156 });
157
158 if (!inst->coord_reg) return -1;
159
160 return 0;
161}
162
164{
165 rlm_cache_redis_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_cache_redis_thread_t);
166 rlm_cache_redis_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_cache_redis_t);
167
168 t->rtcluster = fr_redis_ct_alloc(t, inst->tls_conf, mctx->el, &inst->conf, NULL, NULL, false);
169 if (!t->rtcluster) return -1;
170 t->inst = inst;
171
172 return 0;
173}
174
176{
177 rlm_cache_redis_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_cache_redis_thread_t);
178 rlm_cache_redis_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_cache_redis_t);
179
180 if (!inst->conf.use_cluster_map) return 0;
181
182 t->cw = fr_coord_attach(t, mctx->el, inst->coord_reg);
183
184 if (!t->cw) {
185 ERROR("Failed to attach to coordinator");
186 return -1;
187 }
188
189 if ((inst->conf.trunk_conf.start == 0) || (fr_schedule_worker_id() != 0)) return 0;
190
191 return fr_redis_ct_map_bootstrap(t->rtcluster, t->cw, inst->coord_pair_reg);
192}
193
195{
196 rlm_cache_redis_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_cache_redis_thread_t);
197
198 if (!t->cw) return 0;
199
200 fr_coord_detach(t->cw, true);
201 t->cw = NULL;
202 return 0;
203}
204
205static int mod_detach(module_detach_ctx_t const *mctx)
206{
207 rlm_cache_redis_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_cache_redis_t);
208
209 if (!inst->conf.use_cluster_map) return 0;
210
211 fr_coord_deregister(inst->coord_reg);
212 talloc_free(inst->coord_pair_reg);
213 return 0;
214}
215
216static int mod_load(void)
217{
219 return redis_dict_init();
220}
221
223{
224 talloc_free(c);
225}
226
228{
229 size_t i;
230 for (i = 0; i < talloc_array_length(rctx->cmd_str); i++) {
231 if (!rctx->cmd_str[i]) continue;
232 redisFreeCommand(rctx->cmd_str[i]);
233 }
234 return 0;
235}
236
237/** Process the results of Redis cache commands
238 *
239 * Initiating a cluster remap and query redirection if needed.
240 *
241 */
244{
245 switch (fr_redis_command_set_rcode(cmds)) {
247 return rcode;
248
250 {
251 rlm_cache_redis_thread_t *thread = talloc_get_type_abort(module_thread(inst->mi)->data,
253
254 if (inst->conf.use_cluster_map) fr_redis_ct_map_get(thread->rtcluster, thread->cw,
255 inst->coord_pair_reg, false);
256 }
258
261 return CACHE_YIELD;
262
265 return CACHE_YIELD;
266
268 RPERROR("Server returned error");
269 return CACHE_ERROR;
270
271 default:
272 return CACHE_ERROR;
273 }
274}
275
276static void cache_redis_cancel(UNUSED rlm_cache_config_t const *config, UNUSED void *instance, request_t *request,
277 UNUSED void *handle, void *rctx)
278{
279 rlm_cache_redis_rctx_t *cache_rctx = talloc_get_type_abort(rctx, rlm_cache_redis_rctx_t);
280
281 RDEBUG2("Forcibly cancelling pending redis cache request");
282 fr_redis_async_cmd_cancel(cache_rctx->cmd);
283}
284
286 void *instance, UNUSED request_t *request,
287 UNUSED void *handle, void *rctx)
288{
289 rlm_cache_redis_t *driver = instance;
290 rlm_cache_redis_rctx_t *cache_rctx = talloc_get_type_abort(rctx, rlm_cache_redis_rctx_t);
291
292 *out = cache_rctx->entry;
293 return cache_redis_results(request, driver, cache_rctx->cmds, cache_rctx->cmd, cache_rctx->rcode);
294}
295
296static void cache_entry_find_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
297{
298 rlm_cache_redis_rctx_t *cache_rctx = talloc_get_type_abort(rctx, rlm_cache_redis_rctx_t);
299 size_t i;
300#ifdef HAVE_TALLOC_ZERO_POOLED_OBJECT
301 size_t pool_size = 0;
302#endif
303 map_list_t head;
305
306 if (reply->type != REDIS_REPLY_ARRAY) {
307 REDEBUG("Bad result type, expected array, got %s",
308 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
309 error:
310 cache_rctx->rcode = CACHE_ERROR;
311 return;
312 }
313
314 RDEBUG3("Entry contains %zu elements", reply->elements);
315
316 if (reply->elements == 0) {
317 cache_rctx->rcode = CACHE_MISS;
318 return;
319 }
320
321 if (reply->elements % 3) {
322 REDEBUG("Invalid number of reply elements (%zu). "
323 "Reply must contain triplets of keys operators and values",
324 reply->elements);
325 goto error;
326 }
327
328 map_list_init(&head);
329
330#ifdef HAVE_TALLOC_ZERO_POOLED_OBJECT
331 /*
332 * We can get a pretty good idea of the required size of the pool
333 */
334 for (i = 0; i < reply->elements; i += 3) {
335 pool_size += sizeof(map_t) + (sizeof(tmpl_t) * 2);
336 if (reply->element[i]->type == REDIS_REPLY_STRING) pool_size += reply->element[i]->len + 1;
337 }
338
339 /*
340 * reply->elements gives us the number of chunks, as the maps are triplets, and there
341 * are three chunks per map
342 */
343
344 MEM(c = talloc_zero_pooled_object(NULL, rlm_cache_entry_t, reply->elements, pool_size));
345#else
346 MEM(c = talloc_zero(NULL, rlm_cache_entry_t));
347#endif
348 map_list_init(&c->maps);
349 /*
350 * Convert the key/value pairs back into maps
351 */
352 for (i = 0; i < reply->elements; i += 3) {
353 if (fr_redis_reply_to_map(c, &head, request,
354 reply->element[i], reply->element[i + 1], reply->element[i + 2]) < 0) {
355 talloc_free(c);
356 cache_rctx->rcode = CACHE_ERROR;
357 return;
358 }
359 }
360
361 /*
362 * Pull out the cache created date
363 */
364 if (tmpl_attr_tail_da(map_list_head(&head)->lhs) == attr_cache_created) {
365 map_t *map;
366
367 c->created = tmpl_value(map_list_head(&head)->rhs)->vb_date;
368
369 map = map_list_pop_head(&head);
370 talloc_free(map);
371 }
372
373 /*
374 * Pull out the cache expires date
375 */
376 if (tmpl_attr_tail_da(map_list_head(&head)->lhs) == attr_cache_expires) {
377 map_t *map;
378
379 c->expires = tmpl_value(map_list_head(&head)->rhs)->vb_date;
380
381 map = map_list_pop_head(&head);
382 talloc_free(map);
383 }
384
385 if (unlikely(fr_value_box_copy(c, &c->key, cache_rctx->key) < 0)) goto error;
386
387 map_list_move(&c->maps, &head);
388 cache_rctx->entry = c;
389}
390
391/** Locate a cache entry in redis
392 *
393 * @copydetails cache_entry_find_t
394 */
396 UNUSED rlm_cache_config_t const *config, void *instance,
397 request_t *request, UNUSED void *handle, fr_value_box_t const *key)
398{
399 rlm_cache_redis_t *driver = instance;
400 rlm_cache_redis_thread_t *thread = talloc_get_type_abort(module_thread(driver->mi)->data, rlm_cache_redis_thread_t);
403 int cmd_len;
405
406 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), rlm_cache_redis_rctx_t));
407 MEM(cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
408 rctx->cmds = cmds;
409 rctx->key = key;
410 rctx->cmd_str = talloc_zero_array(rctx, char *, 1);
411 talloc_set_destructor(rctx, cache_redis_rctx_free);
412
413 RDEBUG3("LRANGE %pV 0 -1", key);
414 cmd_len = redisFormatCommand(&rctx->cmd_str[0], "LRANGE %b 0 -1", key->vb_strvalue, key->vb_length);
415 if (cmd_len < 0) {
416 RERROR("Failed formatting redis command");
417 error:
418 talloc_free(rctx);
419 return CACHE_ERROR;
420 }
422 rctx) != FR_REDIS_PIPELINE_OK) goto error;
423
424 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &ret, thread->rtcluster, (uint8_t const *)key->vb_strvalue,
425 key->vb_length, cmds, false, NULL);
426 REDIS_ASYNC_START_RCODE_PROCESS(ret, thread->rtcluster, thread->cw, thread->inst->coord_pair_reg,
427 "Failed to enqueue Redis cache find commands", CACHE_ERROR)
428
429 *rctx_out = rctx;
430 return CACHE_YIELD;
431}
432
434 void *instance, UNUSED request_t *request,
435 UNUSED void *handle, void *rctx)
436{
437 rlm_cache_redis_t *driver = instance;
438 rlm_cache_redis_rctx_t *cache_rctx = talloc_get_type_abort(rctx, rlm_cache_redis_rctx_t);
439
440 *out = cache_rctx->entry;
441
442 return cache_redis_results(request, driver, cache_rctx->cmds, cache_rctx->cmd, cache_rctx->rcode);
443}
444
445static void cache_entry_insert_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
446{
447 rlm_cache_redis_rctx_t *cache_rctx = talloc_get_type_abort(rctx, rlm_cache_redis_rctx_t);
448
449 RDEBUG3("Command results");
450 RINDENT();
452 REXDENT();
453
454 cache_rctx->rcode = CACHE_OK;
455}
456
457/** Insert a new entry into the data store
458 *
459 * @copydetails cache_entry_insert_t
460 */
461static cache_status_t cache_entry_insert(UNUSED void **rctx_out, UNUSED rlm_cache_config_t const *config, void *instance,
462 request_t *request, UNUSED void *handle, const rlm_cache_entry_t *c)
463{
464 rlm_cache_redis_t *driver = instance;
465 rlm_cache_redis_thread_t *thread = talloc_get_type_abort(module_thread(driver->mi)->data, rlm_cache_redis_thread_t);
468 int cmd_len;
470
471 TALLOC_CTX *pool;
472
473 map_t *map = NULL;
474
475 static char const command[] = "RPUSH";
476 char const **argv;
477 size_t *argv_len;
478 char const **argv_p;
479 size_t *argv_len_p;
480 size_t i;
481
482 int cnt;
483
484 tmpl_t expires_value;
485 map_t expires = {
486 .op = T_OP_SET,
487 .lhs = driver->expires_attr,
488 .rhs = &expires_value,
489 };
490
491 tmpl_t created_value;
492 map_t created = {
493 .op = T_OP_SET,
494 .lhs = driver->created_attr,
495 .rhs = &created_value,
496 };
497
498 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), rlm_cache_redis_rctx_t));
499 rctx->entry = UNCONST(rlm_cache_entry_t *, c);
500
501 /*
502 * Encode the entry created date
503 */
504 tmpl_init_shallow(&created_value, TMPL_TYPE_DATA, T_BARE_WORD, "<TEMP>", 6, NULL);
505 fr_value_box_init(&created_value.data.literal, FR_TYPE_DATE, NULL, true);
506 tmpl_value(&created_value)->vb_date = c->created;
507
508 /*
509 * Encode the entry expiry time
510 *
511 * Although Redis objects expire on their own, we still need this
512 * to ignore entries that were created before the last epoch.
513 */
514 tmpl_init_shallow(&expires_value, TMPL_TYPE_DATA, T_BARE_WORD, "<TEMP>", 6, NULL);
515 fr_value_box_init(&expires_value.data.literal, FR_TYPE_DATE, NULL, true);
516 tmpl_value(&expires_value)->vb_date = c->expires;
517
518 cnt = map_list_num_elements(&c->maps) + 2;
519
520 /*
521 * The majority of serialized entries should be under 1k.
522 *
523 * @todo We should really calculate this using some sort of moving average.
524 */
525 pool = talloc_pool(rctx, 1024);
526 if (!pool) return CACHE_ERROR;
527
528 argv_p = argv = talloc_array(pool, char const *, (cnt * 3) + 2); /* pair = 3 + cmd + key */
529 argv_len_p = argv_len = talloc_array(pool, size_t, (cnt * 3) + 2); /* pair = 3 + cmd + key */
530
531 *argv_p++ = command;
532 *argv_len_p++ = sizeof(command) - 1;
533
534 *argv_p++ = (char const *)c->key.vb_strvalue;
535 *argv_len_p++ = c->key.vb_length;
536
537 /*
538 * Add the maps to the command string in reverse order
539 */
540 if (fr_redis_tuple_from_map(pool, argv_p, argv_len_p, &created) < 0) {
541 REDEBUG("Failed encoding map as Redis K/V pair");
542 talloc_free(rctx);
543 return CACHE_ERROR;
544 }
545 argv_p += 3;
546 argv_len_p += 3;
547 if (fr_redis_tuple_from_map(pool, argv_p, argv_len_p, &expires) < 0) {
548 REDEBUG("Failed encoding map as Redis K/V pair");
549 talloc_free(rctx);
550 return CACHE_ERROR;
551 }
552 argv_p += 3;
553 argv_len_p += 3;
554 while ((map = map_list_next(&c->maps, map))) {
555 if (fr_redis_tuple_from_map(pool, argv_p, argv_len_p, map) < 0) {
556 REDEBUG("Failed encoding map as Redis K/V pair");
557 talloc_free(rctx);
558 return CACHE_ERROR;
559 }
560 argv_p += 3;
561 argv_len_p += 3;
562 }
563
564 MEM(cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
565 rctx->cmds = cmds;
566 rctx->key = &c->key;
567 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, 3));
568 talloc_set_destructor(rctx, cache_redis_rctx_free);
569
570 RDEBUG3("Pipelining commands");
571
572 if (fr_unix_time_ispos(c->expires)) {
573 RDEBUG3("MULTI");
574 if (fr_redis_command_literal_add(cmds, "MULTI", NULL, NULL) != FR_REDIS_PIPELINE_OK) {
575 error:
576 talloc_free(rctx);
577 return CACHE_ERROR;
578 };
579 }
580
581 RDEBUG3("DEL \"%pV\"", &c->key);
582 cmd_len = redisFormatCommand(&rctx->cmd_str[0], "DEL %b", (uint8_t const *)c->key.vb_strvalue, c->key.vb_length);
583 if (cmd_len < 0) {
584 format_error:
585 RERROR("Failed formatting redis command");
586 goto error;
587 }
588 if (fr_redis_command_preformatted_add(cmds, rctx->cmd_str[0], cmd_len, NULL,
589 NULL) != FR_REDIS_PIPELINE_OK) goto error;
590
591 if (RDEBUG_ENABLED3) {
592 RDEBUG3("argv command");
593 RINDENT();
594 for (i = 0; i < talloc_array_length(argv); i++) {
595 RDEBUG3("%pV", fr_box_strvalue_len(argv[i], argv_len[i]));
596 }
597 REXDENT();
598 }
599 cmd_len = redisFormatCommandArgv(&rctx->cmd_str[1], talloc_array_length(argv), argv, argv_len);
600 if (cmd_len < 0) goto format_error;
601
602 if (fr_unix_time_ispos(c->expires)) {
603 if (fr_redis_command_preformatted_add(cmds, rctx->cmd_str[1], cmd_len, NULL,
604 NULL) != FR_REDIS_PIPELINE_OK) goto error;
605
606 RDEBUG3("EXPIREAT \"%pV\" %" PRIu64, &c->key, fr_unix_time_to_sec(c->expires));
607 cmd_len = redisFormatCommand(&rctx->cmd_str[2], "EXPIREAT %b %" PRIu64,
608 (uint8_t const *)c->key.vb_strvalue, (size_t)c->key.vb_length,
610 if (cmd_len < 0) goto format_error;
611 if (fr_redis_command_preformatted_add(cmds, rctx->cmd_str[2], cmd_len, NULL,
612 NULL) != FR_REDIS_PIPELINE_OK) goto error;
613
614 RDEBUG3("EXEC");
616 rctx) != FR_REDIS_PIPELINE_OK) goto error;
617 } else {
619 rctx) != FR_REDIS_PIPELINE_OK) goto error;
620 }
621
622 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &ret, thread->rtcluster, (uint8_t const *)c->key.vb_strvalue,
623 c->key.vb_length, cmds, false, NULL);
624 REDIS_ASYNC_START_RCODE_PROCESS(ret, thread->rtcluster, thread->cw, thread->inst->coord_pair_reg,
625 "Failed to enqueue Redis cache insert commands", CACHE_ERROR)
626
627 *rctx_out = rctx;
628 return CACHE_YIELD;
629}
630
632 UNUSED request_t *request, UNUSED void *handle, void *rctx)
633{
634 rlm_cache_redis_t *driver = instance;
635 rlm_cache_redis_rctx_t *cache_rctx = talloc_get_type_abort(rctx, rlm_cache_redis_rctx_t);
636
637 return cache_redis_results(request, driver, cache_rctx->cmds, cache_rctx->cmd, cache_rctx->rcode);
638}
639
640static void cache_entry_expire_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
641{
642 rlm_cache_redis_rctx_t *cache_rctx = talloc_get_type_abort(rctx, rlm_cache_redis_rctx_t);
643
644 if (reply->type == REDIS_REPLY_INTEGER) {
645 cache_rctx->rcode = (reply->integer) ? CACHE_OK : CACHE_MISS;
646 return;
647 }
648
649 REDEBUG("Bad result type, expected integer, got %s",
650 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
651
652 cache_rctx->rcode = CACHE_ERROR;
653}
654
655/** Call delete the cache entry from redis
656 *
657 * @copydetails cache_entry_expire_t
658 */
659static cache_status_t cache_entry_expire(UNUSED void **rctx_out, UNUSED rlm_cache_config_t const *config, void *instance,
660 request_t *request, UNUSED void *handle, fr_value_box_t const *key)
661{
662 rlm_cache_redis_t *driver = instance;
663 rlm_cache_redis_thread_t *thread = talloc_get_type_abort(module_thread(driver->mi)->data, rlm_cache_redis_thread_t);
666 int cmd_len;
668
669 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), rlm_cache_redis_rctx_t));
670 MEM(cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
671 rctx->cmds = cmds;
672 rctx->key = key;
673 rctx->cmd_str = talloc_zero_array(rctx, char *, 1);
674 talloc_set_destructor(rctx, cache_redis_rctx_free);
675
676 cmd_len = redisFormatCommand(&rctx->cmd_str[0], "DEL %b", (uint8_t const *)key->vb_strvalue, key->vb_length);
677 if (cmd_len < 0) {
678 RERROR("Failed formatting redis command");
679 error:
680 talloc_free(rctx);
681 return CACHE_ERROR;
682 }
684 rctx) != FR_REDIS_PIPELINE_OK) goto error;
685
686 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &ret, thread->rtcluster, (uint8_t const *)key->vb_strvalue,
687 key->vb_length, cmds, false, NULL);
688 REDIS_ASYNC_START_RCODE_PROCESS(ret, thread->rtcluster, thread->cw, thread->inst->coord_pair_reg,
689 "Failed to enqueue Redis cache expire command", CACHE_ERROR)
690
691 *rctx_out = rctx;
692 return CACHE_YIELD;
693}
694
697 .common = {
698 .magic = MODULE_MAGIC_INIT,
699 .name = "cache_redis",
700 .onload = mod_load,
702 .coord_attach = mod_coord_attach,
703 .detach = mod_detach,
704 .inst_size = sizeof(rlm_cache_redis_t),
706 .thread_instantiate = mod_thread_instantiate,
707 .thread_detach = mod_thread_detach,
708 .config = driver_config,
709 },
710 .free = cache_entry_free,
711 .find = cache_entry_find,
712 .find_resume = cache_entry_find_resume,
713 .find_cancel = cache_redis_cancel,
714 .insert = cache_entry_insert,
715 .insert_resume = cache_entry_insert_resume,
716 .insert_cancel = cache_redis_cancel,
717 .expire = cache_entry_expire,
718 .expire_resume = cache_entry_expire_resume,
719 .expire_cancel = cache_redis_cancel,
720};
static int const char char buffer[256]
Definition acutest.h:576
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:186
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:391
#define unlikely(_x)
Definition build.h:455
#define UNUSED
Definition build.h:384
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:669
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
CONF_SECTION * cf_section_find(CONF_SECTION const *cs, char const *name1, char const *name2)
Find a CONF_SECTION with name1 and optionally name2.
Definition cf_util.c:1204
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:345
#define CF_IDENT_ANY
Definition cf_util.h:80
int fr_redis_ct_map_bootstrap(fr_redis_ct_t *rtcluster, fr_coord_worker_t *cw, fr_coord_pair_reg_t *coord_pair_reg)
Initiate bootstrapping of the cluster map.
fr_redis_async_cmd_t * fr_redis_async_cmd_start(TALLOC_CTX *ctx, request_t *request, fr_redis_async_rcode_t *rcode, fr_redis_ct_t *rtcluster, uint8_t const *key, size_t key_len, fr_redis_command_set_t *cmds, bool read_only, fr_redis_ct_node_t *node)
Start running a command set on an async redis cluster.
fr_redis_async_rcode_t fr_redis_ct_map_get(fr_redis_ct_t *rtcluster, fr_coord_worker_t *cw, fr_coord_pair_reg_t *coord_pair_reg, bool force)
Initiate updating of the cluster map.
void fr_redis_async_cmd_cancel(fr_redis_async_cmd_t *cmd)
Cancel a Redis async command.
fr_redis_async_rcode_t fr_redis_async_cmd_resend(fr_redis_async_cmd_t *cmd)
Re-submit a redis async command set.
fr_redis_async_rcode_t fr_redis_async_cmd_redirect(fr_redis_async_cmd_t *cmd)
Re-submit a redis async command set on a different node.
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.
Structure for holding the state of an async redis command set.
Thread local state for a cluster.
#define REDIS_ASYNC_COORD_CALLBACKS(_thread_type)
#define REDIS_ASYNC_START_RCODE_PROCESS(_rcode, _cluster, _cw, _coord_pair_reg, _error_msg, _error_ret)
Convenience macro to reduce boilerplate.
fr_coord_reg_t * fr_coord_register(fr_coord_reg_ctx_t *reg_ctx)
Register a coordinator.
Definition coord.c:137
fr_coord_worker_t * fr_coord_attach(TALLOC_CTX *ctx, fr_event_list_t *el, fr_coord_reg_t *coord_reg)
Attach a worker to a coordinator.
Definition coord.c:662
void fr_coord_deregister(fr_coord_reg_t *coord_reg)
De-register a coordinator.
Definition coord.c:172
int fr_coord_detach(fr_coord_worker_t *cw, bool exiting)
Signal a coordinator that a worker wants to detach.
Definition coord.c:623
A coordinator registration.
Definition coord.c:85
The worker end of worker <-> coordinator communication.
Definition coord.c:73
fr_coord_pair_reg_t * fr_coord_pair_register(fr_coord_pair_reg_ctx_t *reg_ctx)
Register a set of callbacks for pair list based coordinator messages.
Definition coord_pair.c:113
struct fr_coord_pair_reg_s fr_coord_pair_reg_t
Definition coord_pair.h:32
#define FR_COORD_PAIR_CB_CTX_SET(_in_cb, _out_cb, _reg)
Set up ctx on pair list callbacks.
Definition coord_pair.h:93
#define MEM(x)
Definition debug.h:38
#define ERROR(fmt,...)
Definition dhcpclient.c:40
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition dict_util.c:2637
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
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
talloc_free(hp)
TALLOC_CTX * unlang_interpret_frame_talloc_ctx(request_t *request)
Get a talloc_ctx which is valid only for this frame.
Definition interpret.c:2053
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition log.h:460
#define RDEBUG_ENABLED3
True if request debug level 1-3 messages are enabled.
Definition log.h:352
#define RDEBUG3(fmt,...)
Definition log.h:360
#define RERROR(fmt,...)
Definition log.h:315
#define RPERROR(fmt,...)
Definition log.h:319
#define RINDENT()
Indent R* messages by one level.
Definition log.h:447
@ L_DBG_LVL_3
3rd highest priority debug messages (-xxx | -Xx).
Definition log.h:69
@ FR_TYPE_DATE
Unix time stamp, always has value >2^31.
unsigned char uint8_t
fr_event_list_t * el
Event list to register any IO handlers and timers against.
Definition module_ctx.h:68
module_instance_t * mi
Module instance to detach.
Definition module_ctx.h:57
void * thread
Thread instance data.
Definition module_ctx.h:67
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:64
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for detach calls.
Definition module_ctx.h:56
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
fr_redis_async_rcode_t fr_redis_command_set_rcode(fr_redis_command_set_t *cmds)
Extract the rcode from a command set.
Definition pipeline.c:994
fr_redis_pipeline_status_t fr_redis_command_preformatted_add(fr_redis_command_set_t *cmds, char const *cmd_str, size_t cmd_len, fr_redis_command_complete_t complete, void *rctx)
Add an preformatted command to the command set as formatted by redisCommandFormat or it's variants.
Definition pipeline.c:479
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
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
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 const conf_parser_t config[]
Definition base.c:162
#define REDEBUG(fmt,...)
#define RDEBUG2(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:153
int redis_dict_init(void)
Load the Redis dictionaries.
Definition redis.c:136
#define REDIS_COMMON_CONFIG
Definition base.h:143
fr_redis_async_rcode_t
Definition base.h:80
@ REDIS_ASYNC_RCODE_MOVE
Attempt operation on an alternative node with remap.
Definition base.h:88
@ REDIS_ASYNC_RCODE_ERROR
Unrecoverable error.
Definition base.h:82
@ REDIS_ASYNC_RCODE_ASK
Attempt operation on an alternative node.
Definition base.h:87
@ REDIS_ASYNC_RCODE_TRY_AGAIN
Try the operation again.
Definition base.h:86
@ REDIS_ASYNC_RCODE_SUCCESS
Operation was successful.
Definition base.h:81
int fr_redis_tuple_from_map(TALLOC_CTX *pool, char const *out[], size_t out_len[], map_t *map)
Add a single map pair to an existing command string as three elements.
Definition redis.c:474
void fr_redis_version_print(void)
Print the version of libhiredis the server was built against.
Definition redis.c:107
fr_table_num_sorted_t const redis_reply_types[]
Definition redis.c:31
@ REDIS_RCODE_SUCCESS
Operation was successful.
Definition base.h:70
int fr_redis_reply_to_map(TALLOC_CTX *ctx, map_list_t *out, request_t *request, redisReply *key, redisReply *op, redisReply *value)
Convert a pair of redis reply objects to a map.
Definition redis.c:381
Configuration parameters for a redis connection.
Definition base.h:114
fr_value_box_t key
Key used to identify entry.
Definition rlm_cache.h:74
map_list_t maps
Head of the maps list.
Definition rlm_cache.h:79
fr_unix_time_t created
When the entry was created.
Definition rlm_cache.h:76
module_t common
Common fields for all loadable modules.
Definition rlm_cache.h:341
cache_status_t
Definition rlm_cache.h:39
@ CACHE_ERROR
Fatal error.
Definition rlm_cache.h:41
@ CACHE_YIELD
The driver has pushed an async.
Definition rlm_cache.h:44
@ CACHE_OK
Cache entry found/updated.
Definition rlm_cache.h:42
@ CACHE_MISS
Cache entry notfound.
Definition rlm_cache.h:43
fr_unix_time_t expires
When the entry expires.
Definition rlm_cache.h:77
Configuration for the rlm_cache module.
Definition rlm_cache.h:52
Definition rlm_cache.h:73
static cache_status_t cache_entry_find_resume(rlm_cache_entry_t **out, UNUSED rlm_cache_config_t const *config, void *instance, UNUSED request_t *request, UNUSED void *handle, void *rctx)
static int mod_detach(module_detach_ctx_t const *mctx)
static int mod_load(void)
fr_coord_worker_t * cw
Coord-worker for fetching cluster map.
static cache_status_t cache_entry_expire_resume(UNUSED rlm_cache_config_t const *config, void *instance, UNUSED request_t *request, UNUSED void *handle, void *rctx)
fr_redis_conf_t conf
Connection parameters for the Redis server.
static fr_dict_attr_t const * attr_cache_created
static int mod_coord_attach(module_thread_inst_ctx_t const *mctx)
static cache_status_t cache_entry_expire(UNUSED void **rctx_out, UNUSED rlm_cache_config_t const *config, void *instance, request_t *request, UNUSED void *handle, fr_value_box_t const *key)
Call delete the cache entry from redis.
static void cache_entry_free(rlm_cache_entry_t *c)
fr_dict_attr_autoload_t rlm_cache_redis_dict_attr[]
rlm_cache_driver_t rlm_cache_redis
rlm_cache_entry_t * entry
static void cache_entry_insert_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
tmpl_t * expires_attr
LHS of the Cache-Expires map.
static fr_dict_t const * dict_freeradius
static conf_parser_t driver_config[]
fr_value_box_t const * key
static void cache_entry_find_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
static void cache_entry_expire_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
static cache_status_t cache_entry_insert_resume(rlm_cache_entry_t **out, UNUSED rlm_cache_config_t const *config, void *instance, UNUSED request_t *request, UNUSED void *handle, void *rctx)
rlm_cache_redis_t const * inst
Module instance.
static fr_dict_t const * dict_redis
static fr_dict_attr_t const * attr_cache_expires
static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
module_instance_t const * mi
Module instance.
fr_redis_ct_t * rtcluster
Per thread Redis cluster.
static int cache_redis_rctx_free(rlm_cache_redis_rctx_t *rctx)
CONF_SECTION * tls_conf
TLS CONF_SECTION.
static void cache_redis_cancel(UNUSED rlm_cache_config_t const *config, UNUSED void *instance, request_t *request, UNUSED void *handle, void *rctx)
fr_redis_async_cmd_t * cmd
fr_dict_autoload_t rlm_cache_redis_dict[]
static cache_status_t cache_redis_results(request_t *request, rlm_cache_redis_t *inst, fr_redis_command_set_t *cmds, fr_redis_async_cmd_t *cmd, cache_status_t rcode)
Process the results of Redis cache commands.
tmpl_t * created_attr
LHS of the Cache-Created map.
static cache_status_t cache_entry_find(UNUSED rlm_cache_entry_t **out, void **rctx_out, UNUSED rlm_cache_config_t const *config, void *instance, request_t *request, UNUSED void *handle, fr_value_box_t const *key)
Locate a cache entry in redis.
static int mod_thread_detach(module_thread_inst_ctx_t const *mctx)
static cache_status_t cache_entry_insert(UNUSED void **rctx_out, UNUSED rlm_cache_config_t const *config, void *instance, request_t *request, UNUSED void *handle, const rlm_cache_entry_t *c)
Insert a new entry into the data store.
static int mod_instantiate(module_inst_ctx_t const *mctx)
Create a new rlm_cache_redis instance.
fr_coord_pair_reg_t * coord_pair_reg
Coord pair registration.
fr_redis_command_set_t * cmds
fr_coord_reg_t * coord_reg
Coordinator registration.
int fr_schedule_worker_id(void)
Return the worker id for the current thread.
Definition schedule.c:110
struct map_s map_t
Definition map.h:33
#define MODULE_THREAD_INST(_ctype)
Definition module.h:258
char const * name
Instance name e.g. user_database.
Definition module.h:357
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:351
void * data
Module's instance data.
Definition module.h:293
module_instance_t const * parent
Parent module's instance (if any).
Definition module.h:359
module_instantiate_t instantiate
Callback to allow the module to register any per-instance resources like sockets and file handles.
Definition module.h:227
void * data
Thread specific instance data.
Definition module.h:374
static module_thread_instance_t * module_thread(module_instance_t const *mi)
Retrieve module/thread specific instance for a module.
Definition module.h:513
Module instance data.
Definition module.h:287
#define tmpl_value(_tmpl)
Definition tmpl.h:937
ssize_t tmpl_afrom_attr_str(TALLOC_CTX *ctx, tmpl_attr_error_t *err, tmpl_t **out, char const *name, tmpl_rules_t const *rules))
Parse a string into a TMPL_TYPE_ATTR_* type tmpl_t.
@ TMPL_TYPE_DATA
Value in native boxed format.
Definition tmpl.h:138
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition tmpl.h:801
tmpl_t * tmpl_init_shallow(tmpl_t *vpt, tmpl_type_t type, fr_token_t quote, char const *name, ssize_t len, tmpl_rules_t const *t_rules))
Initialise a tmpl without copying the input name string.
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition snprintf.c:689
eap_aka_sim_process_conf_t * inst
Value pair map.
Definition map.h:77
fr_token_t op
The operator that controls insertion of the dst attribute.
Definition map.h:82
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition table.h:804
#define talloc_zero_pooled_object(_ctx, _type, _num_subobjects, _total_subobjects_size)
Definition talloc.h:208
#define talloc_asprintf
Definition talloc.h:151
#define fr_unix_time_ispos(_a)
Definition time.h:372
static int64_t fr_unix_time_to_sec(fr_unix_time_t delta)
Definition time.h:506
@ T_BARE_WORD
Definition token.h:118
@ T_OP_SET
Definition token.h:82
static fr_slen_t head
Definition xlat.h:421
int fr_value_box_copy(TALLOC_CTX *ctx, fr_value_box_t *dst, const fr_value_box_t *src)
Copy value data verbatim duplicating any buffers.
Definition value.c:4416
#define fr_box_strvalue_len(_val, _len)
Definition value.h:309
#define fr_value_box_init(_vb, _type, _enumv, _tainted)
Initialise a fr_value_box_t.
Definition value.h:610
static size_t char ** out
Definition value.h:1030