24 #define LOG_PREFIX "cache - redis"
26 #include <freeradius-devel/server/base.h>
27 #include <freeradius-devel/util/debug.h>
28 #include <freeradius-devel/util/value.h>
30 #include "../../rlm_cache.h"
31 #include <freeradius-devel/redis/base.h>
32 #include <freeradius-devel/redis/cluster.h>
86 buffer,
"modules.cache.pool", NULL);
88 ERROR(
"Cluster failure");
96 ERROR(
"Cache-Created attribute not defined");
101 ERROR(
"Cache-Expires attribute not defined");
133 redisReply *reply = NULL;
137 #ifdef HAVE_TALLOC_ZERO_POOLED_OBJECT
138 size_t pool_size = 0;
142 map_list_init(&
head);
150 RDEBUG3(
"LRANGE %pV 0 -1", key);
151 reply = redisCommand(conn->
handle,
"LRANGE %b 0 -1", key->vb_strvalue, key->vb_length);
155 RERROR(
"Failed retrieving entry for key \"%pV\"", key);
164 if (reply->type != REDIS_REPLY_ARRAY) {
165 REDEBUG(
"Bad result type, expected array, got %s",
170 RDEBUG3(
"Entry contains %zu elements", reply->elements);
172 if (reply->elements == 0) {
177 if (reply->elements % 3) {
178 REDEBUG(
"Invalid number of reply elements (%zu). "
179 "Reply must contain triplets of keys operators and values",
184 #ifdef HAVE_TALLOC_ZERO_POOLED_OBJECT
188 for (i = 0; i < reply->elements; i += 3) {
190 if (reply->element[i]->type == REDIS_REPLY_STRING) pool_size += reply->element[i]->len + 1;
202 map_list_init(&c->
maps);
206 for (i = 0; i < reply->elements; i += 3) {
208 reply->element[i], reply->element[i + 1], reply->element[i + 2]) < 0) {
224 map = map_list_pop_head(&
head);
236 map = map_list_pop_head(&
head);
264 redisReply *reply = NULL;
267 static char const command[] =
"RPUSH";
273 unsigned int pipelined = 0;
274 redisReply *replies[5];
275 size_t reply_cnt = 0, i;
283 .rhs = &expires_value,
290 .rhs = &created_value,
310 cnt = map_list_num_elements(&c->
maps) + 2;
317 pool = talloc_pool(request, 1024);
320 argv_p = argv = talloc_array(pool,
char const *, (cnt * 3) + 2);
321 argv_len_p = argv_len = talloc_array(pool,
size_t, (cnt * 3) + 2);
324 *argv_len_p++ =
sizeof(command) - 1;
326 *argv_p++ = (
char const *)c->
key.vb_strvalue;
327 *argv_len_p++ = c->
key.vb_length;
333 REDEBUG(
"Failed encoding map as Redis K/V pair");
340 REDEBUG(
"Failed encoding map as Redis K/V pair");
346 while ((map = map_list_next(&c->
maps, map))) {
348 REDEBUG(
"Failed encoding map as Redis K/V pair");
356 RDEBUG3(
"Pipelining commands");
366 if (redisAppendCommand(conn->
handle,
"MULTI") != REDIS_OK) {
369 RERROR(
"Failed appending Redis command to output buffer: %s", conn->
handle->errstr);
378 if (redisAppendCommand(conn->
handle,
"DEL %b", (
uint8_t const *)c->
key.vb_strvalue, c->
key.vb_length) != REDIS_OK)
goto append_error;
384 for (i = 0; i < talloc_array_length(argv); i++) {
389 redisAppendCommandArgv(conn->
handle, talloc_array_length(argv), argv, argv_len);
396 RDEBUG3(
"EXPIREAT \"%pV\" %" PRIu64,
399 if (redisAppendCommand(conn->
handle,
"EXPIREAT %b %" PRIu64, (
uint8_t const *)c->
key.vb_strvalue, (
size_t)c->
key.vb_length,
403 if (redisAppendCommand(conn->
handle,
"EXEC") != REDIS_OK)
goto append_error;
415 RPERROR(
"Failed inserting entry");
439 redisReply *reply = NULL;
446 reply = redisCommand(conn->
handle,
"DEL %b", (
uint8_t const *)key->vb_strvalue, key->vb_length);
451 RERROR(
"Failed expiring entry");
458 if (reply->type == REDIS_REPLY_INTEGER) {
460 if (reply->integer) cache_status =
CACHE_OK;
465 REDEBUG(
"Bad result type, expected integer, got %s",
476 .name =
"cache_redis",
static int const char char buffer[256]
int cf_section_parse(TALLOC_CTX *ctx, void *base, CONF_SECTION *cs)
Parse a configuration section into user-supplied variables.
#define CONF_PARSER_TERMINATOR
#define cf_section_rules_push(_cs, _rule)
Defines a CONF_PAIR to C data type mapping.
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.
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.
fr_redis_cluster_t * fr_redis_cluster_alloc(TALLOC_CTX *ctx, CONF_SECTION *module, fr_redis_conf_t *conf, bool triggers_enabled, char const *log_prefix, char const *trigger_prefix, fr_pair_list_t *trigger_args)
Allocate and initialise a new cluster structure.
Redis connection sequence state.
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
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.
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.
#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.
module_instance_t * mi
Instance of the module being instantiated.
Temporary structure to hold arguments for instantiation calls.
static const conf_parser_t config[]
static void fr_redis_pipeline_free(redisReply *reply[], size_t num)
redisContext * handle
Hiredis context used when issuing commands.
#define REDIS_COMMON_CONFIG
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_reply_print(fr_log_lvl_t lvl, redisReply *reply, request_t *request, int idx)
Print the response data in a useful treelike form.
static void fr_redis_reply_free(redisReply **reply)
Wrap freeReplyObject so we consistently check for NULL pointers.
void fr_redis_version_print(void)
Print the version of libhiredis the server was built against.
fr_redis_rcode_t fr_redis_command_status(fr_redis_conn_t *conn, redisReply *reply)
Check the reply for errors.
fr_table_num_sorted_t const redis_reply_types[]
fr_redis_rcode_t fr_redis_pipeline_result(unsigned int *pipelined, fr_redis_rcode_t *rcode, redisReply *out[], size_t out_len, fr_redis_conn_t *conn)
Simplifies handling of pipelined commands with Redis cluster.
fr_redis_rcode_t
Codes are ordered inversely by priority.
@ REDIS_RCODE_SUCCESS
Operation was successful.
@ REDIS_RCODE_TRY_AGAIN
Try the operation again.
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.
Connection handle, holding a redis context.
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_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_insert(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_load(void)
fr_redis_conf_t conf
Connection parameters for the Redis server.
static fr_dict_attr_t const * attr_cache_created
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
tmpl_t * expires_attr
LHS of the Cache-Expires map.
static fr_dict_t const * dict_freeradius
static conf_parser_t driver_config[]
static cache_status_t cache_entry_expire(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 fr_dict_attr_t const * attr_cache_expires
static cache_status_t cache_entry_find(rlm_cache_entry_t **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.
fr_redis_cluster_t * cluster
fr_dict_autoload_t rlm_cache_redis_dict[]
tmpl_t * created_attr
LHS of the Cache-Created map.
static int mod_instantiate(module_inst_ctx_t const *mctx)
Create a new rlm_cache_redis instance.
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).
#define tmpl_value(_tmpl)
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
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.
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)
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