29 RCSID(
"$Id: a393f98a94b4a32ca24ecc1c59adc2e0c5b668df $")
34 #include <freeradius-devel/redis/base.h>
35 #include <freeradius-devel/redis/cluster.h>
37 #include <freeradius-devel/server/base.h>
38 #include <freeradius-devel/server/cf_util.h>
39 #include <freeradius-devel/server/modpriv.h>
40 #include <freeradius-devel/server/module_rlm.h>
41 #include <freeradius-devel/server/pool.h>
43 #include <freeradius-devel/unlang/xlat.h>
44 #include <freeradius-devel/unlang/xlat_func.h>
46 #include <freeradius-devel/util/base16.h>
47 #include <freeradius-devel/util/debug.h>
48 #include <freeradius-devel/util/log.h>
49 #include <freeradius-devel/util/talloc.h>
50 #include <freeradius-devel/util/types.h>
51 #include <freeradius-devel/util/value.h>
99 .subcs_type =
"redis_lua_func_t", .name2 =
CF_IDENT_ANY },
134 body = *((
char **)
out);
168 int argc,
char const **argv,
size_t arg_len[])
170 bool maybe_more =
false;
176 if (read_only) redisAppendCommand(conn->
handle,
"READONLY");
177 redisAppendCommandArgv(conn->
handle, argc, argv, arg_len);
179 redisAppendCommand(conn->
handle,
"READWRITE");
180 }
else goto parse_reply;
187 if (redisGetReply(conn->
handle, (
void **)&reply) == REDIS_OK) maybe_more =
true;
193 *status_out = status;
196 if (redisGetReply(conn->
handle, (
void **)&reply) != REDIS_OK)
return -1;
198 if (redisGetReply(conn->
handle, (
void **)&reply) != REDIS_OK)
return -1;
211 if ((redisGetReply(conn->
handle, (
void **)&reply) == REDIS_OK) && read_only) maybe_more =
true;
215 *status_out = status;
218 if (redisGetReply(conn->
handle, (
void **)&reply) != REDIS_OK)
return -1;
226 *status_out = status;
228 if (!read_only)
return 0;
234 if ((redisGetReply(conn->
handle, (
void **)&reply) != REDIS_OK) ||
241 *status_out = status;
276 if (
fr_inet_pton_port(&node_addr.inet.dst_ipaddr, &node_addr.inet.dst_port, in_head->vb_strvalue, in_head->vb_length,
277 AF_UNSPEC,
true,
true) < 0) {
278 RPEDEBUG(
"Failed parsing node address");
283 RPEDEBUG(
"Failed locating cluster node");
289 REDEBUG(
"No connections available for cluster node");
336 unsigned long idx = 0;
341 if (idx_vb) idx = idx_vb->vb_uint32;
352 RDEBUG2(
"No node available for this key slot");
357 REDEBUG(
"Failed retrieving node information");
391 redisReply *reply = NULL;
397 char key_count[
sizeof(
"184467440737095551615")];
408 RPERROR(
"Failed converting key count to string");
411 fr_value_box_list_talloc_free_head(
in);
417 arg_len[0] =
sizeof(
"EVALSHA") - 1;
419 arg_len[1] =
sizeof(func->
digest) - 1;
421 arg_len[2] = strlen(key_count);
426 REDEBUG(
"Too many arguments (%i)", argc);
442 argv[argc] = vb->vb_strvalue;
443 arg_len[argc++] = vb->vb_length;
451 key = (
uint8_t const *)argv[3];
452 key_len = arg_len[3];
458 bool script_load_done =
false;
465 for (
int i = 2; i < argc; i++)
RDEBUG3(
"[%i] %s", i, argv[i]);
469 func->
read_only, argc, argv, arg_len) == -2) {
482 char const *script_load_argv[] = {
488 size_t script_load_arg_len[] = {
489 (
sizeof(
"SCRIPT") - 1),
490 (
sizeof(
"LOAD") - 1),
491 (talloc_array_length(func->
body) - 1)
497 if (script_load_done) {
513 script_load_argv, script_load_arg_len) == -2) {
518 script_load_done =
true;
523 if (reply->type != REDIS_REPLY_STRING) {
524 REDEBUG(
"Unexpected reply type after loading function");
526 goto script_load_failed;
529 if (strcmp(reply->str, func->
digest) != 0) {
530 REDEBUG(
"Function digest %s, does not match calculated digest %s", reply->str, func->
digest);
531 goto script_load_failed;
551 RPERROR(
"Failed processing reply");
597 bool read_only =
false;
604 redisReply *reply = NULL;
625 RDEBUG3(
"Overriding node selection");
629 AF_UNSPEC,
true,
true) < 0) {
630 RPEDEBUG(
"Failed parsing node address");
635 RPEDEBUG(
"Failed locating cluster node");
641 REDEBUG(
"No connections available for cluster node");
645 fr_value_box_list_talloc_free_head(
in);
649 REDEBUG(
"Too many arguments (%i)", argc);
665 argv[argc] = vb->vb_strvalue;
666 arg_len[argc++] = vb->vb_length;
669 RDEBUG2(
"Executing command: %pV", fr_value_box_list_head(
in));
673 for (
int i = 1; i < argc; i++)
RDEBUG2(
"[%i] %s", i, argv[i]);
677 if (
redis_command(&status, &reply, request, conn, read_only, argc, argv, arg_len) == -2) {
681 if (!reply)
goto fail;
689 REDEBUG(
"Key served by a different node: %pV", &vb);
711 RDEBUG2(
"REDIS command arguments");
715 REDEBUG(
"Too many arguments (%i)", argc);
720 argv[argc] = vb->vb_strvalue;
721 arg_len[argc] = vb->vb_length;
734 key = (
uint8_t const *)argv[1];
735 key_len = arg_len[1];
741 RDEBUG2(
"Executing command: %pV", fr_value_box_list_head(
in));
745 for (
int i = 1; i < argc; i++)
RDEBUG2(
"[%i] %s", i, argv[i]);
749 if (
redis_command(&status, &reply, request, conn, read_only, argc, argv, arg_len) == -2) {
766 RPERROR(
"Failed processing reply");
785 if (!
inst->cluster)
return -1;
790 if (talloc_array_length(
inst->lua.funcs) == 0)
return 0;
793 if (ret <= 0)
return 0;
795 for (i = 0; i < ret; i++) {
808 char const *script_load_argv[] = {
814 size_t script_load_arg_len[] = {
815 (
sizeof(
"SCRIPT") - 1),
816 (
sizeof(
"LOAD") - 1),
817 (talloc_array_length(func->body) - 1)
827 NUM_ELEMENTS(script_load_argv), script_load_argv, script_load_arg_len) == -2) {
841 PERROR(
"Loading lua function \"%s\" onto node failed", func->name);
845 DEBUG2(
"Loaded lua function \"%s\" onto node", func->name);
849 PWARN(
"Loading lua function \"%s\" onto node failed", func->name);
#define fr_base16_encode(_out, _in)
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.
#define CONF_PARSER_TERMINATOR
#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
#define FR_CONF_SUBSECTION_ALLOC(_name, _type, _flags, _struct, _field, _subcs)
A conf_parser_t multi-subsection.
#define FR_CONF_OFFSET_SUBSECTION(_name, _flags, _struct, _field, _subcs)
conf_parser_t which populates a sub-struct using a CONF_SECTION
@ CONF_FLAG_MULTI
CONF_PAIR can have multiple copies.
@ CONF_FLAG_OK_MISSING
OK if it's missing.
@ CONF_FLAG_SUBSECTION
Instead of putting the information into a configuration structure, the configuration file routines MA...
Defines a CONF_PAIR to C data type mapping.
Common header for all CONF_* types.
char const * cf_section_name2(CONF_SECTION const *cs)
Return the second identifier of a CONF_SECTION.
CONF_SECTION * cf_item_to_section(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_SECTION.
#define cf_log_err(_cf, _fmt,...)
#define cf_log_debug(_cf, _fmt,...)
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_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.
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.
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.
int fr_redis_cluster_port(uint16_t *out, fr_redis_cluster_node_t const *node)
Return the port of a particular node.
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.
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.
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_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.
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.
fr_table_num_sorted_t const fr_redis_cluster_rcodes_table[]
int fr_redis_cluster_ipaddr(fr_ipaddr_t *out, fr_redis_cluster_node_t const *node)
Return the ipaddr of a particular node.
Indexes in the fr_redis_cluster_node_t array for a single key slot.
bool close_conn
Set by caller of fr_redis_cluster_state_next, to indicate that connection must be closed,...
fr_redis_cluster_rcode_t
Return values for internal functions.
@ FR_REDIS_CLUSTER_RCODE_NO_CONNECTION
Operation failed because we couldn't find a live connection.
Redis connection sequence state.
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
static int fr_dcursor_append(fr_dcursor_t *cursor, void *v)
Insert a single item at the end of the list.
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
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.
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.
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.
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)
#define REXDENT()
Exdent (unindent) R* messages by one level.
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
#define RPEDEBUG(fmt,...)
#define DEBUG_ENABLED3
True if global debug level 1-3 messages are enabled.
#define RINDENT()
Indent R* messages by one level.
@ L_DBG_LVL_OFF
No debug messages.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_UINT64
64 Bit unsigned integer.
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 instantiation calls.
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)
module_t common
Common fields presented by all modules.
void fr_pool_connection_release(fr_pool_t *pool, request_t *request, void *conn)
Release a connection.
void * fr_pool_connection_get(fr_pool_t *pool, request_t *request)
Reserve a connection in the connection pool.
int fr_pool_connection_close(fr_pool_t *pool, request_t *request, void *conn)
Delete a connection from the connection pool.
static const conf_parser_t config[]
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.
redisContext * handle
Hiredis context used when issuing commands.
#define REDIS_COMMON_CONFIG
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_redis_rcode_t
Codes are ordered inversely by priority.
@ REDIS_RCODE_RECONNECT
Transitory error, caller should retry the operation with a new connection.
@ REDIS_RCODE_SUCCESS
Operation was successful.
@ REDIS_RCODE_MOVE
Attempt operation on an alternative node with remap.
@ REDIS_RCODE_TRY_AGAIN
Try the operation again.
@ REDIS_RCODE_NO_SCRIPT
Script doesn't exist.
@ REDIS_RCODE_ERROR
Unrecoverable library/server error.
Configuration parameters for a redis connection.
Connection handle, holding a redis context.
static int mod_load(void)
rlm_redis_lua_t lua
Array of functions to register.
static xlat_arg_parser_t const redis_remap_xlat_args[]
static conf_parser_t module_lua[]
fr_redis_conf_t conf
Connection parameters for the Redis server.
static int redis_lua_func_instantiate(xlat_inst_ctx_t const *xctx)
Copies the function configuration into xlat function instance data.
redis_lua_func_t ** funcs
Array of functions to register.
char digest[(SHA1_DIGEST_LENGTH *2)+1]
pre-computed hash of lua code.
static int mod_bootstrap(module_inst_ctx_t const *mctx)
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.
redis_lua_func_t * func
Function configuration.
static conf_parser_t module_lua_func[]
char const * body
the actual lua code.
char const * name
Friendly name for the function. Used to register the equivalent xlat.
static xlat_arg_parser_t const redis_node_xlat_args[]
fr_redis_cluster_t * cluster
Redis cluster.
static xlat_arg_parser_t const redis_args[]
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.
static int mod_instantiate(module_inst_ctx_t const *mctx)
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.
static conf_parser_t module_config[]
static xlat_arg_parser_t const redis_lua_func_args[]
bool read_only
Function has no side effects.
Instance of a redis lua func xlat.
A lua function or stored procedure we make available as an xlat.
rlm_redis module instance
static int instantiate(module_inst_ctx_t const *mctx)
bool fr_sbuff_next_if_char(fr_sbuff_t *sbuff, char c)
Return true if the current char matches, and if it does, advance.
#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.
void * data
Module's instance data.
void * boot
Data allocated during the boostrap phase.
void fr_sha1_init(fr_sha1_ctx *context)
void fr_sha1_final(uint8_t digest[static SHA1_DIGEST_LENGTH], fr_sha1_ctx *context)
void fr_sha1_update(fr_sha1_ctx *context, uint8_t const *in, size_t len)
#define SHA1_DIGEST_LENGTH
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
eap_aka_sim_process_conf_t * inst
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
#define talloc_get_type_abort_const
#define talloc_foreach(_array, _iter)
Iterate over a talloced array of elements.
bool required
Argument must be present, and non-empty.
@ XLAT_ARG_VARIADIC_EMPTY_KEEP
Empty argument groups are left alone, and either passed through as empty groups or null boxes.
#define XLAT_ARG_PARSER_TERMINATOR
@ XLAT_ACTION_FAIL
An xlat function failed.
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
Definition for a single argument consumend by an xlat function.
Holds information necessary for binding or connecting to a socket.
#define fr_type_is_string(_x)
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.
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.
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.
#define fr_box_ipaddr(_val)
#define fr_box_strvalue_len(_val, _len)
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
#define fr_value_box_list_foreach(_list_head, _iter)
static size_t char ** out
void const * inst
xlat instance data.
void * uctx
Passed to the registration function.
module_ctx_t const * mctx
Synthesised module calling ctx.
void * inst
xlat instance data to populate.
An xlat instantiation ctx.
int xlat_func_args_set(xlat_t *x, xlat_arg_parser_t const args[])
Register the arguments of an xlat.
#define xlat_func_instantiate_set(_xlat, _instantiate, _inst_struct, _detach, _uctx)
Set a callback for global instantiation of xlat functions.