From: George Thessalonikefs Date: Wed, 11 Oct 2023 09:44:55 +0000 (+0200) Subject: - Fix #850: [FR] Ability to use specific database in Redis, with new X-Git-Tag: release-1.19.0rc1~33 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e98b89651e2b87f8bbf8aa061133c6748ecb02a3;p=thirdparty%2Funbound.git - Fix #850: [FR] Ability to use specific database in Redis, with new redis-logical-db configuration option. --- diff --git a/cachedb/redis.c b/cachedb/redis.c index 93a575a4c..6cc975901 100644 --- a/cachedb/redis.c +++ b/cachedb/redis.c @@ -59,11 +59,28 @@ struct redis_moddata { const char* server_path; /* server's unix path, or "", NULL if unused */ const char* server_password; /* server's AUTH password, or "", NULL if unused */ struct timeval timeout; /* timeout for connection setup and commands */ + int logical_db; /* the redis logical database to use */ }; static redisReply* redis_command(struct module_env*, struct cachedb_env*, const char*, const uint8_t*, size_t); +static void +moddata_clean(struct redis_moddata** moddata) { + if(!moddata || !*moddata) + return; + if((*moddata)->ctxs) { + int i; + for(i = 0; i < (*moddata)->numctxs; i++) { + if((*moddata)->ctxs[i]) + redisFree((*moddata)->ctxs[i]); + } + free((*moddata)->ctxs); + } + free(*moddata); + *moddata = NULL; +} + static redisContext* redis_connect(const struct redis_moddata* moddata) { @@ -97,10 +114,21 @@ redis_connect(const struct redis_moddata* moddata) } freeReplyObject(rep); } + if(moddata->logical_db > 0) { + redisReply* rep; + rep = redisCommand(ctx, "SELECT %d", moddata->logical_db); + if(!rep || rep->type == REDIS_REPLY_ERROR) { + log_err("failed to set logical database (%d)", + moddata->logical_db); + freeReplyObject(rep); + goto fail; + } + freeReplyObject(rep); + } verbose(VERB_OPS, "Connection to Redis established"); return ctx; - fail: +fail: if(ctx) redisFree(ctx); return NULL; @@ -117,14 +145,13 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env) moddata = calloc(1, sizeof(struct redis_moddata)); if(!moddata) { log_err("out of memory"); - return 0; + goto fail; } moddata->numctxs = env->cfg->num_threads; moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*)); if(!moddata->ctxs) { log_err("out of memory"); - free(moddata); - return 0; + goto fail; } /* note: server_host is a shallow reference to configured string. * we don't have to free it in this module. */ @@ -134,8 +161,15 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env) moddata->server_password = env->cfg->redis_server_password; moddata->timeout.tv_sec = env->cfg->redis_timeout / 1000; moddata->timeout.tv_usec = (env->cfg->redis_timeout % 1000) * 1000; - for(i = 0; i < moddata->numctxs; i++) - moddata->ctxs[i] = redis_connect(moddata); + moddata->logical_db = env->cfg->redis_logical_db; + for(i = 0; i < moddata->numctxs; i++) { + redisContext* ctx = redis_connect(moddata); + if(!ctx) { + log_err("redis_init: failed to init redis"); + goto fail; + } + moddata->ctxs[i] = ctx; + } cachedb_env->backend_data = moddata; if(env->cfg->redis_expire_records) { redisReply* rep = NULL; @@ -148,7 +182,7 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env) log_err("redis_init: failed to init redis, the " "redis-expire-records option requires the SETEX command " "(redis >= 2.0.0)"); - return 0; + goto fail; } redis_reply_type = rep->type; freeReplyObject(rep); @@ -160,11 +194,14 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env) log_err("redis_init: failed to init redis, the " "redis-expire-records option requires the SETEX command " "(redis >= 2.0.0)"); - return 0; + goto fail; } } - return 1; + +fail: + moddata_clean(&moddata); + return 0; } static void @@ -175,18 +212,7 @@ redis_deinit(struct module_env* env, struct cachedb_env* cachedb_env) (void)env; verbose(VERB_OPS, "Redis deinitialization"); - - if(!moddata) - return; - if(moddata->ctxs) { - int i; - for(i = 0; i < moddata->numctxs; i++) { - if(moddata->ctxs[i]) - redisFree(moddata->ctxs[i]); - } - free(moddata->ctxs); - } - free(moddata); + moddata_clean(&moddata); } /* diff --git a/doc/Changelog b/doc/Changelog index a943cce57..40da8e55c 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +11 October 2023: George + - Fix #850: [FR] Ability to use specific database in Redis, with new + redis-logical-db configuration option. + 10 October 2023: George - Fix infinite loop when reading multiple lines of input on a broken remote control socket. Addesses #947 and #948. diff --git a/doc/example.conf.in b/doc/example.conf.in index 0980212e1..ca8f929e4 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -1236,6 +1236,8 @@ remote-control: # redis-timeout: 100 # # set timeout on redis records based on DNS response TTL # redis-expire-records: no +# # redis logical database to use, 0 is the default database. +# redis-logical-db: 0 # IPSet # Add specify domain into set via ipset. diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 84b903f49..2992e0a4a 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -2707,6 +2707,17 @@ Unbound is configured with \fBserve-expired\fR and \fBserve-expired-ttl\fR is 0, this option is internally reverted to "no". Redis SETEX support is required for this option (Redis >= 2.0.0). This option defaults to no. +.TP +.B redis-logical-db: \fI +The logical database in Redis to use. +These are databases in the same Redis instance sharing the same configuration +and persisted in the same RDB/AOF file. +If unsure about using this option, Redis documentation +(https://redis.io/commands/select/) suggests not to use a single Redis instance +for multiple unrelated applications. +The default database in Redis is 0 while other logical databases need to be +explicitly SELECT'ed upon connecting. +This option defaults to 0. .SS DNSTAP Logging Options DNSTAP support, when compiled in by using \fB\-\-enable\-dnstap\fR, is enabled in the \fBdnstap:\fR section. diff --git a/util/config_file.c b/util/config_file.c index 454096342..80003e632 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -388,6 +388,7 @@ config_create(void) cfg->redis_timeout = 100; cfg->redis_server_port = 6379; cfg->redis_expire_records = 0; + cfg->redis_logical_db = 0; #endif /* USE_REDIS */ #endif /* USE_CACHEDB */ #ifdef USE_IPSET @@ -1313,6 +1314,7 @@ config_get_option(struct config_file* cfg, const char* opt, else O_STR(opt, "redis-server-password", redis_server_password) else O_DEC(opt, "redis-timeout", redis_timeout) else O_YNO(opt, "redis-expire-records", redis_expire_records) + else O_DEC(opt, "redis-logical-db", redis_logical_db) #endif /* USE_REDIS */ #endif /* USE_CACHEDB */ #ifdef USE_IPSET diff --git a/util/config_file.h b/util/config_file.h index 452f3c6a7..588012f82 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -712,6 +712,8 @@ struct config_file { int redis_timeout; /** set timeout on redis records based on DNS response ttl */ int redis_expire_records; + /** set the redis logical database upon connection */ + int redis_logical_db; #endif #endif /** Downstream DNS Cookies */ diff --git a/util/configlexer.lex b/util/configlexer.lex index 3fcdfa62e..142810a2f 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -563,6 +563,7 @@ redis-server-path{COLON} { YDVAR(1, VAR_CACHEDB_REDISPATH) } redis-server-password{COLON} { YDVAR(1, VAR_CACHEDB_REDISPASSWORD) } redis-timeout{COLON} { YDVAR(1, VAR_CACHEDB_REDISTIMEOUT) } redis-expire-records{COLON} { YDVAR(1, VAR_CACHEDB_REDISEXPIRERECORDS) } +redis-logical-db{COLON} { YDVAR(1, VAR_CACHEDB_REDISLOGICALDB) } ipset{COLON} { YDVAR(0, VAR_IPSET) } name-v4{COLON} { YDVAR(1, VAR_IPSET_NAME_V4) } name-v6{COLON} { YDVAR(1, VAR_IPSET_NAME_V6) } diff --git a/util/configparser.y b/util/configparser.y index d8f25a67e..6237d0c08 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -179,6 +179,7 @@ extern struct config_parser_state* cfg_parser; %token VAR_CACHEDB VAR_CACHEDB_BACKEND VAR_CACHEDB_SECRETSEED %token VAR_CACHEDB_REDISHOST VAR_CACHEDB_REDISPORT VAR_CACHEDB_REDISTIMEOUT %token VAR_CACHEDB_REDISEXPIRERECORDS VAR_CACHEDB_REDISPATH VAR_CACHEDB_REDISPASSWORD +%token VAR_CACHEDB_REDISLOGICALDB %token VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM VAR_FOR_UPSTREAM %token VAR_AUTH_ZONE VAR_ZONEFILE VAR_MASTER VAR_URL VAR_FOR_DOWNSTREAM %token VAR_FALLBACK_ENABLED VAR_TLS_ADDITIONAL_PORT VAR_LOW_RTT VAR_LOW_RTT_PERMIL @@ -3701,7 +3702,8 @@ contents_cachedb: contents_cachedb content_cachedb | ; content_cachedb: cachedb_backend_name | cachedb_secret_seed | redis_server_host | redis_server_port | redis_timeout | - redis_expire_records | redis_server_path | redis_server_password + redis_expire_records | redis_server_path | redis_server_password | + redis_logical_db ; cachedb_backend_name: VAR_CACHEDB_BACKEND STRING_ARG { @@ -3804,6 +3806,21 @@ redis_expire_records: VAR_CACHEDB_REDISEXPIRERECORDS STRING_ARG free($2); } ; +redis_logical_db: VAR_CACHEDB_REDISLOGICALDB STRING_ARG + { + #if defined(USE_CACHEDB) && defined(USE_REDIS) + int db; + OUTYY(("P(redis_logical_db:%s)\n", $2)); + db = atoi($2); + if((db == 0 && strcmp($2, "0") != 0) || db < 0) + yyerror("valid redis logical database index expected"); + else cfg_parser->cfg->redis_logical_db = db; + #else + OUTYY(("P(Compiled without cachedb or redis, ignoring)\n")); + #endif + free($2); + } + ; server_tcp_connection_limit: VAR_TCP_CONNECTION_LIMIT STRING_ARG STRING_ARG { OUTYY(("P(server_tcp_connection_limit:%s %s)\n", $2, $3));