Unverified Commit 2968d8e3 authored by Salvatore Sanfilippo's avatar Salvatore Sanfilippo Committed by GitHub
Browse files

Merge branch 'unstable' into ModuleSecurity

parents ff682d79 c6fb9d09
...@@ -88,7 +88,7 @@ typedef struct streamNACK { ...@@ -88,7 +88,7 @@ typedef struct streamNACK {
/* Stream propagation informations, passed to functions in order to propagate /* Stream propagation informations, passed to functions in order to propagate
* XCLAIM commands to AOF and slaves. */ * XCLAIM commands to AOF and slaves. */
typedef struct sreamPropInfo { typedef struct streamPropInfo {
robj *keyname; robj *keyname;
robj *groupname; robj *groupname;
} streamPropInfo; } streamPropInfo;
...@@ -98,6 +98,7 @@ struct client; ...@@ -98,6 +98,7 @@ struct client;
stream *streamNew(void); stream *streamNew(void);
void freeStream(stream *s); void freeStream(stream *s);
unsigned long streamLength(const robj *subject);
size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi); size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi);
void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev); void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev);
int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields); int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields);
......
...@@ -621,7 +621,7 @@ void hincrbyfloatCommand(client *c) { ...@@ -621,7 +621,7 @@ void hincrbyfloatCommand(client *c) {
} }
char buf[MAX_LONG_DOUBLE_CHARS]; char buf[MAX_LONG_DOUBLE_CHARS];
int len = ld2string(buf,sizeof(buf),value,1); int len = ld2string(buf,sizeof(buf),value,LD_STR_HUMAN);
new = sdsnewlen(buf,len); new = sdsnewlen(buf,len);
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE); hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
addReplyBulkCBuffer(c,buf,len); addReplyBulkCBuffer(c,buf,len);
...@@ -772,8 +772,8 @@ void genericHgetallCommand(client *c, int flags) { ...@@ -772,8 +772,8 @@ void genericHgetallCommand(client *c, int flags) {
hashTypeIterator *hi; hashTypeIterator *hi;
int length, count = 0; int length, count = 0;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymap[c->resp]))
|| checkType(c,o,OBJ_HASH)) return; == NULL || checkType(c,o,OBJ_HASH)) return;
/* We return a map if the user requested keys and values, like in the /* We return a map if the user requested keys and values, like in the
* HGETALL case. Otherwise to use a flat array makes more sense. */ * HGETALL case. Otherwise to use a flat array makes more sense. */
......
...@@ -402,7 +402,7 @@ void lrangeCommand(client *c) { ...@@ -402,7 +402,7 @@ void lrangeCommand(client *c) {
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) || if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return; (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray)) == NULL
|| checkType(c,o,OBJ_LIST)) return; || checkType(c,o,OBJ_LIST)) return;
llen = listTypeLength(o); llen = listTypeLength(o);
...@@ -414,7 +414,7 @@ void lrangeCommand(client *c) { ...@@ -414,7 +414,7 @@ void lrangeCommand(client *c) {
/* Invariant: start >= 0, so this test will be true when end < 0. /* Invariant: start >= 0, so this test will be true when end < 0.
* The range is empty when start > end or start >= length. */ * The range is empty when start > end or start >= length. */
if (start > end || start >= llen) { if (start > end || start >= llen) {
addReplyNull(c); addReply(c,shared.emptyarray);
return; return;
} }
if (end >= llen) end = llen-1; if (end >= llen) end = llen-1;
...@@ -606,7 +606,7 @@ void rpoplpushCommand(client *c) { ...@@ -606,7 +606,7 @@ void rpoplpushCommand(client *c) {
* Blocking POP operations * Blocking POP operations
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
/* This is a helper function for handleClientsBlockedOnLists(). It's work /* This is a helper function for handleClientsBlockedOnKeys(). It's work
* is to serve a specific client (receiver) that is blocked on 'key' * is to serve a specific client (receiver) that is blocked on 'key'
* in the context of the specified 'db', doing the following: * in the context of the specified 'db', doing the following:
* *
......
...@@ -418,10 +418,10 @@ void spopWithCountCommand(client *c) { ...@@ -418,10 +418,10 @@ void spopWithCountCommand(client *c) {
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp])) if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
== NULL || checkType(c,set,OBJ_SET)) return; == NULL || checkType(c,set,OBJ_SET)) return;
/* If count is zero, serve an empty multibulk ASAP to avoid special /* If count is zero, serve an empty set ASAP to avoid special
* cases later. */ * cases later. */
if (count == 0) { if (count == 0) {
addReplyNull(c); addReply(c,shared.emptyset[c->resp]);
return; return;
} }
...@@ -632,13 +632,13 @@ void srandmemberWithCountCommand(client *c) { ...@@ -632,13 +632,13 @@ void srandmemberWithCountCommand(client *c) {
uniq = 0; uniq = 0;
} }
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyset[c->resp]))
== NULL || checkType(c,set,OBJ_SET)) return; == NULL || checkType(c,set,OBJ_SET)) return;
size = setTypeSize(set); size = setTypeSize(set);
/* If count is zero, serve it ASAP to avoid special cases later. */ /* If count is zero, serve it ASAP to avoid special cases later. */
if (count == 0) { if (count == 0) {
addReplyNull(c); addReply(c,shared.emptyset[c->resp]);
return; return;
} }
...@@ -813,7 +813,7 @@ void sinterGenericCommand(client *c, robj **setkeys, ...@@ -813,7 +813,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
} }
addReply(c,shared.czero); addReply(c,shared.czero);
} else { } else {
addReplyNull(c); addReply(c,shared.emptyset[c->resp]);
} }
return; return;
} }
......
...@@ -67,6 +67,12 @@ void freeStream(stream *s) { ...@@ -67,6 +67,12 @@ void freeStream(stream *s) {
zfree(s); zfree(s);
} }
/* Return the length of a stream. */
unsigned long streamLength(const robj *subject) {
stream *s = subject->ptr;
return s->length;
}
/* Generate the next stream item ID given the previous one. If the current /* Generate the next stream item ID given the previous one. If the current
* milliseconds Unix time is greater than the previous one, just use this * milliseconds Unix time is greater than the previous one, just use this
* as time part and start with sequence part of zero. Otherwise we use the * as time part and start with sequence part of zero. Otherwise we use the
...@@ -173,9 +179,19 @@ int streamCompareID(streamID *a, streamID *b) { ...@@ -173,9 +179,19 @@ int streamCompareID(streamID *a, streamID *b) {
* C_ERR if an ID was given via 'use_id', but adding it failed since the * C_ERR if an ID was given via 'use_id', but adding it failed since the
* current top ID is greater or equal. */ * current top ID is greater or equal. */
int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) { int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
/* If an ID was given, check that it's greater than the last entry ID
* or return an error. */ /* Generate the new entry ID. */
if (use_id && streamCompareID(use_id,&s->last_id) <= 0) return C_ERR; streamID id;
if (use_id)
id = *use_id;
else
streamNextID(&s->last_id,&id);
/* Check that the new ID is greater than the last entry ID
* or return an error. Automatically generated IDs might
* overflow (and wrap-around) when incrementing the sequence
part. */
if (streamCompareID(&id,&s->last_id) <= 0) return C_ERR;
/* Add the new entry. */ /* Add the new entry. */
raxIterator ri; raxIterator ri;
...@@ -192,13 +208,6 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_ ...@@ -192,13 +208,6 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
} }
raxStop(&ri); raxStop(&ri);
/* Generate the new entry ID. */
streamID id;
if (use_id)
id = *use_id;
else
streamNextID(&s->last_id,&id);
/* We have to add the key into the radix tree in lexicographic order, /* We have to add the key into the radix tree in lexicographic order,
* to do so we consider the ID as a single 128 bit number written in * to do so we consider the ID as a single 128 bit number written in
* big endian, so that the most significant bytes are the first ones. */ * big endian, so that the most significant bytes are the first ones. */
...@@ -242,17 +251,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_ ...@@ -242,17 +251,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
* the current node is full. */ * the current node is full. */
if (lp != NULL) { if (lp != NULL) {
if (server.stream_node_max_bytes && if (server.stream_node_max_bytes &&
lp_bytes > server.stream_node_max_bytes) lp_bytes >= server.stream_node_max_bytes)
{ {
lp = NULL; lp = NULL;
} else if (server.stream_node_max_entries) { } else if (server.stream_node_max_entries) {
int64_t count = lpGetInteger(lpFirst(lp)); int64_t count = lpGetInteger(lpFirst(lp));
if (count > server.stream_node_max_entries) lp = NULL; if (count >= server.stream_node_max_entries) lp = NULL;
} }
} }
int flags = STREAM_ITEM_FLAG_NONE; int flags = STREAM_ITEM_FLAG_NONE;
if (lp == NULL || lp_bytes > server.stream_node_max_bytes) { if (lp == NULL || lp_bytes >= server.stream_node_max_bytes) {
master_id = id; master_id = id;
streamEncodeID(rax_key,&id); streamEncodeID(rax_key,&id);
/* Create the listpack having the master entry ID and fields. */ /* Create the listpack having the master entry ID and fields. */
...@@ -1070,26 +1079,6 @@ robj *streamTypeLookupWriteOrCreate(client *c, robj *key) { ...@@ -1070,26 +1079,6 @@ robj *streamTypeLookupWriteOrCreate(client *c, robj *key) {
return o; return o;
} }
/* Helper function to convert a string to an unsigned long long value.
* The function attempts to use the faster string2ll() function inside
* Redis: if it fails, strtoull() is used instead. The function returns
* 1 if the conversion happened successfully or 0 if the number is
* invalid or out of range. */
int string2ull(const char *s, unsigned long long *value) {
long long ll;
if (string2ll(s,strlen(s),&ll)) {
if (ll < 0) return 0; /* Negative values are out of range. */
*value = ll;
return 1;
}
errno = 0;
char *endptr = NULL;
*value = strtoull(s,&endptr,10);
if (errno == EINVAL || errno == ERANGE || !(*s != '\0' && *endptr == '\0'))
return 0; /* strtoull() failed. */
return 1; /* Conversion done! */
}
/* Parse a stream ID in the format given by clients to Redis, that is /* Parse a stream ID in the format given by clients to Redis, that is
* <ms>-<seq>, and converts it into a streamID structure. If * <ms>-<seq>, and converts it into a streamID structure. If
* the specified ID is invalid C_ERR is returned and an error is reported * the specified ID is invalid C_ERR is returned and an error is reported
...@@ -1217,6 +1206,14 @@ void xaddCommand(client *c) { ...@@ -1217,6 +1206,14 @@ void xaddCommand(client *c) {
return; return;
} }
/* Return ASAP if minimal ID (0-0) was given so we avoid possibly creating
* a new stream and have streamAppendItem fail, leaving an empty key in the
* database. */
if (id_given && id.ms == 0 && id.seq == 0) {
addReplyError(c,"The ID specified in XADD must be greater than 0-0");
return;
}
/* Lookup the stream at key. */ /* Lookup the stream at key. */
robj *o; robj *o;
stream *s; stream *s;
......
...@@ -1357,9 +1357,8 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) { ...@@ -1357,9 +1357,8 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
/* Optimize: check if the element is too large or the list /* Optimize: check if the element is too large or the list
* becomes too long *before* executing zzlInsert. */ * becomes too long *before* executing zzlInsert. */
zobj->ptr = zzlInsert(zobj->ptr,ele,score); zobj->ptr = zzlInsert(zobj->ptr,ele,score);
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries) if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST); sdslen(ele) > server.zset_max_ziplist_value)
if (sdslen(ele) > server.zset_max_ziplist_value)
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST); zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
if (newscore) *newscore = score; if (newscore) *newscore = score;
*flags |= ZADD_ADDED; *flags |= ZADD_ADDED;
...@@ -2427,7 +2426,7 @@ void zrangeGenericCommand(client *c, int reverse) { ...@@ -2427,7 +2426,7 @@ void zrangeGenericCommand(client *c, int reverse) {
return; return;
} }
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL
|| checkType(c,zobj,OBJ_ZSET)) return; || checkType(c,zobj,OBJ_ZSET)) return;
/* Sanitize indexes. */ /* Sanitize indexes. */
...@@ -2439,7 +2438,7 @@ void zrangeGenericCommand(client *c, int reverse) { ...@@ -2439,7 +2438,7 @@ void zrangeGenericCommand(client *c, int reverse) {
/* Invariant: start >= 0, so this test will be true when end < 0. /* Invariant: start >= 0, so this test will be true when end < 0.
* The range is empty when start > end or start >= length. */ * The range is empty when start > end or start >= length. */
if (start > end || start >= llen) { if (start > end || start >= llen) {
addReplyNull(c); addReply(c,shared.emptyarray);
return; return;
} }
if (end >= llen) end = llen-1; if (end >= llen) end = llen-1;
...@@ -2575,7 +2574,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { ...@@ -2575,7 +2574,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
} }
/* Ok, lookup the key and get the range */ /* Ok, lookup the key and get the range */
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL || if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL ||
checkType(c,zobj,OBJ_ZSET)) return; checkType(c,zobj,OBJ_ZSET)) return;
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
...@@ -2595,7 +2594,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { ...@@ -2595,7 +2594,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */ /* No "first" element in the specified interval. */
if (eptr == NULL) { if (eptr == NULL) {
addReplyNull(c); addReply(c,shared.emptyarray);
return; return;
} }
...@@ -2662,7 +2661,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { ...@@ -2662,7 +2661,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */ /* No "first" element in the specified interval. */
if (ln == NULL) { if (ln == NULL) {
addReplyNull(c); addReply(c,shared.emptyarray);
return; return;
} }
...@@ -2920,7 +2919,7 @@ void genericZrangebylexCommand(client *c, int reverse) { ...@@ -2920,7 +2919,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
} }
/* Ok, lookup the key and get the range */ /* Ok, lookup the key and get the range */
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL || if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL ||
checkType(c,zobj,OBJ_ZSET)) checkType(c,zobj,OBJ_ZSET))
{ {
zslFreeLexRange(&range); zslFreeLexRange(&range);
...@@ -2943,7 +2942,7 @@ void genericZrangebylexCommand(client *c, int reverse) { ...@@ -2943,7 +2942,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */ /* No "first" element in the specified interval. */
if (eptr == NULL) { if (eptr == NULL) {
addReplyNull(c); addReply(c,shared.emptyarray);
zslFreeLexRange(&range); zslFreeLexRange(&range);
return; return;
} }
...@@ -3007,7 +3006,7 @@ void genericZrangebylexCommand(client *c, int reverse) { ...@@ -3007,7 +3006,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */ /* No "first" element in the specified interval. */
if (ln == NULL) { if (ln == NULL) {
addReplyNull(c); addReply(c,shared.emptyarray);
zslFreeLexRange(&range); zslFreeLexRange(&range);
return; return;
} }
...@@ -3161,7 +3160,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey ...@@ -3161,7 +3160,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
/* No candidate for zpopping, return empty. */ /* No candidate for zpopping, return empty. */
if (!zobj) { if (!zobj) {
addReplyNull(c); addReply(c,shared.emptyarray);
return; return;
} }
......
/*
* Copyright (c) 2019, Redis Labs
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "server.h"
#include "connhelpers.h"
#include "adlist.h"
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#define REDIS_TLS_PROTO_TLSv1 (1<<0)
#define REDIS_TLS_PROTO_TLSv1_1 (1<<1)
#define REDIS_TLS_PROTO_TLSv1_2 (1<<2)
#define REDIS_TLS_PROTO_TLSv1_3 (1<<3)
/* Use safe defaults */
#ifdef TLS1_3_VERSION
#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2|REDIS_TLS_PROTO_TLSv1_3)
#else
#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2)
#endif
extern ConnectionType CT_Socket;
SSL_CTX *redis_tls_ctx;
static int parseProtocolsConfig(const char *str) {
int i, count = 0;
int protocols = 0;
if (!str) return REDIS_TLS_PROTO_DEFAULT;
sds *tokens = sdssplitlen(str, strlen(str), " ", 1, &count);
if (!tokens) {
serverLog(LL_WARNING, "Invalid tls-protocols configuration string");
return -1;
}
for (i = 0; i < count; i++) {
if (!strcasecmp(tokens[i], "tlsv1")) protocols |= REDIS_TLS_PROTO_TLSv1;
else if (!strcasecmp(tokens[i], "tlsv1.1")) protocols |= REDIS_TLS_PROTO_TLSv1_1;
else if (!strcasecmp(tokens[i], "tlsv1.2")) protocols |= REDIS_TLS_PROTO_TLSv1_2;
else if (!strcasecmp(tokens[i], "tlsv1.3")) {
#ifdef TLS1_3_VERSION
protocols |= REDIS_TLS_PROTO_TLSv1_3;
#else
serverLog(LL_WARNING, "TLSv1.3 is specified in tls-protocols but not supported by OpenSSL.");
protocols = -1;
break;
#endif
} else {
serverLog(LL_WARNING, "Invalid tls-protocols specified. "
"Use a combination of 'TLSv1', 'TLSv1.1', 'TLSv1.2' and 'TLSv1.3'.");
protocols = -1;
break;
}
}
sdsfreesplitres(tokens, count);
return protocols;
}
/* list of connections with pending data already read from the socket, but not
* served to the reader yet. */
static list *pending_list = NULL;
void tlsInit(void) {
ERR_load_crypto_strings();
SSL_load_error_strings();
SSL_library_init();
if (!RAND_poll()) {
serverLog(LL_WARNING, "OpenSSL: Failed to seed random number generator.");
}
pending_list = listCreate();
/* Server configuration */
server.tls_auth_clients = 1; /* Secure by default */
}
/* Attempt to configure/reconfigure TLS. This operation is atomic and will
* leave the SSL_CTX unchanged if fails.
*/
int tlsConfigure(redisTLSContextConfig *ctx_config) {
char errbuf[256];
SSL_CTX *ctx = NULL;
if (!ctx_config->cert_file) {
serverLog(LL_WARNING, "No tls-cert-file configured!");
goto error;
}
if (!ctx_config->key_file) {
serverLog(LL_WARNING, "No tls-key-file configured!");
goto error;
}
if (!ctx_config->ca_cert_file && !ctx_config->ca_cert_dir) {
serverLog(LL_WARNING, "Either tls-ca-cert-file or tls-ca-cert-dir must be configured!");
goto error;
}
ctx = SSL_CTX_new(SSLv23_method());
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
#endif
int protocols = parseProtocolsConfig(ctx_config->protocols);
if (protocols == -1) goto error;
if (!(protocols & REDIS_TLS_PROTO_TLSv1))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
if (!(protocols & REDIS_TLS_PROTO_TLSv1_1))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1);
#ifdef SSL_OP_NO_TLSv1_2
if (!(protocols & REDIS_TLS_PROTO_TLSv1_2))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2);
#endif
#ifdef SSL_OP_NO_TLSv1_3
if (!(protocols & REDIS_TLS_PROTO_TLSv1_3))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3);
#endif
#ifdef SSL_OP_NO_COMPRESSION
SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
#endif
#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
#endif
if (ctx_config->prefer_server_ciphers)
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
SSL_CTX_set_ecdh_auto(ctx, 1);
if (SSL_CTX_use_certificate_file(ctx, ctx_config->cert_file, SSL_FILETYPE_PEM) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to load certificate: %s: %s", ctx_config->cert_file, errbuf);
goto error;
}
if (SSL_CTX_use_PrivateKey_file(ctx, ctx_config->key_file, SSL_FILETYPE_PEM) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to load private key: %s: %s", ctx_config->key_file, errbuf);
goto error;
}
if (SSL_CTX_load_verify_locations(ctx, ctx_config->ca_cert_file, ctx_config->ca_cert_dir) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to configure CA certificate(s) file/directory: %s", errbuf);
goto error;
}
if (ctx_config->dh_params_file) {
FILE *dhfile = fopen(ctx_config->dh_params_file, "r");
DH *dh = NULL;
if (!dhfile) {
serverLog(LL_WARNING, "Failed to load %s: %s", ctx_config->dh_params_file, strerror(errno));
goto error;
}
dh = PEM_read_DHparams(dhfile, NULL, NULL, NULL);
fclose(dhfile);
if (!dh) {
serverLog(LL_WARNING, "%s: failed to read DH params.", ctx_config->dh_params_file);
goto error;
}
if (SSL_CTX_set_tmp_dh(ctx, dh) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to load DH params file: %s: %s", ctx_config->dh_params_file, errbuf);
DH_free(dh);
goto error;
}
DH_free(dh);
}
if (ctx_config->ciphers && !SSL_CTX_set_cipher_list(ctx, ctx_config->ciphers)) {
serverLog(LL_WARNING, "Failed to configure ciphers: %s", ctx_config->ciphers);
goto error;
}
#ifdef TLS1_3_VERSION
if (ctx_config->ciphersuites && !SSL_CTX_set_ciphersuites(ctx, ctx_config->ciphersuites)) {
serverLog(LL_WARNING, "Failed to configure ciphersuites: %s", ctx_config->ciphersuites);
goto error;
}
#endif
SSL_CTX_free(redis_tls_ctx);
redis_tls_ctx = ctx;
return C_OK;
error:
if (ctx) SSL_CTX_free(ctx);
return C_ERR;
}
#ifdef TLS_DEBUGGING
#define TLSCONN_DEBUG(fmt, ...) \
serverLog(LL_DEBUG, "TLSCONN: " fmt, __VA_ARGS__)
#else
#define TLSCONN_DEBUG(fmt, ...)
#endif
ConnectionType CT_TLS;
/* Normal socket connections have a simple events/handler correlation.
*
* With TLS connections we need to handle cases where during a logical read
* or write operation, the SSL library asks to block for the opposite
* socket operation.
*
* When this happens, we need to do two things:
* 1. Make sure we register for the even.
* 2. Make sure we know which handler needs to execute when the
* event fires. That is, if we notify the caller of a write operation
* that it blocks, and SSL asks for a read, we need to trigger the
* write handler again on the next read event.
*
*/
typedef enum {
WANT_READ = 1,
WANT_WRITE
} WantIOType;
#define TLS_CONN_FLAG_READ_WANT_WRITE (1<<0)
#define TLS_CONN_FLAG_WRITE_WANT_READ (1<<1)
#define TLS_CONN_FLAG_FD_SET (1<<2)
typedef struct tls_connection {
connection c;
int flags;
SSL *ssl;
char *ssl_error;
listNode *pending_list_node;
} tls_connection;
connection *connCreateTLS(void) {
tls_connection *conn = zcalloc(sizeof(tls_connection));
conn->c.type = &CT_TLS;
conn->c.fd = -1;
conn->ssl = SSL_new(redis_tls_ctx);
return (connection *) conn;
}
connection *connCreateAcceptedTLS(int fd, int require_auth) {
tls_connection *conn = (tls_connection *) connCreateTLS();
conn->c.fd = fd;
conn->c.state = CONN_STATE_ACCEPTING;
if (!require_auth) {
/* We still verify certificates if provided, but don't require them.
*/
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, NULL);
}
SSL_set_fd(conn->ssl, conn->c.fd);
SSL_set_accept_state(conn->ssl);
return (connection *) conn;
}
static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask);
/* Process the return code received from OpenSSL>
* Update the want parameter with expected I/O.
* Update the connection's error state if a real error has occured.
* Returns an SSL error code, or 0 if no further handling is required.
*/
static int handleSSLReturnCode(tls_connection *conn, int ret_value, WantIOType *want) {
if (ret_value <= 0) {
int ssl_err = SSL_get_error(conn->ssl, ret_value);
switch (ssl_err) {
case SSL_ERROR_WANT_WRITE:
*want = WANT_WRITE;
return 0;
case SSL_ERROR_WANT_READ:
*want = WANT_READ;
return 0;
case SSL_ERROR_SYSCALL:
conn->c.last_errno = errno;
if (conn->ssl_error) zfree(conn->ssl_error);
conn->ssl_error = errno ? zstrdup(strerror(errno)) : NULL;
break;
default:
/* Error! */
conn->c.last_errno = 0;
if (conn->ssl_error) zfree(conn->ssl_error);
conn->ssl_error = zmalloc(512);
ERR_error_string_n(ERR_get_error(), conn->ssl_error, 512);
break;
}
return ssl_err;
}
return 0;
}
void registerSSLEvent(tls_connection *conn, WantIOType want) {
int mask = aeGetFileEvents(server.el, conn->c.fd);
switch (want) {
case WANT_READ:
if (mask & AE_WRITABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
if (!(mask & AE_READABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE,
tlsEventHandler, conn);
break;
case WANT_WRITE:
if (mask & AE_READABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
if (!(mask & AE_WRITABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE,
tlsEventHandler, conn);
break;
default:
serverAssert(0);
break;
}
}
void updateSSLEvent(tls_connection *conn) {
int mask = aeGetFileEvents(server.el, conn->c.fd);
int need_read = conn->c.read_handler || (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ);
int need_write = conn->c.write_handler || (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE);
if (need_read && !(mask & AE_READABLE))
aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE, tlsEventHandler, conn);
if (!need_read && (mask & AE_READABLE))
aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
if (need_write && !(mask & AE_WRITABLE))
aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE, tlsEventHandler, conn);
if (!need_write && (mask & AE_WRITABLE))
aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
}
static void tlsHandleEvent(tls_connection *conn, int mask) {
int ret;
TLSCONN_DEBUG("tlsEventHandler(): fd=%d, state=%d, mask=%d, r=%d, w=%d, flags=%d",
fd, conn->c.state, mask, conn->c.read_handler != NULL, conn->c.write_handler != NULL,
conn->flags);
ERR_clear_error();
switch (conn->c.state) {
case CONN_STATE_CONNECTING:
if (connGetSocketError((connection *) conn)) {
conn->c.last_errno = errno;
conn->c.state = CONN_STATE_ERROR;
} else {
if (!(conn->flags & TLS_CONN_FLAG_FD_SET)) {
SSL_set_fd(conn->ssl, conn->c.fd);
conn->flags |= TLS_CONN_FLAG_FD_SET;
}
ret = SSL_connect(conn->ssl);
if (ret <= 0) {
WantIOType want = 0;
if (!handleSSLReturnCode(conn, ret, &want)) {
registerSSLEvent(conn, want);
/* Avoid hitting UpdateSSLEvent, which knows nothing
* of what SSL_connect() wants and instead looks at our
* R/W handlers.
*/
return;
}
/* If not handled, it's an error */
conn->c.state = CONN_STATE_ERROR;
} else {
conn->c.state = CONN_STATE_CONNECTED;
}
}
if (!callHandler((connection *) conn, conn->c.conn_handler)) return;
conn->c.conn_handler = NULL;
break;
case CONN_STATE_ACCEPTING:
ret = SSL_accept(conn->ssl);
if (ret <= 0) {
WantIOType want = 0;
if (!handleSSLReturnCode(conn, ret, &want)) {
/* Avoid hitting UpdateSSLEvent, which knows nothing
* of what SSL_connect() wants and instead looks at our
* R/W handlers.
*/
registerSSLEvent(conn, want);
return;
}
/* If not handled, it's an error */
conn->c.state = CONN_STATE_ERROR;
} else {
conn->c.state = CONN_STATE_CONNECTED;
}
if (!callHandler((connection *) conn, conn->c.conn_handler)) return;
conn->c.conn_handler = NULL;
break;
case CONN_STATE_CONNECTED:
{
int call_read = ((mask & AE_READABLE) && conn->c.read_handler) ||
((mask & AE_WRITABLE) && (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE));
int call_write = ((mask & AE_WRITABLE) && conn->c.write_handler) ||
((mask & AE_READABLE) && (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ));
/* Normally we execute the readable event first, and the writable
* event laster. This is useful as sometimes we may be able
* to serve the reply of a query immediately after processing the
* query.
*
* However if WRITE_BARRIER is set in the mask, our application is
* asking us to do the reverse: never fire the writable event
* after the readable. In such a case, we invert the calls.
* This is useful when, for instance, we want to do things
* in the beforeSleep() hook, like fsynching a file to disk,
* before replying to a client. */
int invert = conn->c.flags & CONN_FLAG_WRITE_BARRIER;
if (!invert && call_read) {
conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE;
if (!callHandler((connection *) conn, conn->c.read_handler)) return;
}
/* Fire the writable event. */
if (call_write) {
conn->flags &= ~TLS_CONN_FLAG_WRITE_WANT_READ;
if (!callHandler((connection *) conn, conn->c.write_handler)) return;
}
/* If we have to invert the call, fire the readable event now
* after the writable one. */
if (invert && call_read) {
conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE;
if (!callHandler((connection *) conn, conn->c.read_handler)) return;
}
/* If SSL has pending that, already read from the socket, we're at
* risk of not calling the read handler again, make sure to add it
* to a list of pending connection that should be handled anyway. */
if ((mask & AE_READABLE)) {
if (SSL_pending(conn->ssl) > 0) {
if (!conn->pending_list_node) {
listAddNodeTail(pending_list, conn);
conn->pending_list_node = listLast(pending_list);
}
} else if (conn->pending_list_node) {
listDelNode(pending_list, conn->pending_list_node);
conn->pending_list_node = NULL;
}
}
break;
}
default:
break;
}
updateSSLEvent(conn);
}
static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask) {
UNUSED(el);
UNUSED(fd);
tls_connection *conn = clientData;
tlsHandleEvent(conn, mask);
}
static void connTLSClose(connection *conn_) {
tls_connection *conn = (tls_connection *) conn_;
if (conn->ssl) {
SSL_free(conn->ssl);
conn->ssl = NULL;
}
if (conn->ssl_error) {
zfree(conn->ssl_error);
conn->ssl_error = NULL;
}
if (conn->pending_list_node) {
listDelNode(pending_list, conn->pending_list_node);
conn->pending_list_node = NULL;
}
CT_Socket.close(conn_);
}
static int connTLSAccept(connection *_conn, ConnectionCallbackFunc accept_handler) {
tls_connection *conn = (tls_connection *) _conn;
int ret;
if (conn->c.state != CONN_STATE_ACCEPTING) return C_ERR;
ERR_clear_error();
/* Try to accept */
conn->c.conn_handler = accept_handler;
ret = SSL_accept(conn->ssl);
if (ret <= 0) {
WantIOType want = 0;
if (!handleSSLReturnCode(conn, ret, &want)) {
registerSSLEvent(conn, want); /* We'll fire back */
return C_OK;
} else {
conn->c.state = CONN_STATE_ERROR;
return C_ERR;
}
}
conn->c.state = CONN_STATE_CONNECTED;
if (!callHandler((connection *) conn, conn->c.conn_handler)) return C_OK;
conn->c.conn_handler = NULL;
return C_OK;
}
static int connTLSConnect(connection *conn_, const char *addr, int port, const char *src_addr, ConnectionCallbackFunc connect_handler) {
tls_connection *conn = (tls_connection *) conn_;
if (conn->c.state != CONN_STATE_NONE) return C_ERR;
ERR_clear_error();
/* Initiate Socket connection first */
if (CT_Socket.connect(conn_, addr, port, src_addr, connect_handler) == C_ERR) return C_ERR;
/* Return now, once the socket is connected we'll initiate
* TLS connection from the event handler.
*/
return C_OK;
}
static int connTLSWrite(connection *conn_, const void *data, size_t data_len) {
tls_connection *conn = (tls_connection *) conn_;
int ret, ssl_err;
if (conn->c.state != CONN_STATE_CONNECTED) return -1;
ERR_clear_error();
ret = SSL_write(conn->ssl, data, data_len);
if (ret <= 0) {
WantIOType want = 0;
if (!(ssl_err = handleSSLReturnCode(conn, ret, &want))) {
if (want == WANT_READ) conn->flags |= TLS_CONN_FLAG_WRITE_WANT_READ;
updateSSLEvent(conn);
errno = EAGAIN;
return -1;
} else {
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
((ssl_err == SSL_ERROR_SYSCALL && !errno))) {
conn->c.state = CONN_STATE_CLOSED;
return 0;
} else {
conn->c.state = CONN_STATE_ERROR;
return -1;
}
}
}
return ret;
}
static int connTLSRead(connection *conn_, void *buf, size_t buf_len) {
tls_connection *conn = (tls_connection *) conn_;
int ret;
int ssl_err;
if (conn->c.state != CONN_STATE_CONNECTED) return -1;
ERR_clear_error();
ret = SSL_read(conn->ssl, buf, buf_len);
if (ret <= 0) {
WantIOType want = 0;
if (!(ssl_err = handleSSLReturnCode(conn, ret, &want))) {
if (want == WANT_WRITE) conn->flags |= TLS_CONN_FLAG_READ_WANT_WRITE;
updateSSLEvent(conn);
errno = EAGAIN;
return -1;
} else {
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
((ssl_err == SSL_ERROR_SYSCALL) && !errno)) {
conn->c.state = CONN_STATE_CLOSED;
return 0;
} else {
conn->c.state = CONN_STATE_ERROR;
return -1;
}
}
}
return ret;
}
static const char *connTLSGetLastError(connection *conn_) {
tls_connection *conn = (tls_connection *) conn_;
if (conn->ssl_error) return conn->ssl_error;
return NULL;
}
int connTLSSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier) {
conn->write_handler = func;
if (barrier)
conn->flags |= CONN_FLAG_WRITE_BARRIER;
else
conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
updateSSLEvent((tls_connection *) conn);
return C_OK;
}
int connTLSSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
conn->read_handler = func;
updateSSLEvent((tls_connection *) conn);
return C_OK;
}
static void setBlockingTimeout(tls_connection *conn, long long timeout) {
anetBlock(NULL, conn->c.fd);
anetSendTimeout(NULL, conn->c.fd, timeout);
anetRecvTimeout(NULL, conn->c.fd, timeout);
}
static void unsetBlockingTimeout(tls_connection *conn) {
anetNonBlock(NULL, conn->c.fd);
anetSendTimeout(NULL, conn->c.fd, 0);
anetRecvTimeout(NULL, conn->c.fd, 0);
}
static int connTLSBlockingConnect(connection *conn_, const char *addr, int port, long long timeout) {
tls_connection *conn = (tls_connection *) conn_;
int ret;
if (conn->c.state != CONN_STATE_NONE) return C_ERR;
/* Initiate socket blocking connect first */
if (CT_Socket.blocking_connect(conn_, addr, port, timeout) == C_ERR) return C_ERR;
/* Initiate TLS connection now. We set up a send/recv timeout on the socket,
* which means the specified timeout will not be enforced accurately. */
SSL_set_fd(conn->ssl, conn->c.fd);
setBlockingTimeout(conn, timeout);
if ((ret = SSL_connect(conn->ssl)) <= 0) {
conn->c.state = CONN_STATE_ERROR;
return C_ERR;
}
unsetBlockingTimeout(conn);
conn->c.state = CONN_STATE_CONNECTED;
return C_OK;
}
static ssize_t connTLSSyncWrite(connection *conn_, char *ptr, ssize_t size, long long timeout) {
tls_connection *conn = (tls_connection *) conn_;
setBlockingTimeout(conn, timeout);
SSL_clear_mode(conn->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
int ret = SSL_write(conn->ssl, ptr, size);
SSL_set_mode(conn->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
unsetBlockingTimeout(conn);
return ret;
}
static ssize_t connTLSSyncRead(connection *conn_, char *ptr, ssize_t size, long long timeout) {
tls_connection *conn = (tls_connection *) conn_;
setBlockingTimeout(conn, timeout);
int ret = SSL_read(conn->ssl, ptr, size);
unsetBlockingTimeout(conn);
return ret;
}
static ssize_t connTLSSyncReadLine(connection *conn_, char *ptr, ssize_t size, long long timeout) {
tls_connection *conn = (tls_connection *) conn_;
ssize_t nread = 0;
setBlockingTimeout(conn, timeout);
size--;
while(size) {
char c;
if (SSL_read(conn->ssl,&c,1) <= 0) {
nread = -1;
goto exit;
}
if (c == '\n') {
*ptr = '\0';
if (nread && *(ptr-1) == '\r') *(ptr-1) = '\0';
goto exit;
} else {
*ptr++ = c;
*ptr = '\0';
nread++;
}
size--;
}
exit:
unsetBlockingTimeout(conn);
return nread;
}
ConnectionType CT_TLS = {
.ae_handler = tlsEventHandler,
.accept = connTLSAccept,
.connect = connTLSConnect,
.blocking_connect = connTLSBlockingConnect,
.read = connTLSRead,
.write = connTLSWrite,
.close = connTLSClose,
.set_write_handler = connTLSSetWriteHandler,
.set_read_handler = connTLSSetReadHandler,
.get_last_error = connTLSGetLastError,
.sync_write = connTLSSyncWrite,
.sync_read = connTLSSyncRead,
.sync_readline = connTLSSyncReadLine,
};
int tlsHasPendingData() {
if (!pending_list)
return 0;
return listLength(pending_list) > 0;
}
void tlsProcessPendingData() {
listIter li;
listNode *ln;
listRewind(pending_list,&li);
while((ln = listNext(&li))) {
tls_connection *conn = listNodeValue(ln);
tlsHandleEvent(conn, AE_READABLE);
}
}
#else /* USE_OPENSSL */
void tlsInit(void) {
}
int tlsConfigure(redisTLSContextConfig *ctx_config) {
UNUSED(ctx_config);
return C_OK;
}
connection *connCreateTLS(void) {
return NULL;
}
connection *connCreateAcceptedTLS(int fd, int require_auth) {
UNUSED(fd);
UNUSED(require_auth);
return NULL;
}
int tlsHasPendingData() {
return 0;
}
void tlsProcessPendingData() {
}
#endif
...@@ -60,6 +60,7 @@ ...@@ -60,6 +60,7 @@
* use the most significant bits instead of the full 24 bits. */ * use the most significant bits instead of the full 24 bits. */
#define TRACKING_TABLE_SIZE (1<<24) #define TRACKING_TABLE_SIZE (1<<24)
rax **TrackingTable = NULL; rax **TrackingTable = NULL;
unsigned long TrackingTableUsedSlots = 0;
robj *TrackingChannelName; robj *TrackingChannelName;
/* Remove the tracking state from the client 'c'. Note that there is not much /* Remove the tracking state from the client 'c'. Note that there is not much
...@@ -109,8 +110,10 @@ void trackingRememberKeys(client *c) { ...@@ -109,8 +110,10 @@ void trackingRememberKeys(client *c) {
sds sdskey = c->argv[idx]->ptr; sds sdskey = c->argv[idx]->ptr;
uint64_t hash = crc64(0, uint64_t hash = crc64(0,
(unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1); (unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
if (TrackingTable[hash] == NULL) if (TrackingTable[hash] == NULL) {
TrackingTable[hash] = raxNew(); TrackingTable[hash] = raxNew();
TrackingTableUsedSlots++;
}
raxTryInsert(TrackingTable[hash], raxTryInsert(TrackingTable[hash],
(unsigned char*)&c->id,sizeof(c->id),NULL,NULL); (unsigned char*)&c->id,sizeof(c->id),NULL,NULL);
} }
...@@ -151,37 +154,59 @@ void sendTrackingMessage(client *c, long long hash) { ...@@ -151,37 +154,59 @@ void sendTrackingMessage(client *c, long long hash) {
} }
} }
/* This function is called from signalModifiedKey() or other places in Redis /* Invalidates a caching slot: this is actually the low level implementation
* when a key changes value. In the context of keys tracking, our task here is * of the API that Redis calls externally, that is trackingInvalidateKey(). */
* to send a notification to every client that may have keys about such . */ void trackingInvalidateSlot(uint64_t slot) {
void trackingInvalidateKey(robj *keyobj) { if (TrackingTable == NULL || TrackingTable[slot] == NULL) return;
sds sdskey = keyobj->ptr;
uint64_t hash = crc64(0,
(unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
if (TrackingTable == NULL || TrackingTable[hash] == NULL) return;
raxIterator ri; raxIterator ri;
raxStart(&ri,TrackingTable[hash]); raxStart(&ri,TrackingTable[slot]);
raxSeek(&ri,"^",NULL,0); raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) { while(raxNext(&ri)) {
uint64_t id; uint64_t id;
memcpy(&id,ri.key,ri.key_len); memcpy(&id,ri.key,sizeof(id));
client *c = lookupClientByID(id); client *c = lookupClientByID(id);
if (c == NULL || !(c->flags & CLIENT_TRACKING)) continue; if (c == NULL || !(c->flags & CLIENT_TRACKING)) continue;
sendTrackingMessage(c,hash); sendTrackingMessage(c,slot);
} }
raxStop(&ri); raxStop(&ri);
/* Free the tracking table: we'll create the radix tree and populate it /* Free the tracking table: we'll create the radix tree and populate it
* again if more keys will be modified in this hash slot. */ * again if more keys will be modified in this caching slot. */
raxFree(TrackingTable[hash]); raxFree(TrackingTable[slot]);
TrackingTable[hash] = NULL; TrackingTable[slot] = NULL;
TrackingTableUsedSlots--;
} }
void trackingInvalidateKeysOnFlush(int dbid) { /* This function is called from signalModifiedKey() or other places in Redis
UNUSED(dbid); * when a key changes value. In the context of keys tracking, our task here is
if (server.tracking_clients == 0) return; * to send a notification to every client that may have keys about such caching
* slot. */
void trackingInvalidateKey(robj *keyobj) {
if (TrackingTable == NULL || TrackingTableUsedSlots == 0) return;
sds sdskey = keyobj->ptr;
uint64_t hash = crc64(0,
(unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
trackingInvalidateSlot(hash);
}
/* This function is called when one or all the Redis databases are flushed
* (dbid == -1 in case of FLUSHALL). Caching slots are not specific for
* each DB but are global: currently what we do is sending a special
* notification to clients with tracking enabled, invalidating the caching
* slot "-1", which means, "all the keys", in order to avoid flooding clients
* with many invalidation messages for all the keys they may hold.
*
* However trying to flush the tracking table here is very costly:
* we need scanning 16 million caching slots in the table to check
* if they are used, this introduces a big delay. So what we do is to really
* flush the table in the case of FLUSHALL. When a FLUSHDB is called instead
* we just send the invalidation message to all the clients, but don't
* flush the table: it will slowly get garbage collected as more keys
* are modified in the used caching slots. */
void trackingInvalidateKeysOnFlush(int dbid) {
if (server.tracking_clients) {
listNode *ln; listNode *ln;
listIter li; listIter li;
listRewind(server.clients,&li); listRewind(server.clients,&li);
...@@ -191,4 +216,81 @@ void trackingInvalidateKeysOnFlush(int dbid) { ...@@ -191,4 +216,81 @@ void trackingInvalidateKeysOnFlush(int dbid) {
sendTrackingMessage(c,-1); sendTrackingMessage(c,-1);
} }
} }
}
/* In case of FLUSHALL, reclaim all the memory used by tracking. */
if (dbid == -1 && TrackingTable) {
for (int j = 0; j < TRACKING_TABLE_SIZE && TrackingTableUsedSlots > 0; j++) {
if (TrackingTable[j] != NULL) {
raxFree(TrackingTable[j]);
TrackingTable[j] = NULL;
TrackingTableUsedSlots--;
}
}
/* If there are no clients with tracking enabled, we can even
* reclaim the memory used by the table itself. The code assumes
* the table is allocated only if there is at least one client alive
* with tracking enabled. */
if (server.tracking_clients == 0) {
zfree(TrackingTable);
TrackingTable = NULL;
}
}
}
/* Tracking forces Redis to remember information about which client may have
* keys about certian caching slots. In workloads where there are a lot of
* reads, but keys are hardly modified, the amount of information we have
* to remember server side could be a lot: for each 16 millions of caching
* slots we may end with a radix tree containing many entries.
*
* So Redis allows the user to configure a maximum fill rate for the
* invalidation table. This function makes sure that we don't go over the
* specified fill rate: if we are over, we can just evict informations about
* random caching slots, and send invalidation messages to clients like if
* the key was modified. */
void trackingLimitUsedSlots(void) {
static unsigned int timeout_counter = 0;
if (server.tracking_table_max_fill == 0) return; /* No limits set. */
unsigned int max_slots =
(TRACKING_TABLE_SIZE/100) * server.tracking_table_max_fill;
if (TrackingTableUsedSlots <= max_slots) {
timeout_counter = 0;
return; /* Limit not reached. */
}
/* We have to invalidate a few slots to reach the limit again. The effort
* we do here is proportional to the number of times we entered this
* function and found that we are still over the limit. */
int effort = 100 * (timeout_counter+1);
/* Let's start at a random position, and perform linear probing, in order
* to improve cache locality. However once we are able to find an used
* slot, jump again randomly, in order to avoid creating big holes in the
* table (that will make this funciton use more resourced later). */
while(effort > 0) {
unsigned int idx = rand() % TRACKING_TABLE_SIZE;
do {
effort--;
idx = (idx+1) % TRACKING_TABLE_SIZE;
if (TrackingTable[idx] != NULL) {
trackingInvalidateSlot(idx);
if (TrackingTableUsedSlots <= max_slots) {
timeout_counter = 0;
return; /* Return ASAP: we are again under the limit. */
} else {
break; /* Jump to next random position. */
}
}
} while(effort > 0);
}
timeout_counter++;
}
/* This is just used in order to access the amount of used slots in the
* tracking table. */
unsigned long long trackingGetUsedSlots(void) {
return TrackingTableUsedSlots;
} }
...@@ -423,6 +423,26 @@ int string2ll(const char *s, size_t slen, long long *value) { ...@@ -423,6 +423,26 @@ int string2ll(const char *s, size_t slen, long long *value) {
return 1; return 1;
} }
/* Helper function to convert a string to an unsigned long long value.
* The function attempts to use the faster string2ll() function inside
* Redis: if it fails, strtoull() is used instead. The function returns
* 1 if the conversion happened successfully or 0 if the number is
* invalid or out of range. */
int string2ull(const char *s, unsigned long long *value) {
long long ll;
if (string2ll(s,strlen(s),&ll)) {
if (ll < 0) return 0; /* Negative values are out of range. */
*value = ll;
return 1;
}
errno = 0;
char *endptr = NULL;
*value = strtoull(s,&endptr,10);
if (errno == EINVAL || errno == ERANGE || !(*s != '\0' && *endptr == '\0'))
return 0; /* strtoull() failed. */
return 1; /* Conversion done! */
}
/* Convert a string into a long. Returns 1 if the string could be parsed into a /* Convert a string into a long. Returns 1 if the string could be parsed into a
* (non-overflowing) long, 0 otherwise. The value will be set to the parsed * (non-overflowing) long, 0 otherwise. The value will be set to the parsed
* value when appropriate. */ * value when appropriate. */
...@@ -468,6 +488,27 @@ int string2ld(const char *s, size_t slen, long double *dp) { ...@@ -468,6 +488,27 @@ int string2ld(const char *s, size_t slen, long double *dp) {
return 1; return 1;
} }
/* Convert a string into a double. Returns 1 if the string could be parsed
* into a (non-overflowing) double, 0 otherwise. The value will be set to
* the parsed value when appropriate.
*
* Note that this function demands that the string strictly represents
* a double: no spaces or other characters before or after the string
* representing the number are accepted. */
int string2d(const char *s, size_t slen, double *dp) {
errno = 0;
char *eptr;
*dp = strtod(s, &eptr);
if (slen == 0 ||
isspace(((const char*)s)[0]) ||
(size_t)(eptr-(char*)s) != slen ||
(errno == ERANGE &&
(*dp == HUGE_VAL || *dp == -HUGE_VAL || *dp == 0)) ||
isnan(*dp))
return 0;
return 1;
}
/* Convert a double to a string representation. Returns the number of bytes /* Convert a double to a string representation. Returns the number of bytes
* required. The representation should always be parsable by strtod(3). * required. The representation should always be parsable by strtod(3).
* This function does not support human-friendly formatting like ld2string * This function does not support human-friendly formatting like ld2string
...@@ -510,15 +551,17 @@ int d2string(char *buf, size_t len, double value) { ...@@ -510,15 +551,17 @@ int d2string(char *buf, size_t len, double value) {
return len; return len;
} }
/* Convert a long double into a string. If humanfriendly is non-zero /* Create a string object from a long double.
* it does not use exponential format and trims trailing zeroes at the end, * If mode is humanfriendly it does not use exponential format and trims trailing
* however this results in loss of precision. Otherwise exp format is used * zeroes at the end (may result in loss of precision).
* and the output of snprintf() is not modified. * If mode is default exp format is used and the output of snprintf()
* is not modified (may result in loss of precision).
* If mode is hex hexadecimal format is used (no loss of precision)
* *
* The function returns the length of the string or zero if there was not * The function returns the length of the string or zero if there was not
* enough buffer room to store it. */ * enough buffer room to store it. */
int ld2string(char *buf, size_t len, long double value, int humanfriendly) { int ld2string(char *buf, size_t len, long double value, ld2string_mode mode) {
size_t l; size_t l = 0;
if (isinf(value)) { if (isinf(value)) {
/* Libc in odd systems (Hi Solaris!) will format infinite in a /* Libc in odd systems (Hi Solaris!) will format infinite in a
...@@ -531,13 +574,23 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) { ...@@ -531,13 +574,23 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) {
memcpy(buf,"-inf",4); memcpy(buf,"-inf",4);
l = 4; l = 4;
} }
} else if (humanfriendly) { } else {
switch (mode) {
case LD_STR_AUTO:
l = snprintf(buf,len,"%.17Lg",value);
if (l+1 > len) return 0; /* No room. */
break;
case LD_STR_HEX:
l = snprintf(buf,len,"%La",value);
if (l+1 > len) return 0; /* No room. */
break;
case LD_STR_HUMAN:
/* We use 17 digits precision since with 128 bit floats that precision /* We use 17 digits precision since with 128 bit floats that precision
* after rounding is able to represent most small decimal numbers in a * after rounding is able to represent most small decimal numbers in a
* way that is "non surprising" for the user (that is, most small * way that is "non surprising" for the user (that is, most small
* decimal numbers will be represented in a way that when converted * decimal numbers will be represented in a way that when converted
* back into a string are exactly the same as what the user typed.) */ * back into a string are exactly the same as what the user typed.) */
l = snprintf(buf,len,"%.17Lf", value); l = snprintf(buf,len,"%.17Lf",value);
if (l+1 > len) return 0; /* No room. */ if (l+1 > len) return 0; /* No room. */
/* Now remove trailing zeroes after the '.' */ /* Now remove trailing zeroes after the '.' */
if (strchr(buf,'.') != NULL) { if (strchr(buf,'.') != NULL) {
...@@ -548,9 +601,9 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) { ...@@ -548,9 +601,9 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) {
} }
if (*p == '.') l--; if (*p == '.') l--;
} }
} else { break;
l = snprintf(buf,len,"%.17Lg", value); default: return 0; /* Invalid mode. */
if (l+1 > len) return 0; /* No room. */ }
} }
buf[l] = '\0'; buf[l] = '\0';
return l; return l;
......
...@@ -38,6 +38,13 @@ ...@@ -38,6 +38,13 @@
* This should be the size of the buffer given to ld2string */ * This should be the size of the buffer given to ld2string */
#define MAX_LONG_DOUBLE_CHARS 5*1024 #define MAX_LONG_DOUBLE_CHARS 5*1024
/* long double to string convertion options */
typedef enum {
LD_STR_AUTO, /* %.17Lg */
LD_STR_HUMAN, /* %.17Lf + Trimming of trailing zeros */
LD_STR_HEX /* %La */
} ld2string_mode;
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase); int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
int stringmatch(const char *p, const char *s, int nocase); int stringmatch(const char *p, const char *s, int nocase);
int stringmatchlen_fuzz_test(void); int stringmatchlen_fuzz_test(void);
...@@ -46,10 +53,12 @@ uint32_t digits10(uint64_t v); ...@@ -46,10 +53,12 @@ uint32_t digits10(uint64_t v);
uint32_t sdigits10(int64_t v); uint32_t sdigits10(int64_t v);
int ll2string(char *s, size_t len, long long value); int ll2string(char *s, size_t len, long long value);
int string2ll(const char *s, size_t slen, long long *value); int string2ll(const char *s, size_t slen, long long *value);
int string2ull(const char *s, unsigned long long *value);
int string2l(const char *s, size_t slen, long *value); int string2l(const char *s, size_t slen, long *value);
int string2ld(const char *s, size_t slen, long double *dp); int string2ld(const char *s, size_t slen, long double *dp);
int string2d(const char *s, size_t slen, double *dp);
int d2string(char *buf, size_t len, double value); int d2string(char *buf, size_t len, double value);
int ld2string(char *buf, size_t len, long double value, int humanfriendly); int ld2string(char *buf, size_t len, long double value, ld2string_mode mode);
sds getAbsolutePath(char *filename); sds getAbsolutePath(char *filename);
unsigned long getTimeZone(void); unsigned long getTimeZone(void);
int pathIsBaseName(char *path); int pathIsBaseName(char *path);
......
...@@ -294,6 +294,26 @@ size_t zmalloc_get_rss(void) { ...@@ -294,6 +294,26 @@ size_t zmalloc_get_rss(void) {
return t_info.resident_size; return t_info.resident_size;
} }
#elif defined(__FreeBSD__)
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <unistd.h>
size_t zmalloc_get_rss(void) {
struct kinfo_proc info;
size_t infolen = sizeof(info);
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
if (sysctl(mib, 4, &info, &infolen, NULL, 0) == 0)
return (size_t)info.ki_rssize;
return 0L;
}
#else #else
size_t zmalloc_get_rss(void) { size_t zmalloc_get_rss(void) {
/* If we can't get the RSS in an OS-specific way for this system just /* If we can't get the RSS in an OS-specific way for this system just
...@@ -306,6 +326,7 @@ size_t zmalloc_get_rss(void) { ...@@ -306,6 +326,7 @@ size_t zmalloc_get_rss(void) {
#endif #endif
#if defined(USE_JEMALLOC) #if defined(USE_JEMALLOC)
int zmalloc_get_allocator_info(size_t *allocated, int zmalloc_get_allocator_info(size_t *allocated,
size_t *active, size_t *active,
size_t *resident) { size_t *resident) {
...@@ -327,13 +348,52 @@ int zmalloc_get_allocator_info(size_t *allocated, ...@@ -327,13 +348,52 @@ int zmalloc_get_allocator_info(size_t *allocated,
je_mallctl("stats.allocated", allocated, &sz, NULL, 0); je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
return 1; return 1;
} }
void set_jemalloc_bg_thread(int enable) {
/* let jemalloc do purging asynchronously, required when there's no traffic
* after flushdb */
char val = !!enable;
je_mallctl("background_thread", NULL, 0, &val, 1);
}
int jemalloc_purge() {
/* return all unused (reserved) pages to the OS */
char tmp[32];
unsigned narenas = 0;
size_t sz = sizeof(unsigned);
if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, 0)) {
sprintf(tmp, "arena.%d.purge", narenas);
if (!je_mallctl(tmp, NULL, 0, NULL, 0))
return 0;
}
return -1;
}
#else #else
int zmalloc_get_allocator_info(size_t *allocated, int zmalloc_get_allocator_info(size_t *allocated,
size_t *active, size_t *active,
size_t *resident) { size_t *resident) {
*allocated = *resident = *active = 0; *allocated = *resident = *active = 0;
return 1; return 1;
} }
void set_jemalloc_bg_thread(int enable) {
((void)(enable));
}
int jemalloc_purge() {
return 0;
}
#endif
#if defined(__APPLE__)
/* For proc_pidinfo() used later in zmalloc_get_smap_bytes_by_field().
* Note that this file cannot be included in zmalloc.h because it includes
* a Darwin queue.h file where there is a "LIST_HEAD" macro (!) defined
* conficting with Redis user code. */
#include <libproc.h>
#endif #endif
/* Get the sum of the specified field (converted form kb to bytes) in /* Get the sum of the specified field (converted form kb to bytes) in
...@@ -375,7 +435,28 @@ size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) { ...@@ -375,7 +435,28 @@ size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
return bytes; return bytes;
} }
#else #else
/* Get sum of the specified field from libproc api call.
* As there are per page value basis we need to convert
* them accordingly.
*
* Note that AnonHugePages is a no-op as THP feature
* is not supported in this platform
*/
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) { size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
#if defined(__APPLE__)
struct proc_regioninfo pri;
if (proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri, PROC_PIDREGIONINFO_SIZE) ==
PROC_PIDREGIONINFO_SIZE) {
if (!strcmp(field, "Private_Dirty:")) {
return (size_t)pri.pri_pages_dirtied * 4096;
} else if (!strcmp(field, "Rss:")) {
return (size_t)pri.pri_pages_resident * 4096;
} else if (!strcmp(field, "AnonHugePages:")) {
return 0;
}
}
return 0;
#endif
((void) field); ((void) field);
((void) pid); ((void) pid);
return 0; return 0;
......
...@@ -86,6 +86,8 @@ size_t zmalloc_used_memory(void); ...@@ -86,6 +86,8 @@ size_t zmalloc_used_memory(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
size_t zmalloc_get_rss(void); size_t zmalloc_get_rss(void);
int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident); int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident);
void set_jemalloc_bg_thread(int enable);
int jemalloc_purge();
size_t zmalloc_get_private_dirty(long pid); size_t zmalloc_get_private_dirty(long pid);
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid); size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);
size_t zmalloc_get_memory_size(void); size_t zmalloc_get_memory_size(void);
......
...@@ -8,6 +8,7 @@ source ../instances.tcl ...@@ -8,6 +8,7 @@ source ../instances.tcl
source ../../support/cluster.tcl ; # Redis Cluster client. source ../../support/cluster.tcl ; # Redis Cluster client.
set ::instances_count 20 ; # How many instances we use at max. set ::instances_count 20 ; # How many instances we use at max.
set ::tlsdir "../../tls"
proc main {} { proc main {} {
parse_options parse_options
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
# are preseved across iterations. # are preseved across iterations.
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
source "../../../tests/support/cli.tcl"
test "Create a 5 nodes cluster" { test "Create a 5 nodes cluster" {
create_cluster 5 5 create_cluster 5 5
...@@ -79,6 +80,7 @@ test "Cluster consistency during live resharding" { ...@@ -79,6 +80,7 @@ test "Cluster consistency during live resharding" {
--cluster-to $target \ --cluster-to $target \
--cluster-slots 100 \ --cluster-slots 100 \
--cluster-yes \ --cluster-yes \
{*}[rediscli_tls_config "../../../tests"] \
| [info nameofexecutable] \ | [info nameofexecutable] \
../tests/helpers/onlydots.tcl \ ../tests/helpers/onlydots.tcl \
&] 0] &] 0]
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
# other masters have slaves. # other masters have slaves.
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
source "../../../tests/support/cli.tcl"
# Create a cluster with 5 master and 15 slaves, to make sure there are no # Create a cluster with 5 master and 15 slaves, to make sure there are no
# empty masters and make rebalancing simpler to handle during the test. # empty masters and make rebalancing simpler to handle during the test.
...@@ -33,7 +34,9 @@ test "Resharding all the master #0 slots away from it" { ...@@ -33,7 +34,9 @@ test "Resharding all the master #0 slots away from it" {
set output [exec \ set output [exec \
../../../src/redis-cli --cluster rebalance \ ../../../src/redis-cli --cluster rebalance \
127.0.0.1:[get_instance_attrib redis 0 port] \ 127.0.0.1:[get_instance_attrib redis 0 port] \
{*}[rediscli_tls_config "../../../tests"] \
--cluster-weight ${master0_id}=0 >@ stdout ] --cluster-weight ${master0_id}=0 >@ stdout ]
} }
test "Master #0 should lose its replicas" { test "Master #0 should lose its replicas" {
...@@ -51,6 +54,7 @@ test "Resharding back some slot to master #0" { ...@@ -51,6 +54,7 @@ test "Resharding back some slot to master #0" {
set output [exec \ set output [exec \
../../../src/redis-cli --cluster rebalance \ ../../../src/redis-cli --cluster rebalance \
127.0.0.1:[get_instance_attrib redis 0 port] \ 127.0.0.1:[get_instance_attrib redis 0 port] \
{*}[rediscli_tls_config "../../../tests"] \
--cluster-weight ${master0_id}=.01 \ --cluster-weight ${master0_id}=.01 \
--cluster-use-empty-masters >@ stdout] --cluster-use-empty-masters >@ stdout]
} }
......
source tests/support/redis.tcl source tests/support/redis.tcl
source tests/support/util.tcl source tests/support/util.tcl
set ::tlsdir "tests/tls"
# This function sometimes writes sometimes blocking-reads from lists/sorted # This function sometimes writes sometimes blocking-reads from lists/sorted
# sets. There are multiple processes like this executing at the same time # sets. There are multiple processes like this executing at the same time
# so that we have some chance to trap some corner condition if there is # so that we have some chance to trap some corner condition if there is
...@@ -8,8 +10,8 @@ source tests/support/util.tcl ...@@ -8,8 +10,8 @@ source tests/support/util.tcl
# space to just a few elements, and balance the operations so that it is # space to just a few elements, and balance the operations so that it is
# unlikely that lists and zsets just get more data without ever causing # unlikely that lists and zsets just get more data without ever causing
# blocking. # blocking.
proc bg_block_op {host port db ops} { proc bg_block_op {host port db ops tls} {
set r [redis $host $port] set r [redis $host $port 0 $tls]
$r select $db $r select $db
for {set j 0} {$j < $ops} {incr j} { for {set j 0} {$j < $ops} {incr j} {
...@@ -49,4 +51,4 @@ proc bg_block_op {host port db ops} { ...@@ -49,4 +51,4 @@ proc bg_block_op {host port db ops} {
} }
} }
bg_block_op [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] bg_block_op [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]
source tests/support/redis.tcl source tests/support/redis.tcl
source tests/support/util.tcl source tests/support/util.tcl
proc bg_complex_data {host port db ops} { set ::tlsdir "tests/tls"
set r [redis $host $port]
proc bg_complex_data {host port db ops tls} {
set r [redis $host $port 0 $tls]
$r select $db $r select $db
createComplexDataset $r $ops createComplexDataset $r $ops
} }
bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]
source tests/support/redis.tcl source tests/support/redis.tcl
proc gen_write_load {host port seconds} { set ::tlsdir "tests/tls"
proc gen_write_load {host port seconds tls} {
set start_time [clock seconds] set start_time [clock seconds]
set r [redis $host $port 1] set r [redis $host $port 1 $tls]
$r select 9 $r select 9
while 1 { while 1 {
$r set [expr rand()] [expr rand()] $r set [expr rand()] [expr rand()]
...@@ -12,4 +14,4 @@ proc gen_write_load {host port seconds} { ...@@ -12,4 +14,4 @@ proc gen_write_load {host port seconds} {
} }
} }
gen_write_load [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] gen_write_load [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3]
...@@ -17,6 +17,7 @@ source ../support/test.tcl ...@@ -17,6 +17,7 @@ source ../support/test.tcl
set ::verbose 0 set ::verbose 0
set ::valgrind 0 set ::valgrind 0
set ::tls 0
set ::pause_on_error 0 set ::pause_on_error 0
set ::simulate_error 0 set ::simulate_error 0
set ::failed 0 set ::failed 0
...@@ -69,7 +70,19 @@ proc spawn_instance {type base_port count {conf {}}} { ...@@ -69,7 +70,19 @@ proc spawn_instance {type base_port count {conf {}}} {
# Write the instance config file. # Write the instance config file.
set cfgfile [file join $dirname $type.conf] set cfgfile [file join $dirname $type.conf]
set cfg [open $cfgfile w] set cfg [open $cfgfile w]
if {$::tls} {
puts $cfg "tls-port $port"
puts $cfg "tls-replication yes"
puts $cfg "tls-cluster yes"
puts $cfg "port 0"
puts $cfg [format "tls-cert-file %s/../../tls/redis.crt" [pwd]]
puts $cfg [format "tls-key-file %s/../../tls/redis.key" [pwd]]
puts $cfg [format "tls-dh-params-file %s/../../tls/redis.dh" [pwd]]
puts $cfg [format "tls-ca-cert-file %s/../../tls/ca.crt" [pwd]]
puts $cfg "loglevel debug"
} else {
puts $cfg "port $port" puts $cfg "port $port"
}
puts $cfg "dir ./$dirname" puts $cfg "dir ./$dirname"
puts $cfg "logfile log.txt" puts $cfg "logfile log.txt"
# Add additional config files # Add additional config files
...@@ -88,7 +101,7 @@ proc spawn_instance {type base_port count {conf {}}} { ...@@ -88,7 +101,7 @@ proc spawn_instance {type base_port count {conf {}}} {
} }
# Push the instance into the right list # Push the instance into the right list
set link [redis 127.0.0.1 $port] set link [redis 127.0.0.1 $port 0 $::tls]
$link reconnect 1 $link reconnect 1
lappend ::${type}_instances [list \ lappend ::${type}_instances [list \
pid $pid \ pid $pid \
...@@ -148,6 +161,13 @@ proc parse_options {} { ...@@ -148,6 +161,13 @@ proc parse_options {} {
set ::simulate_error 1 set ::simulate_error 1
} elseif {$opt eq {--valgrind}} { } elseif {$opt eq {--valgrind}} {
set ::valgrind 1 set ::valgrind 1
} elseif {$opt eq {--tls}} {
package require tls 1.6
::tls::init \
-cafile "$::tlsdir/ca.crt" \
-certfile "$::tlsdir/redis.crt" \
-keyfile "$::tlsdir/redis.key"
set ::tls 1
} elseif {$opt eq "--help"} { } elseif {$opt eq "--help"} {
puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests." puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests."
puts "\nOptions:" puts "\nOptions:"
...@@ -492,7 +512,7 @@ proc restart_instance {type id} { ...@@ -492,7 +512,7 @@ proc restart_instance {type id} {
} }
# Connect with it with a fresh link # Connect with it with a fresh link
set link [redis 127.0.0.1 $port] set link [redis 127.0.0.1 $port 0 $::tls]
$link reconnect 1 $link reconnect 1
set_instance_attrib $type $id link $link set_instance_attrib $type $id link $link
......
...@@ -13,8 +13,9 @@ tags {"aof"} { ...@@ -13,8 +13,9 @@ tags {"aof"} {
# cleaned after a child responsible for an AOF rewrite exited. This buffer # cleaned after a child responsible for an AOF rewrite exited. This buffer
# was subsequently appended to the new AOF, resulting in duplicate commands. # was subsequently appended to the new AOF, resulting in duplicate commands.
start_server_aof [list dir $server_path] { start_server_aof [list dir $server_path] {
set client [redis [srv host] [srv port]] set client [redis [srv host] [srv port] 0 $::tls]
set bench [open "|src/redis-benchmark -q -p [srv port] -c 20 -n 20000 incr foo" "r+"] set bench [open "|src/redis-benchmark -q -s [srv unixsocket] -c 20 -n 20000 incr foo" "r+"]
after 100 after 100
# Benchmark should be running by now: start background rewrite # Benchmark should be running by now: start background rewrite
...@@ -29,7 +30,7 @@ tags {"aof"} { ...@@ -29,7 +30,7 @@ tags {"aof"} {
# Restart server to replay AOF # Restart server to replay AOF
start_server_aof [list dir $server_path] { start_server_aof [list dir $server_path] {
set client [redis [srv host] [srv port]] set client [redis [srv host] [srv port] 0 $::tls]
assert_equal 20000 [$client get foo] assert_equal 20000 [$client get foo]
} }
} }
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment