1 /*
2 * cachedb/redis.c - cachedb redis module
3 *
4 * Copyright (c) 2018, NLnet Labs. All rights reserved.
5 *
6 * This software is open source.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
14 *
15 * Redistributions in binary form must reproduce the above copyright notice,
16 * this list of conditions and the following disclaimer in the documentation
17 * and/or other materials provided with the distribution.
18 *
19 * Neither the name of the NLNET LABS nor the names of its contributors may
20 * be used to endorse or promote products derived from this software without
21 * specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36 /**
37 * \file
38 *
39 * This file contains a module that uses the redis database to cache
40 * dns responses.
41 */
42
43 #include "config.h"
44 #ifdef USE_CACHEDB
45 #include "cachedb/redis.h"
46 #include "cachedb/cachedb.h"
47 #include "util/alloc.h"
48 #include "util/config_file.h"
49 #include "util/locks.h"
50 #include "util/timeval_func.h"
51 #include "sldns/sbuffer.h"
52
53 #ifdef USE_REDIS
54 #include "hiredis/hiredis.h"
55
56 struct redis_moddata {
57 /* thread-specific redis contexts */
58 redisContext** ctxs;
59 redisContext** replica_ctxs;
60 /* number of ctx entries */
61 int numctxs;
62 /* server's IP address or host name */
63 const char* server_host;
64 const char* replica_server_host;
65 /* server's TCP port */
66 int server_port;
67 int replica_server_port;
68 /* server's unix path, or "", NULL if unused */
69 const char* server_path;
70 const char* replica_server_path;
71 /* server's AUTH password, or "", NULL if unused */
72 const char* server_password;
73 const char* replica_server_password;
74 /* timeout for commands */
75 struct timeval command_timeout;
76 struct timeval replica_command_timeout;
77 /* timeout for connection setup */
78 struct timeval connect_timeout;
79 struct timeval replica_connect_timeout;
80 /* the reconnect interval time. */
81 struct timeval reconnect_interval;
82 struct timeval replica_reconnect_interval;
83 /* reconnect attempts, 0 if connected, counts up failed reconnects. */
84 int reconnect_attempts;
85 int replica_reconnect_attempts;
86 /* Lock on reconnect_wait time. */
87 lock_basic_type wait_lock;
88 lock_basic_type replica_wait_lock;
89 /* reconnect wait time, wait until it has passed before reconnect. */
90 struct timeval reconnect_wait;
91 struct timeval replica_reconnect_wait;
92 /* the redis logical database to use */
93 int logical_db;
94 int replica_logical_db;
95 /* if the SET with EX command is supported */
96 int set_with_ex_available;
97 };
98
99 /** The limit on the number of redis connect attempts. After failure if
100 * the number is exceeded, the reconnects are throttled by the wait time. */
101 #define REDIS_RECONNECT_ATTEMPT_LIMIT 3
102
103 static redisReply* redis_command(struct module_env*, struct cachedb_env*,
104 const char*, const uint8_t*, size_t, int);
105
106 static void
moddata_clean(struct redis_moddata ** moddata)107 moddata_clean(struct redis_moddata** moddata) {
108 if(!moddata || !*moddata)
109 return;
110 if((*moddata)->ctxs) {
111 int i;
112 for(i = 0; i < (*moddata)->numctxs; i++) {
113 if((*moddata)->ctxs[i])
114 redisFree((*moddata)->ctxs[i]);
115 }
116 free((*moddata)->ctxs);
117 }
118 if((*moddata)->replica_ctxs) {
119 int i;
120 for(i = 0; i < (*moddata)->numctxs; i++) {
121 if((*moddata)->replica_ctxs[i])
122 redisFree((*moddata)->replica_ctxs[i]);
123 }
124 free((*moddata)->replica_ctxs);
125 }
126 lock_basic_destroy(&(*moddata)->wait_lock);
127 lock_basic_destroy(&(*moddata)->replica_wait_lock);
128 free(*moddata);
129 *moddata = NULL;
130 }
131
132 static redisContext*
redis_connect(const char * host,int port,const char * path,const char * password,int logical_db,const struct timeval connect_timeout,const struct timeval command_timeout,const struct timeval * reconnect_interval,int * reconnect_attempts,struct timeval * reconnect_wait,lock_basic_type * wait_lock,struct timeval * now_tv,const char * infostr)133 redis_connect(const char* host, int port, const char* path,
134 const char* password, int logical_db,
135 const struct timeval connect_timeout,
136 const struct timeval command_timeout,
137 const struct timeval* reconnect_interval,
138 int* reconnect_attempts,
139 struct timeval* reconnect_wait,
140 lock_basic_type* wait_lock,
141 struct timeval* now_tv,
142 const char* infostr)
143 {
144 struct timeval now_val;
145 redisContext* ctx;
146 #ifdef THREADS_DISABLED
147 /* Fix attribute unused warning.
148 * wait_lock is only used with lock_basic_* functions that are nop'ed
149 * when compiled without thread support. */
150 (void)wait_lock;
151 #endif /* THREADS_DISABLED */
152
153 /* See if the redis server is down, and reconnect has to wait. */
154 if(*reconnect_attempts > REDIS_RECONNECT_ATTEMPT_LIMIT) {
155 /* Acquire lock to look at timeval, the integer has atomic
156 * integrity. */
157 struct timeval wait_tv;
158 if(now_tv) {
159 now_val = *now_tv;
160 } else {
161 if(gettimeofday(&now_val, NULL) < 0)
162 log_err("redis: gettimeofday: %s",
163 strerror(errno));
164 }
165 lock_basic_lock(wait_lock);
166 wait_tv = *reconnect_wait;
167 lock_basic_unlock(wait_lock);
168 if(timeval_smaller(&now_val, &wait_tv)) {
169 verbose(VERB_ALGO, "redis %sdown, reconnect wait",
170 infostr);
171 return NULL;
172 }
173 }
174
175 if(path && path[0]!=0) {
176 ctx = redisConnectUnixWithTimeout(path, connect_timeout);
177 } else {
178 ctx = redisConnectWithTimeout(host, port, connect_timeout);
179 }
180 if(!ctx || ctx->err) {
181 const char *errstr = "out of memory";
182 if(ctx)
183 errstr = ctx->errstr;
184 log_err("failed to connect to redis %sserver: %s", infostr, errstr);
185 goto fail;
186 }
187 if(redisSetTimeout(ctx, command_timeout) != REDIS_OK) {
188 log_err("failed to set redis %stimeout, %s", infostr, ctx->errstr);
189 goto fail;
190 }
191 if(password && password[0]!=0) {
192 redisReply* rep;
193 rep = redisCommand(ctx, "AUTH %s", password);
194 if(!rep || rep->type == REDIS_REPLY_ERROR) {
195 log_err("failed to authenticate %swith password", infostr);
196 freeReplyObject(rep);
197 goto fail;
198 }
199 freeReplyObject(rep);
200 }
201 if(logical_db > 0) {
202 redisReply* rep;
203 rep = redisCommand(ctx, "SELECT %d", logical_db);
204 if(!rep || rep->type == REDIS_REPLY_ERROR) {
205 log_err("failed %sto set logical database (%d)",
206 infostr, logical_db);
207 freeReplyObject(rep);
208 goto fail;
209 }
210 freeReplyObject(rep);
211 }
212 *reconnect_attempts = 0;
213 if(verbosity >= VERB_OPS) {
214 char port_str[6+1];
215 port_str[0] = ' ';
216 (void)snprintf(port_str+1, sizeof(port_str)-1, "%d", port);
217 verbose(VERB_OPS, "Connection to Redis %sestablished (%s%s)",
218 infostr,
219 path&&path[0]!=0?path:host,
220 path&&path[0]!=0?"":port_str);
221 }
222 return ctx;
223
224 fail:
225 if(ctx)
226 redisFree(ctx);
227 (*reconnect_attempts)++;
228 if(*reconnect_attempts > REDIS_RECONNECT_ATTEMPT_LIMIT) {
229 /* Wait for the reconnect interval before trying again. */
230 struct timeval tv;
231 if(now_tv) {
232 now_val = *now_tv;
233 } else {
234 if(gettimeofday(&now_val, NULL) < 0)
235 log_err("redis: gettimeofday: %s",
236 strerror(errno));
237 }
238 tv = now_val;
239 timeval_add(&tv, reconnect_interval);
240 lock_basic_lock(wait_lock);
241 *reconnect_wait = tv;
242 lock_basic_unlock(wait_lock);
243 verbose(VERB_ALGO, "redis %sreconnect wait until %d.%6.6d",
244 infostr, (int)tv.tv_sec, (int)tv.tv_usec);
245 }
246 return NULL;
247 }
248
249 static void
set_timeout(struct timeval * timeout,int value,int explicit_value)250 set_timeout(struct timeval* timeout, int value, int explicit_value)
251 {
252 int v = explicit_value != 0 ? explicit_value : value;
253 timeout->tv_sec = v / 1000;
254 timeout->tv_usec = (v % 1000) * 1000;
255 }
256
257 static int
redis_init(struct module_env * env,struct cachedb_env * cachedb_env)258 redis_init(struct module_env* env, struct cachedb_env* cachedb_env)
259 {
260 int i;
261 struct redis_moddata* moddata = NULL;
262
263 verbose(VERB_OPS, "Redis initialization");
264
265 moddata = calloc(1, sizeof(struct redis_moddata));
266 if(!moddata) {
267 log_err("out of memory");
268 goto fail;
269 }
270 lock_basic_init(&moddata->wait_lock);
271 lock_protect(&moddata->wait_lock, &moddata->reconnect_wait,
272 sizeof(moddata->reconnect_wait));
273 lock_basic_init(&moddata->replica_wait_lock);
274 lock_protect(&moddata->replica_wait_lock,
275 &moddata->replica_reconnect_wait,
276 sizeof(moddata->replica_reconnect_wait));
277 moddata->numctxs = env->cfg->num_threads;
278 /* note: server_host and similar string configuration options are
279 * shallow references to configured strings; we don't have to free them
280 * in this module. */
281 moddata->server_host = env->cfg->redis_server_host;
282 moddata->replica_server_host = env->cfg->redis_replica_server_host;
283
284 moddata->server_port = env->cfg->redis_server_port;
285 moddata->replica_server_port = env->cfg->redis_replica_server_port;
286
287 moddata->server_path = env->cfg->redis_server_path;
288 moddata->replica_server_path = env->cfg->redis_replica_server_path;
289
290 moddata->server_password = env->cfg->redis_server_password;
291 moddata->replica_server_password = env->cfg->redis_replica_server_password;
292
293 set_timeout(&moddata->command_timeout,
294 env->cfg->redis_timeout,
295 env->cfg->redis_command_timeout);
296 set_timeout(&moddata->replica_command_timeout,
297 env->cfg->redis_replica_timeout,
298 env->cfg->redis_replica_command_timeout);
299 set_timeout(&moddata->connect_timeout,
300 env->cfg->redis_timeout,
301 env->cfg->redis_connect_timeout);
302 set_timeout(&moddata->replica_connect_timeout,
303 env->cfg->redis_replica_timeout,
304 env->cfg->redis_replica_connect_timeout);
305 set_timeout(&moddata->reconnect_interval, 1000, 0);
306 set_timeout(&moddata->replica_reconnect_interval, 1000, 0);
307
308 moddata->logical_db = env->cfg->redis_logical_db;
309 moddata->replica_logical_db = env->cfg->redis_replica_logical_db;
310
311 moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
312 if(!moddata->ctxs) {
313 log_err("out of memory");
314 goto fail;
315 }
316 if((moddata->replica_server_host && moddata->replica_server_host[0]!=0)
317 || (moddata->replica_server_path && moddata->replica_server_path[0]!=0)) {
318 /* There is a replica configured, allocate ctxs */
319 moddata->replica_ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
320 if(!moddata->replica_ctxs) {
321 log_err("out of memory");
322 goto fail;
323 }
324 }
325 for(i = 0; i < moddata->numctxs; i++) {
326 redisContext* ctx = redis_connect(
327 moddata->server_host,
328 moddata->server_port,
329 moddata->server_path,
330 moddata->server_password,
331 moddata->logical_db,
332 moddata->connect_timeout,
333 moddata->command_timeout,
334 &moddata->reconnect_interval,
335 &moddata->reconnect_attempts,
336 &moddata->reconnect_wait,
337 &moddata->wait_lock,
338 env->now_tv,
339 "");
340 if(!ctx) {
341 log_err("redis_init: failed to init redis "
342 "(for thread %d)", i);
343 /* And continue, the context can be established
344 * later, just like after a disconnect. */
345 }
346 moddata->ctxs[i] = ctx;
347 }
348 if(moddata->replica_ctxs) {
349 for(i = 0; i < moddata->numctxs; i++) {
350 redisContext* ctx = redis_connect(
351 moddata->replica_server_host,
352 moddata->replica_server_port,
353 moddata->replica_server_path,
354 moddata->replica_server_password,
355 moddata->replica_logical_db,
356 moddata->replica_connect_timeout,
357 moddata->replica_command_timeout,
358 &moddata->replica_reconnect_interval,
359 &moddata->replica_reconnect_attempts,
360 &moddata->replica_reconnect_wait,
361 &moddata->replica_wait_lock,
362 env->now_tv,
363 "replica ");
364 if(!ctx) {
365 log_err("redis_init: failed to init redis "
366 "replica (for thread %d)", i);
367 /* And continue, the context can be established
368 * later, just like after a disconnect. */
369 }
370 moddata->replica_ctxs[i] = ctx;
371 }
372 }
373 cachedb_env->backend_data = moddata;
374 if(env->cfg->redis_expire_records &&
375 moddata->ctxs[env->alloc->thread_num] != NULL) {
376 redisReply* rep = NULL;
377 int redis_reply_type = 0;
378 /** check if set with ex command is supported */
379 rep = redis_command(env, cachedb_env,
380 "SET __UNBOUND_REDIS_CHECK__ none EX 1", NULL, 0, 1);
381 if(!rep) {
382 /** init failed, no response from redis server*/
383 goto set_with_ex_fail;
384 }
385 redis_reply_type = rep->type;
386 freeReplyObject(rep);
387 switch(redis_reply_type) {
388 case REDIS_REPLY_STATUS:
389 break;
390 default:
391 /** init failed, set_with_ex command not supported */
392 goto set_with_ex_fail;
393 }
394 moddata->set_with_ex_available = 1;
395 }
396 return 1;
397
398 set_with_ex_fail:
399 log_err("redis_init: failure during redis_init, the "
400 "redis-expire-records option requires the SET with EX command "
401 "(redis >= 2.6.12)");
402 return 1;
403 fail:
404 moddata_clean(&moddata);
405 return 0;
406 }
407
408 static void
redis_deinit(struct module_env * env,struct cachedb_env * cachedb_env)409 redis_deinit(struct module_env* env, struct cachedb_env* cachedb_env)
410 {
411 struct redis_moddata* moddata = (struct redis_moddata*)
412 cachedb_env->backend_data;
413 (void)env;
414
415 verbose(VERB_OPS, "Redis deinitialization");
416 moddata_clean(&moddata);
417 }
418
419 /*
420 * Send a redis command and get a reply. Unified so that it can be used for
421 * both SET and GET. If 'data' is non-NULL the command is supposed to be
422 * SET and GET otherwise, but the implementation of this function is agnostic
423 * about the semantics (except for logging): 'command', 'data', and 'data_len'
424 * are opaquely passed to redisCommand().
425 * This function first checks whether a connection with a redis server has
426 * been established; if not it tries to set up a new one.
427 * It returns redisReply returned from redisCommand() or NULL if some low
428 * level error happens. The caller is responsible to check the return value,
429 * if it's non-NULL, it has to free it with freeReplyObject().
430 */
431 static redisReply*
redis_command(struct module_env * env,struct cachedb_env * cachedb_env,const char * command,const uint8_t * data,size_t data_len,int write)432 redis_command(struct module_env* env, struct cachedb_env* cachedb_env,
433 const char* command, const uint8_t* data, size_t data_len, int write)
434 {
435 redisContext* ctx, **ctx_selector;
436 redisReply* rep;
437 struct redis_moddata* d = (struct redis_moddata*)
438 cachedb_env->backend_data;
439
440 /* We assume env->alloc->thread_num is a unique ID for each thread
441 * in [0, num-of-threads). We could treat it as an error condition
442 * if the assumption didn't hold, but it seems to be a fundamental
443 * assumption throughout the unbound architecture, so we simply assert
444 * it. */
445 log_assert(env->alloc->thread_num < d->numctxs);
446
447 ctx_selector = !write && d->replica_ctxs
448 ?d->replica_ctxs
449 :d->ctxs;
450 ctx = ctx_selector[env->alloc->thread_num];
451
452 /* If we've not established a connection to the server or we've closed
453 * it on a failure, try to re-establish a new one. Failures will be
454 * logged in redis_connect(). */
455 if(!ctx) {
456 if(!write && d->replica_ctxs) {
457 ctx = redis_connect(
458 d->replica_server_host,
459 d->replica_server_port,
460 d->replica_server_path,
461 d->replica_server_password,
462 d->replica_logical_db,
463 d->replica_connect_timeout,
464 d->replica_command_timeout,
465 &d->replica_reconnect_interval,
466 &d->replica_reconnect_attempts,
467 &d->replica_reconnect_wait,
468 &d->replica_wait_lock,
469 env->now_tv,
470 "replica ");
471 } else {
472 ctx = redis_connect(
473 d->server_host,
474 d->server_port,
475 d->server_path,
476 d->server_password,
477 d->logical_db,
478 d->connect_timeout,
479 d->command_timeout,
480 &d->reconnect_interval,
481 &d->reconnect_attempts,
482 &d->reconnect_wait,
483 &d->wait_lock,
484 env->now_tv,
485 "");
486 }
487 ctx_selector[env->alloc->thread_num] = ctx;
488 }
489 if(!ctx) return NULL;
490
491 /* Send the command and get a reply, synchronously. */
492 rep = (redisReply*)redisCommand(ctx, command, data, data_len);
493 if(!rep) {
494 /* Once an error as a NULL-reply is returned the context cannot
495 * be reused and we'll need to set up a new connection. */
496 log_err("redis_command: failed to receive a reply, "
497 "closing connection: %s", ctx->errstr);
498 redisFree(ctx);
499 ctx_selector[env->alloc->thread_num] = NULL;
500 return NULL;
501 }
502
503 /* Check error in reply to unify logging in that case.
504 * The caller may perform context-dependent checks and logging. */
505 if(rep->type == REDIS_REPLY_ERROR)
506 log_err("redis: %s resulted in an error: %s",
507 data ? "set" : "get", rep->str);
508
509 return rep;
510 }
511
512 static int
redis_lookup(struct module_env * env,struct cachedb_env * cachedb_env,char * key,struct sldns_buffer * result_buffer)513 redis_lookup(struct module_env* env, struct cachedb_env* cachedb_env,
514 char* key, struct sldns_buffer* result_buffer)
515 {
516 redisReply* rep;
517 /* Supported commands:
518 * - "GET " + key
519 */
520 #define REDIS_LOOKUP_MAX_BUF_LEN \
521 4 /* "GET " */ \
522 +(CACHEDB_HASHSIZE/8)*2 /* key hash */ \
523 + 1 /* \0 */
524 char cmdbuf[REDIS_LOOKUP_MAX_BUF_LEN];
525 int n;
526 int ret = 0;
527
528 verbose(VERB_ALGO, "redis_lookup of %s", key);
529
530 n = snprintf(cmdbuf, sizeof(cmdbuf), "GET %s", key);
531 if(n < 0 || n >= (int)sizeof(cmdbuf)) {
532 log_err("redis_lookup: unexpected failure to build command");
533 return 0;
534 }
535
536 rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0, 0);
537 if(!rep)
538 return 0;
539 switch(rep->type) {
540 case REDIS_REPLY_NIL:
541 verbose(VERB_ALGO, "redis_lookup: no data cached");
542 break;
543 case REDIS_REPLY_STRING:
544 verbose(VERB_ALGO, "redis_lookup found %d bytes",
545 (int)rep->len);
546 if((size_t)rep->len > sldns_buffer_capacity(result_buffer)) {
547 log_err("redis_lookup: replied data too long: %lu",
548 (size_t)rep->len);
549 break;
550 }
551 sldns_buffer_clear(result_buffer);
552 sldns_buffer_write(result_buffer, rep->str, rep->len);
553 sldns_buffer_flip(result_buffer);
554 ret = 1;
555 break;
556 case REDIS_REPLY_ERROR:
557 break; /* already logged */
558 default:
559 log_err("redis_lookup: unexpected type of reply for (%d)",
560 rep->type);
561 break;
562 }
563 freeReplyObject(rep);
564 return ret;
565 }
566
567 static void
redis_store(struct module_env * env,struct cachedb_env * cachedb_env,char * key,uint8_t * data,size_t data_len,time_t ttl)568 redis_store(struct module_env* env, struct cachedb_env* cachedb_env,
569 char* key, uint8_t* data, size_t data_len, time_t ttl)
570 {
571 redisReply* rep;
572 int n;
573 struct redis_moddata* moddata = (struct redis_moddata*)
574 cachedb_env->backend_data;
575 int set_ttl = (moddata->set_with_ex_available &&
576 env->cfg->redis_expire_records &&
577 (!env->cfg->serve_expired || env->cfg->serve_expired_ttl > 0));
578 /* Supported commands:
579 * - "SET " + key + " %b"
580 * - "SET " + key + " %b EX " + ttl
581 * older redis 2.0.0 was "SETEX " + key + " " + ttl + " %b"
582 * - "EXPIRE " + key + " 0"
583 */
584 #define REDIS_STORE_MAX_BUF_LEN \
585 7 /* "EXPIRE " */ \
586 +(CACHEDB_HASHSIZE/8)*2 /* key hash */ \
587 + 7 /* " %b EX " */ \
588 + 20 /* ttl (uint64_t) */ \
589 + 1 /* \0 */
590 char cmdbuf[REDIS_STORE_MAX_BUF_LEN];
591
592 if (!set_ttl) {
593 verbose(VERB_ALGO, "redis_store %s (%d bytes)", key, (int)data_len);
594 /* build command to set to a binary safe string */
595 n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b", key);
596 } else if(ttl == 0) {
597 /* use the EXPIRE command, SET with EX 0 is an invalid time. */
598 /* Replies with REDIS_REPLY_INTEGER of 1. */
599 verbose(VERB_ALGO, "redis_store expire %s (%d bytes)",
600 key, (int)data_len);
601 n = snprintf(cmdbuf, sizeof(cmdbuf), "EXPIRE %s 0", key);
602 data = NULL;
603 data_len = 0;
604 } else {
605 /* add expired ttl time to redis ttl to avoid premature eviction of key */
606 ttl += env->cfg->serve_expired_ttl;
607 verbose(VERB_ALGO, "redis_store %s (%d bytes) with ttl %u",
608 key, (int)data_len, (unsigned)(uint32_t)ttl);
609 /* build command to set to a binary safe string */
610 n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b EX %u", key,
611 (unsigned)(uint32_t)ttl);
612 }
613
614
615 if(n < 0 || n >= (int)sizeof(cmdbuf)) {
616 log_err("redis_store: unexpected failure to build command");
617 return;
618 }
619
620 rep = redis_command(env, cachedb_env, cmdbuf, data, data_len, 1);
621 if(rep) {
622 verbose(VERB_ALGO, "redis_store set completed");
623 if(rep->type != REDIS_REPLY_STATUS &&
624 rep->type != REDIS_REPLY_ERROR &&
625 rep->type != REDIS_REPLY_INTEGER) {
626 log_err("redis_store: unexpected type of reply (%d)",
627 rep->type);
628 }
629 freeReplyObject(rep);
630 }
631 }
632
633 struct cachedb_backend redis_backend = { "redis",
634 redis_init, redis_deinit, redis_lookup, redis_store
635 };
636 #endif /* USE_REDIS */
637 #endif /* USE_CACHEDB */
638