10fb34990SDag-Erling Smørgrav /* 20fb34990SDag-Erling Smørgrav * cachedb/redis.c - cachedb redis module 30fb34990SDag-Erling Smørgrav * 40fb34990SDag-Erling Smørgrav * Copyright (c) 2018, NLnet Labs. All rights reserved. 50fb34990SDag-Erling Smørgrav * 60fb34990SDag-Erling Smørgrav * This software is open source. 70fb34990SDag-Erling Smørgrav * 80fb34990SDag-Erling Smørgrav * Redistribution and use in source and binary forms, with or without 90fb34990SDag-Erling Smørgrav * modification, are permitted provided that the following conditions 100fb34990SDag-Erling Smørgrav * are met: 110fb34990SDag-Erling Smørgrav * 120fb34990SDag-Erling Smørgrav * Redistributions of source code must retain the above copyright notice, 130fb34990SDag-Erling Smørgrav * this list of conditions and the following disclaimer. 140fb34990SDag-Erling Smørgrav * 150fb34990SDag-Erling Smørgrav * Redistributions in binary form must reproduce the above copyright notice, 160fb34990SDag-Erling Smørgrav * this list of conditions and the following disclaimer in the documentation 170fb34990SDag-Erling Smørgrav * and/or other materials provided with the distribution. 180fb34990SDag-Erling Smørgrav * 190fb34990SDag-Erling Smørgrav * Neither the name of the NLNET LABS nor the names of its contributors may 200fb34990SDag-Erling Smørgrav * be used to endorse or promote products derived from this software without 210fb34990SDag-Erling Smørgrav * specific prior written permission. 220fb34990SDag-Erling Smørgrav * 230fb34990SDag-Erling Smørgrav * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 240fb34990SDag-Erling Smørgrav * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 250fb34990SDag-Erling Smørgrav * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 260fb34990SDag-Erling Smørgrav * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 270fb34990SDag-Erling Smørgrav * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 280fb34990SDag-Erling Smørgrav * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 290fb34990SDag-Erling Smørgrav * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 300fb34990SDag-Erling Smørgrav * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 310fb34990SDag-Erling Smørgrav * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 320fb34990SDag-Erling Smørgrav * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 330fb34990SDag-Erling Smørgrav * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 340fb34990SDag-Erling Smørgrav */ 350fb34990SDag-Erling Smørgrav 360fb34990SDag-Erling Smørgrav /** 370fb34990SDag-Erling Smørgrav * \file 380fb34990SDag-Erling Smørgrav * 390fb34990SDag-Erling Smørgrav * This file contains a module that uses the redis database to cache 400fb34990SDag-Erling Smørgrav * dns responses. 410fb34990SDag-Erling Smørgrav */ 420fb34990SDag-Erling Smørgrav 430fb34990SDag-Erling Smørgrav #include "config.h" 440fb34990SDag-Erling Smørgrav #ifdef USE_CACHEDB 450fb34990SDag-Erling Smørgrav #include "cachedb/redis.h" 460fb34990SDag-Erling Smørgrav #include "cachedb/cachedb.h" 470fb34990SDag-Erling Smørgrav #include "util/alloc.h" 480fb34990SDag-Erling Smørgrav #include "util/config_file.h" 490fb34990SDag-Erling Smørgrav #include "sldns/sbuffer.h" 500fb34990SDag-Erling Smørgrav 510fb34990SDag-Erling Smørgrav #ifdef USE_REDIS 520fb34990SDag-Erling Smørgrav #include "hiredis/hiredis.h" 530fb34990SDag-Erling Smørgrav 540fb34990SDag-Erling Smørgrav struct redis_moddata { 55*be771a7bSCy Schubert /* thread-specific redis contexts */ 56*be771a7bSCy Schubert redisContext** ctxs; 57*be771a7bSCy Schubert redisContext** replica_ctxs; 58*be771a7bSCy Schubert /* number of ctx entries */ 59*be771a7bSCy Schubert int numctxs; 60*be771a7bSCy Schubert /* server's IP address or host name */ 61*be771a7bSCy Schubert const char* server_host; 62*be771a7bSCy Schubert const char* replica_server_host; 63*be771a7bSCy Schubert /* server's TCP port */ 64*be771a7bSCy Schubert int server_port; 65*be771a7bSCy Schubert int replica_server_port; 66*be771a7bSCy Schubert /* server's unix path, or "", NULL if unused */ 67*be771a7bSCy Schubert const char* server_path; 68*be771a7bSCy Schubert const char* replica_server_path; 69*be771a7bSCy Schubert /* server's AUTH password, or "", NULL if unused */ 70*be771a7bSCy Schubert const char* server_password; 71*be771a7bSCy Schubert const char* replica_server_password; 72*be771a7bSCy Schubert /* timeout for commands */ 73*be771a7bSCy Schubert struct timeval command_timeout; 74*be771a7bSCy Schubert struct timeval replica_command_timeout; 75*be771a7bSCy Schubert /* timeout for connection setup */ 76*be771a7bSCy Schubert struct timeval connect_timeout; 77*be771a7bSCy Schubert struct timeval replica_connect_timeout; 78*be771a7bSCy Schubert /* the redis logical database to use */ 79*be771a7bSCy Schubert int logical_db; 80*be771a7bSCy Schubert int replica_logical_db; 81*be771a7bSCy Schubert /* if the SET with EX command is supported */ 82*be771a7bSCy Schubert int set_with_ex_available; 830fb34990SDag-Erling Smørgrav }; 840fb34990SDag-Erling Smørgrav 8525039b37SCy Schubert static redisReply* redis_command(struct module_env*, struct cachedb_env*, 86*be771a7bSCy Schubert const char*, const uint8_t*, size_t, int); 8725039b37SCy Schubert 88103ba509SCy Schubert static void 89103ba509SCy Schubert moddata_clean(struct redis_moddata** moddata) { 90103ba509SCy Schubert if(!moddata || !*moddata) 91103ba509SCy Schubert return; 92103ba509SCy Schubert if((*moddata)->ctxs) { 93103ba509SCy Schubert int i; 94103ba509SCy Schubert for(i = 0; i < (*moddata)->numctxs; i++) { 95103ba509SCy Schubert if((*moddata)->ctxs[i]) 96103ba509SCy Schubert redisFree((*moddata)->ctxs[i]); 97103ba509SCy Schubert } 98103ba509SCy Schubert free((*moddata)->ctxs); 99103ba509SCy Schubert } 100*be771a7bSCy Schubert if((*moddata)->replica_ctxs) { 101*be771a7bSCy Schubert int i; 102*be771a7bSCy Schubert for(i = 0; i < (*moddata)->numctxs; i++) { 103*be771a7bSCy Schubert if((*moddata)->replica_ctxs[i]) 104*be771a7bSCy Schubert redisFree((*moddata)->replica_ctxs[i]); 105*be771a7bSCy Schubert } 106*be771a7bSCy Schubert free((*moddata)->replica_ctxs); 107*be771a7bSCy Schubert } 108103ba509SCy Schubert free(*moddata); 109103ba509SCy Schubert *moddata = NULL; 110103ba509SCy Schubert } 111103ba509SCy Schubert 1120fb34990SDag-Erling Smørgrav static redisContext* 113*be771a7bSCy Schubert redis_connect(const char* host, int port, const char* path, 114*be771a7bSCy Schubert const char* password, int logical_db, 115*be771a7bSCy Schubert const struct timeval connect_timeout, 116*be771a7bSCy Schubert const struct timeval command_timeout) 1170fb34990SDag-Erling Smørgrav { 1180fb34990SDag-Erling Smørgrav redisContext* ctx; 1190fb34990SDag-Erling Smørgrav 120*be771a7bSCy Schubert if(path && path[0]!=0) { 121*be771a7bSCy Schubert ctx = redisConnectUnixWithTimeout(path, connect_timeout); 1228f76bb7dSCy Schubert } else { 123*be771a7bSCy Schubert ctx = redisConnectWithTimeout(host, port, connect_timeout); 1248f76bb7dSCy Schubert } 1250fb34990SDag-Erling Smørgrav if(!ctx || ctx->err) { 1260fb34990SDag-Erling Smørgrav const char *errstr = "out of memory"; 1270fb34990SDag-Erling Smørgrav if(ctx) 1280fb34990SDag-Erling Smørgrav errstr = ctx->errstr; 1290fb34990SDag-Erling Smørgrav log_err("failed to connect to redis server: %s", errstr); 1300fb34990SDag-Erling Smørgrav goto fail; 1310fb34990SDag-Erling Smørgrav } 132*be771a7bSCy Schubert if(redisSetTimeout(ctx, command_timeout) != REDIS_OK) { 133*be771a7bSCy Schubert log_err("failed to set redis timeout, %s", ctx->errstr); 1340fb34990SDag-Erling Smørgrav goto fail; 1350fb34990SDag-Erling Smørgrav } 136*be771a7bSCy Schubert if(password && password[0]!=0) { 1378f76bb7dSCy Schubert redisReply* rep; 138*be771a7bSCy Schubert rep = redisCommand(ctx, "AUTH %s", password); 1398f76bb7dSCy Schubert if(!rep || rep->type == REDIS_REPLY_ERROR) { 1408f76bb7dSCy Schubert log_err("failed to authenticate with password"); 1418f76bb7dSCy Schubert freeReplyObject(rep); 1428f76bb7dSCy Schubert goto fail; 1438f76bb7dSCy Schubert } 1448f76bb7dSCy Schubert freeReplyObject(rep); 1458f76bb7dSCy Schubert } 146*be771a7bSCy Schubert if(logical_db > 0) { 147103ba509SCy Schubert redisReply* rep; 148*be771a7bSCy Schubert rep = redisCommand(ctx, "SELECT %d", logical_db); 149103ba509SCy Schubert if(!rep || rep->type == REDIS_REPLY_ERROR) { 150103ba509SCy Schubert log_err("failed to set logical database (%d)", 151*be771a7bSCy Schubert logical_db); 152103ba509SCy Schubert freeReplyObject(rep); 153103ba509SCy Schubert goto fail; 154103ba509SCy Schubert } 155103ba509SCy Schubert freeReplyObject(rep); 156103ba509SCy Schubert } 157*be771a7bSCy Schubert if(verbosity >= VERB_OPS) { 158*be771a7bSCy Schubert char port_str[6+1]; 159*be771a7bSCy Schubert port_str[0] = ' '; 160*be771a7bSCy Schubert (void)snprintf(port_str+1, sizeof(port_str)-1, "%d", port); 161*be771a7bSCy Schubert verbose(VERB_OPS, "Connection to Redis established (%s%s)", 162*be771a7bSCy Schubert path&&path[0]!=0?path:host, 163*be771a7bSCy Schubert path&&path[0]!=0?"":port_str); 164*be771a7bSCy Schubert } 1650fb34990SDag-Erling Smørgrav return ctx; 1660fb34990SDag-Erling Smørgrav 1670fb34990SDag-Erling Smørgrav fail: 1680fb34990SDag-Erling Smørgrav if(ctx) 1690fb34990SDag-Erling Smørgrav redisFree(ctx); 1700fb34990SDag-Erling Smørgrav return NULL; 1710fb34990SDag-Erling Smørgrav } 1720fb34990SDag-Erling Smørgrav 173*be771a7bSCy Schubert static void 174*be771a7bSCy Schubert set_timeout(struct timeval* timeout, int value, int explicit_value) 175*be771a7bSCy Schubert { 176*be771a7bSCy Schubert int v = explicit_value != 0 ? explicit_value : value; 177*be771a7bSCy Schubert timeout->tv_sec = v / 1000; 178*be771a7bSCy Schubert timeout->tv_usec = (v % 1000) * 1000; 179*be771a7bSCy Schubert } 180*be771a7bSCy Schubert 1810fb34990SDag-Erling Smørgrav static int 1820fb34990SDag-Erling Smørgrav redis_init(struct module_env* env, struct cachedb_env* cachedb_env) 1830fb34990SDag-Erling Smørgrav { 1840fb34990SDag-Erling Smørgrav int i; 1850fb34990SDag-Erling Smørgrav struct redis_moddata* moddata = NULL; 1860fb34990SDag-Erling Smørgrav 1878f76bb7dSCy Schubert verbose(VERB_OPS, "Redis initialization"); 1880fb34990SDag-Erling Smørgrav 1890fb34990SDag-Erling Smørgrav moddata = calloc(1, sizeof(struct redis_moddata)); 1900fb34990SDag-Erling Smørgrav if(!moddata) { 1910fb34990SDag-Erling Smørgrav log_err("out of memory"); 192103ba509SCy Schubert goto fail; 1930fb34990SDag-Erling Smørgrav } 1940fb34990SDag-Erling Smørgrav moddata->numctxs = env->cfg->num_threads; 195*be771a7bSCy Schubert /* note: server_host and similar string configuration options are 196*be771a7bSCy Schubert * shallow references to configured strings; we don't have to free them 197*be771a7bSCy Schubert * in this module. */ 198*be771a7bSCy Schubert moddata->server_host = env->cfg->redis_server_host; 199*be771a7bSCy Schubert moddata->replica_server_host = env->cfg->redis_replica_server_host; 200*be771a7bSCy Schubert 201*be771a7bSCy Schubert moddata->server_port = env->cfg->redis_server_port; 202*be771a7bSCy Schubert moddata->replica_server_port = env->cfg->redis_replica_server_port; 203*be771a7bSCy Schubert 204*be771a7bSCy Schubert moddata->server_path = env->cfg->redis_server_path; 205*be771a7bSCy Schubert moddata->replica_server_path = env->cfg->redis_replica_server_path; 206*be771a7bSCy Schubert 207*be771a7bSCy Schubert moddata->server_password = env->cfg->redis_server_password; 208*be771a7bSCy Schubert moddata->replica_server_password = env->cfg->redis_replica_server_password; 209*be771a7bSCy Schubert 210*be771a7bSCy Schubert set_timeout(&moddata->command_timeout, 211*be771a7bSCy Schubert env->cfg->redis_timeout, 212*be771a7bSCy Schubert env->cfg->redis_command_timeout); 213*be771a7bSCy Schubert set_timeout(&moddata->replica_command_timeout, 214*be771a7bSCy Schubert env->cfg->redis_replica_timeout, 215*be771a7bSCy Schubert env->cfg->redis_replica_command_timeout); 216*be771a7bSCy Schubert set_timeout(&moddata->connect_timeout, 217*be771a7bSCy Schubert env->cfg->redis_timeout, 218*be771a7bSCy Schubert env->cfg->redis_connect_timeout); 219*be771a7bSCy Schubert set_timeout(&moddata->replica_connect_timeout, 220*be771a7bSCy Schubert env->cfg->redis_replica_timeout, 221*be771a7bSCy Schubert env->cfg->redis_replica_connect_timeout); 222*be771a7bSCy Schubert 223*be771a7bSCy Schubert moddata->logical_db = env->cfg->redis_logical_db; 224*be771a7bSCy Schubert moddata->replica_logical_db = env->cfg->redis_replica_logical_db; 225*be771a7bSCy Schubert 2260fb34990SDag-Erling Smørgrav moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*)); 2270fb34990SDag-Erling Smørgrav if(!moddata->ctxs) { 2280fb34990SDag-Erling Smørgrav log_err("out of memory"); 229103ba509SCy Schubert goto fail; 2300fb34990SDag-Erling Smørgrav } 231*be771a7bSCy Schubert if((moddata->replica_server_host && moddata->replica_server_host[0]!=0) 232*be771a7bSCy Schubert || (moddata->replica_server_path && moddata->replica_server_path[0]!=0)) { 233*be771a7bSCy Schubert /* There is a replica configured, allocate ctxs */ 234*be771a7bSCy Schubert moddata->replica_ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*)); 235*be771a7bSCy Schubert if(!moddata->replica_ctxs) { 236*be771a7bSCy Schubert log_err("out of memory"); 237103ba509SCy Schubert goto fail; 238103ba509SCy Schubert } 239*be771a7bSCy Schubert } 240*be771a7bSCy Schubert for(i = 0; i < moddata->numctxs; i++) { 241*be771a7bSCy Schubert redisContext* ctx = redis_connect( 242*be771a7bSCy Schubert moddata->server_host, 243*be771a7bSCy Schubert moddata->server_port, 244*be771a7bSCy Schubert moddata->server_path, 245*be771a7bSCy Schubert moddata->server_password, 246*be771a7bSCy Schubert moddata->logical_db, 247*be771a7bSCy Schubert moddata->connect_timeout, 248*be771a7bSCy Schubert moddata->command_timeout); 249*be771a7bSCy Schubert if(!ctx) { 250*be771a7bSCy Schubert log_err("redis_init: failed to init redis " 251*be771a7bSCy Schubert "(for thread %d)", i); 252*be771a7bSCy Schubert /* And continue, the context can be established 253*be771a7bSCy Schubert * later, just like after a disconnect. */ 254*be771a7bSCy Schubert } 255103ba509SCy Schubert moddata->ctxs[i] = ctx; 256103ba509SCy Schubert } 257*be771a7bSCy Schubert if(moddata->replica_ctxs) { 258*be771a7bSCy Schubert for(i = 0; i < moddata->numctxs; i++) { 259*be771a7bSCy Schubert redisContext* ctx = redis_connect( 260*be771a7bSCy Schubert moddata->replica_server_host, 261*be771a7bSCy Schubert moddata->replica_server_port, 262*be771a7bSCy Schubert moddata->replica_server_path, 263*be771a7bSCy Schubert moddata->replica_server_password, 264*be771a7bSCy Schubert moddata->replica_logical_db, 265*be771a7bSCy Schubert moddata->replica_connect_timeout, 266*be771a7bSCy Schubert moddata->replica_command_timeout); 267*be771a7bSCy Schubert if(!ctx) { 268*be771a7bSCy Schubert log_err("redis_init: failed to init redis " 269*be771a7bSCy Schubert "replica (for thread %d)", i); 270*be771a7bSCy Schubert /* And continue, the context can be established 271*be771a7bSCy Schubert * later, just like after a disconnect. */ 272*be771a7bSCy Schubert } 273*be771a7bSCy Schubert moddata->replica_ctxs[i] = ctx; 274*be771a7bSCy Schubert } 275*be771a7bSCy Schubert } 2760fb34990SDag-Erling Smørgrav cachedb_env->backend_data = moddata; 277*be771a7bSCy Schubert if(env->cfg->redis_expire_records && 278*be771a7bSCy Schubert moddata->ctxs[env->alloc->thread_num] != NULL) { 27925039b37SCy Schubert redisReply* rep = NULL; 28025039b37SCy Schubert int redis_reply_type = 0; 281*be771a7bSCy Schubert /** check if set with ex command is supported */ 28225039b37SCy Schubert rep = redis_command(env, cachedb_env, 283*be771a7bSCy Schubert "SET __UNBOUND_REDIS_CHECK__ none EX 1", NULL, 0, 1); 28425039b37SCy Schubert if(!rep) { 28525039b37SCy Schubert /** init failed, no response from redis server*/ 286*be771a7bSCy Schubert goto set_with_ex_fail; 28725039b37SCy Schubert } 28825039b37SCy Schubert redis_reply_type = rep->type; 28925039b37SCy Schubert freeReplyObject(rep); 29025039b37SCy Schubert switch(redis_reply_type) { 29125039b37SCy Schubert case REDIS_REPLY_STATUS: 29225039b37SCy Schubert break; 29325039b37SCy Schubert default: 294*be771a7bSCy Schubert /** init failed, set_with_ex command not supported */ 295*be771a7bSCy Schubert goto set_with_ex_fail; 29625039b37SCy Schubert } 297*be771a7bSCy Schubert moddata->set_with_ex_available = 1; 29825039b37SCy Schubert } 2990fb34990SDag-Erling Smørgrav return 1; 300103ba509SCy Schubert 301*be771a7bSCy Schubert set_with_ex_fail: 302*be771a7bSCy Schubert log_err("redis_init: failure during redis_init, the " 303*be771a7bSCy Schubert "redis-expire-records option requires the SET with EX command " 304*be771a7bSCy Schubert "(redis >= 2.6.2)"); 305*be771a7bSCy Schubert return 1; 306103ba509SCy Schubert fail: 307103ba509SCy Schubert moddata_clean(&moddata); 308103ba509SCy Schubert return 0; 3090fb34990SDag-Erling Smørgrav } 3100fb34990SDag-Erling Smørgrav 3110fb34990SDag-Erling Smørgrav static void 3120fb34990SDag-Erling Smørgrav redis_deinit(struct module_env* env, struct cachedb_env* cachedb_env) 3130fb34990SDag-Erling Smørgrav { 3140fb34990SDag-Erling Smørgrav struct redis_moddata* moddata = (struct redis_moddata*) 3150fb34990SDag-Erling Smørgrav cachedb_env->backend_data; 3160fb34990SDag-Erling Smørgrav (void)env; 3170fb34990SDag-Erling Smørgrav 3188f76bb7dSCy Schubert verbose(VERB_OPS, "Redis deinitialization"); 319103ba509SCy Schubert moddata_clean(&moddata); 3200fb34990SDag-Erling Smørgrav } 3210fb34990SDag-Erling Smørgrav 3220fb34990SDag-Erling Smørgrav /* 3230fb34990SDag-Erling Smørgrav * Send a redis command and get a reply. Unified so that it can be used for 3240fb34990SDag-Erling Smørgrav * both SET and GET. If 'data' is non-NULL the command is supposed to be 3250fb34990SDag-Erling Smørgrav * SET and GET otherwise, but the implementation of this function is agnostic 3260fb34990SDag-Erling Smørgrav * about the semantics (except for logging): 'command', 'data', and 'data_len' 3270fb34990SDag-Erling Smørgrav * are opaquely passed to redisCommand(). 3280fb34990SDag-Erling Smørgrav * This function first checks whether a connection with a redis server has 3290fb34990SDag-Erling Smørgrav * been established; if not it tries to set up a new one. 3300fb34990SDag-Erling Smørgrav * It returns redisReply returned from redisCommand() or NULL if some low 3310fb34990SDag-Erling Smørgrav * level error happens. The caller is responsible to check the return value, 3320fb34990SDag-Erling Smørgrav * if it's non-NULL, it has to free it with freeReplyObject(). 3330fb34990SDag-Erling Smørgrav */ 3340fb34990SDag-Erling Smørgrav static redisReply* 3350fb34990SDag-Erling Smørgrav redis_command(struct module_env* env, struct cachedb_env* cachedb_env, 336*be771a7bSCy Schubert const char* command, const uint8_t* data, size_t data_len, int write) 3370fb34990SDag-Erling Smørgrav { 338*be771a7bSCy Schubert redisContext* ctx, **ctx_selector; 3390fb34990SDag-Erling Smørgrav redisReply* rep; 3400fb34990SDag-Erling Smørgrav struct redis_moddata* d = (struct redis_moddata*) 3410fb34990SDag-Erling Smørgrav cachedb_env->backend_data; 3420fb34990SDag-Erling Smørgrav 3430fb34990SDag-Erling Smørgrav /* We assume env->alloc->thread_num is a unique ID for each thread 3440fb34990SDag-Erling Smørgrav * in [0, num-of-threads). We could treat it as an error condition 3450fb34990SDag-Erling Smørgrav * if the assumption didn't hold, but it seems to be a fundamental 3460fb34990SDag-Erling Smørgrav * assumption throughout the unbound architecture, so we simply assert 3470fb34990SDag-Erling Smørgrav * it. */ 3480fb34990SDag-Erling Smørgrav log_assert(env->alloc->thread_num < d->numctxs); 349*be771a7bSCy Schubert 350*be771a7bSCy Schubert ctx_selector = !write && d->replica_ctxs 351*be771a7bSCy Schubert ?d->replica_ctxs 352*be771a7bSCy Schubert :d->ctxs; 353*be771a7bSCy Schubert ctx = ctx_selector[env->alloc->thread_num]; 3540fb34990SDag-Erling Smørgrav 3550fb34990SDag-Erling Smørgrav /* If we've not established a connection to the server or we've closed 3560fb34990SDag-Erling Smørgrav * it on a failure, try to re-establish a new one. Failures will be 3570fb34990SDag-Erling Smørgrav * logged in redis_connect(). */ 3580fb34990SDag-Erling Smørgrav if(!ctx) { 359*be771a7bSCy Schubert if(!write && d->replica_ctxs) { 360*be771a7bSCy Schubert ctx = redis_connect( 361*be771a7bSCy Schubert d->replica_server_host, 362*be771a7bSCy Schubert d->replica_server_port, 363*be771a7bSCy Schubert d->replica_server_path, 364*be771a7bSCy Schubert d->replica_server_password, 365*be771a7bSCy Schubert d->replica_logical_db, 366*be771a7bSCy Schubert d->replica_connect_timeout, 367*be771a7bSCy Schubert d->replica_command_timeout); 368*be771a7bSCy Schubert } else { 369*be771a7bSCy Schubert ctx = redis_connect( 370*be771a7bSCy Schubert d->server_host, 371*be771a7bSCy Schubert d->server_port, 372*be771a7bSCy Schubert d->server_path, 373*be771a7bSCy Schubert d->server_password, 374*be771a7bSCy Schubert d->logical_db, 375*be771a7bSCy Schubert d->connect_timeout, 376*be771a7bSCy Schubert d->command_timeout); 3770fb34990SDag-Erling Smørgrav } 378*be771a7bSCy Schubert ctx_selector[env->alloc->thread_num] = ctx; 379*be771a7bSCy Schubert } 380*be771a7bSCy Schubert if(!ctx) return NULL; 3810fb34990SDag-Erling Smørgrav 3820fb34990SDag-Erling Smørgrav /* Send the command and get a reply, synchronously. */ 3830fb34990SDag-Erling Smørgrav rep = (redisReply*)redisCommand(ctx, command, data, data_len); 3840fb34990SDag-Erling Smørgrav if(!rep) { 3850fb34990SDag-Erling Smørgrav /* Once an error as a NULL-reply is returned the context cannot 3860fb34990SDag-Erling Smørgrav * be reused and we'll need to set up a new connection. */ 3870fb34990SDag-Erling Smørgrav log_err("redis_command: failed to receive a reply, " 3880fb34990SDag-Erling Smørgrav "closing connection: %s", ctx->errstr); 3890fb34990SDag-Erling Smørgrav redisFree(ctx); 390*be771a7bSCy Schubert ctx_selector[env->alloc->thread_num] = NULL; 3910fb34990SDag-Erling Smørgrav return NULL; 3920fb34990SDag-Erling Smørgrav } 3930fb34990SDag-Erling Smørgrav 3940fb34990SDag-Erling Smørgrav /* Check error in reply to unify logging in that case. 3950fb34990SDag-Erling Smørgrav * The caller may perform context-dependent checks and logging. */ 3960fb34990SDag-Erling Smørgrav if(rep->type == REDIS_REPLY_ERROR) 3970fb34990SDag-Erling Smørgrav log_err("redis: %s resulted in an error: %s", 3980fb34990SDag-Erling Smørgrav data ? "set" : "get", rep->str); 3990fb34990SDag-Erling Smørgrav 4000fb34990SDag-Erling Smørgrav return rep; 4010fb34990SDag-Erling Smørgrav } 4020fb34990SDag-Erling Smørgrav 4030fb34990SDag-Erling Smørgrav static int 4040fb34990SDag-Erling Smørgrav redis_lookup(struct module_env* env, struct cachedb_env* cachedb_env, 4050fb34990SDag-Erling Smørgrav char* key, struct sldns_buffer* result_buffer) 4060fb34990SDag-Erling Smørgrav { 4070fb34990SDag-Erling Smørgrav redisReply* rep; 4080fb34990SDag-Erling Smørgrav char cmdbuf[4+(CACHEDB_HASHSIZE/8)*2+1]; /* "GET " + key */ 4090fb34990SDag-Erling Smørgrav int n; 4100fb34990SDag-Erling Smørgrav int ret = 0; 4110fb34990SDag-Erling Smørgrav 4120fb34990SDag-Erling Smørgrav verbose(VERB_ALGO, "redis_lookup of %s", key); 4130fb34990SDag-Erling Smørgrav 4140fb34990SDag-Erling Smørgrav n = snprintf(cmdbuf, sizeof(cmdbuf), "GET %s", key); 4150fb34990SDag-Erling Smørgrav if(n < 0 || n >= (int)sizeof(cmdbuf)) { 4160fb34990SDag-Erling Smørgrav log_err("redis_lookup: unexpected failure to build command"); 4170fb34990SDag-Erling Smørgrav return 0; 4180fb34990SDag-Erling Smørgrav } 4190fb34990SDag-Erling Smørgrav 420*be771a7bSCy Schubert rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0, 0); 4210fb34990SDag-Erling Smørgrav if(!rep) 4220fb34990SDag-Erling Smørgrav return 0; 4230fb34990SDag-Erling Smørgrav switch(rep->type) { 4240fb34990SDag-Erling Smørgrav case REDIS_REPLY_NIL: 4250fb34990SDag-Erling Smørgrav verbose(VERB_ALGO, "redis_lookup: no data cached"); 4260fb34990SDag-Erling Smørgrav break; 4270fb34990SDag-Erling Smørgrav case REDIS_REPLY_STRING: 4280fb34990SDag-Erling Smørgrav verbose(VERB_ALGO, "redis_lookup found %d bytes", 4290fb34990SDag-Erling Smørgrav (int)rep->len); 4300fb34990SDag-Erling Smørgrav if((size_t)rep->len > sldns_buffer_capacity(result_buffer)) { 4310fb34990SDag-Erling Smørgrav log_err("redis_lookup: replied data too long: %lu", 4320fb34990SDag-Erling Smørgrav (size_t)rep->len); 4330fb34990SDag-Erling Smørgrav break; 4340fb34990SDag-Erling Smørgrav } 4350fb34990SDag-Erling Smørgrav sldns_buffer_clear(result_buffer); 4360fb34990SDag-Erling Smørgrav sldns_buffer_write(result_buffer, rep->str, rep->len); 4370fb34990SDag-Erling Smørgrav sldns_buffer_flip(result_buffer); 4380fb34990SDag-Erling Smørgrav ret = 1; 4390fb34990SDag-Erling Smørgrav break; 4400fb34990SDag-Erling Smørgrav case REDIS_REPLY_ERROR: 4410fb34990SDag-Erling Smørgrav break; /* already logged */ 4420fb34990SDag-Erling Smørgrav default: 4430fb34990SDag-Erling Smørgrav log_err("redis_lookup: unexpected type of reply for (%d)", 4440fb34990SDag-Erling Smørgrav rep->type); 4450fb34990SDag-Erling Smørgrav break; 4460fb34990SDag-Erling Smørgrav } 4470fb34990SDag-Erling Smørgrav freeReplyObject(rep); 4480fb34990SDag-Erling Smørgrav return ret; 4490fb34990SDag-Erling Smørgrav } 4500fb34990SDag-Erling Smørgrav 4510fb34990SDag-Erling Smørgrav static void 4520fb34990SDag-Erling Smørgrav redis_store(struct module_env* env, struct cachedb_env* cachedb_env, 45325039b37SCy Schubert char* key, uint8_t* data, size_t data_len, time_t ttl) 4540fb34990SDag-Erling Smørgrav { 4550fb34990SDag-Erling Smørgrav redisReply* rep; 4560fb34990SDag-Erling Smørgrav int n; 457*be771a7bSCy Schubert struct redis_moddata* moddata = (struct redis_moddata*) 458*be771a7bSCy Schubert cachedb_env->backend_data; 459*be771a7bSCy Schubert int set_ttl = (moddata->set_with_ex_available && 460*be771a7bSCy Schubert env->cfg->redis_expire_records && 46125039b37SCy Schubert (!env->cfg->serve_expired || env->cfg->serve_expired_ttl > 0)); 46225039b37SCy Schubert /* Supported commands: 46325039b37SCy Schubert * - "SET " + key + " %b" 464*be771a7bSCy Schubert * - "SET " + key + " %b EX " + ttl 465*be771a7bSCy Schubert * older redis 2.0.0 was "SETEX " + key + " " + ttl + " %b" 466*be771a7bSCy Schubert * - "EXPIRE " + key + " 0" 46725039b37SCy Schubert */ 46825039b37SCy Schubert char cmdbuf[6+(CACHEDB_HASHSIZE/8)*2+11+3+1]; 4690fb34990SDag-Erling Smørgrav 47025039b37SCy Schubert if (!set_ttl) { 4710fb34990SDag-Erling Smørgrav verbose(VERB_ALGO, "redis_store %s (%d bytes)", key, (int)data_len); 4720fb34990SDag-Erling Smørgrav /* build command to set to a binary safe string */ 4730fb34990SDag-Erling Smørgrav n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b", key); 474*be771a7bSCy Schubert } else if(ttl == 0) { 475*be771a7bSCy Schubert /* use the EXPIRE command, SET with EX 0 is an invalid time. */ 476*be771a7bSCy Schubert /* Replies with REDIS_REPLY_INTEGER of 1. */ 477*be771a7bSCy Schubert verbose(VERB_ALGO, "redis_store expire %s (%d bytes)", 478*be771a7bSCy Schubert key, (int)data_len); 479*be771a7bSCy Schubert n = snprintf(cmdbuf, sizeof(cmdbuf), "EXPIRE %s 0", key); 480*be771a7bSCy Schubert data = NULL; 481*be771a7bSCy Schubert data_len = 0; 48225039b37SCy Schubert } else { 48325039b37SCy Schubert /* add expired ttl time to redis ttl to avoid premature eviction of key */ 48425039b37SCy Schubert ttl += env->cfg->serve_expired_ttl; 48525039b37SCy Schubert verbose(VERB_ALGO, "redis_store %s (%d bytes) with ttl %u", 486*be771a7bSCy Schubert key, (int)data_len, (unsigned)(uint32_t)ttl); 48725039b37SCy Schubert /* build command to set to a binary safe string */ 488*be771a7bSCy Schubert n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b EX %u", key, 489*be771a7bSCy Schubert (unsigned)(uint32_t)ttl); 49025039b37SCy Schubert } 49125039b37SCy Schubert 49225039b37SCy Schubert 4930fb34990SDag-Erling Smørgrav if(n < 0 || n >= (int)sizeof(cmdbuf)) { 4940fb34990SDag-Erling Smørgrav log_err("redis_store: unexpected failure to build command"); 4950fb34990SDag-Erling Smørgrav return; 4960fb34990SDag-Erling Smørgrav } 4970fb34990SDag-Erling Smørgrav 498*be771a7bSCy Schubert rep = redis_command(env, cachedb_env, cmdbuf, data, data_len, 1); 4990fb34990SDag-Erling Smørgrav if(rep) { 5000fb34990SDag-Erling Smørgrav verbose(VERB_ALGO, "redis_store set completed"); 5010fb34990SDag-Erling Smørgrav if(rep->type != REDIS_REPLY_STATUS && 502*be771a7bSCy Schubert rep->type != REDIS_REPLY_ERROR && 503*be771a7bSCy Schubert rep->type != REDIS_REPLY_INTEGER) { 5040fb34990SDag-Erling Smørgrav log_err("redis_store: unexpected type of reply (%d)", 5050fb34990SDag-Erling Smørgrav rep->type); 5060fb34990SDag-Erling Smørgrav } 5070fb34990SDag-Erling Smørgrav freeReplyObject(rep); 5080fb34990SDag-Erling Smørgrav } 5090fb34990SDag-Erling Smørgrav } 5100fb34990SDag-Erling Smørgrav 5110fb34990SDag-Erling Smørgrav struct cachedb_backend redis_backend = { "redis", 5120fb34990SDag-Erling Smørgrav redis_init, redis_deinit, redis_lookup, redis_store 5130fb34990SDag-Erling Smørgrav }; 5140fb34990SDag-Erling Smørgrav #endif /* USE_REDIS */ 5150fb34990SDag-Erling Smørgrav #endif /* USE_CACHEDB */ 516