xref: /netbsd-src/external/bsd/unbound/dist/cachedb/redis.c (revision 91f7d55fb697b5e0475da4718fa34c3a3ebeac85)
17cd94d69Schristos /*
27cd94d69Schristos  * cachedb/redis.c - cachedb redis module
37cd94d69Schristos  *
47cd94d69Schristos  * Copyright (c) 2018, NLnet Labs. All rights reserved.
57cd94d69Schristos  *
67cd94d69Schristos  * This software is open source.
77cd94d69Schristos  *
87cd94d69Schristos  * Redistribution and use in source and binary forms, with or without
97cd94d69Schristos  * modification, are permitted provided that the following conditions
107cd94d69Schristos  * are met:
117cd94d69Schristos  *
127cd94d69Schristos  * Redistributions of source code must retain the above copyright notice,
137cd94d69Schristos  * this list of conditions and the following disclaimer.
147cd94d69Schristos  *
157cd94d69Schristos  * Redistributions in binary form must reproduce the above copyright notice,
167cd94d69Schristos  * this list of conditions and the following disclaimer in the documentation
177cd94d69Schristos  * and/or other materials provided with the distribution.
187cd94d69Schristos  *
197cd94d69Schristos  * Neither the name of the NLNET LABS nor the names of its contributors may
207cd94d69Schristos  * be used to endorse or promote products derived from this software without
217cd94d69Schristos  * specific prior written permission.
227cd94d69Schristos  *
237cd94d69Schristos  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
247cd94d69Schristos  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
257cd94d69Schristos  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
267cd94d69Schristos  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
277cd94d69Schristos  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
287cd94d69Schristos  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
297cd94d69Schristos  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
307cd94d69Schristos  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
317cd94d69Schristos  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
327cd94d69Schristos  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
337cd94d69Schristos  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
347cd94d69Schristos  */
357cd94d69Schristos 
367cd94d69Schristos /**
377cd94d69Schristos  * \file
387cd94d69Schristos  *
397cd94d69Schristos  * This file contains a module that uses the redis database to cache
407cd94d69Schristos  * dns responses.
417cd94d69Schristos  */
427cd94d69Schristos 
437cd94d69Schristos #include "config.h"
447cd94d69Schristos #ifdef USE_CACHEDB
457cd94d69Schristos #include "cachedb/redis.h"
467cd94d69Schristos #include "cachedb/cachedb.h"
477cd94d69Schristos #include "util/alloc.h"
487cd94d69Schristos #include "util/config_file.h"
497cd94d69Schristos #include "sldns/sbuffer.h"
507cd94d69Schristos 
517cd94d69Schristos #ifdef USE_REDIS
527cd94d69Schristos #include "hiredis/hiredis.h"
537cd94d69Schristos 
547cd94d69Schristos struct redis_moddata {
557cd94d69Schristos 	redisContext** ctxs;	/* thread-specific redis contexts */
567cd94d69Schristos 	int numctxs;		/* number of ctx entries */
577cd94d69Schristos 	const char* server_host; /* server's IP address or host name */
587cd94d69Schristos 	int server_port;	 /* server's TCP port */
59*91f7d55fSchristos 	const char* server_path; /* server's unix path, or "", NULL if unused */
60*91f7d55fSchristos 	const char* server_password; /* server's AUTH password, or "", NULL if unused */
617cd94d69Schristos 	struct timeval timeout;	 /* timeout for connection setup and commands */
62*91f7d55fSchristos 	int logical_db;		/* the redis logical database to use */
637cd94d69Schristos };
647cd94d69Schristos 
65d0eba39bSchristos static redisReply* redis_command(struct module_env*, struct cachedb_env*,
66d0eba39bSchristos 	const char*, const uint8_t*, size_t);
67d0eba39bSchristos 
68*91f7d55fSchristos static void
moddata_clean(struct redis_moddata ** moddata)69*91f7d55fSchristos moddata_clean(struct redis_moddata** moddata) {
70*91f7d55fSchristos 	if(!moddata || !*moddata)
71*91f7d55fSchristos 		return;
72*91f7d55fSchristos 	if((*moddata)->ctxs) {
73*91f7d55fSchristos 		int i;
74*91f7d55fSchristos 		for(i = 0; i < (*moddata)->numctxs; i++) {
75*91f7d55fSchristos 			if((*moddata)->ctxs[i])
76*91f7d55fSchristos 				redisFree((*moddata)->ctxs[i]);
77*91f7d55fSchristos 		}
78*91f7d55fSchristos 		free((*moddata)->ctxs);
79*91f7d55fSchristos 	}
80*91f7d55fSchristos 	free(*moddata);
81*91f7d55fSchristos 	*moddata = NULL;
82*91f7d55fSchristos }
83*91f7d55fSchristos 
847cd94d69Schristos static redisContext*
redis_connect(const struct redis_moddata * moddata)857cd94d69Schristos redis_connect(const struct redis_moddata* moddata)
867cd94d69Schristos {
877cd94d69Schristos 	redisContext* ctx;
887cd94d69Schristos 
89*91f7d55fSchristos 	if(moddata->server_path && moddata->server_path[0]!=0) {
90*91f7d55fSchristos 		ctx = redisConnectUnixWithTimeout(moddata->server_path,
91*91f7d55fSchristos 			moddata->timeout);
92*91f7d55fSchristos 	} else {
937cd94d69Schristos 		ctx = redisConnectWithTimeout(moddata->server_host,
947cd94d69Schristos 			moddata->server_port, moddata->timeout);
95*91f7d55fSchristos 	}
967cd94d69Schristos 	if(!ctx || ctx->err) {
977cd94d69Schristos 		const char *errstr = "out of memory";
987cd94d69Schristos 		if(ctx)
997cd94d69Schristos 			errstr = ctx->errstr;
1007cd94d69Schristos 		log_err("failed to connect to redis server: %s", errstr);
1017cd94d69Schristos 		goto fail;
1027cd94d69Schristos 	}
1037cd94d69Schristos 	if(redisSetTimeout(ctx, moddata->timeout) != REDIS_OK) {
1047cd94d69Schristos 		log_err("failed to set redis timeout");
1057cd94d69Schristos 		goto fail;
1067cd94d69Schristos 	}
107*91f7d55fSchristos 	if(moddata->server_password && moddata->server_password[0]!=0) {
108*91f7d55fSchristos 		redisReply* rep;
109*91f7d55fSchristos 		rep = redisCommand(ctx, "AUTH %s", moddata->server_password);
110*91f7d55fSchristos 		if(!rep || rep->type == REDIS_REPLY_ERROR) {
111*91f7d55fSchristos 			log_err("failed to authenticate with password");
112*91f7d55fSchristos 			freeReplyObject(rep);
113*91f7d55fSchristos 			goto fail;
114*91f7d55fSchristos 		}
115*91f7d55fSchristos 		freeReplyObject(rep);
116*91f7d55fSchristos 	}
117*91f7d55fSchristos 	if(moddata->logical_db > 0) {
118*91f7d55fSchristos 		redisReply* rep;
119*91f7d55fSchristos 		rep = redisCommand(ctx, "SELECT %d", moddata->logical_db);
120*91f7d55fSchristos 		if(!rep || rep->type == REDIS_REPLY_ERROR) {
121*91f7d55fSchristos 			log_err("failed to set logical database (%d)",
122*91f7d55fSchristos 				moddata->logical_db);
123*91f7d55fSchristos 			freeReplyObject(rep);
124*91f7d55fSchristos 			goto fail;
125*91f7d55fSchristos 		}
126*91f7d55fSchristos 		freeReplyObject(rep);
127*91f7d55fSchristos 	}
128*91f7d55fSchristos 	verbose(VERB_OPS, "Connection to Redis established");
1297cd94d69Schristos 	return ctx;
1307cd94d69Schristos 
1317cd94d69Schristos fail:
1327cd94d69Schristos 	if(ctx)
1337cd94d69Schristos 		redisFree(ctx);
1347cd94d69Schristos 	return NULL;
1357cd94d69Schristos }
1367cd94d69Schristos 
1377cd94d69Schristos static int
redis_init(struct module_env * env,struct cachedb_env * cachedb_env)1387cd94d69Schristos redis_init(struct module_env* env, struct cachedb_env* cachedb_env)
1397cd94d69Schristos {
1407cd94d69Schristos 	int i;
1417cd94d69Schristos 	struct redis_moddata* moddata = NULL;
1427cd94d69Schristos 
143*91f7d55fSchristos 	verbose(VERB_OPS, "Redis initialization");
1447cd94d69Schristos 
1457cd94d69Schristos 	moddata = calloc(1, sizeof(struct redis_moddata));
1467cd94d69Schristos 	if(!moddata) {
1477cd94d69Schristos 		log_err("out of memory");
148*91f7d55fSchristos 		goto fail;
1497cd94d69Schristos 	}
1507cd94d69Schristos 	moddata->numctxs = env->cfg->num_threads;
1517cd94d69Schristos 	moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
1527cd94d69Schristos 	if(!moddata->ctxs) {
1537cd94d69Schristos 		log_err("out of memory");
154*91f7d55fSchristos 		goto fail;
1557cd94d69Schristos 	}
1567cd94d69Schristos 	/* note: server_host is a shallow reference to configured string.
1577cd94d69Schristos 	 * we don't have to free it in this module. */
1587cd94d69Schristos 	moddata->server_host = env->cfg->redis_server_host;
1597cd94d69Schristos 	moddata->server_port = env->cfg->redis_server_port;
160*91f7d55fSchristos 	moddata->server_path = env->cfg->redis_server_path;
161*91f7d55fSchristos 	moddata->server_password = env->cfg->redis_server_password;
1627cd94d69Schristos 	moddata->timeout.tv_sec = env->cfg->redis_timeout / 1000;
1637cd94d69Schristos 	moddata->timeout.tv_usec = (env->cfg->redis_timeout % 1000) * 1000;
164*91f7d55fSchristos 	moddata->logical_db = env->cfg->redis_logical_db;
165*91f7d55fSchristos 	for(i = 0; i < moddata->numctxs; i++) {
166*91f7d55fSchristos 		redisContext* ctx = redis_connect(moddata);
167*91f7d55fSchristos 		if(!ctx) {
168*91f7d55fSchristos 			log_err("redis_init: failed to init redis");
169*91f7d55fSchristos 			goto fail;
170*91f7d55fSchristos 		}
171*91f7d55fSchristos 		moddata->ctxs[i] = ctx;
172*91f7d55fSchristos 	}
1737cd94d69Schristos 	cachedb_env->backend_data = moddata;
174d0eba39bSchristos 	if(env->cfg->redis_expire_records) {
175d0eba39bSchristos 		redisReply* rep = NULL;
176d0eba39bSchristos 		int redis_reply_type = 0;
177d0eba39bSchristos 		/** check if setex command is supported */
178d0eba39bSchristos 		rep = redis_command(env, cachedb_env,
179d0eba39bSchristos 			"SETEX __UNBOUND_REDIS_CHECK__ 1 none", NULL, 0);
180d0eba39bSchristos 		if(!rep) {
181d0eba39bSchristos 			/** init failed, no response from redis server*/
182d0eba39bSchristos 			log_err("redis_init: failed to init redis, the "
183d0eba39bSchristos 				"redis-expire-records option requires the SETEX command "
184d0eba39bSchristos 				"(redis >= 2.0.0)");
185*91f7d55fSchristos 			goto fail;
186d0eba39bSchristos 		}
187d0eba39bSchristos 		redis_reply_type = rep->type;
188d0eba39bSchristos 		freeReplyObject(rep);
189d0eba39bSchristos 		switch(redis_reply_type) {
190d0eba39bSchristos 		case REDIS_REPLY_STATUS:
191d0eba39bSchristos 			break;
192d0eba39bSchristos 		default:
193d0eba39bSchristos 			/** init failed, setex command not supported */
194d0eba39bSchristos 			log_err("redis_init: failed to init redis, the "
195d0eba39bSchristos 				"redis-expire-records option requires the SETEX command "
196d0eba39bSchristos 				"(redis >= 2.0.0)");
197*91f7d55fSchristos 			goto fail;
198d0eba39bSchristos 		}
199d0eba39bSchristos 	}
2007cd94d69Schristos 	return 1;
201*91f7d55fSchristos 
202*91f7d55fSchristos fail:
203*91f7d55fSchristos 	moddata_clean(&moddata);
204*91f7d55fSchristos 	return 0;
2057cd94d69Schristos }
2067cd94d69Schristos 
2077cd94d69Schristos static void
redis_deinit(struct module_env * env,struct cachedb_env * cachedb_env)2087cd94d69Schristos redis_deinit(struct module_env* env, struct cachedb_env* cachedb_env)
2097cd94d69Schristos {
2107cd94d69Schristos 	struct redis_moddata* moddata = (struct redis_moddata*)
2117cd94d69Schristos 		cachedb_env->backend_data;
2127cd94d69Schristos 	(void)env;
2137cd94d69Schristos 
214*91f7d55fSchristos 	verbose(VERB_OPS, "Redis deinitialization");
215*91f7d55fSchristos 	moddata_clean(&moddata);
2167cd94d69Schristos }
2177cd94d69Schristos 
2187cd94d69Schristos /*
2197cd94d69Schristos  * Send a redis command and get a reply.  Unified so that it can be used for
2207cd94d69Schristos  * both SET and GET.  If 'data' is non-NULL the command is supposed to be
2217cd94d69Schristos  * SET and GET otherwise, but the implementation of this function is agnostic
2227cd94d69Schristos  * about the semantics (except for logging): 'command', 'data', and 'data_len'
2237cd94d69Schristos  * are opaquely passed to redisCommand().
2247cd94d69Schristos  * This function first checks whether a connection with a redis server has
2257cd94d69Schristos  * been established; if not it tries to set up a new one.
2267cd94d69Schristos  * It returns redisReply returned from redisCommand() or NULL if some low
2277cd94d69Schristos  * level error happens.  The caller is responsible to check the return value,
2287cd94d69Schristos  * if it's non-NULL, it has to free it with freeReplyObject().
2297cd94d69Schristos  */
2307cd94d69Schristos static redisReply*
redis_command(struct module_env * env,struct cachedb_env * cachedb_env,const char * command,const uint8_t * data,size_t data_len)2317cd94d69Schristos redis_command(struct module_env* env, struct cachedb_env* cachedb_env,
2327cd94d69Schristos 	const char* command, const uint8_t* data, size_t data_len)
2337cd94d69Schristos {
2347cd94d69Schristos 	redisContext* ctx;
2357cd94d69Schristos 	redisReply* rep;
2367cd94d69Schristos 	struct redis_moddata* d = (struct redis_moddata*)
2377cd94d69Schristos 		cachedb_env->backend_data;
2387cd94d69Schristos 
2397cd94d69Schristos 	/* We assume env->alloc->thread_num is a unique ID for each thread
2407cd94d69Schristos 	 * in [0, num-of-threads).  We could treat it as an error condition
2417cd94d69Schristos 	 * if the assumption didn't hold, but it seems to be a fundamental
2427cd94d69Schristos 	 * assumption throughout the unbound architecture, so we simply assert
2437cd94d69Schristos 	 * it. */
2447cd94d69Schristos 	log_assert(env->alloc->thread_num < d->numctxs);
2457cd94d69Schristos 	ctx = d->ctxs[env->alloc->thread_num];
2467cd94d69Schristos 
2477cd94d69Schristos 	/* If we've not established a connection to the server or we've closed
2487cd94d69Schristos 	 * it on a failure, try to re-establish a new one.   Failures will be
2497cd94d69Schristos 	 * logged in redis_connect(). */
2507cd94d69Schristos 	if(!ctx) {
2517cd94d69Schristos 		ctx = redis_connect(d);
2527cd94d69Schristos 		d->ctxs[env->alloc->thread_num] = ctx;
2537cd94d69Schristos 	}
2547cd94d69Schristos 	if(!ctx)
2557cd94d69Schristos 		return NULL;
2567cd94d69Schristos 
2577cd94d69Schristos 	/* Send the command and get a reply, synchronously. */
2587cd94d69Schristos 	rep = (redisReply*)redisCommand(ctx, command, data, data_len);
2597cd94d69Schristos 	if(!rep) {
2607cd94d69Schristos 		/* Once an error as a NULL-reply is returned the context cannot
2617cd94d69Schristos 		 * be reused and we'll need to set up a new connection. */
2627cd94d69Schristos 		log_err("redis_command: failed to receive a reply, "
2637cd94d69Schristos 			"closing connection: %s", ctx->errstr);
2647cd94d69Schristos 		redisFree(ctx);
2657cd94d69Schristos 		d->ctxs[env->alloc->thread_num] = NULL;
2667cd94d69Schristos 		return NULL;
2677cd94d69Schristos 	}
2687cd94d69Schristos 
2697cd94d69Schristos 	/* Check error in reply to unify logging in that case.
2707cd94d69Schristos 	 * The caller may perform context-dependent checks and logging. */
2717cd94d69Schristos 	if(rep->type == REDIS_REPLY_ERROR)
2727cd94d69Schristos 		log_err("redis: %s resulted in an error: %s",
2737cd94d69Schristos 			data ? "set" : "get", rep->str);
2747cd94d69Schristos 
2757cd94d69Schristos 	return rep;
2767cd94d69Schristos }
2777cd94d69Schristos 
2787cd94d69Schristos static int
redis_lookup(struct module_env * env,struct cachedb_env * cachedb_env,char * key,struct sldns_buffer * result_buffer)2797cd94d69Schristos redis_lookup(struct module_env* env, struct cachedb_env* cachedb_env,
2807cd94d69Schristos 	char* key, struct sldns_buffer* result_buffer)
2817cd94d69Schristos {
2827cd94d69Schristos 	redisReply* rep;
2837cd94d69Schristos 	char cmdbuf[4+(CACHEDB_HASHSIZE/8)*2+1]; /* "GET " + key */
2847cd94d69Schristos 	int n;
2857cd94d69Schristos 	int ret = 0;
2867cd94d69Schristos 
2877cd94d69Schristos 	verbose(VERB_ALGO, "redis_lookup of %s", key);
2887cd94d69Schristos 
2897cd94d69Schristos 	n = snprintf(cmdbuf, sizeof(cmdbuf), "GET %s", key);
2907cd94d69Schristos 	if(n < 0 || n >= (int)sizeof(cmdbuf)) {
2917cd94d69Schristos 		log_err("redis_lookup: unexpected failure to build command");
2927cd94d69Schristos 		return 0;
2937cd94d69Schristos 	}
2947cd94d69Schristos 
2957cd94d69Schristos 	rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0);
2967cd94d69Schristos 	if(!rep)
2977cd94d69Schristos 		return 0;
2987cd94d69Schristos 	switch(rep->type) {
2997cd94d69Schristos 	case REDIS_REPLY_NIL:
3007cd94d69Schristos 		verbose(VERB_ALGO, "redis_lookup: no data cached");
3017cd94d69Schristos 		break;
3027cd94d69Schristos 	case REDIS_REPLY_STRING:
3037cd94d69Schristos 		verbose(VERB_ALGO, "redis_lookup found %d bytes",
3047cd94d69Schristos 			(int)rep->len);
3057cd94d69Schristos 		if((size_t)rep->len > sldns_buffer_capacity(result_buffer)) {
3067cd94d69Schristos 			log_err("redis_lookup: replied data too long: %lu",
3077cd94d69Schristos 				(size_t)rep->len);
3087cd94d69Schristos 			break;
3097cd94d69Schristos 		}
3107cd94d69Schristos 		sldns_buffer_clear(result_buffer);
3117cd94d69Schristos 		sldns_buffer_write(result_buffer, rep->str, rep->len);
3127cd94d69Schristos 		sldns_buffer_flip(result_buffer);
3137cd94d69Schristos 		ret = 1;
3147cd94d69Schristos 		break;
3157cd94d69Schristos 	case REDIS_REPLY_ERROR:
3167cd94d69Schristos 		break;		/* already logged */
3177cd94d69Schristos 	default:
3187cd94d69Schristos 		log_err("redis_lookup: unexpected type of reply for (%d)",
3197cd94d69Schristos 			rep->type);
3207cd94d69Schristos 		break;
3217cd94d69Schristos 	}
3227cd94d69Schristos 	freeReplyObject(rep);
3237cd94d69Schristos 	return ret;
3247cd94d69Schristos }
3257cd94d69Schristos 
3267cd94d69Schristos 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)3277cd94d69Schristos redis_store(struct module_env* env, struct cachedb_env* cachedb_env,
328d0eba39bSchristos 	char* key, uint8_t* data, size_t data_len, time_t ttl)
3297cd94d69Schristos {
3307cd94d69Schristos 	redisReply* rep;
3317cd94d69Schristos 	int n;
332d0eba39bSchristos 	int set_ttl = (env->cfg->redis_expire_records &&
333d0eba39bSchristos 		(!env->cfg->serve_expired || env->cfg->serve_expired_ttl > 0));
334d0eba39bSchristos 	/* Supported commands:
335d0eba39bSchristos 	 * - "SET " + key + " %b"
336d0eba39bSchristos 	 * - "SETEX " + key + " " + ttl + " %b"
337d0eba39bSchristos 	 */
338d0eba39bSchristos 	char cmdbuf[6+(CACHEDB_HASHSIZE/8)*2+11+3+1];
3397cd94d69Schristos 
340d0eba39bSchristos 	if (!set_ttl) {
3417cd94d69Schristos 		verbose(VERB_ALGO, "redis_store %s (%d bytes)", key, (int)data_len);
3427cd94d69Schristos 		/* build command to set to a binary safe string */
3437cd94d69Schristos 		n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b", key);
344d0eba39bSchristos 	} else {
345d0eba39bSchristos 		/* add expired ttl time to redis ttl to avoid premature eviction of key */
346d0eba39bSchristos 		ttl += env->cfg->serve_expired_ttl;
347d0eba39bSchristos 		verbose(VERB_ALGO, "redis_store %s (%d bytes) with ttl %u",
348d0eba39bSchristos 			key, (int)data_len, (uint32_t)ttl);
349d0eba39bSchristos 		/* build command to set to a binary safe string */
350d0eba39bSchristos 		n = snprintf(cmdbuf, sizeof(cmdbuf), "SETEX %s %u %%b", key,
351d0eba39bSchristos 			(uint32_t)ttl);
352d0eba39bSchristos 	}
353d0eba39bSchristos 
354d0eba39bSchristos 
3557cd94d69Schristos 	if(n < 0 || n >= (int)sizeof(cmdbuf)) {
3567cd94d69Schristos 		log_err("redis_store: unexpected failure to build command");
3577cd94d69Schristos 		return;
3587cd94d69Schristos 	}
3597cd94d69Schristos 
3607cd94d69Schristos 	rep = redis_command(env, cachedb_env, cmdbuf, data, data_len);
3617cd94d69Schristos 	if(rep) {
3627cd94d69Schristos 		verbose(VERB_ALGO, "redis_store set completed");
3637cd94d69Schristos 		if(rep->type != REDIS_REPLY_STATUS &&
3647cd94d69Schristos 			rep->type != REDIS_REPLY_ERROR) {
3657cd94d69Schristos 			log_err("redis_store: unexpected type of reply (%d)",
3667cd94d69Schristos 				rep->type);
3677cd94d69Schristos 		}
3687cd94d69Schristos 		freeReplyObject(rep);
3697cd94d69Schristos 	}
3707cd94d69Schristos }
3717cd94d69Schristos 
3727cd94d69Schristos struct cachedb_backend redis_backend = { "redis",
3737cd94d69Schristos 	redis_init, redis_deinit, redis_lookup, redis_store
3747cd94d69Schristos };
3757cd94d69Schristos #endif	/* USE_REDIS */
3767cd94d69Schristos #endif /* USE_CACHEDB */
377