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
147 /* See if the redis server is down, and reconnect has to wait. */
148 if(*reconnect_attempts > REDIS_RECONNECT_ATTEMPT_LIMIT) {
149 /* Acquire lock to look at timeval, the integer has atomic
150 * integrity. */
151 struct timeval wait_tv;
152 if(now_tv) {
153 now_val = *now_tv;
154 } else {
155 if(gettimeofday(&now_val, NULL) < 0)
156 log_err("redis: gettimeofday: %s",
157 strerror(errno));
158 }
159 lock_basic_lock(wait_lock);
160 wait_tv = *reconnect_wait;
161 lock_basic_unlock(wait_lock);
162 if(timeval_smaller(&now_val, &wait_tv)) {
163 verbose(VERB_ALGO, "redis %sdown, reconnect wait",
164 infostr);
165 return NULL;
166 }
167 }
168
169 if(path && path[0]!=0) {
170 ctx = redisConnectUnixWithTimeout(path, connect_timeout);
171 } else {
172 ctx = redisConnectWithTimeout(host, port, connect_timeout);
173 }
174 if(!ctx || ctx->err) {
175 const char *errstr = "out of memory";
176 if(ctx)
177 errstr = ctx->errstr;
178 log_err("failed to connect to redis %sserver: %s", infostr, errstr);
179 goto fail;
180 }
181 if(redisSetTimeout(ctx, command_timeout) != REDIS_OK) {
182 log_err("failed to set redis %stimeout, %s", infostr, ctx->errstr);
183 goto fail;
184 }
185 if(password && password[0]!=0) {
186 redisReply* rep;
187 rep = redisCommand(ctx, "AUTH %s", password);
188 if(!rep || rep->type == REDIS_REPLY_ERROR) {
189 log_err("failed to authenticate %swith password", infostr);
190 freeReplyObject(rep);
191 goto fail;
192 }
193 freeReplyObject(rep);
194 }
195 if(logical_db > 0) {
196 redisReply* rep;
197 rep = redisCommand(ctx, "SELECT %d", logical_db);
198 if(!rep || rep->type == REDIS_REPLY_ERROR) {
199 log_err("failed %sto set logical database (%d)",
200 infostr, logical_db);
201 freeReplyObject(rep);
202 goto fail;
203 }
204 freeReplyObject(rep);
205 }
206 *reconnect_attempts = 0;
207 if(verbosity >= VERB_OPS) {
208 char port_str[6+1];
209 port_str[0] = ' ';
210 (void)snprintf(port_str+1, sizeof(port_str)-1, "%d", port);
211 verbose(VERB_OPS, "Connection to Redis %sestablished (%s%s)",
212 infostr,
213 path&&path[0]!=0?path:host,
214 path&&path[0]!=0?"":port_str);
215 }
216 return ctx;
217
218 fail:
219 if(ctx)
220 redisFree(ctx);
221 (*reconnect_attempts)++;
222 if(*reconnect_attempts > REDIS_RECONNECT_ATTEMPT_LIMIT) {
223 /* Wait for the reconnect interval before trying again. */
224 struct timeval tv;
225 if(now_tv) {
226 now_val = *now_tv;
227 } else {
228 if(gettimeofday(&now_val, NULL) < 0)
229 log_err("redis: gettimeofday: %s",
230 strerror(errno));
231 }
232 tv = now_val;
233 timeval_add(&tv, reconnect_interval);
234 lock_basic_lock(wait_lock);
235 *reconnect_wait = tv;
236 lock_basic_unlock(wait_lock);
237 verbose(VERB_ALGO, "redis %sreconnect wait until %d.%6.6d",
238 infostr, (int)tv.tv_sec, (int)tv.tv_usec);
239 }
240 return NULL;
241 }
242
243 static void
set_timeout(struct timeval * timeout,int value,int explicit_value)244 set_timeout(struct timeval* timeout, int value, int explicit_value)
245 {
246 int v = explicit_value != 0 ? explicit_value : value;
247 timeout->tv_sec = v / 1000;
248 timeout->tv_usec = (v % 1000) * 1000;
249 }
250
251 static int
redis_init(struct module_env * env,struct cachedb_env * cachedb_env)252 redis_init(struct module_env* env, struct cachedb_env* cachedb_env)
253 {
254 int i;
255 struct redis_moddata* moddata = NULL;
256
257 verbose(VERB_OPS, "Redis initialization");
258
259 moddata = calloc(1, sizeof(struct redis_moddata));
260 if(!moddata) {
261 log_err("out of memory");
262 goto fail;
263 }
264 lock_basic_init(&moddata->wait_lock);
265 lock_protect(&moddata->wait_lock, &moddata->reconnect_wait,
266 sizeof(moddata->reconnect_wait));
267 lock_basic_init(&moddata->replica_wait_lock);
268 lock_protect(&moddata->replica_wait_lock,
269 &moddata->replica_reconnect_wait,
270 sizeof(moddata->replica_reconnect_wait));
271 moddata->numctxs = env->cfg->num_threads;
272 /* note: server_host and similar string configuration options are
273 * shallow references to configured strings; we don't have to free them
274 * in this module. */
275 moddata->server_host = env->cfg->redis_server_host;
276 moddata->replica_server_host = env->cfg->redis_replica_server_host;
277
278 moddata->server_port = env->cfg->redis_server_port;
279 moddata->replica_server_port = env->cfg->redis_replica_server_port;
280
281 moddata->server_path = env->cfg->redis_server_path;
282 moddata->replica_server_path = env->cfg->redis_replica_server_path;
283
284 moddata->server_password = env->cfg->redis_server_password;
285 moddata->replica_server_password = env->cfg->redis_replica_server_password;
286
287 set_timeout(&moddata->command_timeout,
288 env->cfg->redis_timeout,
289 env->cfg->redis_command_timeout);
290 set_timeout(&moddata->replica_command_timeout,
291 env->cfg->redis_replica_timeout,
292 env->cfg->redis_replica_command_timeout);
293 set_timeout(&moddata->connect_timeout,
294 env->cfg->redis_timeout,
295 env->cfg->redis_connect_timeout);
296 set_timeout(&moddata->replica_connect_timeout,
297 env->cfg->redis_replica_timeout,
298 env->cfg->redis_replica_connect_timeout);
299 set_timeout(&moddata->reconnect_interval, 1000, 0);
300 set_timeout(&moddata->replica_reconnect_interval, 1000, 0);
301
302 moddata->logical_db = env->cfg->redis_logical_db;
303 moddata->replica_logical_db = env->cfg->redis_replica_logical_db;
304
305 moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
306 if(!moddata->ctxs) {
307 log_err("out of memory");
308 goto fail;
309 }
310 if((moddata->replica_server_host && moddata->replica_server_host[0]!=0)
311 || (moddata->replica_server_path && moddata->replica_server_path[0]!=0)) {
312 /* There is a replica configured, allocate ctxs */
313 moddata->replica_ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
314 if(!moddata->replica_ctxs) {
315 log_err("out of memory");
316 goto fail;
317 }
318 }
319 for(i = 0; i < moddata->numctxs; i++) {
320 redisContext* ctx = redis_connect(
321 moddata->server_host,
322 moddata->server_port,
323 moddata->server_path,
324 moddata->server_password,
325 moddata->logical_db,
326 moddata->connect_timeout,
327 moddata->command_timeout,
328 &moddata->reconnect_interval,
329 &moddata->reconnect_attempts,
330 &moddata->reconnect_wait,
331 &moddata->wait_lock,
332 env->now_tv,
333 "");
334 if(!ctx) {
335 log_err("redis_init: failed to init redis "
336 "(for thread %d)", i);
337 /* And continue, the context can be established
338 * later, just like after a disconnect. */
339 }
340 moddata->ctxs[i] = ctx;
341 }
342 if(moddata->replica_ctxs) {
343 for(i = 0; i < moddata->numctxs; i++) {
344 redisContext* ctx = redis_connect(
345 moddata->replica_server_host,
346 moddata->replica_server_port,
347 moddata->replica_server_path,
348 moddata->replica_server_password,
349 moddata->replica_logical_db,
350 moddata->replica_connect_timeout,
351 moddata->replica_command_timeout,
352 &moddata->replica_reconnect_interval,
353 &moddata->replica_reconnect_attempts,
354 &moddata->replica_reconnect_wait,
355 &moddata->replica_wait_lock,
356 env->now_tv,
357 "replica ");
358 if(!ctx) {
359 log_err("redis_init: failed to init redis "
360 "replica (for thread %d)", i);
361 /* And continue, the context can be established
362 * later, just like after a disconnect. */
363 }
364 moddata->replica_ctxs[i] = ctx;
365 }
366 }
367 cachedb_env->backend_data = moddata;
368 if(env->cfg->redis_expire_records &&
369 moddata->ctxs[env->alloc->thread_num] != NULL) {
370 redisReply* rep = NULL;
371 int redis_reply_type = 0;
372 /** check if set with ex command is supported */
373 rep = redis_command(env, cachedb_env,
374 "SET __UNBOUND_REDIS_CHECK__ none EX 1", NULL, 0, 1);
375 if(!rep) {
376 /** init failed, no response from redis server*/
377 goto set_with_ex_fail;
378 }
379 redis_reply_type = rep->type;
380 freeReplyObject(rep);
381 switch(redis_reply_type) {
382 case REDIS_REPLY_STATUS:
383 break;
384 default:
385 /** init failed, set_with_ex command not supported */
386 goto set_with_ex_fail;
387 }
388 moddata->set_with_ex_available = 1;
389 }
390 return 1;
391
392 set_with_ex_fail:
393 log_err("redis_init: failure during redis_init, the "
394 "redis-expire-records option requires the SET with EX command "
395 "(redis >= 2.6.12)");
396 return 1;
397 fail:
398 moddata_clean(&moddata);
399 return 0;
400 }
401
402 static void
redis_deinit(struct module_env * env,struct cachedb_env * cachedb_env)403 redis_deinit(struct module_env* env, struct cachedb_env* cachedb_env)
404 {
405 struct redis_moddata* moddata = (struct redis_moddata*)
406 cachedb_env->backend_data;
407 (void)env;
408
409 verbose(VERB_OPS, "Redis deinitialization");
410 moddata_clean(&moddata);
411 }
412
413 /*
414 * Send a redis command and get a reply. Unified so that it can be used for
415 * both SET and GET. If 'data' is non-NULL the command is supposed to be
416 * SET and GET otherwise, but the implementation of this function is agnostic
417 * about the semantics (except for logging): 'command', 'data', and 'data_len'
418 * are opaquely passed to redisCommand().
419 * This function first checks whether a connection with a redis server has
420 * been established; if not it tries to set up a new one.
421 * It returns redisReply returned from redisCommand() or NULL if some low
422 * level error happens. The caller is responsible to check the return value,
423 * if it's non-NULL, it has to free it with freeReplyObject().
424 */
425 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)426 redis_command(struct module_env* env, struct cachedb_env* cachedb_env,
427 const char* command, const uint8_t* data, size_t data_len, int write)
428 {
429 redisContext* ctx, **ctx_selector;
430 redisReply* rep;
431 struct redis_moddata* d = (struct redis_moddata*)
432 cachedb_env->backend_data;
433
434 /* We assume env->alloc->thread_num is a unique ID for each thread
435 * in [0, num-of-threads). We could treat it as an error condition
436 * if the assumption didn't hold, but it seems to be a fundamental
437 * assumption throughout the unbound architecture, so we simply assert
438 * it. */
439 log_assert(env->alloc->thread_num < d->numctxs);
440
441 ctx_selector = !write && d->replica_ctxs
442 ?d->replica_ctxs
443 :d->ctxs;
444 ctx = ctx_selector[env->alloc->thread_num];
445
446 /* If we've not established a connection to the server or we've closed
447 * it on a failure, try to re-establish a new one. Failures will be
448 * logged in redis_connect(). */
449 if(!ctx) {
450 if(!write && d->replica_ctxs) {
451 ctx = redis_connect(
452 d->replica_server_host,
453 d->replica_server_port,
454 d->replica_server_path,
455 d->replica_server_password,
456 d->replica_logical_db,
457 d->replica_connect_timeout,
458 d->replica_command_timeout,
459 &d->replica_reconnect_interval,
460 &d->replica_reconnect_attempts,
461 &d->replica_reconnect_wait,
462 &d->replica_wait_lock,
463 env->now_tv,
464 "replica ");
465 } else {
466 ctx = redis_connect(
467 d->server_host,
468 d->server_port,
469 d->server_path,
470 d->server_password,
471 d->logical_db,
472 d->connect_timeout,
473 d->command_timeout,
474 &d->reconnect_interval,
475 &d->reconnect_attempts,
476 &d->reconnect_wait,
477 &d->wait_lock,
478 env->now_tv,
479 "");
480 }
481 ctx_selector[env->alloc->thread_num] = ctx;
482 }
483 if(!ctx) return NULL;
484
485 /* Send the command and get a reply, synchronously. */
486 rep = (redisReply*)redisCommand(ctx, command, data, data_len);
487 if(!rep) {
488 /* Once an error as a NULL-reply is returned the context cannot
489 * be reused and we'll need to set up a new connection. */
490 log_err("redis_command: failed to receive a reply, "
491 "closing connection: %s", ctx->errstr);
492 redisFree(ctx);
493 ctx_selector[env->alloc->thread_num] = NULL;
494 return NULL;
495 }
496
497 /* Check error in reply to unify logging in that case.
498 * The caller may perform context-dependent checks and logging. */
499 if(rep->type == REDIS_REPLY_ERROR)
500 log_err("redis: %s resulted in an error: %s",
501 data ? "set" : "get", rep->str);
502
503 return rep;
504 }
505
506 static int
redis_lookup(struct module_env * env,struct cachedb_env * cachedb_env,char * key,struct sldns_buffer * result_buffer)507 redis_lookup(struct module_env* env, struct cachedb_env* cachedb_env,
508 char* key, struct sldns_buffer* result_buffer)
509 {
510 redisReply* rep;
511 /* Supported commands:
512 * - "GET " + key
513 */
514 #define REDIS_LOOKUP_MAX_BUF_LEN \
515 4 /* "GET " */ \
516 +(CACHEDB_HASHSIZE/8)*2 /* key hash */ \
517 + 1 /* \0 */
518 char cmdbuf[REDIS_LOOKUP_MAX_BUF_LEN];
519 int n;
520 int ret = 0;
521
522 verbose(VERB_ALGO, "redis_lookup of %s", key);
523
524 n = snprintf(cmdbuf, sizeof(cmdbuf), "GET %s", key);
525 if(n < 0 || n >= (int)sizeof(cmdbuf)) {
526 log_err("redis_lookup: unexpected failure to build command");
527 return 0;
528 }
529
530 rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0, 0);
531 if(!rep)
532 return 0;
533 switch(rep->type) {
534 case REDIS_REPLY_NIL:
535 verbose(VERB_ALGO, "redis_lookup: no data cached");
536 break;
537 case REDIS_REPLY_STRING:
538 verbose(VERB_ALGO, "redis_lookup found %d bytes",
539 (int)rep->len);
540 if((size_t)rep->len > sldns_buffer_capacity(result_buffer)) {
541 log_err("redis_lookup: replied data too long: %lu",
542 (size_t)rep->len);
543 break;
544 }
545 sldns_buffer_clear(result_buffer);
546 sldns_buffer_write(result_buffer, rep->str, rep->len);
547 sldns_buffer_flip(result_buffer);
548 ret = 1;
549 break;
550 case REDIS_REPLY_ERROR:
551 break; /* already logged */
552 default:
553 log_err("redis_lookup: unexpected type of reply for (%d)",
554 rep->type);
555 break;
556 }
557 freeReplyObject(rep);
558 return ret;
559 }
560
561 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)562 redis_store(struct module_env* env, struct cachedb_env* cachedb_env,
563 char* key, uint8_t* data, size_t data_len, time_t ttl)
564 {
565 redisReply* rep;
566 int n;
567 struct redis_moddata* moddata = (struct redis_moddata*)
568 cachedb_env->backend_data;
569 int set_ttl = (moddata->set_with_ex_available &&
570 env->cfg->redis_expire_records &&
571 (!env->cfg->serve_expired || env->cfg->serve_expired_ttl > 0));
572 /* Supported commands:
573 * - "SET " + key + " %b"
574 * - "SET " + key + " %b EX " + ttl
575 * older redis 2.0.0 was "SETEX " + key + " " + ttl + " %b"
576 * - "EXPIRE " + key + " 0"
577 */
578 #define REDIS_STORE_MAX_BUF_LEN \
579 7 /* "EXPIRE " */ \
580 +(CACHEDB_HASHSIZE/8)*2 /* key hash */ \
581 + 7 /* " %b EX " */ \
582 + 20 /* ttl (uint64_t) */ \
583 + 1 /* \0 */
584 char cmdbuf[REDIS_STORE_MAX_BUF_LEN];
585
586 if (!set_ttl) {
587 verbose(VERB_ALGO, "redis_store %s (%d bytes)", key, (int)data_len);
588 /* build command to set to a binary safe string */
589 n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b", key);
590 } else if(ttl == 0) {
591 /* use the EXPIRE command, SET with EX 0 is an invalid time. */
592 /* Replies with REDIS_REPLY_INTEGER of 1. */
593 verbose(VERB_ALGO, "redis_store expire %s (%d bytes)",
594 key, (int)data_len);
595 n = snprintf(cmdbuf, sizeof(cmdbuf), "EXPIRE %s 0", key);
596 data = NULL;
597 data_len = 0;
598 } else {
599 /* add expired ttl time to redis ttl to avoid premature eviction of key */
600 ttl += env->cfg->serve_expired_ttl;
601 verbose(VERB_ALGO, "redis_store %s (%d bytes) with ttl %u",
602 key, (int)data_len, (unsigned)(uint32_t)ttl);
603 /* build command to set to a binary safe string */
604 n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b EX %u", key,
605 (unsigned)(uint32_t)ttl);
606 }
607
608
609 if(n < 0 || n >= (int)sizeof(cmdbuf)) {
610 log_err("redis_store: unexpected failure to build command");
611 return;
612 }
613
614 rep = redis_command(env, cachedb_env, cmdbuf, data, data_len, 1);
615 if(rep) {
616 verbose(VERB_ALGO, "redis_store set completed");
617 if(rep->type != REDIS_REPLY_STATUS &&
618 rep->type != REDIS_REPLY_ERROR &&
619 rep->type != REDIS_REPLY_INTEGER) {
620 log_err("redis_store: unexpected type of reply (%d)",
621 rep->type);
622 }
623 freeReplyObject(rep);
624 }
625 }
626
627 struct cachedb_backend redis_backend = { "redis",
628 redis_init, redis_deinit, redis_lookup, redis_store
629 };
630 #endif /* USE_REDIS */
631 #endif /* USE_CACHEDB */
632