139#include <freeradius-devel/util/debug.h>
147# undef HAVE_REDIS_SSL
151#include <freeradius-devel/tls/strerror.h>
152#include <hiredis/hiredis_ssl.h>
155#define MAX_REPLICAS 5
241#define CONFIGURE_NODE(_node, _addr, _port) \
243 _node->ioconf = (fr_redis_io_conf_t) { \
245 .database = rtcluster->conf->database, \
246 .username = rtcluster->conf->username, \
247 .password = rtcluster->conf->password, \
248 .use_tls = rtcluster->conf->use_tls, \
250 _node->ioconf.hostname = talloc_strdup(rtcluster, _addr); \
251 _node->ioconf.log_prefix = talloc_asprintf(rtcluster, "%s %s:%d", rtcluster->conf->log_prefix, \
252 _addr, _node->ioconf.port); \
253 if (rtcluster->conf->trunk_conf.conn_triggers) { \
254 module_trigger_args_build(rtcluster, &_node->trigger_args, NULL, \
255 &(module_trigger_args_t) { \
256 .module = rtcluster->conf->module_name, \
257 .name = rtcluster->conf->inst_name, \
259 .port = _node->ioconf.port \
262 _node->trunk = fr_redis_trunk_alloc(rtcluster, &_node->ioconf, &_node->trigger_args, rtcluster->active, \
263 rtcluster->active_uctx, rtcluster->active_oneshot); \
264 if (!_node->trunk) goto error; \
280 p = memchr(key,
'{', key_len);
286 q = memchr(p,
'}', key_len - (p - key));
287 if (!q || (q == p + 1))
goto all;
303 uint8_t const *key,
size_t key_len)
307 if (!key || (key_len == 0)) {
359 return &rtcluster->
node[key_slot->
replica[replica_num]];
371 if (!node)
return NULL;
386 if (!node)
return -1;
400 bool dst_unavail =
false;
484 .rtcluster = rtcluster,
486 .read_only = read_only,
492 switch (rtcluster->
state) {
563 ERROR(
"Asked to redirect to a node not in the current cluster map");
568 cmd->
node = cluster_node;
597 if (ret != 0)
return CMP(ret, 0);
603static int _redis_cluster_thread_free(
fr_redis_ct_t *rtcluster)
605 if (rtcluster->ssl_ctx) SSL_CTX_free(rtcluster->ssl_ctx);
630 .active_uctx = active_uctx,
631 .active_oneshot = active_oneshot
633 MEM(our_tconf = talloc_memdup(rtcluster, &
conf->trunk_conf,
sizeof(
conf->trunk_conf)));
636 rtcluster->
tconf = our_tconf;
641 ERROR(
"%s - Maximum number of connected nodes allowed is %i",
conf->log_prefix,
UINT8_MAX - 1);
647 if (
conf->max_nodes == 0) {
648 ERROR(
"%s - Minimum number of nodes allowed is 1",
conf->log_prefix);
660 for (i = 1; i <=
conf->max_nodes; i++) {
661 rtcluster->
node[i].
id = i;
671 fr_tls_conf_t *tls_conf;
673 ERROR(
"%s - Missing TLS configuration",
conf->log_prefix);
677 tls_conf = fr_tls_conf_parse_client(tls_cs);
679 ERROR(
"%s - Failed to parse TLS configuration",
conf->log_prefix);
683 rtcluster->ssl_ctx = fr_tls_ctx_alloc(tls_conf,
true);
684 if (!rtcluster->ssl_ctx) {
685 ERROR(
"%s - Failed to allocate SSL context",
conf->log_prefix);
688 talloc_set_destructor(rtcluster, _redis_cluster_thread_free);
690 WARN(
"%s - No redis SSL support, ignoring \"use_tls = yes\"",
conf->log_prefix);
694 if (
conf->use_cluster_map)
return rtcluster;
700 for (s = 0; s < talloc_array_length(
conf->hostname); s++) {
708 ERROR(
"Reached maximum connected nodes");
713 PERROR(
"Failed parsing %s",
conf->hostname[s]);
716 if (port == 0) port =
conf->port;
726 ERROR(
"%s - No bootstrap servers configured",
conf->log_prefix);
739 return rtcluster->
el;
744 return rtcluster->
tconf;
750 return rtcluster->ssl_ctx;
764 fr_pair_t *
vp, *shard = NULL, *slot, *start, *end, *node, *role, *node_ip, *node_port;
777#define SET_INACTIVE(_node) \
779 (_node)->is_active = false; \
780 (_node)->is_master = false; \
781 talloc_const_free((_node)->ioconf.log_prefix); \
782 (_node)->ioconf.log_prefix = NULL; \
783 TALLOC_FREE((_node)->trunk); \
784 fr_pair_list_free(&(_node)->trigger_args); \
785 fr_rb_delete(rtcluster->used_nodes, _node); \
786 fr_fifo_push(rtcluster->free_nodes, _node); \
789#define SET_ACTIVE(_node) \
791 fr_rb_insert(rtcluster->used_nodes, _node); \
792 fr_fifo_pop(rtcluster->free_nodes); \
793 (_node)->is_active = true; \
794 active[(_node)->id] = true; \
795 rollback[r++] = (_node)->id; \
800 ERROR(
"Missing cluster ID");
806 ERROR(
"Got map for cluster ID %d, expected ID %d",
vp->vp_uint16, rtcluster->
cluster_id);
812 memset(&key_slot_pending, 0,
sizeof(key_slot_pending));
813 memset(active, 0,
sizeof(active));
814 memset(master, 0,
sizeof(master));
823 if (role->vp_uint8 == 1) {
824 DEBUG3(
"Master node %pP", node);
839 ERROR(
"Missing master node");
855 active[cluster_node->
id] =
true;
857 master[cluster_node->
id] =
true;
864 if (role->vp_uint8 != 2)
continue;
866 DEBUG3(
"Replica node %pP", node);
878 active[cluster_node->id] =
true;
883 if (!cluster_node)
goto out_of_nodes;
894 ERROR(
"Missing slot start");
898 ERROR(
"Value of %d for slot start greater than expected maximum %d",
904 ERROR(
"Missing slot end");
908 ERROR(
"Value of %d for slot end greater than expected maximum %d",
912 if (
unlikely(end->vp_uint16 < start->vp_uint16)) {
913 ERROR(
"Value of %d for slot end less than value of %d for slot start",
914 end->vp_uint16, start->vp_uint16);
917 DEBUG4(
"Setting nodes for slots %d to %d", start->vp_uint16, end->vp_uint16);
918 for (i = start->vp_uint16; i <= end->vp_uint16; i++) {
919 memcpy(&key_slot_pending[i], &tmp_slot,
sizeof(*key_slot_pending));
924 memcpy(&rtcluster->
key_slot, &key_slot_pending,
sizeof(rtcluster->
key_slot));
952 }
else if (master[i]) {
1008 TALLOC_CTX *local = talloc_new(NULL);
1024 if (!
vp)
goto error;
1026 for (i = 0; i < talloc_array_length(
conf->hostname); i++) {
1032 if (!
vp)
goto error;
1034 if (
conf->password) {
1037 if (
conf->username) {
1043 if (
conf->use_tls) {
1044 uintptr_t tls_conf = (uintptr_t)rtcluster->
tls_cs;
1046 if (!
vp)
goto error;
1048 if (!
vp)
goto error;
1054 if (ret < 0)
return -1;
1086 local = talloc_new(NULL);
1096 if (!
vp)
goto error;
1130 switch (rtcluster->
state) {
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
A section grouping multiple CONF_PAIR.
fr_redis_conf_t const * conf
Redis configuration for the cluster.
static int _fr_redis_async_cmd_free(fr_redis_async_cmd_t *cmd)
fr_redis_ct_t * rtcluster
Cluster the request is waiting for.
fr_redis_ct_state_t state
State of the cluster.
#define SET_ACTIVE(_node)
bool read_only
Should this command be run read only.
void fr_redis_ct_request_yield(TALLOC_CTX *ctx, fr_redis_ct_t *rtcluster, request_t *request)
Add a request to the list of those waiting for the cluster map.
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.
request_t * request
The request waiting for the map.
fr_rb_tree_t * used_nodes
Active nodes.
request_t * request
Request this command set relates to.
bool active_oneshot
Should the callback only be called once.
size_t key_len
Length of key.
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.
uint16_t cluster_id
Number assigned to the cluster by coordinator.
static uint16_t cluster_key_hash(uint8_t const *key, size_t key_len)
Resolve key to key slot index.
trunk_conf_t const * tconf
Configuration for all trunks in the cluster.
fr_redis_async_rcode_t fr_redis_ct_node_addr_by_role(TALLOC_CTX *ctx, fr_redis_io_conf_t *out[], uint8_t *count_out, fr_redis_ct_t *rtcluster, bool is_master, bool is_replica)
fr_dlist_head_t pend_reqs
Requests awaiting cluster map.
trunk_conf_t const * fr_redis_ct_trunk_conf(fr_redis_ct_t *rtcluster)
fr_redis_trunk_t * rtrunk
Trunk the command set is currently running on.
fr_redis_ct_node_t * node
Array of nodes in this 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.
fr_dlist_t entry
Entry in the list of pending requests.
fr_redis_ct_node_t * node
Specific node to run command set on.
void fr_redis_async_cmd_cancel(fr_redis_async_cmd_t *cmd)
Cancel a Redis async command.
uint8_t num_replicas
Number of replica nodes.
fr_redis_ct_key_slot_t key_slot[KEY_SLOTS]
fr_redis_ct_node_t * fr_redis_ct_node_by_addr(fr_redis_ct_t *rtcluster, fr_redis_io_conf_t *ioconf)
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_trunk_t * trunk
Trunk connection to this node.
#define MAX_REPLICAS
Maximum number of replicas associated with a keyslot.
fr_redis_ct_node_t const * fr_redis_ct_replica(fr_redis_ct_t *rtcluster, fr_redis_ct_key_slot_t const *key_slot, uint8_t replica_num)
Return the replica node that would be used for a particular key slot.
fr_redis_ct_node_t * fr_redis_async_cmd_node(fr_redis_async_cmd_t *cmd)
Fetch the cluster node a command was last sent to.
fr_redis_trunk_t * fr_redis_async_cmd_trunk(fr_redis_async_cmd_t *cmd)
Fetch the redis trunk a command is associated with.
#define SET_INACTIVE(_node)
fr_redis_ct_key_slot_t const * fr_redis_ct_slot_by_key(fr_redis_ct_t *rtcluster, request_t *request, uint8_t const *key, size_t key_len)
Resolve key to key slot.
CONF_SECTION const * tls_cs
TLS CONF_SECTION.
fr_rb_node_t rbnode
Entry in the tree of used nodes.
#define CONFIGURE_NODE(_node, _addr, _port)
fr_pair_list_t trigger_args
Pairs to pass to trigger functions.
fr_redis_ct_key_slot_t const * key_slot
Key slot identified from the command key.
static int _fr_redis_ct_pend_req_free(fr_redis_ct_pend_req_t *pend_req)
Ensure pending request is removed from the list on freeing.
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.
uint8_t master
id of the master node.
uint8_t replica_no
Current replica number being used.
fr_redis_ct_t * rtcluster
Cluster this command set is running on.
fr_time_t map_updated
Time the cluster last updated.
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.
@ CLUSTER_FAIL
The coordinator reported a failed cluster map update.
@ CLUSTER_MAP_FETCHING
The cluster map is currently being fetched.
@ CLUSTER_READY
The cluster is available to handle requests.
@ CLUSTER_INIT
Cluster has been initialised.
static fr_cmp_ret_t _cluster_thread_node_cmp(void const *one, void const *two)
Compare two redis nodes to check equality.
uint8_t replica[MAX_REPLICAS]
Array of ids of replica nodes.
uint8_t const * key
Key used to identify key slot.
char name[INET6_ADDRSTRLEN]
fr_redis_ct_t * rtcluster
Cluster this node belongs to.
fr_fifo_t * free_nodes
Nodes not currently active.
int fr_redis_ct_port(uint16_t *out, fr_redis_ct_node_t const *node)
Return the port of a particular node.
bool is_master
Is this node currently a master.
fr_redis_ct_node_t const * fr_redis_ct_master(fr_redis_ct_t *rtcluster, fr_redis_ct_key_slot_t const *key_slot)
Return the master node that would be used for a particular key slot.
fr_redis_command_set_t * cmds
Command set to run.
bool delay_start
Prevent connections from spawning immediately.
fr_event_list_t * fr_redis_ct_el(fr_redis_ct_t *rtcluster)
bool is_active
Is this node currently active.
fr_dlist_t entry
Entry in the list of commands waiting for a cluster remap.
fr_redis_trunk_active_t active
Callback to run when the trunk becomes active.
fr_dlist_head_t pend_cmds
Commands awaiting cluster map.
int fr_redis_ct_map_update(fr_redis_ct_t *rtcluster, fr_pair_list_t const *list)
Update a Redis cluster map from a pair list returned from a coordinator.
void * active_uctx
Uctx to pass to active callback.
int fr_redis_ct_map_fail(fr_redis_ct_t *rtcluster, UNUSED fr_pair_list_t const *list)
Process a cluster map fail message from the coordinator.
char const * fr_redis_ct_ipaddr(fr_redis_ct_node_t const *node)
Return the ipaddr of a particular node.
uint8_t id
Array offset in the array of available nodes.
fr_redis_io_conf_t ioconf
Connection config for this node.
static fr_redis_async_rcode_t fr_redis_async_cmd_enqueue(fr_redis_async_cmd_t *cmd)
Enqueue a command set on a node identified by the key.
Structure for holding the state of an async redis command set.
Structure to record that a request is waiting for the cluster map.
Thread local state for a cluster.
Redis asynchronous cluster management.
#define KEY_SLOTS
Maximum number of keyslots (should not change).
struct fr_redis_async_cmd_s fr_redis_async_cmd_t
The worker end of worker <-> coordinator communication.
int fr_worker_to_coord_pair_send(fr_coord_worker_t *cw, fr_coord_pair_reg_t *coord_pair_reg, fr_pair_list_t *list)
Send a pair list from a worker to a coordinator.
struct fr_coord_pair_reg_s fr_coord_pair_reg_t
uint16_t fr_crc16_xmodem(uint8_t const *in, size_t in_len)
CRC16 implementation according to CCITT standards.
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_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.
#define fr_dlist_talloc_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Head of a doubly linked list.
Entry in a doubly linked list.
void * fr_fifo_peek(fr_fifo_t *fi)
Examine the next element that would be popped.
int fr_fifo_push(fr_fifo_t *fi, void *data)
Push data onto the fifo.
void * fr_fifo_pop(fr_fifo_t *fi)
Pop data off of the fifo.
#define fr_fifo_create(_ctx, _max_entries, _node_free)
Creates a fifo.
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.
fr_dict_attr_t const * attr_redis_node_role
fr_dict_attr_t const * attr_redis_bootstrap_node
fr_dict_attr_t const * attr_redis_use_tls
fr_dict_attr_t const * attr_redis_slot_end
fr_dict_attr_t const * attr_redis_force_update
fr_dict_attr_t const * attr_redis_packet_type
fr_dict_attr_t const * attr_redis_log_prefix
fr_dict_attr_t const * attr_redis_node
fr_dict_attr_t const * attr_redis_node_port
fr_dict_attr_t const * attr_redis_slot
fr_dict_attr_t const * attr_redis_slot_start
fr_dict_attr_t const * attr_redis_max_nodes
fr_dict_attr_t const * attr_redis_password
fr_dict_attr_t const * attr_redis_bootstrap_port
fr_dict_attr_t const * attr_redis_cluster_id
fr_dict_attr_t const * attr_redis_tls_conf
fr_dict_attr_t const * attr_redis_username
fr_dict_attr_t const * attr_redis_node_endpoint
fr_dict_attr_t const * attr_redis_shard
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Stores all information relating to an event list.
fr_cmp_ret_t
Result of an ordering comparison.
int fr_pair_append_by_da(TALLOC_CTX *ctx, fr_pair_t **out, fr_pair_list_t *list, fr_dict_attr_t const *da)
Alloc a new fr_pair_t (and append)
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
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.
int fr_redis_command_set_reset(fr_redis_command_set_t *cmds)
Reset a command set to it's state before enqueuing.
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.
void fr_redis_command_set_cancel(fr_redis_command_set_t *cmds)
Cancel a command set.
Represents a collection of pipelined commands.
fr_redis_pipeline_status_t
@ FR_REDIS_PIPELINE_OK
No failure.
@ FR_REDIS_PIPELINE_DST_UNAVAILABLE
Cluster or host is down.
void(* fr_redis_trunk_active_t)(fr_redis_trunk_t *rtrunk, void *uctx)
uint32_t fr_rand(void)
Return a 32-bit random number.
uint32_t fr_rb_num_elements(fr_rb_tree_t *tree)
Return how many nodes there are in a tree.
int fr_rb_find(void **found, fr_rb_tree_t const *tree, void const *data)
Find an element in the tree, returning the data, not the node.
void * fr_rb_iter_init_inorder(fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Initialise an in-order iterator.
int fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
void * fr_rb_iter_next_inorder(UNUSED fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Return the next node.
#define fr_rb_inline_alloc(_ctx, _type, _field, _data_cmp, _data_free)
Allocs a red black tree.
Iterator structure for in-order traversal of an rbtree.
The main red black tree structure.
uint8_t max_nodes
Maximum number of cluster nodes to connect to.
@ REDIS_ASYNC_RCODE_BOOTSTRAP
The caller should issue a request to bootstrap the cluster map.
@ REDIS_ASYNC_RCODE_ERROR
Unrecoverable error.
@ REDIS_ASYNC_RCODE_GETMAP
The caller should issue a request to update the cluster map.
@ REDIS_ASYNC_RCODE_FAIL
The command set trunk request has been failed.
@ REDIS_ASYNC_RCODE_TRY_AGAIN
Try the operation again.
@ REDIS_ASYNC_RCODE_SUCCESS
Operation was successful.
@ FR_REDIS_CLUSTER_MAP_BOOTSTRAP
@ FR_REDIS_CLUSTER_MAP_GET
struct fr_redis_ct_s fr_redis_ct_t
Configuration parameters for a redis connection.
static char buff[sizeof("18446744073709551615")+3]
Stores an attribute, a value and various bits of other data.
static size_t talloc_strlen(char const *s)
Returns the length of a talloc array containing a string.
static int64_t fr_time_to_sec(fr_time_t when)
Convert an fr_time_t (internal time) to number of sec since the unix epoch (wallclock time)
bool always_writable
Set to true if our ability to write requests to a connection handle is not dependent on the state of ...
Common configuration parameters for a trunk.
static fr_event_list_t * el
#define fr_pair_list_append_by_da(_ctx, _vp, _list, _attr, _val, _tainted)
Append a pair to a list, assigning its value.
#define fr_strerror_const(_msg)
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_strvalue_len(_val, _len)
static size_t char ** out