xref: /freebsd/contrib/unbound/cachedb/redis.c (revision b2efd602aea8b3cbc3fb215b9611946d04fceb10)
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