All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
rlm_redis.c
Go to the documentation of this file.
1 /*
2  * This program is is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or (at
5  * your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: 2656884de6c493c24e7d3ba36f79a799a5be23cc $
19  * @file rlm_redis.c
20  * @brief Driver for the Redis noSQL key value store.
21  *
22  * @author Gabriel Blanchard
23  *
24  * @copyright 2015 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
25  * @copyright 2011 TekSavvy Solutions <gabe@teksavvy.com>
26  * @copyright 2000,2006,2015 The FreeRADIUS server project
27  */
28 
29 RCSID("$Id: 2656884de6c493c24e7d3ba36f79a799a5be23cc $")
30 
31 #include <freeradius-devel/radiusd.h>
32 #include <freeradius-devel/modules.h>
33 #include <freeradius-devel/modpriv.h>
34 #include <freeradius-devel/rad_assert.h>
35 
36 #include "redis.h"
37 #include "cluster.h"
38 
39 #define MAX_QUERY_LEN 4096 //!< Maximum command length.
40 #define MAX_REDIS_ARGS 16 //!< Maximum number of arguments.
41 
45 };
46 
47 /** rlm_redis module instance
48  *
49  */
50 typedef struct rlm_redis_t {
51  fr_redis_conf_t conf; //!< Connection parameters for the Redis server.
52  //!< Must be first field in this struct.
53 
54  char const *name; //!< Instance name.
55 
56  fr_redis_cluster_t *cluster; //!< Redis cluster.
57 } rlm_redis_t;
58 
59 /** Change the state of a connection to READONLY execute a command and switch to READWRITE
60  *
61  * @param[out] status_out Where to write the status from the command.
62  * @param[out] reply_out Where to write the reply associated with the highest priority status.
63  * @param[in] request The current request.
64  * @param[in] conn to issue commands with.
65  * @param[in] argc Redis command argument count.
66  * @param[in] argv Redis command arguments.
67  * @return
68  * - 0 success.
69  * - -1 normal failure.
70  * - -2 failure that may leave the connection in a READONLY state.
71  */
72 static int redis_command_read_only(fr_redis_rcode_t *status_out, redisReply **reply_out,
73  REQUEST *request, fr_redis_conn_t *conn, int argc, char const **argv)
74 {
75  bool maybe_more = false;
76  redisReply *reply;
77  fr_redis_rcode_t status;
78 
79  *reply_out = NULL;
80 
81  redisAppendCommand(conn->handle, "READONLY");
82  redisAppendCommandArgv(conn->handle, argc, argv, NULL);
83  redisAppendCommand(conn->handle, "READWRITE");
84 
85  /*
86  * Process the response for READONLY
87  */
88  reply = NULL; /* Doesn't set reply to NULL on error *sigh* */
89  if (redisGetReply(conn->handle, (void **)&reply) == REDIS_OK) maybe_more = true;
90  status = fr_redis_command_status(conn, reply);
91  if (status != REDIS_RCODE_SUCCESS) {
92  REDEBUG("Setting READONLY failed");
93 
94  *reply_out = reply;
95  *status_out = status;
96 
97  if (maybe_more) {
98  if (redisGetReply(conn->handle, (void **)&reply) != REDIS_OK) return -1;
99  fr_redis_reply_free(reply);
100  if (redisGetReply(conn->handle, (void **)&reply) != REDIS_OK) return -1;
101  fr_redis_reply_free(reply);
102  }
103  return -1;
104  }
105 
106  fr_redis_reply_free(reply);
107 
108  /*
109  * Process the response for the command
110  */
111  reply = NULL;
112  if (redisGetReply(conn->handle, (void **)&reply) == REDIS_OK) maybe_more = true;
113  status = fr_redis_command_status(conn, reply);
114  if (status != REDIS_RCODE_SUCCESS) {
115  *reply_out = reply;
116  *status_out = status;
117 
118  if (maybe_more) {
119  if (redisGetReply(conn->handle, (void **)&reply) != REDIS_OK) return -1;
120  fr_redis_reply_free(reply);
121  }
122  return -1;
123  }
124 
125  *reply_out = reply;
126  *status_out = status;
127 
128  /*
129  * Process the response for READWRITE
130  */
131  reply = NULL;
132  status = fr_redis_command_status(conn, reply);
133  if ((redisGetReply(conn->handle, (void **)&reply) != REDIS_OK) || (status != REDIS_RCODE_SUCCESS)) {
134  REDEBUG("Setting READWRITE failed");
135 
136  fr_redis_reply_free(*reply_out);
137  *reply_out = reply;
138  *status_out = status;
139 
140  return -2;
141  }
142  return 0;
143 }
144 
145 static ssize_t redis_xlat(char **out, size_t outlen,
146  void const *mod_inst, UNUSED void const *xlat_inst,
147  REQUEST *request, char const *fmt)
148 {
149  rlm_redis_t const *inst = mod_inst;
150  fr_redis_conn_t *conn;
151 
152  bool read_only = false;
153  uint8_t const *key = NULL;
154  size_t key_len = 0;
155 
157  fr_redis_rcode_t status;
158  redisReply *reply = NULL;
159  int s_ret;
160 
161  size_t len;
162  int ret;
163 
164  char const *p = fmt, *q;
165 
166  int argc;
167  char const *argv[MAX_REDIS_ARGS];
168  char argv_buf[MAX_QUERY_LEN];
169 
170  if (p[0] == '-') {
171  p++;
172  read_only = true;
173  }
174 
175  /*
176  * Hack to allow querying against a specific node for testing
177  */
178  if (p[0] == '@') {
179  fr_ipaddr_t ipaddr;
180  uint16_t port;
181  fr_connection_pool_t *pool;
182 
183  RDEBUG3("Overriding node selection");
184 
185  p++;
186  q = strchr(p, ' ');
187  if (!q) {
188  REDEBUG("Found node specifier but no command, format is [-][@<host>[:port]] <redis command>");
189  return -1;
190  }
191 
192  if (fr_inet_pton_port(&ipaddr, &port, p, q - p, AF_UNSPEC, true, true) < 0) {
193  REDEBUG("Failed parsing node address: %s", fr_strerror());
194  return -1;
195  }
196 
197  p = q + 1;
198 
199  if (fr_redis_cluster_pool_by_node_addr(&pool, inst->cluster, &ipaddr, port, true) < 0) {
200  REDEBUG("Failed locating cluster node: %s", fr_strerror());
201  return -1;
202  }
203 
204  conn = fr_connection_get(pool);
205  if (!conn) {
206  REDEBUG("No connections available for cluster node");
207  return -1;
208  }
209 
210  argc = rad_expand_xlat(request, p, MAX_REDIS_ARGS, argv, false, sizeof(argv_buf), argv_buf);
211  if (argc <= 0) {
212  REDEBUG("Invalid command: %s", p);
213  fr_connection_release(pool, conn);
214  return -1;
215  }
216 
217  RDEBUG2("Executing command: %s", argv[0]);
218  if (argc > 1) {
219  RDEBUG2("With argments");
220  RINDENT();
221  for (int i = 1; i < argc; i++) RDEBUG2("[%i] %s", i, argv[i]);
222  REXDENT();
223  }
224 
225  if (!read_only) {
226  reply = redisCommandArgv(conn->handle, argc, argv, NULL);
227  status = fr_redis_command_status(conn, reply);
228  } else if (redis_command_read_only(&status, &reply, request, conn, argc, argv) == -2) {
229  goto close_conn;
230  }
231 
232  switch (status) {
233  case REDIS_RCODE_SUCCESS:
234  goto reply_parse;
235 
237  close_conn:
238  fr_connection_close(pool, conn);
239  ret = -1;
240  goto finish;
241 
242  default:
243  fr_connection_release(pool, conn);
244  ret = -1;
245  goto finish;
246  }
247  }
248 
249  /*
250  * Normal node selection and execution based on key
251  */
252  argc = rad_expand_xlat(request, p, MAX_REDIS_ARGS, argv, false, sizeof(argv_buf), argv_buf);
253  if (argc <= 0) {
254  REDEBUG("Invalid command: %s", p);
255  ret = -1;
256  goto finish;
257  }
258 
259  /*
260  * If we've got multiple arguments, the second one is usually the key.
261  * The Redis docs say commands should be analysed first to get key
262  * positions, but this involves sending them to the server, which is
263  * just as expensive as sending them to the wrong server and receiving
264  * a redirect.
265  */
266  if (argc > 1) {
267  key = (uint8_t const *)argv[1];
268  key_len = strlen((char const *)key);
269  }
270  for (s_ret = fr_redis_cluster_state_init(&state, &conn, inst->cluster, request, key, key_len, read_only);
271  s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */
272  s_ret = fr_redis_cluster_state_next(&state, &conn, inst->cluster, request, status, &reply)) {
273  RDEBUG2("Executing command: %s", argv[0]);
274  if (argc > 1) {
275  RDEBUG2("With arguments");
276  RINDENT();
277  for (int i = 1; i < argc; i++) RDEBUG2("[%i] %s", i, argv[i]);
278  REXDENT();
279  }
280  if (!read_only) {
281  reply = redisCommandArgv(conn->handle, argc, argv, NULL);
282  status = fr_redis_command_status(conn, reply);
283  } else if (redis_command_read_only(&status, &reply, request, conn, argc, argv) == -2) {
284  state.close_conn = true;
285  }
286  }
287  if (s_ret != REDIS_RCODE_SUCCESS) {
288  ret = -1;
289  goto finish;
290  }
291 
292 reply_parse:
293  rad_assert(reply); /* clang scan */
294  switch (reply->type) {
295  case REDIS_REPLY_INTEGER:
296  ret = snprintf(*out, outlen, "%lld", reply->integer);
297  break;
298 
299  case REDIS_REPLY_STATUS:
300  case REDIS_REPLY_STRING:
301  len = (((size_t)reply->len) >= outlen) ? outlen - 1: (size_t) reply->len;
302  memcpy(*out, reply->str, len);
303  (*out)[len] = '\0';
304  ret = reply->len;
305  break;
306 
307  default:
308  REDEBUG("Server returned non-value type \"%s\"",
309  fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>"));
310  ret = -1;
311  break;
312  }
313 
314 finish:
315  fr_redis_reply_free(reply);
316  return ret;
317 }
318 
319 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
320 {
321  rlm_redis_t *inst = instance;
322 
324 
325  inst->name = cf_section_name2(conf);
326  if (!inst->name) inst->name = cf_section_name1(conf);
327  inst->conf.prefix = talloc_asprintf(inst, "rlm_redis (%s)", inst->name);
328 
329  xlat_register(inst, inst->name, redis_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN);
330 
331  return 0;
332 }
333 
334 static int mod_instantiate(CONF_SECTION *conf, void *instance)
335 {
336  rlm_redis_t *inst = instance;
337 
338  inst->cluster = fr_redis_cluster_alloc(inst, conf, &inst->conf);
339  if (!inst->cluster) return -1;
340 
341  return 0;
342 }
343 
344 extern module_t rlm_redis;
345 module_t rlm_redis = {
347  .name = "redis",
348  .type = RLM_TYPE_THREAD_SAFE,
349  .inst_size = sizeof(rlm_redis_t),
350  .config = module_config,
351  .bootstrap = mod_bootstrap,
352  .instantiate = mod_instantiate,
353 };
#define RINDENT()
Indent R* messages by one level.
Definition: log.h:265
int xlat_register(void *mod_inst, char const *name, xlat_func_t func, xlat_escape_t escape, xlat_instantiate_t instantiate, size_t inst_size, size_t buf_len)
Register an xlat function.
Definition: xlat.c:717
Metadata exported by the module.
Definition: modules.h:134
Configuration parameters for a redis connection.
Definition: redis.h:88
FR_NAME_NUMBER const redis_reply_types[]
Definition: redis.c:29
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)
Definition: inet.c:632
#define MAX_QUERY_LEN
Maximum command length.
Definition: rlm_redis.c:39
#define RLM_TYPE_THREAD_SAFE
Module is threadsafe.
Definition: modules.h:75
static ssize_t redis_xlat(char **out, size_t outlen, void const *mod_inst, UNUSED void const *xlat_inst, REQUEST *request, char const *fmt)
Definition: rlm_redis.c:145
#define UNUSED
Definition: libradius.h:134
#define RLM_MODULE_INIT
Definition: modules.h:86
#define CONF_PARSER_TERMINATOR
Definition: conffile.h:289
module_t rlm_redis
Definition: rlm_redis.c:345
char const * prefix
Logging prefix for errors in fr_redis_cluster_conn_create.
Definition: redis.h:89
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:686
bool close_conn
Set by caller of fr_redis_cluster_state_next, to indicate that connection must be closed...
Definition: cluster.h:44
struct rlm_redis_t rlm_redis_t
rlm_redis module instance
#define fr_redis_reply_free(_p)
Wrap freeReplyObject so we consistently check for NULL pointers.
Definition: redis.h:56
#define inst
#define XLAT_DEFAULT_BUF_LEN
Definition: xlat.h:89
static CONF_PARSER module_config[]
Definition: rlm_redis.c:42
A redis cluster.
Definition: cluster.c:254
Defines a CONF_PAIR to C data type mapping.
Definition: conffile.h:267
static int mod_instantiate(CONF_SECTION *conf, void *instance)
Definition: rlm_redis.c:334
#define rad_assert(expr)
Definition: rad_assert.h:38
redisContext * handle
Hiredis context used when issuing commands.
Definition: redis.h:81
static int redis_command_read_only(fr_redis_rcode_t *status_out, redisReply **reply_out, REQUEST *request, fr_redis_conn_t *conn, int argc, char const **argv)
Change the state of a connection to READONLY execute a command and switch to READWRITE.
Definition: rlm_redis.c:72
#define REDIS_COMMON_CONFIG
Definition: redis.h:106
void fr_redis_version_print(void)
Print the version of libhiredis the server was built against.
Definition: redis.c:52
#define MAX_REDIS_ARGS
Maximum number of arguments.
Definition: rlm_redis.c:40
fr_redis_rcode_t fr_redis_command_status(fr_redis_conn_t *conn, redisReply *reply)
Check the reply for errors.
Definition: redis.c:76
Redis connection sequence state.
Definition: cluster.h:43
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition: log.h:272
static rs_t * conf
Definition: radsniff.c:46
char const * fr_strerror(void)
Get the last library error.
Definition: log.c:212
char const * cf_section_name1(CONF_SECTION const *cs)
Definition: conffile.c:3592
#define RDEBUG2(fmt,...)
Definition: log.h:244
unsigned int state
Definition: proto_bfd.c:200
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 *request, fr_redis_rcode_t status, redisReply **reply)
Get the next connection to attempt a command against.
Definition: cluster.c:1714
fr_redis_conf_t conf
Connection parameters for the Redis server.
Definition: rlm_redis.c:51
uint64_t magic
Used to validate module struct.
Definition: modules.h:135
Transitory error, caller should retry the operation with a new connection.
Definition: redis.h:71
int fr_redis_cluster_pool_by_node_addr(fr_connection_pool_t **pool, fr_redis_cluster_t *cluster, fr_ipaddr_t *ipaddr, uint16_t port, bool create)
Get the pool associated with a node in the cluster.
Definition: cluster.c:1919
Common functions for interacting with Redis via hiredis.
char const * name
Instance name.
Definition: rlm_redis.c:54
void * fr_connection_get(fr_connection_pool_t *pool)
Reserve a connection in the connection pool.
Definition: connection.c:1291
A connection pool.
Definition: connection.c:85
Try the operation again.
Definition: redis.h:70
int fr_connection_close(fr_connection_pool_t *pool, void *conn)
Delete a connection from the connection pool.
Definition: connection.c:1403
fr_redis_cluster_t * fr_redis_cluster_alloc(TALLOC_CTX *ctx, CONF_SECTION *module, fr_redis_conf_t *conf)
Allocate and initialise a new cluster structure.
Definition: cluster.c:2059
#define REDEBUG(fmt,...)
Definition: log.h:254
Common functions for interacting with Redis cluster via Hiredis.
fr_redis_rcode_t
Codes are ordered inversely by priority.
Definition: redis.h:67
static int mod_bootstrap(CONF_SECTION *conf, void *instance)
Definition: rlm_redis.c:319
int rad_expand_xlat(REQUEST *request, char const *cmd, int max_argc, char const *argv[], bool can_fail, size_t argv_buflen, char *argv_buf)
Split string into words and expand each one.
Definition: util.c:658
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 *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.
Definition: cluster.c:1594
IPv4/6 prefix.
Definition: inet.h:41
rlm_redis module instance
Definition: rlm_redis.c:50
char const * fr_int2str(FR_NAME_NUMBER const *table, int number, char const *def)
Definition: token.c:506
void fr_connection_release(fr_connection_pool_t *pool, void *conn)
Release a connection.
Definition: connection.c:1305
Operation was successfull.
Definition: redis.h:68
#define RCSID(id)
Definition: build.h:135
char const * cf_section_name2(CONF_SECTION const *cs)
Definition: conffile.c:3601
Connection handle, holding a redis context.
Definition: redis.h:80
fr_redis_cluster_t * cluster
Redis cluster.
Definition: rlm_redis.c:56
#define RDEBUG3(fmt,...)
Definition: log.h:245