TLS Support for redis-benchmark (#7959)
This commit is contained in:
parent
66037309c6
commit
39436b2152
@ -257,9 +257,9 @@ REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
|
|||||||
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
|
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
|
||||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o
|
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o
|
||||||
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
|
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
|
||||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o
|
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o
|
||||||
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
|
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
|
||||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o release.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o
|
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o release.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o
|
||||||
REDIS_CHECK_RDB_NAME=redis-check-rdb$(PROG_SUFFIX)
|
REDIS_CHECK_RDB_NAME=redis-check-rdb$(PROG_SUFFIX)
|
||||||
REDIS_CHECK_AOF_NAME=redis-check-aof$(PROG_SUFFIX)
|
REDIS_CHECK_AOF_NAME=redis-check-aof$(PROG_SUFFIX)
|
||||||
|
|
||||||
|
185
src/cli_common.c
Normal file
185
src/cli_common.c
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/* CLI (command line interface) common methods
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, 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 "cli_common.h"
|
||||||
|
#include <errno.h>
|
||||||
|
#include <hiredis.h>
|
||||||
|
#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
|
||||||
|
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <hiredis_ssl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
|
||||||
|
* not building with TLS support.
|
||||||
|
*/
|
||||||
|
int cliSecureConnection(redisContext *c, cliSSLconfig config, const char **err) {
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
static SSL_CTX *ssl_ctx = NULL;
|
||||||
|
|
||||||
|
if (!ssl_ctx) {
|
||||||
|
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
||||||
|
if (!ssl_ctx) {
|
||||||
|
*err = "Failed to create SSL_CTX";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||||
|
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
|
||||||
|
|
||||||
|
if (config.cacert || config.cacertdir) {
|
||||||
|
if (!SSL_CTX_load_verify_locations(ssl_ctx, config.cacert, config.cacertdir)) {
|
||||||
|
*err = "Invalid CA Certificate File/Directory";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) {
|
||||||
|
*err = "Failed to use default CA paths";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.cert && !SSL_CTX_use_certificate_chain_file(ssl_ctx, config.cert)) {
|
||||||
|
*err = "Invalid client certificate";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.key && !SSL_CTX_use_PrivateKey_file(ssl_ctx, config.key, SSL_FILETYPE_PEM)) {
|
||||||
|
*err = "Invalid private key";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL *ssl = SSL_new(ssl_ctx);
|
||||||
|
if (!ssl) {
|
||||||
|
*err = "Failed to create SSL object";
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.sni && !SSL_set_tlsext_host_name(ssl, config.sni)) {
|
||||||
|
*err = "Failed to configure SNI";
|
||||||
|
SSL_free(ssl);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return redisInitiateSSL(c, ssl);
|
||||||
|
|
||||||
|
error:
|
||||||
|
SSL_CTX_free(ssl_ctx);
|
||||||
|
ssl_ctx = NULL;
|
||||||
|
return REDIS_ERR;
|
||||||
|
#else
|
||||||
|
(void) config;
|
||||||
|
(void) c;
|
||||||
|
(void) err;
|
||||||
|
return REDIS_OK;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wrapper around hiredis to allow arbitrary reads and writes.
|
||||||
|
*
|
||||||
|
* We piggybacks on top of hiredis to achieve transparent TLS support,
|
||||||
|
* and use its internal buffers so it can co-exist with commands
|
||||||
|
* previously/later issued on the connection.
|
||||||
|
*
|
||||||
|
* Interface is close to enough to read()/write() so things should mostly
|
||||||
|
* work transparently.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Write a raw buffer through a redisContext. If we already have something
|
||||||
|
* in the buffer (leftovers from hiredis operations) it will be written
|
||||||
|
* as well.
|
||||||
|
*/
|
||||||
|
ssize_t cliWriteConn(redisContext *c, const char *buf, size_t buf_len)
|
||||||
|
{
|
||||||
|
int done = 0;
|
||||||
|
|
||||||
|
/* Append data to buffer which is *usually* expected to be empty
|
||||||
|
* but we don't assume that, and write.
|
||||||
|
*/
|
||||||
|
c->obuf = sdscatlen(c->obuf, buf, buf_len);
|
||||||
|
if (redisBufferWrite(c, &done) == REDIS_ERR) {
|
||||||
|
if (!(c->flags & REDIS_BLOCK))
|
||||||
|
errno = EAGAIN;
|
||||||
|
|
||||||
|
/* On error, we assume nothing was written and we roll back the
|
||||||
|
* buffer to its original state.
|
||||||
|
*/
|
||||||
|
if (sdslen(c->obuf) > buf_len)
|
||||||
|
sdsrange(c->obuf, 0, -(buf_len+1));
|
||||||
|
else
|
||||||
|
sdsclear(c->obuf);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we're done, free up everything. We may have written more than
|
||||||
|
* buf_len (if c->obuf was not initially empty) but we don't have to
|
||||||
|
* tell.
|
||||||
|
*/
|
||||||
|
if (done) {
|
||||||
|
sdsclear(c->obuf);
|
||||||
|
return buf_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write was successful but we have some leftovers which we should
|
||||||
|
* remove from the buffer.
|
||||||
|
*
|
||||||
|
* Do we still have data that was there prior to our buf? If so,
|
||||||
|
* restore buffer to it's original state and report no new data was
|
||||||
|
* writen.
|
||||||
|
*/
|
||||||
|
if (sdslen(c->obuf) > buf_len) {
|
||||||
|
sdsrange(c->obuf, 0, -(buf_len+1));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* At this point we're sure no prior data is left. We flush the buffer
|
||||||
|
* and report how much we've written.
|
||||||
|
*/
|
||||||
|
size_t left = sdslen(c->obuf);
|
||||||
|
sdsclear(c->obuf);
|
||||||
|
return buf_len - left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wrapper around OpenSSL (libssl and libcrypto) initialisation
|
||||||
|
*/
|
||||||
|
int cliSecureInit()
|
||||||
|
{
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
ERR_load_crypto_strings();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
SSL_library_init();
|
||||||
|
#endif
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
44
src/cli_common.h
Normal file
44
src/cli_common.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#ifndef __CLICOMMON_H
|
||||||
|
#define __CLICOMMON_H
|
||||||
|
|
||||||
|
#include <hiredis.h>
|
||||||
|
|
||||||
|
typedef struct cliSSLconfig {
|
||||||
|
/* Requested SNI, or NULL */
|
||||||
|
char *sni;
|
||||||
|
/* CA Certificate file, or NULL */
|
||||||
|
char *cacert;
|
||||||
|
/* Directory where trusted CA certificates are stored, or NULL */
|
||||||
|
char *cacertdir;
|
||||||
|
/* Client certificate to authenticate with, or NULL */
|
||||||
|
char *cert;
|
||||||
|
/* Private key file to authenticate with, or NULL */
|
||||||
|
char *key;
|
||||||
|
} cliSSLconfig;
|
||||||
|
|
||||||
|
/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
|
||||||
|
* not building with TLS support.
|
||||||
|
*/
|
||||||
|
int cliSecureConnection(redisContext *c, cliSSLconfig config, const char **err);
|
||||||
|
|
||||||
|
/* Wrapper around hiredis to allow arbitrary reads and writes.
|
||||||
|
*
|
||||||
|
* We piggybacks on top of hiredis to achieve transparent TLS support,
|
||||||
|
* and use its internal buffers so it can co-exist with commands
|
||||||
|
* previously/later issued on the connection.
|
||||||
|
*
|
||||||
|
* Interface is close to enough to read()/write() so things should mostly
|
||||||
|
* work transparently.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Write a raw buffer through a redisContext. If we already have something
|
||||||
|
* in the buffer (leftovers from hiredis operations) it will be written
|
||||||
|
* as well.
|
||||||
|
*/
|
||||||
|
ssize_t cliWriteConn(redisContext *c, const char *buf, size_t buf_len);
|
||||||
|
|
||||||
|
/* Wrapper around OpenSSL (libssl and libcrypto) initialisation.
|
||||||
|
*/
|
||||||
|
int cliSecureInit();
|
||||||
|
|
||||||
|
#endif /* __CLICOMMON_H */
|
@ -46,13 +46,19 @@
|
|||||||
#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
|
#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
|
||||||
#include <sds.h> /* Use hiredis sds. */
|
#include <sds.h> /* Use hiredis sds. */
|
||||||
#include "ae.h"
|
#include "ae.h"
|
||||||
#include "hiredis.h"
|
#include <hiredis.h>
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <hiredis_ssl.h>
|
||||||
|
#endif
|
||||||
#include "adlist.h"
|
#include "adlist.h"
|
||||||
#include "dict.h"
|
#include "dict.h"
|
||||||
#include "zmalloc.h"
|
#include "zmalloc.h"
|
||||||
#include "atomicvar.h"
|
#include "atomicvar.h"
|
||||||
#include "crc16_slottable.h"
|
#include "crc16_slottable.h"
|
||||||
#include "hdr_histogram.h"
|
#include "hdr_histogram.h"
|
||||||
|
#include "cli_common.h"
|
||||||
|
|
||||||
#define UNUSED(V) ((void) V)
|
#define UNUSED(V) ((void) V)
|
||||||
#define RANDPTR_INITIAL_SIZE 8
|
#define RANDPTR_INITIAL_SIZE 8
|
||||||
@ -76,6 +82,8 @@ static struct config {
|
|||||||
const char *hostip;
|
const char *hostip;
|
||||||
int hostport;
|
int hostport;
|
||||||
const char *hostsocket;
|
const char *hostsocket;
|
||||||
|
int tls;
|
||||||
|
struct cliSSLconfig sslconfig;
|
||||||
int numclients;
|
int numclients;
|
||||||
redisAtomic int liveclients;
|
redisAtomic int liveclients;
|
||||||
int requests;
|
int requests;
|
||||||
@ -280,6 +288,13 @@ static redisContext *getRedisContext(const char *ip, int port,
|
|||||||
fprintf(stderr,"%s: %s\n",hostsocket,err);
|
fprintf(stderr,"%s: %s\n",hostsocket,err);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
if (config.tls==1) {
|
||||||
|
const char *err = NULL;
|
||||||
|
if (cliSecureConnection(ctx, config.sslconfig, &err) == REDIS_ERR && err) {
|
||||||
|
fprintf(stderr, "Could not negotiate a TLS connection: %s\n", err);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (config.auth == NULL)
|
if (config.auth == NULL)
|
||||||
return ctx;
|
return ctx;
|
||||||
if (config.user == NULL)
|
if (config.user == NULL)
|
||||||
@ -308,6 +323,8 @@ cleanup:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static redisConfig *getRedisConfig(const char *ip, int port,
|
static redisConfig *getRedisConfig(const char *ip, int port,
|
||||||
const char *hostsocket)
|
const char *hostsocket)
|
||||||
{
|
{
|
||||||
@ -606,19 +623,26 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
c->start = ustime();
|
c->start = ustime();
|
||||||
c->latency = -1;
|
c->latency = -1;
|
||||||
}
|
}
|
||||||
if (sdslen(c->obuf) > c->written) {
|
const ssize_t buflen = sdslen(c->obuf);
|
||||||
|
const ssize_t writeLen = buflen-c->written;
|
||||||
|
if (writeLen > 0) {
|
||||||
void *ptr = c->obuf+c->written;
|
void *ptr = c->obuf+c->written;
|
||||||
ssize_t nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written);
|
while(1) {
|
||||||
if (nwritten == -1) {
|
/* Optimistically try to write before checking if the file descriptor
|
||||||
|
* is actually writable. At worst we get EAGAIN. */
|
||||||
|
const ssize_t nwritten = cliWriteConn(c->context,ptr,writeLen);
|
||||||
|
if (nwritten != writeLen) {
|
||||||
|
if (nwritten == -1 && errno != EAGAIN) {
|
||||||
if (errno != EPIPE)
|
if (errno != EPIPE)
|
||||||
fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
|
fprintf(stderr, "Error writing to the server: %s\n", strerror(errno));
|
||||||
freeClient(c);
|
freeClient(c);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
c->written += nwritten;
|
} else {
|
||||||
if (sdslen(c->obuf) == c->written) {
|
|
||||||
aeDeleteFileEvent(el,c->context->fd,AE_WRITABLE);
|
aeDeleteFileEvent(el,c->context->fd,AE_WRITABLE);
|
||||||
aeCreateFileEvent(el,c->context->fd,AE_READABLE,readHandler,c);
|
aeCreateFileEvent(el,c->context->fd,AE_READABLE,readHandler,c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -680,6 +704,13 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
|
|||||||
fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr);
|
fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
if (config.tls==1) {
|
||||||
|
const char *err = NULL;
|
||||||
|
if (cliSecureConnection(c->context, config.sslconfig, &err) == REDIS_ERR && err) {
|
||||||
|
fprintf(stderr, "Could not negotiate a TLS connection: %s\n", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
c->thread_id = thread_id;
|
c->thread_id = thread_id;
|
||||||
/* Suppress hiredis cleanup of unused buffers for max speed. */
|
/* Suppress hiredis cleanup of unused buffers for max speed. */
|
||||||
c->context->reader->maxbuf = 0;
|
c->context->reader->maxbuf = 0;
|
||||||
@ -1471,6 +1502,25 @@ int parseOptions(int argc, const char **argv) {
|
|||||||
} else if (!strcmp(argv[i],"--help")) {
|
} else if (!strcmp(argv[i],"--help")) {
|
||||||
exit_status = 0;
|
exit_status = 0;
|
||||||
goto usage;
|
goto usage;
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
} else if (!strcmp(argv[i],"--tls")) {
|
||||||
|
config.tls = 1;
|
||||||
|
} else if (!strcmp(argv[i],"--sni")) {
|
||||||
|
if (lastarg) goto invalid;
|
||||||
|
config.sslconfig.sni = strdup(argv[++i]);
|
||||||
|
} else if (!strcmp(argv[i],"--cacertdir")) {
|
||||||
|
if (lastarg) goto invalid;
|
||||||
|
config.sslconfig.cacertdir = strdup(argv[++i]);
|
||||||
|
} else if (!strcmp(argv[i],"--cacert")) {
|
||||||
|
if (lastarg) goto invalid;
|
||||||
|
config.sslconfig.cacert = strdup(argv[++i]);
|
||||||
|
} else if (!strcmp(argv[i],"--cert")) {
|
||||||
|
if (lastarg) goto invalid;
|
||||||
|
config.sslconfig.cert = strdup(argv[++i]);
|
||||||
|
} else if (!strcmp(argv[i],"--key")) {
|
||||||
|
if (lastarg) goto invalid;
|
||||||
|
config.sslconfig.key = strdup(argv[++i]);
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
/* Assume the user meant to provide an option when the arg starts
|
/* Assume the user meant to provide an option when the arg starts
|
||||||
* with a dash. We're done otherwise and should use the remainder
|
* with a dash. We're done otherwise and should use the remainder
|
||||||
@ -1518,6 +1568,16 @@ usage:
|
|||||||
" -t <tests> Only run the comma separated list of tests. The test\n"
|
" -t <tests> Only run the comma separated list of tests. The test\n"
|
||||||
" names are the same as the ones produced as output.\n"
|
" names are the same as the ones produced as output.\n"
|
||||||
" -I Idle mode. Just open N idle connections and wait.\n"
|
" -I Idle mode. Just open N idle connections and wait.\n"
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
" --tls Establish a secure TLS connection.\n"
|
||||||
|
" --sni <host> Server name indication for TLS.\n"
|
||||||
|
" --cacert <file> CA Certificate file to verify with.\n"
|
||||||
|
" --cacertdir <dir> Directory where trusted CA certificates are stored.\n"
|
||||||
|
" If neither cacert nor cacertdir are specified, the default\n"
|
||||||
|
" system-wide trusted root certs configuration will apply.\n"
|
||||||
|
" --cert <file> Client certificate to authenticate with.\n"
|
||||||
|
" --key <file> Private key file to authenticate with.\n"
|
||||||
|
#endif
|
||||||
" --help Output this help and exit.\n"
|
" --help Output this help and exit.\n"
|
||||||
" --version Output version and exit.\n\n"
|
" --version Output version and exit.\n\n"
|
||||||
"Examples:\n\n"
|
"Examples:\n\n"
|
||||||
@ -1642,6 +1702,12 @@ int main(int argc, const char **argv) {
|
|||||||
|
|
||||||
tag = "";
|
tag = "";
|
||||||
|
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
if (config.tls) {
|
||||||
|
cliSecureInit();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (config.cluster_mode) {
|
if (config.cluster_mode) {
|
||||||
// We only include the slot placeholder {tag} if cluster mode is enabled
|
// We only include the slot placeholder {tag} if cluster mode is enabled
|
||||||
tag = ":{tag}";
|
tag = ":{tag}";
|
||||||
|
162
src/redis-cli.c
162
src/redis-cli.c
@ -61,6 +61,7 @@
|
|||||||
#include "help.h"
|
#include "help.h"
|
||||||
#include "anet.h"
|
#include "anet.h"
|
||||||
#include "ae.h"
|
#include "ae.h"
|
||||||
|
#include "cli_common.h"
|
||||||
|
|
||||||
#define UNUSED(V) ((void) V)
|
#define UNUSED(V) ((void) V)
|
||||||
|
|
||||||
@ -199,11 +200,7 @@ static struct config {
|
|||||||
int hostport;
|
int hostport;
|
||||||
char *hostsocket;
|
char *hostsocket;
|
||||||
int tls;
|
int tls;
|
||||||
char *sni;
|
cliSSLconfig sslconfig;
|
||||||
char *cacert;
|
|
||||||
char *cacertdir;
|
|
||||||
char *cert;
|
|
||||||
char *key;
|
|
||||||
long repeat;
|
long repeat;
|
||||||
long interval;
|
long interval;
|
||||||
int dbnum;
|
int dbnum;
|
||||||
@ -788,71 +785,6 @@ static int cliSelect(void) {
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
|
|
||||||
* not building with TLS support.
|
|
||||||
*/
|
|
||||||
static int cliSecureConnection(redisContext *c, const char **err) {
|
|
||||||
#ifdef USE_OPENSSL
|
|
||||||
static SSL_CTX *ssl_ctx = NULL;
|
|
||||||
|
|
||||||
if (!ssl_ctx) {
|
|
||||||
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
|
||||||
if (!ssl_ctx) {
|
|
||||||
*err = "Failed to create SSL_CTX";
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
|
||||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
|
|
||||||
|
|
||||||
if (config.cacert || config.cacertdir) {
|
|
||||||
if (!SSL_CTX_load_verify_locations(ssl_ctx, config.cacert, config.cacertdir)) {
|
|
||||||
*err = "Invalid CA Certificate File/Directory";
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) {
|
|
||||||
*err = "Failed to use default CA paths";
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.cert && !SSL_CTX_use_certificate_chain_file(ssl_ctx, config.cert)) {
|
|
||||||
*err = "Invalid client certificate";
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.key && !SSL_CTX_use_PrivateKey_file(ssl_ctx, config.key, SSL_FILETYPE_PEM)) {
|
|
||||||
*err = "Invalid private key";
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL *ssl = SSL_new(ssl_ctx);
|
|
||||||
if (!ssl) {
|
|
||||||
*err = "Failed to create SSL object";
|
|
||||||
return REDIS_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.sni && !SSL_set_tlsext_host_name(ssl, config.sni)) {
|
|
||||||
*err = "Failed to configure SNI";
|
|
||||||
SSL_free(ssl);
|
|
||||||
return REDIS_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
return redisInitiateSSL(c, ssl);
|
|
||||||
|
|
||||||
error:
|
|
||||||
SSL_CTX_free(ssl_ctx);
|
|
||||||
ssl_ctx = NULL;
|
|
||||||
return REDIS_ERR;
|
|
||||||
#else
|
|
||||||
(void) c;
|
|
||||||
(void) err;
|
|
||||||
return REDIS_OK;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Select RESP3 mode if redis-cli was started with the -3 option. */
|
/* Select RESP3 mode if redis-cli was started with the -3 option. */
|
||||||
static int cliSwitchProto(void) {
|
static int cliSwitchProto(void) {
|
||||||
redisReply *reply;
|
redisReply *reply;
|
||||||
@ -886,7 +818,7 @@ static int cliConnect(int flags) {
|
|||||||
|
|
||||||
if (!context->err && config.tls) {
|
if (!context->err && config.tls) {
|
||||||
const char *err = NULL;
|
const char *err = NULL;
|
||||||
if (cliSecureConnection(context, &err) == REDIS_ERR && err) {
|
if (cliSecureConnection(context, config.sslconfig, &err) == REDIS_ERR && err) {
|
||||||
fprintf(stderr, "Could not negotiate a TLS connection: %s\n", err);
|
fprintf(stderr, "Could not negotiate a TLS connection: %s\n", err);
|
||||||
redisFree(context);
|
redisFree(context);
|
||||||
context = NULL;
|
context = NULL;
|
||||||
@ -1484,7 +1416,7 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
|
|||||||
c = redisConnect(config.hostip,config.hostport);
|
c = redisConnect(config.hostip,config.hostport);
|
||||||
if (!c->err && config.tls) {
|
if (!c->err && config.tls) {
|
||||||
const char *err = NULL;
|
const char *err = NULL;
|
||||||
if (cliSecureConnection(c, &err) == REDIS_ERR && err) {
|
if (cliSecureConnection(c, config.sslconfig, &err) == REDIS_ERR && err) {
|
||||||
fprintf(stderr, "TLS Error: %s\n", err);
|
fprintf(stderr, "TLS Error: %s\n", err);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -1700,15 +1632,15 @@ static int parseOptions(int argc, char **argv) {
|
|||||||
} else if (!strcmp(argv[i],"--tls")) {
|
} else if (!strcmp(argv[i],"--tls")) {
|
||||||
config.tls = 1;
|
config.tls = 1;
|
||||||
} else if (!strcmp(argv[i],"--sni") && !lastarg) {
|
} else if (!strcmp(argv[i],"--sni") && !lastarg) {
|
||||||
config.sni = argv[++i];
|
config.sslconfig.sni = argv[++i];
|
||||||
} else if (!strcmp(argv[i],"--cacertdir") && !lastarg) {
|
} else if (!strcmp(argv[i],"--cacertdir") && !lastarg) {
|
||||||
config.cacertdir = argv[++i];
|
config.sslconfig.cacertdir = argv[++i];
|
||||||
} else if (!strcmp(argv[i],"--cacert") && !lastarg) {
|
} else if (!strcmp(argv[i],"--cacert") && !lastarg) {
|
||||||
config.cacert = argv[++i];
|
config.sslconfig.cacert = argv[++i];
|
||||||
} else if (!strcmp(argv[i],"--cert") && !lastarg) {
|
} else if (!strcmp(argv[i],"--cert") && !lastarg) {
|
||||||
config.cert = argv[++i];
|
config.sslconfig.cert = argv[++i];
|
||||||
} else if (!strcmp(argv[i],"--key") && !lastarg) {
|
} else if (!strcmp(argv[i],"--key") && !lastarg) {
|
||||||
config.key = argv[++i];
|
config.sslconfig.key = argv[++i];
|
||||||
#endif
|
#endif
|
||||||
} else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
|
} else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
|
||||||
sds version = cliVersion();
|
sds version = cliVersion();
|
||||||
@ -2677,7 +2609,7 @@ static int clusterManagerNodeConnect(clusterManagerNode *node) {
|
|||||||
node->context = redisConnect(node->ip, node->port);
|
node->context = redisConnect(node->ip, node->port);
|
||||||
if (!node->context->err && config.tls) {
|
if (!node->context->err && config.tls) {
|
||||||
const char *err = NULL;
|
const char *err = NULL;
|
||||||
if (cliSecureConnection(node->context, &err) == REDIS_ERR && err) {
|
if (cliSecureConnection(node->context, config.sslconfig, &err) == REDIS_ERR && err) {
|
||||||
fprintf(stderr,"TLS Error: %s\n", err);
|
fprintf(stderr,"TLS Error: %s\n", err);
|
||||||
redisFree(node->context);
|
redisFree(node->context);
|
||||||
node->context = NULL;
|
node->context = NULL;
|
||||||
@ -6898,72 +6830,6 @@ void sendCapa() {
|
|||||||
sendReplconf("capa", "eof");
|
sendReplconf("capa", "eof");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wrapper around hiredis to allow arbitrary reads and writes.
|
|
||||||
*
|
|
||||||
* We piggybacks on top of hiredis to achieve transparent TLS support,
|
|
||||||
* and use its internal buffers so it can co-exist with commands
|
|
||||||
* previously/later issued on the connection.
|
|
||||||
*
|
|
||||||
* Interface is close to enough to read()/write() so things should mostly
|
|
||||||
* work transparently.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Write a raw buffer through a redisContext. If we already have something
|
|
||||||
* in the buffer (leftovers from hiredis operations) it will be written
|
|
||||||
* as well.
|
|
||||||
*/
|
|
||||||
static ssize_t writeConn(redisContext *c, const char *buf, size_t buf_len)
|
|
||||||
{
|
|
||||||
int done = 0;
|
|
||||||
|
|
||||||
/* Append data to buffer which is *usually* expected to be empty
|
|
||||||
* but we don't assume that, and write.
|
|
||||||
*/
|
|
||||||
c->obuf = sdscatlen(c->obuf, buf, buf_len);
|
|
||||||
if (redisBufferWrite(c, &done) == REDIS_ERR) {
|
|
||||||
if (!(c->flags & REDIS_BLOCK))
|
|
||||||
errno = EAGAIN;
|
|
||||||
|
|
||||||
/* On error, we assume nothing was written and we roll back the
|
|
||||||
* buffer to its original state.
|
|
||||||
*/
|
|
||||||
if (sdslen(c->obuf) > buf_len)
|
|
||||||
sdsrange(c->obuf, 0, -(buf_len+1));
|
|
||||||
else
|
|
||||||
sdsclear(c->obuf);
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we're done, free up everything. We may have written more than
|
|
||||||
* buf_len (if c->obuf was not initially empty) but we don't have to
|
|
||||||
* tell.
|
|
||||||
*/
|
|
||||||
if (done) {
|
|
||||||
sdsclear(c->obuf);
|
|
||||||
return buf_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write was successful but we have some leftovers which we should
|
|
||||||
* remove from the buffer.
|
|
||||||
*
|
|
||||||
* Do we still have data that was there prior to our buf? If so,
|
|
||||||
* restore buffer to it's original state and report no new data was
|
|
||||||
* writen.
|
|
||||||
*/
|
|
||||||
if (sdslen(c->obuf) > buf_len) {
|
|
||||||
sdsrange(c->obuf, 0, -(buf_len+1));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* At this point we're sure no prior data is left. We flush the buffer
|
|
||||||
* and report how much we've written.
|
|
||||||
*/
|
|
||||||
size_t left = sdslen(c->obuf);
|
|
||||||
sdsclear(c->obuf);
|
|
||||||
return buf_len - left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read raw bytes through a redisContext. The read operation is not greedy
|
/* Read raw bytes through a redisContext. The read operation is not greedy
|
||||||
* and may not fill the buffer entirely.
|
* and may not fill the buffer entirely.
|
||||||
*/
|
*/
|
||||||
@ -6984,7 +6850,7 @@ unsigned long long sendSync(redisContext *c, char *out_eof) {
|
|||||||
ssize_t nread;
|
ssize_t nread;
|
||||||
|
|
||||||
/* Send the SYNC command. */
|
/* Send the SYNC command. */
|
||||||
if (writeConn(c, "SYNC\r\n", 6) != 6) {
|
if (cliWriteConn(c, "SYNC\r\n", 6) != 6) {
|
||||||
fprintf(stderr,"Error writing to master\n");
|
fprintf(stderr,"Error writing to master\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -7249,7 +7115,7 @@ static void pipeMode(void) {
|
|||||||
while(1) {
|
while(1) {
|
||||||
/* Transfer current buffer to server. */
|
/* Transfer current buffer to server. */
|
||||||
if (obuf_len != 0) {
|
if (obuf_len != 0) {
|
||||||
ssize_t nwritten = writeConn(context,obuf+obuf_pos,obuf_len);
|
ssize_t nwritten = cliWriteConn(context,obuf+obuf_pos,obuf_len);
|
||||||
|
|
||||||
if (nwritten == -1) {
|
if (nwritten == -1) {
|
||||||
if (errno != EAGAIN && errno != EINTR) {
|
if (errno != EAGAIN && errno != EINTR) {
|
||||||
@ -8229,9 +8095,7 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
#ifdef USE_OPENSSL
|
#ifdef USE_OPENSSL
|
||||||
if (config.tls) {
|
if (config.tls) {
|
||||||
ERR_load_crypto_strings();
|
cliSecureInit();
|
||||||
SSL_load_error_strings();
|
|
||||||
SSL_library_init();
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1,5 +1,19 @@
|
|||||||
|
proc redisbenchmark_tls_config {testsdir} {
|
||||||
|
set tlsdir [file join $testsdir tls]
|
||||||
|
set cert [file join $tlsdir redis.crt]
|
||||||
|
set key [file join $tlsdir redis.key]
|
||||||
|
set cacert [file join $tlsdir ca.crt]
|
||||||
|
|
||||||
|
if {$::tls} {
|
||||||
|
return [list --tls --cert $cert --key $key --cacert $cacert]
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
proc redisbenchmark {host port {opts {}}} {
|
proc redisbenchmark {host port {opts {}}} {
|
||||||
set cmd [list src/redis-benchmark -h $host -p $port]
|
set cmd [list src/redis-benchmark -h $host -p $port]
|
||||||
|
lappend cmd {*}[redisbenchmark_tls_config "tests"]
|
||||||
lappend cmd {*}$opts
|
lappend cmd {*}$opts
|
||||||
return $cmd
|
return $cmd
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user