2 * cachedb/redis.c - cachedb redis module
4 * Copyright (c) 2018, NLnet Labs. All rights reserved.
6 * This software is open source.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
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.
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.
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.
39 * This file contains a module that uses the redis database to cache
45 #include "cachedb/redis.h"
46 #include "cachedb/cachedb.h"
47 #include "util/alloc.h"
48 #include "util/config_file.h"
49 #include "sldns/sbuffer.h"
52 #include "hiredis/hiredis.h"
54 struct redis_moddata {
55 redisContext** ctxs; /* thread-specific redis contexts */
56 int numctxs; /* number of ctx entries */
57 const char* server_host; /* server's IP address or host name */
58 int server_port; /* server's TCP port */
59 struct timeval timeout; /* timeout for connection setup and commands */
63 redis_connect(const struct redis_moddata* moddata)
67 ctx = redisConnectWithTimeout(moddata->server_host,
68 moddata->server_port, moddata->timeout);
69 if(!ctx || ctx->err) {
70 const char *errstr = "out of memory";
73 log_err("failed to connect to redis server: %s", errstr);
76 if(redisSetTimeout(ctx, moddata->timeout) != REDIS_OK) {
77 log_err("failed to set redis timeout");
89 redis_init(struct module_env* env, struct cachedb_env* cachedb_env)
92 struct redis_moddata* moddata = NULL;
94 verbose(VERB_ALGO, "redis_init");
96 moddata = calloc(1, sizeof(struct redis_moddata));
98 log_err("out of memory");
101 moddata->numctxs = env->cfg->num_threads;
102 moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
104 log_err("out of memory");
108 /* note: server_host is a shallow reference to configured string.
109 * we don't have to free it in this module. */
110 moddata->server_host = env->cfg->redis_server_host;
111 moddata->server_port = env->cfg->redis_server_port;
112 moddata->timeout.tv_sec = env->cfg->redis_timeout / 1000;
113 moddata->timeout.tv_usec = (env->cfg->redis_timeout % 1000) * 1000;
114 for(i = 0; i < moddata->numctxs; i++)
115 moddata->ctxs[i] = redis_connect(moddata);
116 cachedb_env->backend_data = moddata;
121 redis_deinit(struct module_env* env, struct cachedb_env* cachedb_env)
123 struct redis_moddata* moddata = (struct redis_moddata*)
124 cachedb_env->backend_data;
127 verbose(VERB_ALGO, "redis_deinit");
133 for(i = 0; i < moddata->numctxs; i++) {
135 redisFree(moddata->ctxs[i]);
143 * Send a redis command and get a reply. Unified so that it can be used for
144 * both SET and GET. If 'data' is non-NULL the command is supposed to be
145 * SET and GET otherwise, but the implementation of this function is agnostic
146 * about the semantics (except for logging): 'command', 'data', and 'data_len'
147 * are opaquely passed to redisCommand().
148 * This function first checks whether a connection with a redis server has
149 * been established; if not it tries to set up a new one.
150 * It returns redisReply returned from redisCommand() or NULL if some low
151 * level error happens. The caller is responsible to check the return value,
152 * if it's non-NULL, it has to free it with freeReplyObject().
155 redis_command(struct module_env* env, struct cachedb_env* cachedb_env,
156 const char* command, const uint8_t* data, size_t data_len)
160 struct redis_moddata* d = (struct redis_moddata*)
161 cachedb_env->backend_data;
163 /* We assume env->alloc->thread_num is a unique ID for each thread
164 * in [0, num-of-threads). We could treat it as an error condition
165 * if the assumption didn't hold, but it seems to be a fundamental
166 * assumption throughout the unbound architecture, so we simply assert
168 log_assert(env->alloc->thread_num < d->numctxs);
169 ctx = d->ctxs[env->alloc->thread_num];
171 /* If we've not established a connection to the server or we've closed
172 * it on a failure, try to re-establish a new one. Failures will be
173 * logged in redis_connect(). */
175 ctx = redis_connect(d);
176 d->ctxs[env->alloc->thread_num] = ctx;
181 /* Send the command and get a reply, synchronously. */
182 rep = (redisReply*)redisCommand(ctx, command, data, data_len);
184 /* Once an error as a NULL-reply is returned the context cannot
185 * be reused and we'll need to set up a new connection. */
186 log_err("redis_command: failed to receive a reply, "
187 "closing connection: %s", ctx->errstr);
189 d->ctxs[env->alloc->thread_num] = NULL;
193 /* Check error in reply to unify logging in that case.
194 * The caller may perform context-dependent checks and logging. */
195 if(rep->type == REDIS_REPLY_ERROR)
196 log_err("redis: %s resulted in an error: %s",
197 data ? "set" : "get", rep->str);
203 redis_lookup(struct module_env* env, struct cachedb_env* cachedb_env,
204 char* key, struct sldns_buffer* result_buffer)
207 char cmdbuf[4+(CACHEDB_HASHSIZE/8)*2+1]; /* "GET " + key */
211 verbose(VERB_ALGO, "redis_lookup of %s", key);
213 n = snprintf(cmdbuf, sizeof(cmdbuf), "GET %s", key);
214 if(n < 0 || n >= (int)sizeof(cmdbuf)) {
215 log_err("redis_lookup: unexpected failure to build command");
219 rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0);
223 case REDIS_REPLY_NIL:
224 verbose(VERB_ALGO, "redis_lookup: no data cached");
226 case REDIS_REPLY_STRING:
227 verbose(VERB_ALGO, "redis_lookup found %d bytes",
229 if((size_t)rep->len > sldns_buffer_capacity(result_buffer)) {
230 log_err("redis_lookup: replied data too long: %lu",
234 sldns_buffer_clear(result_buffer);
235 sldns_buffer_write(result_buffer, rep->str, rep->len);
236 sldns_buffer_flip(result_buffer);
239 case REDIS_REPLY_ERROR:
240 break; /* already logged */
242 log_err("redis_lookup: unexpected type of reply for (%d)",
246 freeReplyObject(rep);
251 redis_store(struct module_env* env, struct cachedb_env* cachedb_env,
252 char* key, uint8_t* data, size_t data_len)
255 char cmdbuf[4+(CACHEDB_HASHSIZE/8)*2+3+1]; /* "SET " + key + " %b" */
258 verbose(VERB_ALGO, "redis_store %s (%d bytes)", key, (int)data_len);
260 /* build command to set to a binary safe string */
261 n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b", key);
262 if(n < 0 || n >= (int)sizeof(cmdbuf)) {
263 log_err("redis_store: unexpected failure to build command");
267 rep = redis_command(env, cachedb_env, cmdbuf, data, data_len);
269 verbose(VERB_ALGO, "redis_store set completed");
270 if(rep->type != REDIS_REPLY_STATUS &&
271 rep->type != REDIS_REPLY_ERROR) {
272 log_err("redis_store: unexpected type of reply (%d)",
275 freeReplyObject(rep);
279 struct cachedb_backend redis_backend = { "redis",
280 redis_init, redis_deinit, redis_lookup, redis_store
282 #endif /* USE_REDIS */
283 #endif /* USE_CACHEDB */