28#include <freeradius-devel/server/connection.h>
29#include <freeradius-devel/server/trunk.h>
211 switch (cmds->
treq->state) {
224 talloc_free_children(cmds);
225 memset(cmds, 0,
sizeof(*cmds));
259#define COMMAND_PRE_ALLOC_COUNT 8
260#define COMMAND_PRE_ALLOC_LEN 64
314 switch (tolower(cmd[0])) {
316 if (tolower(cmd[1]) !=
'u')
break;
317 if (
strncasecmp(cmd,
"multi",
sizeof(
"multi") - 1) != 0)
break;
336 if (tolower(cmd[1]) !=
'x')
break;
337 if (
strncasecmp(cmd,
"exec",
sizeof(
"exec") - 1) != 0)
break;
346 if (tolower(cmd[1]) !=
'i')
break;
347 if (
strncasecmp(cmd,
"discard",
sizeof(
"discard") - 1) != 0)
break;
358 if (tolower(cmd[1]) !=
'a')
break;
360 if (
strncasecmp(cmd,
"wait",
sizeof(
"wait") - 1) == 0) {
365 if (
strncasecmp(cmd,
"watch",
sizeof(
"watch") - 1) != 0)
break;
437 char const **argv,
size_t *argv_len,
451 cmd->argv_len = argv_len;
486 char const *p = cmd_str, *end;
494 ERROR(
"Incorrect Redis command format");
497 while (isdigit(*p) && (p < end)) p++;
498 if (*p++ !=
'\r')
goto error;
499 if (*p++ !=
'\n')
goto error;
500 if (*p++ !=
'$')
goto error;
501 while (isdigit(*p) && (p < end)) p++;
502 if (*p++ !=
'\r')
goto error;
503 if (*p++ !=
'\n')
goto error;
511 cmd->str_len = cmd_len;
535 ERROR(
"Refusing to enqueue - Unbalanced transaction start/stop commands");
573 if (!redirect || (redirect->type != REDIS_REPLY_ERROR))
return -1;
598 AF_UNSPEC,
true,
true) < 0) {
629 redisReply *reply = vreply;
643 DEBUG4(
"Ignoring response with SQN %"PRIu64, (h->
rsp_sqn - 1));
683 if (reply->type == REDIS_REPLY_ERROR) {
686 fr_assert_msg(reply->str,
"Error response contained no error string");
731 char const *log_prefix,
void *uctx)
766 request = treq->request;
778 for (i = 0; i < cmd->argc; i++) {
785 cmd->argv, cmd->argv_len);
796 cmd->str, cmd->str_len);
943 void *active_uctx,
bool active_oneshot)
959 .rtcluster = rtcluster,
961 .active_uctx = active_uctx,
964 io_conf->
log_prefix, rtrunk,
false, trigger_args);
965 if (!rtrunk->
trunk) {
#define fr_atexit_thread_local(_name, _free, _uctx)
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
#define CC_NO_UBSAN(_sanitize)
trunk_conf_t const * fr_redis_ct_trunk_conf(fr_redis_ct_t *rtcluster)
fr_event_list_t * fr_redis_ct_el(fr_redis_ct_t *rtcluster)
fr_redis_trunk_active_t active
Callback to run when the trunk becomes active.
Thread local state for a cluster.
Redis asynchronous cluster management.
#define KEY_SLOTS
Maximum number of keyslots (should not change).
@ CONNECTION_STATE_CONNECTED
File descriptor is open (ready for writing).
#define fr_assert_msg(_x, _msg,...)
Calls panic_action ifndef NDEBUG, else logs error and causes the server to exit immediately with code...
#define fr_dlist_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
static void * fr_dlist_head(fr_dlist_head_t const *list_head)
Return the HEAD item of a list or NULL if the list is empty.
#define fr_dlist_foreach(_list_head, _type, _iter)
Iterate over the contents of a list.
static void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
static bool fr_dlist_entry_in_list(fr_dlist_t const *entry)
Check if a list entry is part of a list.
static void fr_dlist_entry_unlink(fr_dlist_t *entry)
Remove an item from the dlist when we don't have access to the head.
static unsigned int fr_dlist_num_elements(fr_dlist_head_t const *head)
Return the number of elements in the dlist.
static void * fr_dlist_pop_tail(fr_dlist_head_t *list_head)
Remove the tail item in a list.
static void * fr_dlist_pop_head(fr_dlist_head_t *list_head)
Remove the head item in a list.
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
static int fr_dlist_move(fr_dlist_head_t *list_dst, fr_dlist_head_t *list_src)
Merge two lists, inserting the source at the tail of the destination.
#define fr_dlist_talloc_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
static int fr_dlist_insert_head(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the head of a list.
static void fr_dlist_entry_init(fr_dlist_t *entry)
Initialise a linked list without metadata.
static void * fr_dlist_next(fr_dlist_head_t const *list_head, void const *ptr)
Get the next item in a list.
static void fr_dlist_clear(fr_dlist_head_t *list_head)
Efficiently remove all elements in a dlist.
Head of a doubly linked list.
Entry in a doubly linked list.
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)
char * fr_inet_ntop(char out[static FR_IPADDR_STRLEN], size_t outlen, fr_ipaddr_t const *addr)
Print the address portion of a fr_ipaddr_t.
#define FR_IPADDR_STRLEN
Like INET6_ADDRSTRLEN but includes space for the textual Zone ID.
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
connection_t * fr_redis_connection_alloc(TALLOC_CTX *ctx, fr_event_list_t *el, connection_conf_t const *conn_conf, fr_redis_io_conf_t const *io_conf, char const *log_prefix)
Allocate an async redis I/O connection.
fr_redis_sqn_t rsp_sqn
Current redis response number.
redisAsyncContext * ac
Async handle for hiredis.
static void fr_redis_connection_ignore_response(fr_redis_handle_t *h, fr_redis_sqn_t sqn)
Ignore a response with a specific sequence number.
static bool fr_redis_connection_process_response(fr_redis_handle_t *h)
Update the response sequence number and check if we should ignore the response.
bool freeing
Ensure that redisAsyncFree doesn't cause a callback loop.
static fr_redis_sqn_t fr_redis_connection_sent_request(fr_redis_handle_t *h)
Tell the handle we sent a command, and get the SQN that command was assigned.
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
#define DEBUG_ENABLED3
True if global debug level 1-3 messages are enabled.
Stores all information relating to an event list.
int strncasecmp(char *s1, char *s2, int n)
static const trunk_io_funcs_t io_funcs
Function prototypes and datatypes for the REST (HTTP) transport.
fr_redis_async_rcode_t rcode
Code from last error returned.
char const * fr_redis_command_get_cmd(fr_redis_command_t *cmd)
request_t * request
Request this commands set is associated with (if any).
static int _command_set_free_list_free_on_exit(void *arg)
Free any free requests when the thread is joined.
fr_redis_command_complete_t complete
Callback to process result from this command.
fr_dlist_head_t sent
Commands sent.
fr_redis_async_rcode_t fr_redis_command_set_rcode(fr_redis_command_set_t *cmds)
Extract the rcode from a command set.
uint16_t txn_start
Number of times a transaction block was started in this 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_io_conf_t const * io_conf
Redis I/O configuration.
static void _redis_pipeline_command_set_cancel(connection_t *conn, void *preq, trunk_cancel_reason_t reason, UNUSED void *uctx)
Deal with cancellation of sent requests.
trunk_t * trunk
Trunk containing all the connections to a specific host.
fr_dlist_t entry
Entry in the command buffer.
static connection_t * _redis_pipeline_connection_alloc(trunk_connection_t *tconn, fr_event_list_t *el, connection_conf_t const *conf, char const *log_prefix, void *uctx)
void * active_uctx
Uctx to pass to active callback.
char * next_node_ip
IP address of node from MOVED / ASK reply.
#define COMMAND_PRE_ALLOC_COUNT
void * rctx
Resume context to write results to.
static void _redis_pipeline_command_set_fail(UNUSED request_t *request, void *preq, UNUSED void *rctx, UNUSED trunk_request_state_t state, UNUSED void *uctx)
Signal the API client that we failed enqueuing the commands.
uint64_t sqn
The sequence number of the command.
fr_redis_command_set_complete_t complete
Notify the creator of the command set that the command set has executed to to completion.
fr_redis_command_fmt_t fmt
Redis command format.
fr_redis_command_set_fail_t fail
Notify the creator of the command set that the command set failed to execute to completion.
fr_redis_trunk_active_t active
Callback to run when the trunk becomes active.
uint16_t txn_end
The number of times a transaction block ended in this command set.
fr_redis_pipeline_status_t redis_command_set_enqueue(fr_redis_trunk_t *rtrunk, fr_redis_command_set_t *cmds)
Enqueue a command set on a specific trunk.
fr_redis_ct_t * rtcluster
Cluster this trunk belongs to.
static void _redis_trunk_active(UNUSED trunk_t *trunk, UNUSED trunk_state_t prev, UNUSED trunk_state_t state, void *uctx)
int fr_redis_command_set_reset(fr_redis_command_set_t *cmds)
Reset a command set to it's state before enqueuing.
static void _redis_pipeline_demux(struct redisAsyncContext *ac, void *vreply, void *privdata)
Callback for for receiving Redis replies.
bool autofree
Should the command set be freed when it is complete.
#define COMMAND_PRE_ALLOC_LEN
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.
static int redis_addr_from_redirect(TALLOC_CTX *ctx, char **addr, uint16_t *port, redisReply *redirect)
Convert a MOVED / ASK reply into an address and port.
void fr_redis_command_set_next_node(fr_redis_command_set_t *cmds, fr_redis_io_conf_t *ioconf)
Extract the next node address and port from a command set.
static fr_redis_pipeline_status_t redis_command_transaction_check(request_t *request, fr_redis_command_type_t *type, fr_redis_command_set_t *cmds, char const *cmd)
static void _redis_pipeline_command_set_free(UNUSED request_t *request, void *preq, UNUSED void *uctx)
Free the command set.
static _Thread_local fr_dlist_head_t * command_set_free_list
The thread local free list.
uint16_t next_node_port
Port of node from MOVED / ASK reply.
void fr_redis_command_set_cancel(fr_redis_command_set_t *cmds)
Cancel a command set.
fr_redis_pipeline_status_t fr_redis_command_argv_add(fr_redis_command_set_t *cmds, size_t argc, char const **argv, size_t *argv_len, fr_redis_command_complete_t complete, void *rctx)
Add a command with arguments to the command set.
void * rctx
To be passed to the callback.
fr_redis_command_type_t type
Redis command type.
static int _redis_command_set_free(fr_redis_command_set_t *cmds)
Free a command set.
static void _redis_pipeline_command_set_complete(UNUSED request_t *request, void *preq, UNUSED void *rctx, UNUSED void *uctx)
Signal the API client that we got a complete set of responses to a command set.
bool txn_watch
Transaction was started with a watch statement.
fr_dlist_head_t completed
Commands complete with replies.
@ FR_REDIS_COMMAND_TRANSACTION_START
Start of a transaction block.
@ FR_REDIS_COMMAND_NORMAL
A normal, non-transactional command.
@ FR_REDIS_COMMAND_TRANSACTION_END
End of a transaction block.
uint8_t redirected
How many times this command set was redirected.
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.
bool blocking
This command set contains one or more commands which block the client (e.g.
fr_redis_trunk_t * fr_redis_trunk_alloc(fr_redis_ct_t *rtcluster, fr_redis_io_conf_t const *io_conf, fr_pair_list_t *trigger_args, fr_redis_trunk_active_t active, void *active_uctx, bool active_oneshot)
Allocate a new trunk.
trunk_request_t * treq
Trunk request this command set is associated with.
int fr_redis_command_set_clear(fr_redis_command_set_t *cmds)
static void _redis_pipeline_mux(UNUSED fr_event_list_t *el, trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
Enqueue one or more command sets onto a redis handle.
fr_dlist_head_t pending
Commands yet to be sent.
fr_redis_command_set_t * cmds
Command set this entry belongs to.
@ FR_REDIS_COMMAND_FMT_ARGV
A command as an argv array.
@ FR_REDIS_COMMAND_FMT_PREFORMATTED
A command preformatted with redisCommandFormat.
@ FR_REDIS_COMMAND_FMT_EXPANDED
A command as a single string.
Represents a single command.
Represents a collection of pipelined commands.
Redis asynchronous command pipelining.
void(* fr_redis_command_set_complete_t)(request_t *request, fr_dlist_head_t *completed, void *rctx)
Do something meaningful with the replies to the commands previously issued.
void(* fr_redis_command_complete_t)(request_t *request, fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Process the reply from a single command.
void(* fr_redis_command_set_fail_t)(request_t *request, fr_dlist_head_t *completed, void *rctx)
Write a failure result to the rctx so that the module is aware that the request failed.
struct fr_redis_trunk_s fr_redis_trunk_t
fr_redis_pipeline_status_t
@ FR_REDIS_PIPELINE_OK
No failure.
@ FR_REDIS_PIPELINE_BAD_CMDS
Malformed command set.
@ FR_REDIS_PIPELINE_DST_UNAVAILABLE
Cluster or host is down.
@ FR_REDIS_PIPELINE_FAIL
Generic failure.
void(* fr_redis_trunk_active_t)(fr_redis_trunk_t *rtrunk, void *uctx)
#define REDIS_ERROR_TRY_AGAIN_STR
@ 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_FAIL
The command set trunk request has been failed.
@ REDIS_ASYNC_RCODE_TRY_AGAIN
Try the operation again.
@ REDIS_ASYNC_RCODE_NO_SCRIPT
Script doesn't exist.
@ REDIS_ASYNC_RCODE_SUCCESS
Operation was successful.
#define REDIS_ERROR_MOVED_STR
#define REDIS_ERROR_ASK_STR
#define REDIS_ERROR_NO_SCRIPT_STR
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_adv_past_str_literal(_sbuff, _needle)
#define fr_sbuff_current(_sbuff_or_marker)
#define fr_sbuff_out(_err, _out, _in)
#define fr_sbuff_init_in(_out, _start, _len_or_end)
#define fr_sbuff_remaining(_sbuff_or_marker)
static char buff[sizeof("18446744073709551615")+3]
fr_aka_sim_id_type_t type
int talloc_link_ctx(TALLOC_CTX *parent, TALLOC_CTX *child)
Link two different parent and child contexts, so the child is freed before the parent.
#define talloc_zero_pooled_object(_ctx, _type, _num_subobjects, _total_subobjects_size)
#define talloc_strdup(_ctx, _str)
void trunk_request_signal_fail(trunk_request_t *treq)
Signal that a trunk request failed.
trunk_watch_entry_t * trunk_add_watch(trunk_t *trunk, trunk_state_t state, trunk_watch_t watch, bool oneshot, void const *uctx)
Add a watch entry to the trunk state list.
trunk_enqueue_t trunk_request_enqueue(trunk_request_t **treq_out, trunk_t *trunk, request_t *request, void *preq, void *rctx)
Enqueue a request that needs data written to the trunk.
int trunk_connection_pop_request(trunk_request_t **treq_out, trunk_connection_t *tconn)
Pop a request off a connection's pending queue.
void trunk_request_signal_cancel(trunk_request_t *treq)
Cancel a trunk request.
trunk_t * trunk_alloc(TALLOC_CTX *ctx, fr_event_list_t *el, trunk_io_funcs_t const *funcs, trunk_conf_t const *conf, char const *log_prefix, void const *uctx, bool delay_start, fr_pair_list_t *trigger_args)
Allocate a new collection of connections.
void trunk_request_mark_blocking(trunk_request_t *treq)
Mark a trunk request as one which will block the connection until it is completed.
void trunk_request_signal_sent(trunk_request_t *treq)
Signal that the request was written to a connection successfully.
void trunk_request_signal_complete(trunk_request_t *treq)
Signal that a trunk request is complete.
Associates request queues with a connection.
Main trunk management handle.
trunk_connection_alloc_t connection_alloc
Allocate a new connection_t.
trunk_cancel_reason_t
Reasons for a request being cancelled.
@ TRUNK_CANCEL_REASON_NONE
Request has not been cancelled.
@ TRUNK_CANCEL_REASON_SIGNAL
Request cancelled due to a signal.
@ TRUNK_CANCEL_REASON_REQUEUE
A previously sent request is being requeued.
@ TRUNK_CANCEL_REASON_MOVE
Request cancelled because it's being moved.
@ TRUNK_STATE_ACTIVE
Trunk has at least one active connection which can service requests.
@ TRUNK_ENQUEUE_DST_UNAVAILABLE
Destination is down.
@ TRUNK_ENQUEUE_OK
Operation was successful.
@ TRUNK_ENQUEUE_IN_BACKLOG
Request should be enqueued in backlog.
trunk_request_state_t
Used for sanity checks and to simplify freeing.
@ TRUNK_REQUEST_STATE_BACKLOG
In the backlog.
@ TRUNK_REQUEST_STATE_PENDING
In the queue of a connection and is pending writing.
@ TRUNK_REQUEST_STATE_SENT
Was written to a socket. Waiting for a response.
I/O functions to pass to trunk_alloc.
static fr_event_list_t * el
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
#define fr_strerror_const(_msg)
#define fr_box_strvalue_len(_val, _len)