24#define LOG_PREFIX "cache - redis"
26#include <freeradius-devel/server/base.h>
27#include <freeradius-devel/util/debug.h>
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>
110 if (
inst->conf.use_tls) {
113 if (!
inst->tls_conf) {
123 ERROR(
"Cache-Created attribute not defined");
128 ERROR(
"Cache-Expires attribute not defined");
132 if (!
inst->conf.use_cluster_map)
return 0;
134 if (
inst->conf.database) {
135 cf_log_err(mctx->
mi->
conf,
"Cannot set Redis database number when cluster in use");
141 .worker_cb = worker_pair_callbacks,
142 .cb_id = REDIS_COORD_PAIR_CALLBACK_ID,
147 if (!
inst->coord_pair_reg)
return -1;
153 .coord_cb = coord_callbacks,
154 .worker_cb = worker_callbacks,
158 if (!
inst->coord_reg)
return -1;
180 if (!
inst->conf.use_cluster_map)
return 0;
185 ERROR(
"Failed to attach to coordinator");
198 if (!t->
cw)
return 0;
209 if (!
inst->conf.use_cluster_map)
return 0;
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]);
255 inst->coord_pair_reg,
false);
268 RPERROR(
"Server returned error");
277 UNUSED void *handle,
void *rctx)
281 RDEBUG2(
"Forcibly cancelling pending redis cache request");
287 UNUSED void *handle,
void *rctx)
300#ifdef HAVE_TALLOC_ZERO_POOLED_OBJECT
301 size_t pool_size = 0;
306 if (reply->type != REDIS_REPLY_ARRAY) {
307 REDEBUG(
"Bad result type, expected array, got %s",
314 RDEBUG3(
"Entry contains %zu elements", reply->elements);
316 if (reply->elements == 0) {
321 if (reply->elements % 3) {
322 REDEBUG(
"Invalid number of reply elements (%zu). "
323 "Reply must contain triplets of keys operators and values",
328 map_list_init(&
head);
330#ifdef HAVE_TALLOC_ZERO_POOLED_OBJECT
334 for (i = 0; i < reply->elements; i += 3) {
336 if (reply->element[i]->type == REDIS_REPLY_STRING) pool_size += reply->element[i]->len + 1;
348 map_list_init(&c->
maps);
352 for (i = 0; i < reply->elements; i += 3) {
354 reply->element[i], reply->element[i + 1], reply->element[i + 2]) < 0) {
369 map = map_list_pop_head(&
head);
381 map = map_list_pop_head(&
head);
388 cache_rctx->
entry = c;
410 rctx->
cmd_str = talloc_zero_array(rctx,
char *, 1);
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);
416 RERROR(
"Failed formatting redis command");
425 key->vb_length, cmds,
false, NULL);
427 "Failed to enqueue Redis cache find commands",
CACHE_ERROR)
435 UNUSED void *handle,
void *rctx)
475 static char const command[] =
"RPUSH";
488 .rhs = &expires_value,
495 .rhs = &created_value,
518 cnt = map_list_num_elements(&c->
maps) + 2;
525 pool = talloc_pool(rctx, 1024);
528 argv_p = argv = talloc_array(pool,
char const *, (cnt * 3) + 2);
529 argv_len_p = argv_len = talloc_array(pool,
size_t, (cnt * 3) + 2);
532 *argv_len_p++ =
sizeof(command) - 1;
534 *argv_p++ = (
char const *)c->
key.vb_strvalue;
535 *argv_len_p++ = c->
key.vb_length;
541 REDEBUG(
"Failed encoding map as Redis K/V pair");
548 REDEBUG(
"Failed encoding map as Redis K/V pair");
554 while ((map = map_list_next(&c->
maps, map))) {
556 REDEBUG(
"Failed encoding map as Redis K/V pair");
567 MEM(rctx->
cmd_str = talloc_zero_array(rctx,
char *, 3));
570 RDEBUG3(
"Pipelining commands");
582 cmd_len = redisFormatCommand(&rctx->
cmd_str[0],
"DEL %b", (
uint8_t const *)c->
key.vb_strvalue, c->
key.vb_length);
585 RERROR(
"Failed formatting redis command");
594 for (i = 0; i < talloc_array_length(argv); i++) {
599 cmd_len = redisFormatCommandArgv(&rctx->
cmd_str[1], talloc_array_length(argv), argv, argv_len);
600 if (cmd_len < 0)
goto format_error;
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;
623 c->
key.vb_length, cmds,
false, NULL);
625 "Failed to enqueue Redis cache insert commands",
CACHE_ERROR)
644 if (reply->type == REDIS_REPLY_INTEGER) {
649 REDEBUG(
"Bad result type, expected integer, got %s",
673 rctx->
cmd_str = talloc_zero_array(rctx,
char *, 1);
676 cmd_len = redisFormatCommand(&rctx->
cmd_str[0],
"DEL %b", (
uint8_t const *)key->vb_strvalue, key->vb_length);
678 RERROR(
"Failed formatting redis command");
687 key->vb_length, cmds,
false, NULL);
689 "Failed to enqueue Redis cache expire command",
CACHE_ERROR)
699 .name =
"cache_redis",
static int const char char buffer[256]
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
#define CONF_PARSER_TERMINATOR
Defines a CONF_PAIR to C data type mapping.
A section grouping multiple CONF_PAIR.
CONF_SECTION * cf_section_find(CONF_SECTION const *cs, char const *name1, char const *name2)
Find a CONF_SECTION with name1 and optionally name2.
#define cf_log_err(_cf, _fmt,...)
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.
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.
void fr_coord_deregister(fr_coord_reg_t *coord_reg)
De-register a coordinator.
int fr_coord_detach(fr_coord_worker_t *cw, bool exiting)
Signal a coordinator that a worker wants to detach.
A coordinator registration.
The worker end of worker <-> coordinator communication.
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.
struct fr_coord_pair_reg_s fr_coord_pair_reg_t
#define FR_COORD_PAIR_CB_CTX_SET(_in_cb, _out_cb, _reg)
Set up ctx on pair list callbacks.
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
#define DICT_AUTOLOAD_TERMINATOR
Specifies an attribute which must be present for the module to function.
Specifies a dictionary which must be loaded/loadable for the module to function.
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
TALLOC_CTX * unlang_interpret_frame_talloc_ctx(request_t *request)
Get a talloc_ctx which is valid only for this frame.
#define REXDENT()
Exdent (unindent) R* messages by one level.
#define RDEBUG_ENABLED3
True if request debug level 1-3 messages are enabled.
#define RINDENT()
Indent R* messages by one level.
@ L_DBG_LVL_3
3rd highest priority debug messages (-xxx | -Xx).
@ FR_TYPE_DATE
Unix time stamp, always has value >2^31.
fr_event_list_t * el
Event list to register any IO handlers and timers against.
module_instance_t * mi
Module instance to detach.
void * thread
Thread instance data.
module_instance_t const * mi
Instance of the module being instantiated.
module_instance_t * mi
Instance of the module being instantiated.
Temporary structure to hold arguments for detach calls.
Temporary structure to hold arguments for instantiation calls.
Temporary structure to hold arguments for thread_instantiation calls.
fr_redis_async_rcode_t fr_redis_command_set_rcode(fr_redis_command_set_t *cmds)
Extract the rcode from a command set.
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.
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.
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.
Represents a single command.
Represents a collection of pipelined commands.
@ FR_REDIS_PIPELINE_OK
No failure.
static const conf_parser_t config[]
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.
int redis_dict_init(void)
Load the Redis dictionaries.
#define REDIS_COMMON_CONFIG
@ REDIS_ASYNC_RCODE_MOVE
Attempt operation on an alternative node with remap.
@ REDIS_ASYNC_RCODE_ERROR
Unrecoverable error.
@ REDIS_ASYNC_RCODE_ASK
Attempt operation on an alternative node.
@ REDIS_ASYNC_RCODE_TRY_AGAIN
Try the operation again.
@ REDIS_ASYNC_RCODE_SUCCESS
Operation was successful.
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.
void fr_redis_version_print(void)
Print the version of libhiredis the server was built against.
fr_table_num_sorted_t const redis_reply_types[]
@ REDIS_RCODE_SUCCESS
Operation was successful.
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.
Configuration parameters for a redis connection.
fr_value_box_t key
Key used to identify entry.
map_list_t maps
Head of the maps list.
fr_unix_time_t created
When the entry was created.
module_t common
Common fields for all loadable modules.
@ CACHE_ERROR
Fatal error.
@ CACHE_YIELD
The driver has pushed an async.
@ CACHE_OK
Cache entry found/updated.
@ CACHE_MISS
Cache entry notfound.
fr_unix_time_t expires
When the entry expires.
Configuration for the rlm_cache module.
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.
#define MODULE_THREAD_INST(_ctype)
char const * name
Instance name e.g. user_database.
CONF_SECTION * conf
Module's instance configuration.
void * data
Module's instance data.
module_instance_t const * parent
Parent module's instance (if any).
module_instantiate_t instantiate
Callback to allow the module to register any per-instance resources like sockets and file handles.
void * data
Thread specific instance data.
static module_thread_instance_t * module_thread(module_instance_t const *mi)
Retrieve module/thread specific instance for a module.
#define tmpl_value(_tmpl)
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.
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
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)
eap_aka_sim_process_conf_t * inst
fr_token_t op
The operator that controls insertion of the dst attribute.
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
#define talloc_zero_pooled_object(_ctx, _type, _num_subobjects, _total_subobjects_size)
#define fr_unix_time_ispos(_a)
static int64_t fr_unix_time_to_sec(fr_unix_time_t delta)
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.
#define fr_box_strvalue_len(_val, _len)
#define fr_value_box_init(_vb, _type, _enumv, _tainted)
Initialise a fr_value_box_t.
static size_t char ** out