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

Merge pull request #6236 from yossigo/poc/conns

Abstract Connections I/O API & TLS Support
parents b8e02f2b 8e29b0b2
...@@ -47,6 +47,10 @@ ...@@ -47,6 +47,10 @@
#include <math.h> #include <math.h>
#include <hiredis.h> #include <hiredis.h>
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <hiredis_ssl.h>
#endif
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */ #include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
#include "dict.h" #include "dict.h"
#include "adlist.h" #include "adlist.h"
...@@ -188,6 +192,12 @@ static struct config { ...@@ -188,6 +192,12 @@ static struct config {
char *hostip; char *hostip;
int hostport; int hostport;
char *hostsocket; char *hostsocket;
int tls;
char *sni;
char *cacert;
char *cacertdir;
char *cert;
char *key;
long repeat; long repeat;
long interval; long interval;
int dbnum; int dbnum;
...@@ -758,6 +768,71 @@ static int cliSelect(void) { ...@@ -758,6 +768,71 @@ 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;
...@@ -789,6 +864,16 @@ static int cliConnect(int flags) { ...@@ -789,6 +864,16 @@ static int cliConnect(int flags) {
context = redisConnectUnix(config.hostsocket); context = redisConnectUnix(config.hostsocket);
} }
if (!context->err && config.tls) {
const char *err = NULL;
if (cliSecureConnection(context, &err) == REDIS_ERR && err) {
fprintf(stderr, "Could not negotiate a TLS connection: %s\n", err);
context = NULL;
redisFree(context);
return REDIS_ERR;
}
}
if (context->err) { if (context->err) {
if (!(flags & CC_QUIET)) { if (!(flags & CC_QUIET)) {
fprintf(stderr,"Could not connect to Redis at "); fprintf(stderr,"Could not connect to Redis at ");
...@@ -804,6 +889,7 @@ static int cliConnect(int flags) { ...@@ -804,6 +889,7 @@ static int cliConnect(int flags) {
return REDIS_ERR; return REDIS_ERR;
} }
/* Set aggressive KEEP_ALIVE socket option in the Redis context socket /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
* in order to prevent timeouts caused by the execution of long * in order to prevent timeouts caused by the execution of long
* commands. At the same time this improves the detection of real * commands. At the same time this improves the detection of real
...@@ -1305,6 +1391,13 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, .. ...@@ -1305,6 +1391,13 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
redisFree(c); redisFree(c);
c = redisConnect(config.hostip,config.hostport); c = redisConnect(config.hostip,config.hostport);
if (!c->err && config.tls) {
const char *err = NULL;
if (cliSecureConnection(c, &err) == REDIS_ERR && err) {
fprintf(stderr, "TLS Error: %s\n", err);
exit(1);
}
}
usleep(1000000); usleep(1000000);
} }
...@@ -1498,6 +1591,20 @@ static int parseOptions(int argc, char **argv) { ...@@ -1498,6 +1591,20 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) { } else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) {
config.cluster_manager_command.flags |= config.cluster_manager_command.flags |=
CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS; CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
#ifdef USE_OPENSSL
} else if (!strcmp(argv[i],"--tls")) {
config.tls = 1;
} else if (!strcmp(argv[i],"--sni")) {
config.sni = argv[++i];
} else if (!strcmp(argv[i],"--cacertdir")) {
config.cacertdir = argv[++i];
} else if (!strcmp(argv[i],"--cacert")) {
config.cacert = argv[++i];
} else if (!strcmp(argv[i],"--cert")) {
config.cert = argv[++i];
} else if (!strcmp(argv[i],"--key")) {
config.key = argv[++i];
#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();
printf("redis-cli %s\n", version); printf("redis-cli %s\n", version);
...@@ -1591,6 +1698,15 @@ static void usage(void) { ...@@ -1591,6 +1698,15 @@ static void usage(void) {
" -x Read last argument from STDIN.\n" " -x Read last argument from STDIN.\n"
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n" " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n"
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n" " -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
#ifdef USE_OPENSSL
" --tls Establish a secure TLS connection.\n"
" --cacert CA Certificate file to verify with.\n"
" --cacertdir 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 Client certificate to authenticate with.\n"
" --key Private key file to authenticate with.\n"
#endif
" --raw Use raw formatting for replies (default when STDOUT is\n" " --raw Use raw formatting for replies (default when STDOUT is\n"
" not a tty).\n" " not a tty).\n"
" --no-raw Force formatted output even when STDOUT is not a tty.\n" " --no-raw Force formatted output even when STDOUT is not a tty.\n"
...@@ -1615,7 +1731,9 @@ static void usage(void) { ...@@ -1615,7 +1731,9 @@ static void usage(void) {
" --pipe Transfer raw Redis protocol from stdin to server.\n" " --pipe Transfer raw Redis protocol from stdin to server.\n"
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n" " --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
" no reply is received within <n> seconds.\n" " no reply is received within <n> seconds.\n"
" Default timeout: %d. Use 0 to wait forever.\n" " Default timeout: %d. Use 0 to wait forever.\n",
REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
fprintf(stderr,
" --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n" " --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n"
" --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n" " --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n"
" --memkeys-samples <n> Sample Redis keys looking for keys consuming a lot of memory.\n" " --memkeys-samples <n> Sample Redis keys looking for keys consuming a lot of memory.\n"
...@@ -1638,8 +1756,7 @@ static void usage(void) { ...@@ -1638,8 +1756,7 @@ static void usage(void) {
" line interface.\n" " line interface.\n"
" --help Output this help and exit.\n" " --help Output this help and exit.\n"
" --version Output version and exit.\n" " --version Output version and exit.\n"
"\n", "\n");
REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
/* Using another fprintf call to avoid -Woverlength-strings compile warning */ /* Using another fprintf call to avoid -Woverlength-strings compile warning */
fprintf(stderr, fprintf(stderr,
"Cluster Manager Commands:\n" "Cluster Manager Commands:\n"
...@@ -2407,6 +2524,15 @@ cleanup: ...@@ -2407,6 +2524,15 @@ cleanup:
static int clusterManagerNodeConnect(clusterManagerNode *node) { static int clusterManagerNodeConnect(clusterManagerNode *node) {
if (node->context) redisFree(node->context); if (node->context) redisFree(node->context);
node->context = redisConnect(node->ip, node->port); node->context = redisConnect(node->ip, node->port);
if (!node->context->err && config.tls) {
const char *err = NULL;
if (cliSecureConnection(node->context, &err) == REDIS_ERR && err) {
fprintf(stderr,"TLS Error: %s\n", err);
redisFree(node->context);
node->context = NULL;
return 0;
}
}
if (node->context->err) { if (node->context->err) {
fprintf(stderr,"Could not connect to Redis at "); fprintf(stderr,"Could not connect to Redis at ");
fprintf(stderr,"%s:%d: %s\n", node->ip, node->port, fprintf(stderr,"%s:%d: %s\n", node->ip, node->port,
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
#include <sys/stat.h> #include <sys/stat.h>
void replicationDiscardCachedMaster(void); void replicationDiscardCachedMaster(void);
void replicationResurrectCachedMaster(int newfd); void replicationResurrectCachedMaster(connection *conn);
void replicationSendAck(void); void replicationSendAck(void);
void putSlaveOnline(client *slave); void putSlaveOnline(client *slave);
int cancelReplicationHandshake(void); int cancelReplicationHandshake(void);
...@@ -57,7 +57,7 @@ char *replicationGetSlaveName(client *c) { ...@@ -57,7 +57,7 @@ char *replicationGetSlaveName(client *c) {
ip[0] = '\0'; ip[0] = '\0';
buf[0] = '\0'; buf[0] = '\0';
if (c->slave_ip[0] != '\0' || if (c->slave_ip[0] != '\0' ||
anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1) connPeerToString(c->conn,ip,sizeof(ip),NULL) != -1)
{ {
/* Note that the 'ip' buffer is always larger than 'c->slave_ip' */ /* Note that the 'ip' buffer is always larger than 'c->slave_ip' */
if (c->slave_ip[0] != '\0') memcpy(ip,c->slave_ip,sizeof(c->slave_ip)); if (c->slave_ip[0] != '\0') memcpy(ip,c->slave_ip,sizeof(c->slave_ip));
...@@ -432,7 +432,7 @@ int replicationSetupSlaveForFullResync(client *slave, long long offset) { ...@@ -432,7 +432,7 @@ int replicationSetupSlaveForFullResync(client *slave, long long offset) {
if (!(slave->flags & CLIENT_PRE_PSYNC)) { if (!(slave->flags & CLIENT_PRE_PSYNC)) {
buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n", buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n",
server.replid,offset); server.replid,offset);
if (write(slave->fd,buf,buflen) != buflen) { if (connWrite(slave->conn,buf,buflen) != buflen) {
freeClientAsync(slave); freeClientAsync(slave);
return C_ERR; return C_ERR;
} }
...@@ -519,7 +519,7 @@ int masterTryPartialResynchronization(client *c) { ...@@ -519,7 +519,7 @@ int masterTryPartialResynchronization(client *c) {
} else { } else {
buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n"); buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n");
} }
if (write(c->fd,buf,buflen) != buflen) { if (connWrite(c->conn,buf,buflen) != buflen) {
freeClientAsync(c); freeClientAsync(c);
return C_OK; return C_OK;
} }
...@@ -685,7 +685,7 @@ void syncCommand(client *c) { ...@@ -685,7 +685,7 @@ void syncCommand(client *c) {
* paths will change the state if we handle the slave differently. */ * paths will change the state if we handle the slave differently. */
c->replstate = SLAVE_STATE_WAIT_BGSAVE_START; c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
if (server.repl_disable_tcp_nodelay) if (server.repl_disable_tcp_nodelay)
anetDisableTcpNoDelay(NULL, c->fd); /* Non critical if it fails. */ connDisableTcpNoDelay(c->conn); /* Non critical if it fails. */
c->repldbfd = -1; c->repldbfd = -1;
c->flags |= CLIENT_SLAVE; c->flags |= CLIENT_SLAVE;
listAddNodeTail(server.slaves,c); listAddNodeTail(server.slaves,c);
...@@ -862,8 +862,7 @@ void putSlaveOnline(client *slave) { ...@@ -862,8 +862,7 @@ void putSlaveOnline(client *slave) {
slave->replstate = SLAVE_STATE_ONLINE; slave->replstate = SLAVE_STATE_ONLINE;
slave->repl_put_online_on_ack = 0; slave->repl_put_online_on_ack = 0;
slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */ slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, if (connSetWriteHandler(slave->conn, sendReplyToClient) == C_ERR) {
sendReplyToClient, slave) == AE_ERR) {
serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno)); serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno));
freeClient(slave); freeClient(slave);
return; return;
...@@ -873,10 +872,8 @@ void putSlaveOnline(client *slave) { ...@@ -873,10 +872,8 @@ void putSlaveOnline(client *slave) {
replicationGetSlaveName(slave)); replicationGetSlaveName(slave));
} }
void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) { void sendBulkToSlave(connection *conn) {
client *slave = privdata; client *slave = connGetPrivateData(conn);
UNUSED(el);
UNUSED(mask);
char buf[PROTO_IOBUF_LEN]; char buf[PROTO_IOBUF_LEN];
ssize_t nwritten, buflen; ssize_t nwritten, buflen;
...@@ -884,10 +881,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -884,10 +881,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
* replication process. Currently the preamble is just the bulk count of * replication process. Currently the preamble is just the bulk count of
* the file in the form "$<length>\r\n". */ * the file in the form "$<length>\r\n". */
if (slave->replpreamble) { if (slave->replpreamble) {
nwritten = write(fd,slave->replpreamble,sdslen(slave->replpreamble)); nwritten = connWrite(conn,slave->replpreamble,sdslen(slave->replpreamble));
if (nwritten == -1) { if (nwritten == -1) {
serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s", serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
strerror(errno)); connGetLastError(conn));
freeClient(slave); freeClient(slave);
return; return;
} }
...@@ -911,10 +908,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -911,10 +908,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
freeClient(slave); freeClient(slave);
return; return;
} }
if ((nwritten = write(fd,buf,buflen)) == -1) { if ((nwritten = connWrite(conn,buf,buflen)) == -1) {
if (errno != EAGAIN) { if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_WARNING,"Write error sending DB to replica: %s", serverLog(LL_WARNING,"Write error sending DB to replica: %s",
strerror(errno)); connGetLastError(conn));
freeClient(slave); freeClient(slave);
} }
return; return;
...@@ -924,11 +921,157 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -924,11 +921,157 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
if (slave->repldboff == slave->repldbsize) { if (slave->repldboff == slave->repldbsize) {
close(slave->repldbfd); close(slave->repldbfd);
slave->repldbfd = -1; slave->repldbfd = -1;
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE); connSetWriteHandler(slave->conn,NULL);
putSlaveOnline(slave); putSlaveOnline(slave);
} }
} }
/* Remove one write handler from the list of connections waiting to be writable
* during rdb pipe transfer. */
void rdbPipeWriteHandlerConnRemoved(struct connection *conn) {
if (!connHasWriteHandler(conn))
return;
connSetWriteHandler(conn, NULL);
server.rdb_pipe_numconns_writing--;
/* if there are no more writes for now for this conn, or write error: */
if (server.rdb_pipe_numconns_writing == 0) {
if (aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
}
}
}
/* Called in diskless master during transfer of data from the rdb pipe, when
* the replica becomes writable again. */
void rdbPipeWriteHandler(struct connection *conn) {
serverAssert(server.rdb_pipe_bufflen>0);
client *slave = connGetPrivateData(conn);
int nwritten;
if ((nwritten = connWrite(conn, server.rdb_pipe_buff + slave->repldboff,
server.rdb_pipe_bufflen - slave->repldboff)) == -1)
{
if (connGetState(conn) == CONN_STATE_CONNECTED)
return; /* equivalent to EAGAIN */
serverLog(LL_WARNING,"Write error sending DB to replica: %s",
connGetLastError(conn));
freeClient(slave);
return;
} else {
slave->repldboff += nwritten;
server.stat_net_output_bytes += nwritten;
if (slave->repldboff < server.rdb_pipe_bufflen)
return; /* more data to write.. */
}
rdbPipeWriteHandlerConnRemoved(conn);
}
/* When the the pipe serving diskless rdb transfer is drained (write end was
* closed), we can clean up all the temporary variables, and cleanup after the
* fork child. */
void RdbPipeCleanup() {
close(server.rdb_pipe_read);
zfree(server.rdb_pipe_conns);
server.rdb_pipe_conns = NULL;
server.rdb_pipe_numconns = 0;
server.rdb_pipe_numconns_writing = 0;
zfree(server.rdb_pipe_buff);
server.rdb_pipe_buff = NULL;
server.rdb_pipe_bufflen = 0;
/* Since we're avoiding to detect the child exited as long as the pipe is
* not drained, so now is the time to check. */
checkChildrenDone();
}
/* Called in diskless master, when there's data to read from the child's rdb pipe */
void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask) {
UNUSED(mask);
UNUSED(clientData);
UNUSED(eventLoop);
int i;
if (!server.rdb_pipe_buff)
server.rdb_pipe_buff = zmalloc(PROTO_IOBUF_LEN);
serverAssert(server.rdb_pipe_numconns_writing==0);
while (1) {
server.rdb_pipe_bufflen = read(fd, server.rdb_pipe_buff, PROTO_IOBUF_LEN);
if (server.rdb_pipe_bufflen < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return;
serverLog(LL_WARNING,"Diskless rdb transfer, read error sending DB to replicas: %s", strerror(errno));
for (i=0; i < server.rdb_pipe_numconns; i++) {
connection *conn = server.rdb_pipe_conns[i];
if (!conn)
continue;
client *slave = connGetPrivateData(conn);
freeClient(slave);
server.rdb_pipe_conns[i] = NULL;
}
killRDBChild();
return;
}
if (server.rdb_pipe_bufflen == 0) {
/* EOF - write end was closed. */
int stillUp = 0;
aeDeleteFileEvent(server.el, server.rdb_pipe_read, AE_READABLE);
for (i=0; i < server.rdb_pipe_numconns; i++)
{
connection *conn = server.rdb_pipe_conns[i];
if (!conn)
continue;
stillUp++;
}
serverLog(LL_WARNING,"Diskless rdb transfer, done reading from pipe, %d replicas still up.", stillUp);
RdbPipeCleanup();
return;
}
int stillAlive = 0;
for (i=0; i < server.rdb_pipe_numconns; i++)
{
int nwritten;
connection *conn = server.rdb_pipe_conns[i];
if (!conn)
continue;
client *slave = connGetPrivateData(conn);
if ((nwritten = connWrite(conn, server.rdb_pipe_buff, server.rdb_pipe_bufflen)) == -1) {
if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_WARNING,"Diskless rdb transfer, write error sending DB to replica: %s",
connGetLastError(conn));
freeClient(slave);
server.rdb_pipe_conns[i] = NULL;
continue;
}
/* An error and still in connected state, is equivalent to EAGAIN */
slave->repldboff = 0;
} else {
slave->repldboff = nwritten;
server.stat_net_output_bytes += nwritten;
}
/* If we were unable to write all the data to one of the replicas,
* setup write handler (and disable pipe read handler, below) */
if (nwritten != server.rdb_pipe_bufflen) {
server.rdb_pipe_numconns_writing++;
connSetWriteHandler(conn, rdbPipeWriteHandler);
}
stillAlive++;
}
if (stillAlive == 0) {
serverLog(LL_WARNING,"Diskless rdb transfer, last replica dropped, killing fork child.");
killRDBChild();
RdbPipeCleanup();
}
/* Remove the pipe read handler if at least one write handler was set. */
if (server.rdb_pipe_numconns_writing || stillAlive == 0) {
aeDeleteFileEvent(server.el, server.rdb_pipe_read, AE_READABLE);
break;
}
}
}
/* This function is called at the end of every background saving, /* This function is called at the end of every background saving,
* or when the replication RDB transfer strategy is modified from * or when the replication RDB transfer strategy is modified from
* disk to socket or the other way around. * disk to socket or the other way around.
...@@ -1015,8 +1158,8 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) { ...@@ -1015,8 +1158,8 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n", slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n",
(unsigned long long) slave->repldbsize); (unsigned long long) slave->repldbsize);
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE); connSetWriteHandler(slave->conn,NULL);
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR) { if (connSetWriteHandler(slave->conn,sendBulkToSlave) == C_ERR) {
freeClient(slave); freeClient(slave);
continue; continue;
} }
...@@ -1084,9 +1227,8 @@ void replicationSendNewlineToMaster(void) { ...@@ -1084,9 +1227,8 @@ void replicationSendNewlineToMaster(void) {
static time_t newline_sent; static time_t newline_sent;
if (time(NULL) != newline_sent) { if (time(NULL) != newline_sent) {
newline_sent = time(NULL); newline_sent = time(NULL);
if (write(server.repl_transfer_s,"\n",1) == -1) {
/* Pinging back in this stage is best-effort. */ /* Pinging back in this stage is best-effort. */
} if (server.repl_transfer_s) connWrite(server.repl_transfer_s, "\n", 1);
} }
} }
...@@ -1100,8 +1242,10 @@ void replicationEmptyDbCallback(void *privdata) { ...@@ -1100,8 +1242,10 @@ void replicationEmptyDbCallback(void *privdata) {
/* Once we have a link with the master and the synchroniziation was /* Once we have a link with the master and the synchroniziation was
* performed, this function materializes the master client we store * performed, this function materializes the master client we store
* at server.master, starting from the specified file descriptor. */ * at server.master, starting from the specified file descriptor. */
void replicationCreateMasterClient(int fd, int dbid) { void replicationCreateMasterClient(connection *conn, int dbid) {
server.master = createClient(fd); server.master = createClient(conn);
if (conn)
connSetReadHandler(server.master->conn, readQueryFromClient);
server.master->flags |= CLIENT_MASTER; server.master->flags |= CLIENT_MASTER;
server.master->authenticated = 1; server.master->authenticated = 1;
server.master->reploff = server.master_initial_offset; server.master->reploff = server.master_initial_offset;
...@@ -1196,7 +1340,7 @@ void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags ...@@ -1196,7 +1340,7 @@ void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags
/* Asynchronously read the SYNC payload we receive from a master */ /* Asynchronously read the SYNC payload we receive from a master */
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */ #define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { void readSyncBulkPayload(connection *conn) {
char buf[4096]; char buf[4096];
ssize_t nread, readlen, nwritten; ssize_t nread, readlen, nwritten;
int use_diskless_load; int use_diskless_load;
...@@ -1204,9 +1348,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1204,9 +1348,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC : int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
EMPTYDB_NO_FLAGS; EMPTYDB_NO_FLAGS;
off_t left; off_t left;
UNUSED(el);
UNUSED(privdata);
UNUSED(mask);
/* Static vars used to hold the EOF mark, and the last bytes received /* Static vars used to hold the EOF mark, and the last bytes received
* form the server: when they match, we reached the end of the transfer. */ * form the server: when they match, we reached the end of the transfer. */
...@@ -1217,7 +1358,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1217,7 +1358,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
/* If repl_transfer_size == -1 we still have to read the bulk length /* If repl_transfer_size == -1 we still have to read the bulk length
* from the master reply. */ * from the master reply. */
if (server.repl_transfer_size == -1) { if (server.repl_transfer_size == -1) {
if (syncReadLine(fd,buf,1024,server.repl_syncio_timeout*1000) == -1) { if (connSyncReadLine(conn,buf,1024,server.repl_syncio_timeout*1000) == -1) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"I/O error reading bulk count from MASTER: %s", "I/O error reading bulk count from MASTER: %s",
strerror(errno)); strerror(errno));
...@@ -1282,7 +1423,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1282,7 +1423,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf); readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf);
} }
nread = read(fd,buf,readlen); nread = connRead(conn,buf,readlen);
if (nread <= 0) { if (nread <= 0) {
serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s", serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s",
(nread == -1) ? strerror(errno) : "connection lost"); (nread == -1) ? strerror(errno) : "connection lost");
...@@ -1390,17 +1531,17 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1390,17 +1531,17 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
* handler, otherwise it will get called recursively since * handler, otherwise it will get called recursively since
* rdbLoad() will call the event loop to process events from time to * rdbLoad() will call the event loop to process events from time to
* time for non blocking loading. */ * time for non blocking loading. */
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE); connSetReadHandler(conn, NULL);
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory"); serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory");
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT; rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
if (use_diskless_load) { if (use_diskless_load) {
rio rdb; rio rdb;
rioInitWithFd(&rdb,fd,server.repl_transfer_size); rioInitWithConn(&rdb,conn,server.repl_transfer_size);
/* Put the socket in blocking mode to simplify RDB transfer. /* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the RDB is received. */ * We'll restore it when the RDB is received. */
anetBlock(NULL,fd); connBlock(conn);
anetRecvTimeout(NULL,fd,server.repl_timeout*1000); connRecvTimeout(conn, server.repl_timeout*1000);
startLoading(server.repl_transfer_size); startLoading(server.repl_transfer_size);
if (rdbLoadRio(&rdb,&rsi,0) != C_OK) { if (rdbLoadRio(&rdb,&rsi,0) != C_OK) {
...@@ -1410,7 +1551,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1410,7 +1551,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
"Failed trying to load the MASTER synchronization DB " "Failed trying to load the MASTER synchronization DB "
"from socket"); "from socket");
cancelReplicationHandshake(); cancelReplicationHandshake();
rioFreeFd(&rdb, NULL); rioFreeConn(&rdb, NULL);
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) { if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
/* Restore the backed up databases. */ /* Restore the backed up databases. */
disklessLoadRestoreBackups(diskless_load_backup,1, disklessLoadRestoreBackups(diskless_load_backup,1,
...@@ -1443,16 +1584,16 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1443,16 +1584,16 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
{ {
serverLog(LL_WARNING,"Replication stream EOF marker is broken"); serverLog(LL_WARNING,"Replication stream EOF marker is broken");
cancelReplicationHandshake(); cancelReplicationHandshake();
rioFreeFd(&rdb, NULL); rioFreeConn(&rdb, NULL);
return; return;
} }
} }
/* Cleanup and restore the socket to the original state to continue /* Cleanup and restore the socket to the original state to continue
* with the normal replication. */ * with the normal replication. */
rioFreeFd(&rdb, NULL); rioFreeConn(&rdb, NULL);
anetNonBlock(NULL,fd); connNonBlock(conn);
anetRecvTimeout(NULL,fd,0); connRecvTimeout(conn,0);
} else { } else {
/* Ensure background save doesn't overwrite synced data */ /* Ensure background save doesn't overwrite synced data */
if (server.rdb_child_pid != -1) { if (server.rdb_child_pid != -1) {
...@@ -1529,7 +1670,7 @@ error: ...@@ -1529,7 +1670,7 @@ error:
#define SYNC_CMD_READ (1<<0) #define SYNC_CMD_READ (1<<0)
#define SYNC_CMD_WRITE (1<<1) #define SYNC_CMD_WRITE (1<<1)
#define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE) #define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE)
char *sendSynchronousCommand(int flags, int fd, ...) { char *sendSynchronousCommand(int flags, connection *conn, ...) {
/* Create the command to send to the master, we use redis binary /* Create the command to send to the master, we use redis binary
* protocol to make sure correct arguments are sent. This function * protocol to make sure correct arguments are sent. This function
...@@ -1540,7 +1681,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) { ...@@ -1540,7 +1681,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
sds cmd = sdsempty(); sds cmd = sdsempty();
sds cmdargs = sdsempty(); sds cmdargs = sdsempty();
size_t argslen = 0; size_t argslen = 0;
va_start(ap,fd); va_start(ap,conn);
while(1) { while(1) {
arg = va_arg(ap, char*); arg = va_arg(ap, char*);
...@@ -1557,12 +1698,12 @@ char *sendSynchronousCommand(int flags, int fd, ...) { ...@@ -1557,12 +1698,12 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
sdsfree(cmdargs); sdsfree(cmdargs);
/* Transfer command to the server. */ /* Transfer command to the server. */
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000) if (connSyncWrite(conn,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
== -1) == -1)
{ {
sdsfree(cmd); sdsfree(cmd);
return sdscatprintf(sdsempty(),"-Writing to master: %s", return sdscatprintf(sdsempty(),"-Writing to master: %s",
strerror(errno)); connGetLastError(conn));
} }
sdsfree(cmd); sdsfree(cmd);
} }
...@@ -1571,7 +1712,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) { ...@@ -1571,7 +1712,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
if (flags & SYNC_CMD_READ) { if (flags & SYNC_CMD_READ) {
char buf[256]; char buf[256];
if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout*1000) if (connSyncReadLine(conn,buf,sizeof(buf),server.repl_syncio_timeout*1000)
== -1) == -1)
{ {
return sdscatprintf(sdsempty(),"-Reading from master: %s", return sdscatprintf(sdsempty(),"-Reading from master: %s",
...@@ -1637,7 +1778,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) { ...@@ -1637,7 +1778,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
#define PSYNC_FULLRESYNC 3 #define PSYNC_FULLRESYNC 3
#define PSYNC_NOT_SUPPORTED 4 #define PSYNC_NOT_SUPPORTED 4
#define PSYNC_TRY_LATER 5 #define PSYNC_TRY_LATER 5
int slaveTryPartialResynchronization(int fd, int read_reply) { int slaveTryPartialResynchronization(connection *conn, int read_reply) {
char *psync_replid; char *psync_replid;
char psync_offset[32]; char psync_offset[32];
sds reply; sds reply;
...@@ -1662,18 +1803,18 @@ int slaveTryPartialResynchronization(int fd, int read_reply) { ...@@ -1662,18 +1803,18 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
} }
/* Issue the PSYNC command */ /* Issue the PSYNC command */
reply = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PSYNC",psync_replid,psync_offset,NULL); reply = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PSYNC",psync_replid,psync_offset,NULL);
if (reply != NULL) { if (reply != NULL) {
serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply); serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply);
sdsfree(reply); sdsfree(reply);
aeDeleteFileEvent(server.el,fd,AE_READABLE); connSetReadHandler(conn, NULL);
return PSYNC_WRITE_ERROR; return PSYNC_WRITE_ERROR;
} }
return PSYNC_WAIT_REPLY; return PSYNC_WAIT_REPLY;
} }
/* Reading half */ /* Reading half */
reply = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); reply = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
if (sdslen(reply) == 0) { if (sdslen(reply) == 0) {
/* The master may send empty newlines after it receives PSYNC /* The master may send empty newlines after it receives PSYNC
* and before to reply, just to keep the connection alive. */ * and before to reply, just to keep the connection alive. */
...@@ -1681,7 +1822,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) { ...@@ -1681,7 +1822,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
return PSYNC_WAIT_REPLY; return PSYNC_WAIT_REPLY;
} }
aeDeleteFileEvent(server.el,fd,AE_READABLE); connSetReadHandler(conn, NULL);
if (!strncmp(reply,"+FULLRESYNC",11)) { if (!strncmp(reply,"+FULLRESYNC",11)) {
char *replid = NULL, *offset = NULL; char *replid = NULL, *offset = NULL;
...@@ -1755,7 +1896,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) { ...@@ -1755,7 +1896,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
/* Setup the replication to continue. */ /* Setup the replication to continue. */
sdsfree(reply); sdsfree(reply);
replicationResurrectCachedMaster(fd); replicationResurrectCachedMaster(conn);
/* If this instance was restarted and we read the metadata to /* If this instance was restarted and we read the metadata to
* PSYNC from the persistence file, our replication backlog could * PSYNC from the persistence file, our replication backlog could
...@@ -1797,29 +1938,23 @@ int slaveTryPartialResynchronization(int fd, int read_reply) { ...@@ -1797,29 +1938,23 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
/* This handler fires when the non blocking connect was able to /* This handler fires when the non blocking connect was able to
* establish a connection with the master. */ * establish a connection with the master. */
void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { void syncWithMaster(connection *conn) {
char tmpfile[256], *err = NULL; char tmpfile[256], *err = NULL;
int dfd = -1, maxtries = 5; int dfd = -1, maxtries = 5;
int sockerr = 0, psync_result; int psync_result;
socklen_t errlen = sizeof(sockerr);
UNUSED(el);
UNUSED(privdata);
UNUSED(mask);
/* If this event fired after the user turned the instance into a master /* If this event fired after the user turned the instance into a master
* with SLAVEOF NO ONE we must just return ASAP. */ * with SLAVEOF NO ONE we must just return ASAP. */
if (server.repl_state == REPL_STATE_NONE) { if (server.repl_state == REPL_STATE_NONE) {
close(fd); connClose(conn);
return; return;
} }
/* Check for errors in the socket: after a non blocking connect() we /* Check for errors in the socket: after a non blocking connect() we
* may find that the socket is in error state. */ * may find that the socket is in error state. */
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1) if (connGetState(conn) != CONN_STATE_CONNECTED) {
sockerr = errno;
if (sockerr) {
serverLog(LL_WARNING,"Error condition on socket for SYNC: %s", serverLog(LL_WARNING,"Error condition on socket for SYNC: %s",
strerror(sockerr)); connGetLastError(conn));
goto error; goto error;
} }
...@@ -1828,18 +1963,19 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1828,18 +1963,19 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
serverLog(LL_NOTICE,"Non blocking connect for SYNC fired the event."); serverLog(LL_NOTICE,"Non blocking connect for SYNC fired the event.");
/* Delete the writable event so that the readable event remains /* Delete the writable event so that the readable event remains
* registered and we can wait for the PONG reply. */ * registered and we can wait for the PONG reply. */
aeDeleteFileEvent(server.el,fd,AE_WRITABLE); connSetReadHandler(conn, syncWithMaster);
connSetWriteHandler(conn, NULL);
server.repl_state = REPL_STATE_RECEIVE_PONG; server.repl_state = REPL_STATE_RECEIVE_PONG;
/* Send the PING, don't check for errors at all, we have the timeout /* Send the PING, don't check for errors at all, we have the timeout
* that will take care about this. */ * that will take care about this. */
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PING",NULL); err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PING",NULL);
if (err) goto write_error; if (err) goto write_error;
return; return;
} }
/* Receive the PONG command. */ /* Receive the PONG command. */
if (server.repl_state == REPL_STATE_RECEIVE_PONG) { if (server.repl_state == REPL_STATE_RECEIVE_PONG) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* We accept only two replies as valid, a positive +PONG reply /* We accept only two replies as valid, a positive +PONG reply
* (we just check for "+") or an authentication error. * (we just check for "+") or an authentication error.
...@@ -1864,13 +2000,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1864,13 +2000,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* AUTH with the master if required. */ /* AUTH with the master if required. */
if (server.repl_state == REPL_STATE_SEND_AUTH) { if (server.repl_state == REPL_STATE_SEND_AUTH) {
if (server.masteruser && server.masterauth) { if (server.masteruser && server.masterauth) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH", err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",
server.masteruser,server.masterauth,NULL); server.masteruser,server.masterauth,NULL);
if (err) goto write_error; if (err) goto write_error;
server.repl_state = REPL_STATE_RECEIVE_AUTH; server.repl_state = REPL_STATE_RECEIVE_AUTH;
return; return;
} else if (server.masterauth) { } else if (server.masterauth) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH",server.masterauth,NULL); err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",server.masterauth,NULL);
if (err) goto write_error; if (err) goto write_error;
server.repl_state = REPL_STATE_RECEIVE_AUTH; server.repl_state = REPL_STATE_RECEIVE_AUTH;
return; return;
...@@ -1881,7 +2017,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1881,7 +2017,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive AUTH reply. */ /* Receive AUTH reply. */
if (server.repl_state == REPL_STATE_RECEIVE_AUTH) { if (server.repl_state == REPL_STATE_RECEIVE_AUTH) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
if (err[0] == '-') { if (err[0] == '-') {
serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err); serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err);
sdsfree(err); sdsfree(err);
...@@ -1894,11 +2030,14 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1894,11 +2030,14 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Set the slave port, so that Master's INFO command can list the /* Set the slave port, so that Master's INFO command can list the
* slave listening port correctly. */ * slave listening port correctly. */
if (server.repl_state == REPL_STATE_SEND_PORT) { if (server.repl_state == REPL_STATE_SEND_PORT) {
sds port = sdsfromlonglong(server.slave_announce_port ? int port;
server.slave_announce_port : server.port); if (server.slave_announce_port) port = server.slave_announce_port;
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF", else if (server.tls_replication && server.tls_port) port = server.tls_port;
"listening-port",port, NULL); else port = server.port;
sdsfree(port); sds portstr = sdsfromlonglong(port);
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
"listening-port",portstr, NULL);
sdsfree(portstr);
if (err) goto write_error; if (err) goto write_error;
sdsfree(err); sdsfree(err);
server.repl_state = REPL_STATE_RECEIVE_PORT; server.repl_state = REPL_STATE_RECEIVE_PORT;
...@@ -1907,7 +2046,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1907,7 +2046,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive REPLCONF listening-port reply. */ /* Receive REPLCONF listening-port reply. */
if (server.repl_state == REPL_STATE_RECEIVE_PORT) { if (server.repl_state == REPL_STATE_RECEIVE_PORT) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support /* Ignore the error if any, not all the Redis versions support
* REPLCONF listening-port. */ * REPLCONF listening-port. */
if (err[0] == '-') { if (err[0] == '-') {
...@@ -1928,7 +2067,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1928,7 +2067,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Set the slave ip, so that Master's INFO command can list the /* Set the slave ip, so that Master's INFO command can list the
* slave IP address port correctly in case of port forwarding or NAT. */ * slave IP address port correctly in case of port forwarding or NAT. */
if (server.repl_state == REPL_STATE_SEND_IP) { if (server.repl_state == REPL_STATE_SEND_IP) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF", err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
"ip-address",server.slave_announce_ip, NULL); "ip-address",server.slave_announce_ip, NULL);
if (err) goto write_error; if (err) goto write_error;
sdsfree(err); sdsfree(err);
...@@ -1938,7 +2077,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1938,7 +2077,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive REPLCONF ip-address reply. */ /* Receive REPLCONF ip-address reply. */
if (server.repl_state == REPL_STATE_RECEIVE_IP) { if (server.repl_state == REPL_STATE_RECEIVE_IP) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support /* Ignore the error if any, not all the Redis versions support
* REPLCONF listening-port. */ * REPLCONF listening-port. */
if (err[0] == '-') { if (err[0] == '-') {
...@@ -1956,7 +2095,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1956,7 +2095,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
* *
* The master will ignore capabilities it does not understand. */ * The master will ignore capabilities it does not understand. */
if (server.repl_state == REPL_STATE_SEND_CAPA) { if (server.repl_state == REPL_STATE_SEND_CAPA) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF", err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
"capa","eof","capa","psync2",NULL); "capa","eof","capa","psync2",NULL);
if (err) goto write_error; if (err) goto write_error;
sdsfree(err); sdsfree(err);
...@@ -1966,7 +2105,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1966,7 +2105,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive CAPA reply. */ /* Receive CAPA reply. */
if (server.repl_state == REPL_STATE_RECEIVE_CAPA) { if (server.repl_state == REPL_STATE_RECEIVE_CAPA) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support /* Ignore the error if any, not all the Redis versions support
* REPLCONF capa. */ * REPLCONF capa. */
if (err[0] == '-') { if (err[0] == '-') {
...@@ -1983,7 +2122,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1983,7 +2122,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
* and the global offset, to try a partial resync at the next * and the global offset, to try a partial resync at the next
* reconnection attempt. */ * reconnection attempt. */
if (server.repl_state == REPL_STATE_SEND_PSYNC) { if (server.repl_state == REPL_STATE_SEND_PSYNC) {
if (slaveTryPartialResynchronization(fd,0) == PSYNC_WRITE_ERROR) { if (slaveTryPartialResynchronization(conn,0) == PSYNC_WRITE_ERROR) {
err = sdsnew("Write error sending the PSYNC command."); err = sdsnew("Write error sending the PSYNC command.");
goto write_error; goto write_error;
} }
...@@ -1999,7 +2138,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1999,7 +2138,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
goto error; goto error;
} }
psync_result = slaveTryPartialResynchronization(fd,1); psync_result = slaveTryPartialResynchronization(conn,1);
if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */ if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */
/* If the master is in an transient error, we should try to PSYNC /* If the master is in an transient error, we should try to PSYNC
...@@ -2028,7 +2167,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -2028,7 +2167,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
* already populated. */ * already populated. */
if (psync_result == PSYNC_NOT_SUPPORTED) { if (psync_result == PSYNC_NOT_SUPPORTED) {
serverLog(LL_NOTICE,"Retrying with SYNC..."); serverLog(LL_NOTICE,"Retrying with SYNC...");
if (syncWrite(fd,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) { if (connSyncWrite(conn,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) {
serverLog(LL_WARNING,"I/O error writing to MASTER: %s", serverLog(LL_WARNING,"I/O error writing to MASTER: %s",
strerror(errno)); strerror(errno));
goto error; goto error;
...@@ -2053,12 +2192,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -2053,12 +2192,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
} }
/* Setup the non blocking download of the bulk file. */ /* Setup the non blocking download of the bulk file. */
if (aeCreateFileEvent(server.el,fd, AE_READABLE,readSyncBulkPayload,NULL) if (connSetReadHandler(conn, readSyncBulkPayload)
== AE_ERR) == C_ERR)
{ {
char conninfo[CONN_INFO_LEN];
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Can't create readable event for SYNC: %s (fd=%d)", "Can't create readable event for SYNC: %s (%s)",
strerror(errno),fd); strerror(errno), connGetInfo(conn, conninfo, sizeof(conninfo)));
goto error; goto error;
} }
...@@ -2070,16 +2210,15 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -2070,16 +2210,15 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
return; return;
error: error:
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
if (dfd != -1) close(dfd); if (dfd != -1) close(dfd);
close(fd); connClose(conn);
server.repl_transfer_s = NULL;
if (server.repl_transfer_fd != -1) if (server.repl_transfer_fd != -1)
close(server.repl_transfer_fd); close(server.repl_transfer_fd);
if (server.repl_transfer_tmpfile) if (server.repl_transfer_tmpfile)
zfree(server.repl_transfer_tmpfile); zfree(server.repl_transfer_tmpfile);
server.repl_transfer_tmpfile = NULL; server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1; server.repl_transfer_fd = -1;
server.repl_transfer_s = -1;
server.repl_state = REPL_STATE_CONNECT; server.repl_state = REPL_STATE_CONNECT;
return; return;
...@@ -2090,26 +2229,18 @@ write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */ ...@@ -2090,26 +2229,18 @@ write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */
} }
int connectWithMaster(void) { int connectWithMaster(void) {
int fd; server.repl_transfer_s = server.tls_replication ? connCreateTLS() : connCreateSocket();
if (connConnect(server.repl_transfer_s, server.masterhost, server.masterport,
fd = anetTcpNonBlockBestEffortBindConnect(NULL, NET_FIRST_BIND_ADDR, syncWithMaster) == C_ERR) {
server.masterhost,server.masterport,NET_FIRST_BIND_ADDR);
if (fd == -1) {
serverLog(LL_WARNING,"Unable to connect to MASTER: %s", serverLog(LL_WARNING,"Unable to connect to MASTER: %s",
strerror(errno)); connGetLastError(server.repl_transfer_s));
connClose(server.repl_transfer_s);
server.repl_transfer_s = NULL;
return C_ERR; return C_ERR;
} }
if (aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE,syncWithMaster,NULL) ==
AE_ERR)
{
close(fd);
serverLog(LL_WARNING,"Can't create readable event for SYNC");
return C_ERR;
}
server.repl_transfer_lastio = server.unixtime; server.repl_transfer_lastio = server.unixtime;
server.repl_transfer_s = fd;
server.repl_state = REPL_STATE_CONNECTING; server.repl_state = REPL_STATE_CONNECTING;
return C_OK; return C_OK;
} }
...@@ -2119,11 +2250,8 @@ int connectWithMaster(void) { ...@@ -2119,11 +2250,8 @@ int connectWithMaster(void) {
* Never call this function directly, use cancelReplicationHandshake() instead. * Never call this function directly, use cancelReplicationHandshake() instead.
*/ */
void undoConnectWithMaster(void) { void undoConnectWithMaster(void) {
int fd = server.repl_transfer_s; connClose(server.repl_transfer_s);
server.repl_transfer_s = NULL;
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
close(fd);
server.repl_transfer_s = -1;
} }
/* Abort the async download of the bulk dataset while SYNC-ing with master. /* Abort the async download of the bulk dataset while SYNC-ing with master.
...@@ -2311,7 +2439,7 @@ void roleCommand(client *c) { ...@@ -2311,7 +2439,7 @@ void roleCommand(client *c) {
char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip; char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip;
if (slaveip[0] == '\0') { if (slaveip[0] == '\0') {
if (anetPeerToString(slave->fd,ip,sizeof(ip),NULL) == -1) if (connPeerToString(slave->conn,ip,sizeof(ip),NULL) == -1)
continue; continue;
slaveip = ip; slaveip = ip;
} }
...@@ -2433,7 +2561,7 @@ void replicationCacheMasterUsingMyself(void) { ...@@ -2433,7 +2561,7 @@ void replicationCacheMasterUsingMyself(void) {
/* The master client we create can be set to any DBID, because /* The master client we create can be set to any DBID, because
* the new master will start its replication stream with SELECT. */ * the new master will start its replication stream with SELECT. */
server.master_initial_offset = server.master_repl_offset; server.master_initial_offset = server.master_repl_offset;
replicationCreateMasterClient(-1,-1); replicationCreateMasterClient(NULL,-1);
/* Use our own ID / offset. */ /* Use our own ID / offset. */
memcpy(server.master->replid, server.replid, sizeof(server.replid)); memcpy(server.master->replid, server.replid, sizeof(server.replid));
...@@ -2462,10 +2590,11 @@ void replicationDiscardCachedMaster(void) { ...@@ -2462,10 +2590,11 @@ void replicationDiscardCachedMaster(void) {
* This function is called when successfully setup a partial resynchronization * This function is called when successfully setup a partial resynchronization
* so the stream of data that we'll receive will start from were this * so the stream of data that we'll receive will start from were this
* master left. */ * master left. */
void replicationResurrectCachedMaster(int newfd) { void replicationResurrectCachedMaster(connection *conn) {
server.master = server.cached_master; server.master = server.cached_master;
server.cached_master = NULL; server.cached_master = NULL;
server.master->fd = newfd; server.master->conn = conn;
connSetPrivateData(server.master->conn, server.master);
server.master->flags &= ~(CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP); server.master->flags &= ~(CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP);
server.master->authenticated = 1; server.master->authenticated = 1;
server.master->lastinteraction = server.unixtime; server.master->lastinteraction = server.unixtime;
...@@ -2474,8 +2603,7 @@ void replicationResurrectCachedMaster(int newfd) { ...@@ -2474,8 +2603,7 @@ void replicationResurrectCachedMaster(int newfd) {
/* Re-add to the list of clients. */ /* Re-add to the list of clients. */
linkClient(server.master); linkClient(server.master);
if (aeCreateFileEvent(server.el, newfd, AE_READABLE, if (connSetReadHandler(server.master->conn, readQueryFromClient)) {
readQueryFromClient, server.master)) {
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno)); serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno));
freeClientAsync(server.master); /* Close ASAP. */ freeClientAsync(server.master); /* Close ASAP. */
} }
...@@ -2483,8 +2611,7 @@ void replicationResurrectCachedMaster(int newfd) { ...@@ -2483,8 +2611,7 @@ void replicationResurrectCachedMaster(int newfd) {
/* We may also need to install the write handler as well if there is /* We may also need to install the write handler as well if there is
* pending data in the write buffers. */ * pending data in the write buffers. */
if (clientHasPendingReplies(server.master)) { if (clientHasPendingReplies(server.master)) {
if (aeCreateFileEvent(server.el, newfd, AE_WRITABLE, if (connSetWriteHandler(server.master->conn, sendReplyToClient)) {
sendReplyToClient, server.master)) {
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno)); serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno));
freeClientAsync(server.master); /* Close ASAP. */ freeClientAsync(server.master); /* Close ASAP. */
} }
...@@ -2854,9 +2981,7 @@ void replicationCron(void) { ...@@ -2854,9 +2981,7 @@ void replicationCron(void) {
server.rdb_child_type != RDB_CHILD_TYPE_SOCKET)); server.rdb_child_type != RDB_CHILD_TYPE_SOCKET));
if (is_presync) { if (is_presync) {
if (write(slave->fd, "\n", 1) == -1) { connWrite(slave->conn, "\n", 1);
/* Don't worry about socket errors, it's just a ping. */
}
} }
} }
......
...@@ -159,13 +159,13 @@ void rioInitWithFile(rio *r, FILE *fp) { ...@@ -159,13 +159,13 @@ void rioInitWithFile(rio *r, FILE *fp) {
r->io.file.autosync = 0; r->io.file.autosync = 0;
} }
/* ------------------- File descriptor implementation ------------------- /* ------------------- Connection implementation -------------------
* We use this RIO implemetnation when reading an RDB file directly from * We use this RIO implemetnation when reading an RDB file directly from
* the socket to the memory via rdbLoadRio(), thus this implementation * the connection to the memory via rdbLoadRio(), thus this implementation
* only implements reading from a file descriptor that is, normally, * only implements reading from a connection that is, normally,
* just a socket. */ * just a socket. */
static size_t rioFdWrite(rio *r, const void *buf, size_t len) { static size_t rioConnWrite(rio *r, const void *buf, size_t len) {
UNUSED(r); UNUSED(r);
UNUSED(buf); UNUSED(buf);
UNUSED(len); UNUSED(len);
...@@ -173,72 +173,72 @@ static size_t rioFdWrite(rio *r, const void *buf, size_t len) { ...@@ -173,72 +173,72 @@ static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
} }
/* Returns 1 or 0 for success/failure. */ /* Returns 1 or 0 for success/failure. */
static size_t rioFdRead(rio *r, void *buf, size_t len) { static size_t rioConnRead(rio *r, void *buf, size_t len) {
size_t avail = sdslen(r->io.fd.buf)-r->io.fd.pos; size_t avail = sdslen(r->io.conn.buf)-r->io.conn.pos;
/* If the buffer is too small for the entire request: realloc. */ /* If the buffer is too small for the entire request: realloc. */
if (sdslen(r->io.fd.buf) + sdsavail(r->io.fd.buf) < len) if (sdslen(r->io.conn.buf) + sdsavail(r->io.conn.buf) < len)
r->io.fd.buf = sdsMakeRoomFor(r->io.fd.buf, len - sdslen(r->io.fd.buf)); r->io.conn.buf = sdsMakeRoomFor(r->io.conn.buf, len - sdslen(r->io.conn.buf));
/* If the remaining unused buffer is not large enough: memmove so that we /* If the remaining unused buffer is not large enough: memmove so that we
* can read the rest. */ * can read the rest. */
if (len > avail && sdsavail(r->io.fd.buf) < len - avail) { if (len > avail && sdsavail(r->io.conn.buf) < len - avail) {
sdsrange(r->io.fd.buf, r->io.fd.pos, -1); sdsrange(r->io.conn.buf, r->io.conn.pos, -1);
r->io.fd.pos = 0; r->io.conn.pos = 0;
} }
/* If we don't already have all the data in the sds, read more */ /* If we don't already have all the data in the sds, read more */
while (len > sdslen(r->io.fd.buf) - r->io.fd.pos) { while (len > sdslen(r->io.conn.buf) - r->io.conn.pos) {
size_t buffered = sdslen(r->io.fd.buf) - r->io.fd.pos; size_t buffered = sdslen(r->io.conn.buf) - r->io.conn.pos;
size_t toread = len - buffered; size_t toread = len - buffered;
/* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of /* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of
* the two. */ * the two. */
if (toread < PROTO_IOBUF_LEN) toread = PROTO_IOBUF_LEN; if (toread < PROTO_IOBUF_LEN) toread = PROTO_IOBUF_LEN;
if (toread > sdsavail(r->io.fd.buf)) toread = sdsavail(r->io.fd.buf); if (toread > sdsavail(r->io.conn.buf)) toread = sdsavail(r->io.conn.buf);
if (r->io.fd.read_limit != 0 && if (r->io.conn.read_limit != 0 &&
r->io.fd.read_so_far + buffered + toread > r->io.fd.read_limit) r->io.conn.read_so_far + buffered + toread > r->io.conn.read_limit)
{ {
if (r->io.fd.read_limit >= r->io.fd.read_so_far - buffered) if (r->io.conn.read_limit >= r->io.conn.read_so_far - buffered)
toread = r->io.fd.read_limit - r->io.fd.read_so_far - buffered; toread = r->io.conn.read_limit - r->io.conn.read_so_far - buffered;
else { else {
errno = EOVERFLOW; errno = EOVERFLOW;
return 0; return 0;
} }
} }
int retval = read(r->io.fd.fd, int retval = connRead(r->io.conn.conn,
(char*)r->io.fd.buf + sdslen(r->io.fd.buf), (char*)r->io.conn.buf + sdslen(r->io.conn.buf),
toread); toread);
if (retval <= 0) { if (retval <= 0) {
if (errno == EWOULDBLOCK) errno = ETIMEDOUT; if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
return 0; return 0;
} }
sdsIncrLen(r->io.fd.buf, retval); sdsIncrLen(r->io.conn.buf, retval);
} }
memcpy(buf, (char*)r->io.fd.buf + r->io.fd.pos, len); memcpy(buf, (char*)r->io.conn.buf + r->io.conn.pos, len);
r->io.fd.read_so_far += len; r->io.conn.read_so_far += len;
r->io.fd.pos += len; r->io.conn.pos += len;
return len; return len;
} }
/* Returns read/write position in file. */ /* Returns read/write position in file. */
static off_t rioFdTell(rio *r) { static off_t rioConnTell(rio *r) {
return r->io.fd.read_so_far; return r->io.conn.read_so_far;
} }
/* Flushes any buffer to target device if applicable. Returns 1 on success /* Flushes any buffer to target device if applicable. Returns 1 on success
* and 0 on failures. */ * and 0 on failures. */
static int rioFdFlush(rio *r) { static int rioConnFlush(rio *r) {
/* Our flush is implemented by the write method, that recognizes a /* Our flush is implemented by the write method, that recognizes a
* buffer set to NULL with a count of zero as a flush request. */ * buffer set to NULL with a count of zero as a flush request. */
return rioFdWrite(r,NULL,0); return rioConnWrite(r,NULL,0);
} }
static const rio rioFdIO = { static const rio rioConnIO = {
rioFdRead, rioConnRead,
rioFdWrite, rioConnWrite,
rioFdTell, rioConnTell,
rioFdFlush, rioConnFlush,
NULL, /* update_checksum */ NULL, /* update_checksum */
0, /* current checksum */ 0, /* current checksum */
0, /* flags */ 0, /* flags */
...@@ -249,108 +249,90 @@ static const rio rioFdIO = { ...@@ -249,108 +249,90 @@ static const rio rioFdIO = {
/* Create an RIO that implements a buffered read from an fd /* Create an RIO that implements a buffered read from an fd
* read_limit argument stops buffering when the reaching the limit. */ * read_limit argument stops buffering when the reaching the limit. */
void rioInitWithFd(rio *r, int fd, size_t read_limit) { void rioInitWithConn(rio *r, connection *conn, size_t read_limit) {
*r = rioFdIO; *r = rioConnIO;
r->io.fd.fd = fd; r->io.conn.conn = conn;
r->io.fd.pos = 0; r->io.conn.pos = 0;
r->io.fd.read_limit = read_limit; r->io.conn.read_limit = read_limit;
r->io.fd.read_so_far = 0; r->io.conn.read_so_far = 0;
r->io.fd.buf = sdsnewlen(NULL, PROTO_IOBUF_LEN); r->io.conn.buf = sdsnewlen(NULL, PROTO_IOBUF_LEN);
sdsclear(r->io.fd.buf); sdsclear(r->io.conn.buf);
} }
/* Release the RIO tream. Optionally returns the unread buffered data /* Release the RIO tream. Optionally returns the unread buffered data
* when the SDS pointer 'remaining' is passed. */ * when the SDS pointer 'remaining' is passed. */
void rioFreeFd(rio *r, sds *remaining) { void rioFreeConn(rio *r, sds *remaining) {
if (remaining && (size_t)r->io.fd.pos < sdslen(r->io.fd.buf)) { if (remaining && (size_t)r->io.conn.pos < sdslen(r->io.conn.buf)) {
if (r->io.fd.pos > 0) sdsrange(r->io.fd.buf, r->io.fd.pos, -1); if (r->io.conn.pos > 0) sdsrange(r->io.conn.buf, r->io.conn.pos, -1);
*remaining = r->io.fd.buf; *remaining = r->io.conn.buf;
} else { } else {
sdsfree(r->io.fd.buf); sdsfree(r->io.conn.buf);
if (remaining) *remaining = NULL; if (remaining) *remaining = NULL;
} }
r->io.fd.buf = NULL; r->io.conn.buf = NULL;
} }
/* ------------------- File descriptors set implementation ------------------ /* ------------------- File descriptor implementation ------------------
* This target is used to write the RDB file to N different replicas via * This target is used to write the RDB file to pipe, when the master just
* sockets, when the master just streams the data to the replicas without * streams the data to the replicas without creating an RDB on-disk image
* creating an RDB on-disk image (diskless replication option). * (diskless replication option).
* It only implements writes. */ * It only implements writes. */
/* Returns 1 or 0 for success/failure. /* Returns 1 or 0 for success/failure.
* The function returns success as long as we are able to correctly write
* to at least one file descriptor.
* *
* When buf is NULL and len is 0, the function performs a flush operation * When buf is NULL and len is 0, the function performs a flush operation
* if there is some pending buffer, so this function is also used in order * if there is some pending buffer, so this function is also used in order
* to implement rioFdsetFlush(). */ * to implement rioFdFlush(). */
static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) { static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
ssize_t retval; ssize_t retval;
int j;
unsigned char *p = (unsigned char*) buf; unsigned char *p = (unsigned char*) buf;
int doflush = (buf == NULL && len == 0); int doflush = (buf == NULL && len == 0);
/* To start we always append to our buffer. If it gets larger than /* For small writes, we rather keep the data in user-space buffer, and flush
* a given size, we actually write to the sockets. */ * it only when it grows. however for larger writes, we prefer to flush
if (len) { * any pre-existing buffer, and write the new one directly without reallocs
r->io.fdset.buf = sdscatlen(r->io.fdset.buf,buf,len); * and memory copying. */
len = 0; /* Prevent entering the while below if we don't flush. */ if (len > PROTO_IOBUF_LEN) {
if (sdslen(r->io.fdset.buf) > PROTO_IOBUF_LEN) doflush = 1; /* First, flush any pre-existing buffered data. */
if (sdslen(r->io.fd.buf)) {
if (rioFdWrite(r, NULL, 0) == 0)
return 0;
} }
/* Write the new data, keeping 'p' and 'len' from the input. */
if (doflush) { } else {
p = (unsigned char*) r->io.fdset.buf; if (len) {
len = sdslen(r->io.fdset.buf); r->io.fd.buf = sdscatlen(r->io.fd.buf,buf,len);
if (sdslen(r->io.fd.buf) > PROTO_IOBUF_LEN)
doflush = 1;
if (!doflush)
return 1;
} }
/* Flusing the buffered data. set 'p' and 'len' accordintly. */
/* Write in little chunchs so that when there are big writes we p = (unsigned char*) r->io.fd.buf;
* parallelize while the kernel is sending data in background to len = sdslen(r->io.fd.buf);
* the TCP socket. */
while(len) {
size_t count = len < 1024 ? len : 1024;
int broken = 0;
for (j = 0; j < r->io.fdset.numfds; j++) {
if (r->io.fdset.state[j] != 0) {
/* Skip FDs alraedy in error. */
broken++;
continue;
} }
/* Make sure to write 'count' bytes to the socket regardless
* of short writes. */
size_t nwritten = 0; size_t nwritten = 0;
while(nwritten != count) { while(nwritten != len) {
retval = write(r->io.fdset.fds[j],p+nwritten,count-nwritten); retval = write(r->io.fd.fd,p+nwritten,len-nwritten);
if (retval <= 0) { if (retval <= 0) {
/* With blocking sockets, which is the sole user of this /* With blocking io, which is the sole user of this
* rio target, EWOULDBLOCK is returned only because of * rio target, EWOULDBLOCK is returned only because of
* the SO_SNDTIMEO socket option, so we translate the error * the SO_SNDTIMEO socket option, so we translate the error
* into one more recognizable by the user. */ * into one more recognizable by the user. */
if (retval == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT; if (retval == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT;
break; return 0; /* error. */
} }
nwritten += retval; nwritten += retval;
} }
if (nwritten != count) { r->io.fd.pos += len;
/* Mark this FD as broken. */ sdsclear(r->io.fd.buf);
r->io.fdset.state[j] = errno;
if (r->io.fdset.state[j] == 0) r->io.fdset.state[j] = EIO;
}
}
if (broken == r->io.fdset.numfds) return 0; /* All the FDs in error. */
p += count;
len -= count;
r->io.fdset.pos += count;
}
if (doflush) sdsclear(r->io.fdset.buf);
return 1; return 1;
} }
/* Returns 1 or 0 for success/failure. */ /* Returns 1 or 0 for success/failure. */
static size_t rioFdsetRead(rio *r, void *buf, size_t len) { static size_t rioFdRead(rio *r, void *buf, size_t len) {
UNUSED(r); UNUSED(r);
UNUSED(buf); UNUSED(buf);
UNUSED(len); UNUSED(len);
...@@ -358,23 +340,23 @@ static size_t rioFdsetRead(rio *r, void *buf, size_t len) { ...@@ -358,23 +340,23 @@ static size_t rioFdsetRead(rio *r, void *buf, size_t len) {
} }
/* Returns read/write position in file. */ /* Returns read/write position in file. */
static off_t rioFdsetTell(rio *r) { static off_t rioFdTell(rio *r) {
return r->io.fdset.pos; return r->io.fd.pos;
} }
/* Flushes any buffer to target device if applicable. Returns 1 on success /* Flushes any buffer to target device if applicable. Returns 1 on success
* and 0 on failures. */ * and 0 on failures. */
static int rioFdsetFlush(rio *r) { static int rioFdFlush(rio *r) {
/* Our flush is implemented by the write method, that recognizes a /* Our flush is implemented by the write method, that recognizes a
* buffer set to NULL with a count of zero as a flush request. */ * buffer set to NULL with a count of zero as a flush request. */
return rioFdsetWrite(r,NULL,0); return rioFdWrite(r,NULL,0);
} }
static const rio rioFdsetIO = { static const rio rioFdIO = {
rioFdsetRead, rioFdRead,
rioFdsetWrite, rioFdWrite,
rioFdsetTell, rioFdTell,
rioFdsetFlush, rioFdFlush,
NULL, /* update_checksum */ NULL, /* update_checksum */
0, /* current checksum */ 0, /* current checksum */
0, /* flags */ 0, /* flags */
...@@ -383,24 +365,16 @@ static const rio rioFdsetIO = { ...@@ -383,24 +365,16 @@ static const rio rioFdsetIO = {
{ { NULL, 0 } } /* union for io-specific vars */ { { NULL, 0 } } /* union for io-specific vars */
}; };
void rioInitWithFdset(rio *r, int *fds, int numfds) { void rioInitWithFd(rio *r, int fd) {
int j; *r = rioFdIO;
r->io.fd.fd = fd;
*r = rioFdsetIO; r->io.fd.pos = 0;
r->io.fdset.fds = zmalloc(sizeof(int)*numfds); r->io.fd.buf = sdsempty();
r->io.fdset.state = zmalloc(sizeof(int)*numfds);
memcpy(r->io.fdset.fds,fds,sizeof(int)*numfds);
for (j = 0; j < numfds; j++) r->io.fdset.state[j] = 0;
r->io.fdset.numfds = numfds;
r->io.fdset.pos = 0;
r->io.fdset.buf = sdsempty();
} }
/* release the rio stream. */ /* release the rio stream. */
void rioFreeFdset(rio *r) { void rioFreeFd(rio *r) {
zfree(r->io.fdset.fds); sdsfree(r->io.fd.buf);
zfree(r->io.fdset.state);
sdsfree(r->io.fdset.buf);
} }
/* ---------------------------- Generic functions ---------------------------- */ /* ---------------------------- Generic functions ---------------------------- */
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include "sds.h" #include "sds.h"
#include "connection.h"
#define RIO_FLAG_READ_ERROR (1<<0) #define RIO_FLAG_READ_ERROR (1<<0)
#define RIO_FLAG_WRITE_ERROR (1<<1) #define RIO_FLAG_WRITE_ERROR (1<<1)
...@@ -76,22 +77,20 @@ struct _rio { ...@@ -76,22 +77,20 @@ struct _rio {
off_t buffered; /* Bytes written since last fsync. */ off_t buffered; /* Bytes written since last fsync. */
off_t autosync; /* fsync after 'autosync' bytes written. */ off_t autosync; /* fsync after 'autosync' bytes written. */
} file; } file;
/* file descriptor */ /* Connection object (used to read from socket) */
struct { struct {
int fd; /* File descriptor. */ connection *conn; /* Connection */
off_t pos; /* pos in buf that was returned */ off_t pos; /* pos in buf that was returned */
sds buf; /* buffered data */ sds buf; /* buffered data */
size_t read_limit; /* don't allow to buffer/read more than that */ size_t read_limit; /* don't allow to buffer/read more than that */
size_t read_so_far; /* amount of data read from the rio (not buffered) */ size_t read_so_far; /* amount of data read from the rio (not buffered) */
} fd; } conn;
/* Multiple FDs target (used to write to N sockets). */ /* FD target (used to write to pipe). */
struct { struct {
int *fds; /* File descriptors. */ int fd; /* File descriptor. */
int *state; /* Error state of each fd. 0 (if ok) or errno. */
int numfds;
off_t pos; off_t pos;
sds buf; sds buf;
} fdset; } fd;
} io; } io;
}; };
...@@ -159,11 +158,11 @@ static inline void rioClearErrors(rio *r) { ...@@ -159,11 +158,11 @@ static inline void rioClearErrors(rio *r) {
void rioInitWithFile(rio *r, FILE *fp); void rioInitWithFile(rio *r, FILE *fp);
void rioInitWithBuffer(rio *r, sds s); void rioInitWithBuffer(rio *r, sds s);
void rioInitWithFd(rio *r, int fd, size_t read_limit); void rioInitWithConn(rio *r, connection *conn, size_t read_limit);
void rioInitWithFdset(rio *r, int *fds, int numfds); void rioInitWithFd(rio *r, int fd);
void rioFreeFdset(rio *r); void rioFreeFd(rio *r);
void rioFreeFd(rio *r, sds* out_remainingBufferedData); void rioFreeConn(rio *r, sds* out_remainingBufferedData);
size_t rioWriteBulkCount(rio *r, char prefix, long count); size_t rioWriteBulkCount(rio *r, char prefix, long count);
size_t rioWriteBulkString(rio *r, const char *buf, size_t len); size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
......
...@@ -61,7 +61,7 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx); ...@@ -61,7 +61,7 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx);
#define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */ #define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */
#define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */ #define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */
struct ldbState { struct ldbState {
int fd; /* Socket of the debugging client. */ connection *conn; /* Connection of the debugging client. */
int active; /* Are we debugging EVAL right now? */ int active; /* Are we debugging EVAL right now? */
int forked; /* Is this a fork()ed debugging session? */ int forked; /* Is this a fork()ed debugging session? */
list *logs; /* List of messages to send to the client. */ list *logs; /* List of messages to send to the client. */
...@@ -1243,7 +1243,7 @@ void scriptingInit(int setup) { ...@@ -1243,7 +1243,7 @@ void scriptingInit(int setup) {
* Note: there is no need to create it again when this function is called * Note: there is no need to create it again when this function is called
* by scriptingReset(). */ * by scriptingReset(). */
if (server.lua_client == NULL) { if (server.lua_client == NULL) {
server.lua_client = createClient(-1); server.lua_client = createClient(NULL);
server.lua_client->flags |= CLIENT_LUA; server.lua_client->flags |= CLIENT_LUA;
} }
...@@ -1734,7 +1734,7 @@ NULL ...@@ -1734,7 +1734,7 @@ NULL
/* Initialize Lua debugger data structures. */ /* Initialize Lua debugger data structures. */
void ldbInit(void) { void ldbInit(void) {
ldb.fd = -1; ldb.conn = NULL;
ldb.active = 0; ldb.active = 0;
ldb.logs = listCreate(); ldb.logs = listCreate();
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree); listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
...@@ -1756,7 +1756,7 @@ void ldbFlushLog(list *log) { ...@@ -1756,7 +1756,7 @@ void ldbFlushLog(list *log) {
void ldbEnable(client *c) { void ldbEnable(client *c) {
c->flags |= CLIENT_LUA_DEBUG; c->flags |= CLIENT_LUA_DEBUG;
ldbFlushLog(ldb.logs); ldbFlushLog(ldb.logs);
ldb.fd = c->fd; ldb.conn = c->conn;
ldb.step = 1; ldb.step = 1;
ldb.bpcount = 0; ldb.bpcount = 0;
ldb.luabp = 0; ldb.luabp = 0;
...@@ -1811,7 +1811,7 @@ void ldbSendLogs(void) { ...@@ -1811,7 +1811,7 @@ void ldbSendLogs(void) {
proto = sdscatlen(proto,"\r\n",2); proto = sdscatlen(proto,"\r\n",2);
listDelNode(ldb.logs,ln); listDelNode(ldb.logs,ln);
} }
if (write(ldb.fd,proto,sdslen(proto)) == -1) { if (connWrite(ldb.conn,proto,sdslen(proto)) == -1) {
/* Avoid warning. We don't check the return value of write() /* Avoid warning. We don't check the return value of write()
* since the next read() will catch the I/O error and will * since the next read() will catch the I/O error and will
* close the debugging session. */ * close the debugging session. */
...@@ -1863,8 +1863,8 @@ int ldbStartSession(client *c) { ...@@ -1863,8 +1863,8 @@ int ldbStartSession(client *c) {
} }
/* Setup our debugging session. */ /* Setup our debugging session. */
anetBlock(NULL,ldb.fd); connBlock(ldb.conn);
anetSendTimeout(NULL,ldb.fd,5000); connSendTimeout(ldb.conn,5000);
ldb.active = 1; ldb.active = 1;
/* First argument of EVAL is the script itself. We split it into different /* First argument of EVAL is the script itself. We split it into different
...@@ -1891,7 +1891,7 @@ void ldbEndSession(client *c) { ...@@ -1891,7 +1891,7 @@ void ldbEndSession(client *c) {
/* If it's a fork()ed session, we just exit. */ /* If it's a fork()ed session, we just exit. */
if (ldb.forked) { if (ldb.forked) {
writeToClient(c->fd, c, 0); writeToClient(c,0);
serverLog(LL_WARNING,"Lua debugging session child exiting"); serverLog(LL_WARNING,"Lua debugging session child exiting");
exitFromChild(0); exitFromChild(0);
} else { } else {
...@@ -1900,8 +1900,8 @@ void ldbEndSession(client *c) { ...@@ -1900,8 +1900,8 @@ void ldbEndSession(client *c) {
} }
/* Otherwise let's restore client's state. */ /* Otherwise let's restore client's state. */
anetNonBlock(NULL,ldb.fd); connNonBlock(ldb.conn);
anetSendTimeout(NULL,ldb.fd,0); connSendTimeout(ldb.conn,0);
/* Close the client connectin after sending the final EVAL reply /* Close the client connectin after sending the final EVAL reply
* in order to signal the end of the debugging session. */ * in order to signal the end of the debugging session. */
...@@ -2538,7 +2538,7 @@ int ldbRepl(lua_State *lua) { ...@@ -2538,7 +2538,7 @@ int ldbRepl(lua_State *lua) {
while(1) { while(1) {
while((argv = ldbReplParseCommand(&argc)) == NULL) { while((argv = ldbReplParseCommand(&argc)) == NULL) {
char buf[1024]; char buf[1024];
int nread = read(ldb.fd,buf,sizeof(buf)); int nread = connRead(ldb.conn,buf,sizeof(buf));
if (nread <= 0) { if (nread <= 0) {
/* Make sure the script runs without user input since the /* Make sure the script runs without user input since the
* client is no longer connected. */ * client is no longer connected. */
......
...@@ -30,6 +30,10 @@ ...@@ -30,6 +30,10 @@
#include "server.h" #include "server.h"
#include "hiredis.h" #include "hiredis.h"
#ifdef USE_OPENSSL
#include "openssl/ssl.h"
#include "hiredis_ssl.h"
#endif
#include "async.h" #include "async.h"
#include <ctype.h> #include <ctype.h>
...@@ -40,6 +44,10 @@ ...@@ -40,6 +44,10 @@
extern char **environ; extern char **environ;
#ifdef USE_OPENSSL
extern SSL_CTX *redis_tls_ctx;
#endif
#define REDIS_SENTINEL_PORT 26379 #define REDIS_SENTINEL_PORT 26379
/* ======================== Sentinel global state =========================== */ /* ======================== Sentinel global state =========================== */
...@@ -1995,6 +2003,19 @@ void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char ...@@ -1995,6 +2003,19 @@ void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char
} }
} }
static int instanceLinkNegotiateTLS(redisAsyncContext *context) {
#ifndef USE_OPENSSL
(void) context;
#else
if (!redis_tls_ctx) return C_ERR;
SSL *ssl = SSL_new(redis_tls_ctx);
if (!ssl) return C_ERR;
if (redisInitiateSSL(&context->c, ssl) == REDIS_ERR) return C_ERR;
#endif
return C_OK;
}
/* Create the async connections for the instance link if the link /* Create the async connections for the instance link if the link
* is disconnected. Note that link->disconnected is true even if just * is disconnected. Note that link->disconnected is true even if just
* one of the two links (commands and pub/sub) is missing. */ * one of the two links (commands and pub/sub) is missing. */
...@@ -2010,7 +2031,11 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) { ...@@ -2010,7 +2031,11 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Commands connection. */ /* Commands connection. */
if (link->cc == NULL) { if (link->cc == NULL) {
link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR); link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
if (link->cc->err) { if (!link->cc->err && server.tls_replication &&
(instanceLinkNegotiateTLS(link->cc) == C_ERR)) {
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to initialize TLS");
instanceLinkCloseConnection(link,link->cc);
} else if (link->cc->err) {
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s", sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",
link->cc->errstr); link->cc->errstr);
instanceLinkCloseConnection(link,link->cc); instanceLinkCloseConnection(link,link->cc);
...@@ -2033,7 +2058,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) { ...@@ -2033,7 +2058,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Pub / Sub */ /* Pub / Sub */
if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) { if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR); link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
if (link->pc->err) { if (!link->pc->err && server.tls_replication &&
(instanceLinkNegotiateTLS(link->pc) == C_ERR)) {
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to initialize TLS");
} else if (link->pc->err) {
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s", sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",
link->pc->errstr); link->pc->errstr);
instanceLinkCloseConnection(link,link->pc); instanceLinkCloseConnection(link,link->pc);
...@@ -2584,8 +2612,9 @@ int sentinelSendHello(sentinelRedisInstance *ri) { ...@@ -2584,8 +2612,9 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
return C_ERR; return C_ERR;
announce_ip = ip; announce_ip = ip;
} }
announce_port = sentinel.announce_port ? if (sentinel.announce_port) announce_port = sentinel.announce_port;
sentinel.announce_port : server.port; else if (server.tls_replication && server.tls_port) announce_port = server.tls_port;
else announce_port = server.port;
/* Format and send the Hello message. */ /* Format and send the Hello message. */
snprintf(payload,sizeof(payload), snprintf(payload,sizeof(payload),
......
...@@ -1752,6 +1752,62 @@ void updateCachedTime(void) { ...@@ -1752,6 +1752,62 @@ void updateCachedTime(void) {
server.daylight_active = tm.tm_isdst; server.daylight_active = tm.tm_isdst;
} }
void checkChildrenDone(void) {
int statloc;
pid_t pid;
/* If we have a diskless rdb child (note that we support only one concurrent
* child), we want to avoid collecting it's exit status and acting on it
* as long as we didn't finish to drain the pipe, since then we're at risk
* of starting a new fork and a new pipe before we're done with the previous
* one. */
if (server.rdb_child_pid != -1 && server.rdb_pipe_conns)
return;
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
int exitcode = WEXITSTATUS(statloc);
int bysignal = 0;
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
/* sigKillChildHandler catches the signal and calls exit(), but we
* must make sure not to flag lastbgsave_status, etc incorrectly.
* We could directly terminate the child process via SIGUSR1
* without handling it, but in this case Valgrind will log an
* annoying error. */
if (exitcode == SERVER_CHILD_NOERROR_RETVAL) {
bysignal = SIGUSR1;
exitcode = 1;
}
if (pid == -1) {
serverLog(LL_WARNING,"wait3() returned an error: %s. "
"rdb_child_pid = %d, aof_child_pid = %d, module_child_pid = %d",
strerror(errno),
(int) server.rdb_child_pid,
(int) server.aof_child_pid,
(int) server.module_child_pid);
} else if (pid == server.rdb_child_pid) {
backgroundSaveDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else if (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else if (pid == server.module_child_pid) {
ModuleForkDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else {
if (!ldbRemoveChild(pid)) {
serverLog(LL_WARNING,
"Warning, detected child with unmatched pid: %ld",
(long)pid);
}
}
updateDictResizePolicy();
closeChildInfoPipe();
}
}
/* This is our timer interrupt, called server.hz times per second. /* This is our timer interrupt, called server.hz times per second.
* Here is where we do a number of things that need to be done asynchronously. * Here is where we do a number of things that need to be done asynchronously.
* For instance: * For instance:
...@@ -1903,51 +1959,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -1903,51 +1959,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* Check if a background saving or AOF rewrite in progress terminated. */ /* Check if a background saving or AOF rewrite in progress terminated. */
if (hasActiveChildProcess() || ldbPendingChildren()) if (hasActiveChildProcess() || ldbPendingChildren())
{ {
int statloc; checkChildrenDone();
pid_t pid;
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
int exitcode = WEXITSTATUS(statloc);
int bysignal = 0;
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
/* sigKillChildHandler catches the signal and calls exit(), but we
* must make sure not to flag lastbgsave_status, etc incorrectly.
* We could directly terminate the child process via SIGUSR1
* without handling it, but in this case Valgrind will log an
* annoying error. */
if (exitcode == SERVER_CHILD_NOERROR_RETVAL) {
bysignal = SIGUSR1;
exitcode = 1;
}
if (pid == -1) {
serverLog(LL_WARNING,"wait3() returned an error: %s. "
"rdb_child_pid = %d, aof_child_pid = %d, module_child_pid = %d",
strerror(errno),
(int) server.rdb_child_pid,
(int) server.aof_child_pid,
(int) server.module_child_pid);
} else if (pid == server.rdb_child_pid) {
backgroundSaveDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else if (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else if (pid == server.module_child_pid) {
ModuleForkDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else {
if (!ldbRemoveChild(pid)) {
serverLog(LL_WARNING,
"Warning, detected child with unmatched pid: %ld",
(long)pid);
}
}
updateDictResizePolicy();
closeChildInfoPipe();
}
} else { } else {
/* If there is not a background saving/rewrite in progress check if /* If there is not a background saving/rewrite in progress check if
* we have to save/rewrite now. */ * we have to save/rewrite now. */
...@@ -2054,6 +2066,11 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -2054,6 +2066,11 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
void beforeSleep(struct aeEventLoop *eventLoop) { void beforeSleep(struct aeEventLoop *eventLoop) {
UNUSED(eventLoop); UNUSED(eventLoop);
/* Handle TLS pending data. (must be done before flushAppendOnlyFile) */
tlsProcessPendingData();
/* If tls still has pending unread data don't sleep at all. */
aeSetDontWait(server.el, tlsHasPendingData());
/* Call the Redis Cluster before sleep function. Note that this function /* Call the Redis Cluster before sleep function. Note that this function
* may change the state of Redis Cluster (from ok to fail or vice versa), * may change the state of Redis Cluster (from ok to fail or vice versa),
* so it's a good idea to call it before serving the unblocked clients * so it's a good idea to call it before serving the unblocked clients
...@@ -2247,11 +2264,13 @@ void initServerConfig(void) { ...@@ -2247,11 +2264,13 @@ void initServerConfig(void) {
server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ; server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ;
server.arch_bits = (sizeof(long) == 8) ? 64 : 32; server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
server.port = CONFIG_DEFAULT_SERVER_PORT; server.port = CONFIG_DEFAULT_SERVER_PORT;
server.tls_port = CONFIG_DEFAULT_SERVER_TLS_PORT;
server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG; server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
server.bindaddr_count = 0; server.bindaddr_count = 0;
server.unixsocket = NULL; server.unixsocket = NULL;
server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM; server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
server.ipfd_count = 0; server.ipfd_count = 0;
server.tlsfd_count = 0;
server.sofd = -1; server.sofd = -1;
server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE; server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE;
server.gopher_enabled = CONFIG_DEFAULT_GOPHER_ENABLED; server.gopher_enabled = CONFIG_DEFAULT_GOPHER_ENABLED;
...@@ -2286,6 +2305,7 @@ void initServerConfig(void) { ...@@ -2286,6 +2305,7 @@ void initServerConfig(void) {
server.aof_rewrite_min_size = AOF_REWRITE_MIN_SIZE; server.aof_rewrite_min_size = AOF_REWRITE_MIN_SIZE;
server.aof_rewrite_base_size = 0; server.aof_rewrite_base_size = 0;
server.aof_rewrite_scheduled = 0; server.aof_rewrite_scheduled = 0;
server.aof_flush_sleep = 0;
server.aof_last_fsync = time(NULL); server.aof_last_fsync = time(NULL);
server.aof_rewrite_time_last = -1; server.aof_rewrite_time_last = -1;
server.aof_rewrite_time_start = -1; server.aof_rewrite_time_start = -1;
...@@ -2297,6 +2317,7 @@ void initServerConfig(void) { ...@@ -2297,6 +2317,7 @@ void initServerConfig(void) {
server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC; server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC; server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC;
server.rdb_key_save_delay = CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY; server.rdb_key_save_delay = CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY;
server.key_load_delay = CONFIG_DEFAULT_KEY_LOAD_DELAY;
server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED; server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED;
server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE; server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE;
server.pidfile = NULL; server.pidfile = NULL;
...@@ -2368,7 +2389,7 @@ void initServerConfig(void) { ...@@ -2368,7 +2389,7 @@ void initServerConfig(void) {
server.repl_state = REPL_STATE_NONE; server.repl_state = REPL_STATE_NONE;
server.repl_transfer_tmpfile = NULL; server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1; server.repl_transfer_fd = -1;
server.repl_transfer_s = -1; server.repl_transfer_s = NULL;
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT; server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA; server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA;
server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY; server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY;
...@@ -2765,6 +2786,11 @@ void initServer(void) { ...@@ -2765,6 +2786,11 @@ void initServer(void) {
server.clients_paused = 0; server.clients_paused = 0;
server.system_memory_size = zmalloc_get_memory_size(); server.system_memory_size = zmalloc_get_memory_size();
if (server.tls_port && tlsConfigure(&server.tls_ctx_config) == C_ERR) {
serverLog(LL_WARNING, "Failed to configure TLS. Check logs for more info.");
exit(1);
}
createSharedObjects(); createSharedObjects();
adjustOpenFilesLimit(); adjustOpenFilesLimit();
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR); server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
...@@ -2780,6 +2806,9 @@ void initServer(void) { ...@@ -2780,6 +2806,9 @@ void initServer(void) {
if (server.port != 0 && if (server.port != 0 &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR) listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
exit(1); exit(1);
if (server.tls_port != 0 &&
listenToPort(server.tls_port,server.tlsfd,&server.tlsfd_count) == C_ERR)
exit(1);
/* Open the listening Unix domain socket. */ /* Open the listening Unix domain socket. */
if (server.unixsocket != NULL) { if (server.unixsocket != NULL) {
...@@ -2794,7 +2823,7 @@ void initServer(void) { ...@@ -2794,7 +2823,7 @@ void initServer(void) {
} }
/* Abort if there are no listening sockets at all. */ /* Abort if there are no listening sockets at all. */
if (server.ipfd_count == 0 && server.sofd < 0) { if (server.ipfd_count == 0 && server.tlsfd_count == 0 && server.sofd < 0) {
serverLog(LL_WARNING, "Configured to not listen anywhere, exiting."); serverLog(LL_WARNING, "Configured to not listen anywhere, exiting.");
exit(1); exit(1);
} }
...@@ -2820,6 +2849,11 @@ void initServer(void) { ...@@ -2820,6 +2849,11 @@ void initServer(void) {
server.aof_child_pid = -1; server.aof_child_pid = -1;
server.module_child_pid = -1; server.module_child_pid = -1;
server.rdb_child_type = RDB_CHILD_TYPE_NONE; server.rdb_child_type = RDB_CHILD_TYPE_NONE;
server.rdb_pipe_conns = NULL;
server.rdb_pipe_numconns = 0;
server.rdb_pipe_numconns_writing = 0;
server.rdb_pipe_buff = NULL;
server.rdb_pipe_bufflen = 0;
server.rdb_bgsave_scheduled = 0; server.rdb_bgsave_scheduled = 0;
server.child_info_pipe[0] = -1; server.child_info_pipe[0] = -1;
server.child_info_pipe[1] = -1; server.child_info_pipe[1] = -1;
...@@ -2866,6 +2900,14 @@ void initServer(void) { ...@@ -2866,6 +2900,14 @@ void initServer(void) {
"Unrecoverable error creating server.ipfd file event."); "Unrecoverable error creating server.ipfd file event.");
} }
} }
for (j = 0; j < server.tlsfd_count; j++) {
if (aeCreateFileEvent(server.el, server.tlsfd[j], AE_READABLE,
acceptTLSHandler,NULL) == AE_ERR)
{
serverPanic(
"Unrecoverable error creating server.tlsfd file event.");
}
}
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE, if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event."); acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");
...@@ -3570,6 +3612,7 @@ void closeListeningSockets(int unlink_unix_socket) { ...@@ -3570,6 +3612,7 @@ void closeListeningSockets(int unlink_unix_socket) {
int j; int j;
for (j = 0; j < server.ipfd_count; j++) close(server.ipfd[j]); for (j = 0; j < server.ipfd_count; j++) close(server.ipfd[j]);
for (j = 0; j < server.tlsfd_count; j++) close(server.tlsfd[j]);
if (server.sofd != -1) close(server.sofd); if (server.sofd != -1) close(server.sofd);
if (server.cluster_enabled) if (server.cluster_enabled)
for (j = 0; j < server.cfd_count; j++) close(server.cfd[j]); for (j = 0; j < server.cfd_count; j++) close(server.cfd[j]);
...@@ -3940,7 +3983,7 @@ sds genRedisInfoString(char *section) { ...@@ -3940,7 +3983,7 @@ sds genRedisInfoString(char *section) {
#endif #endif
(int64_t) getpid(), (int64_t) getpid(),
server.runid, server.runid,
server.port, server.port ? server.port : server.tls_port,
(int64_t)uptime, (int64_t)uptime,
(int64_t)(uptime/(3600*24)), (int64_t)(uptime/(3600*24)),
server.hz, server.hz,
...@@ -4324,7 +4367,7 @@ sds genRedisInfoString(char *section) { ...@@ -4324,7 +4367,7 @@ sds genRedisInfoString(char *section) {
long lag = 0; long lag = 0;
if (slaveip[0] == '\0') { if (slaveip[0] == '\0') {
if (anetPeerToString(slave->fd,ip,sizeof(ip),&port) == -1) if (connPeerToString(slave->conn,ip,sizeof(ip),&port) == -1)
continue; continue;
slaveip = ip; slaveip = ip;
} }
...@@ -4578,7 +4621,7 @@ void redisAsciiArt(void) { ...@@ -4578,7 +4621,7 @@ void redisAsciiArt(void) {
if (!show_logo) { if (!show_logo) {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Running mode=%s, port=%d.", "Running mode=%s, port=%d.",
mode, server.port mode, server.port ? server.port : server.tls_port
); );
} else { } else {
snprintf(buf,1024*16,ascii_logo, snprintf(buf,1024*16,ascii_logo,
...@@ -4586,7 +4629,7 @@ void redisAsciiArt(void) { ...@@ -4586,7 +4629,7 @@ void redisAsciiArt(void) {
redisGitSHA1(), redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0, strtol(redisGitDirty(),NULL,10) > 0,
(sizeof(long) == 8) ? "64" : "32", (sizeof(long) == 8) ? "64" : "32",
mode, server.port, mode, server.port ? server.port : server.tls_port,
(long) getpid() (long) getpid()
); );
serverLogRaw(LL_NOTICE|LL_RAW,buf); serverLogRaw(LL_NOTICE|LL_RAW,buf);
...@@ -4769,7 +4812,7 @@ void redisSetProcTitle(char *title) { ...@@ -4769,7 +4812,7 @@ void redisSetProcTitle(char *title) {
setproctitle("%s %s:%d%s", setproctitle("%s %s:%d%s",
title, title,
server.bindaddr_count ? server.bindaddr[0] : "*", server.bindaddr_count ? server.bindaddr[0] : "*",
server.port, server.port ? server.port : server.tls_port,
server_mode); server_mode);
#else #else
UNUSED(title); UNUSED(title);
...@@ -4920,6 +4963,7 @@ int main(int argc, char **argv) { ...@@ -4920,6 +4963,7 @@ int main(int argc, char **argv) {
ACLInit(); /* The ACL subsystem must be initialized ASAP because the ACLInit(); /* The ACL subsystem must be initialized ASAP because the
basic networking code and client creation depends on it. */ basic networking code and client creation depends on it. */
moduleInitModulesSystem(); moduleInitModulesSystem();
tlsInit();
/* Store the executable path and arguments in a safe place in order /* Store the executable path and arguments in a safe place in order
* to be able to restart the server later. */ * to be able to restart the server later. */
...@@ -5053,7 +5097,7 @@ int main(int argc, char **argv) { ...@@ -5053,7 +5097,7 @@ int main(int argc, char **argv) {
exit(1); exit(1);
} }
} }
if (server.ipfd_count > 0) if (server.ipfd_count > 0 || server.tlsfd_count > 0)
serverLog(LL_NOTICE,"Ready to accept connections"); serverLog(LL_NOTICE,"Ready to accept connections");
if (server.sofd > 0) if (server.sofd > 0)
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket); serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
......
...@@ -66,6 +66,7 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -66,6 +66,7 @@ typedef long long mstime_t; /* millisecond time type. */
#include "quicklist.h" /* Lists are encoded as linked lists of #include "quicklist.h" /* Lists are encoded as linked lists of
N-elements flat arrays */ N-elements flat arrays */
#include "rax.h" /* Radix tree */ #include "rax.h" /* Radix tree */
#include "connection.h" /* Connection abstraction */
/* Following includes allow test functions to be called from Redis main() */ /* Following includes allow test functions to be called from Redis main() */
#include "zipmap.h" #include "zipmap.h"
...@@ -84,6 +85,7 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -84,6 +85,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_MAX_HZ 500 #define CONFIG_MAX_HZ 500
#define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */ #define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */ #define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */
#define CONFIG_DEFAULT_SERVER_TLS_PORT 0 /* TCP port. */
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */ #define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */ #define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */
#define CONFIG_DEFAULT_DBNUM 16 #define CONFIG_DEFAULT_DBNUM 16
...@@ -133,6 +135,7 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -133,6 +135,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC 0 #define CONFIG_DEFAULT_REPL_DISKLESS_SYNC 0
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5 #define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5
#define CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY 0 #define CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY 0
#define CONFIG_DEFAULT_KEY_LOAD_DELAY 0
#define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1 #define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1
#define CONFIG_DEFAULT_SLAVE_READ_ONLY 1 #define CONFIG_DEFAULT_SLAVE_READ_ONLY 1
#define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1 #define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1
...@@ -826,7 +829,7 @@ typedef struct user { ...@@ -826,7 +829,7 @@ typedef struct user {
* Clients are taken in a linked list. */ * Clients are taken in a linked list. */
typedef struct client { typedef struct client {
uint64_t id; /* Client incremental unique ID. */ uint64_t id; /* Client incremental unique ID. */
int fd; /* Client socket. */ connection *conn;
int resp; /* RESP protocol version. Can be 2 or 3. */ int resp; /* RESP protocol version. Can be 2 or 3. */
redisDb *db; /* Pointer to currently SELECTed DB. */ redisDb *db; /* Pointer to currently SELECTed DB. */
robj *name; /* As set by CLIENT SETNAME. */ robj *name; /* As set by CLIENT SETNAME. */
...@@ -1034,6 +1037,22 @@ struct malloc_stats { ...@@ -1034,6 +1037,22 @@ struct malloc_stats {
size_t allocator_resident; size_t allocator_resident;
}; };
/*-----------------------------------------------------------------------------
* TLS Context Configuration
*----------------------------------------------------------------------------*/
typedef struct redisTLSContextConfig {
char *cert_file;
char *key_file;
char *dh_params_file;
char *ca_cert_file;
char *ca_cert_dir;
char *protocols;
char *ciphers;
char *ciphersuites;
int prefer_server_ciphers;
} redisTLSContextConfig;
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Global server state * Global server state
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
...@@ -1088,6 +1107,7 @@ struct redisServer { ...@@ -1088,6 +1107,7 @@ struct redisServer {
pid_t module_child_pid; /* PID of module child */ pid_t module_child_pid; /* PID of module child */
/* Networking */ /* Networking */
int port; /* TCP listening port */ int port; /* TCP listening port */
int tls_port; /* TLS listening port */
int tcp_backlog; /* TCP listen() backlog */ int tcp_backlog; /* TCP listen() backlog */
char *bindaddr[CONFIG_BINDADDR_MAX]; /* Addresses we should bind to */ char *bindaddr[CONFIG_BINDADDR_MAX]; /* Addresses we should bind to */
int bindaddr_count; /* Number of addresses in server.bindaddr[] */ int bindaddr_count; /* Number of addresses in server.bindaddr[] */
...@@ -1095,6 +1115,8 @@ struct redisServer { ...@@ -1095,6 +1115,8 @@ struct redisServer {
mode_t unixsocketperm; /* UNIX socket permission */ mode_t unixsocketperm; /* UNIX socket permission */
int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */ int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */
int ipfd_count; /* Used slots in ipfd[] */ int ipfd_count; /* Used slots in ipfd[] */
int tlsfd[CONFIG_BINDADDR_MAX]; /* TLS socket file descriptors */
int tlsfd_count; /* Used slots in tlsfd[] */
int sofd; /* Unix socket file descriptor */ int sofd; /* Unix socket file descriptor */
int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */ int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */
int cfd_count; /* Used slots in cfd[] */ int cfd_count; /* Used slots in cfd[] */
...@@ -1198,6 +1220,7 @@ struct redisServer { ...@@ -1198,6 +1220,7 @@ struct redisServer {
off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */ off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */
off_t aof_current_size; /* AOF current size. */ off_t aof_current_size; /* AOF current size. */
off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */ off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */
int aof_flush_sleep; /* Micros to sleep before flush. (used by tests) */
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */ int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */
pid_t aof_child_pid; /* PID if rewriting process */ pid_t aof_child_pid; /* PID if rewriting process */
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */ list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */
...@@ -1243,10 +1266,17 @@ struct redisServer { ...@@ -1243,10 +1266,17 @@ struct redisServer {
int rdb_child_type; /* Type of save by active child. */ int rdb_child_type; /* Type of save by active child. */
int lastbgsave_status; /* C_OK or C_ERR */ int lastbgsave_status; /* C_OK or C_ERR */
int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */ int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */
int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */ int rdb_pipe_write; /* RDB pipes used to transfer the rdb */
int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */ int rdb_pipe_read; /* data to the parent process in diskless repl. */
connection **rdb_pipe_conns; /* Connections which are currently the */
int rdb_pipe_numconns; /* target of diskless rdb fork child. */
int rdb_pipe_numconns_writing; /* Number of rdb conns with pending writes. */
char *rdb_pipe_buff; /* In diskless replication, this buffer holds data */
int rdb_pipe_bufflen; /* that was read from the the rdb pipe. */
int rdb_key_save_delay; /* Delay in microseconds between keys while int rdb_key_save_delay; /* Delay in microseconds between keys while
* writing the RDB. (for testings) */ * writing the RDB. (for testings) */
int key_load_delay; /* Delay in microseconds between keys while
* loading aof or rdb. (for testings) */
/* Pipe and data structures for child -> parent info sharing. */ /* Pipe and data structures for child -> parent info sharing. */
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */ int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
struct { struct {
...@@ -1299,7 +1329,7 @@ struct redisServer { ...@@ -1299,7 +1329,7 @@ struct redisServer {
off_t repl_transfer_size; /* Size of RDB to read from master during sync. */ off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
off_t repl_transfer_read; /* Amount of RDB read from master during sync. */ off_t repl_transfer_read; /* Amount of RDB read from master during sync. */
off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */ off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */
int repl_transfer_s; /* Slave -> Master SYNC socket */ connection *repl_transfer_s; /* Slave -> Master SYNC connection */
int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */ int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */
char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */ char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */
time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */ time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
...@@ -1423,6 +1453,11 @@ struct redisServer { ...@@ -1423,6 +1453,11 @@ struct redisServer {
int watchdog_period; /* Software watchdog period in ms. 0 = off */ int watchdog_period; /* Software watchdog period in ms. 0 = off */
/* System hardware info */ /* System hardware info */
size_t system_memory_size; /* Total memory in system as reported by OS */ size_t system_memory_size; /* Total memory in system as reported by OS */
/* TLS Configuration */
int tls_cluster;
int tls_replication;
int tls_auth_clients;
redisTLSContextConfig tls_ctx_config;
}; };
typedef struct pubsubPattern { typedef struct pubsubPattern {
...@@ -1570,12 +1605,12 @@ size_t redisPopcount(void *s, long count); ...@@ -1570,12 +1605,12 @@ size_t redisPopcount(void *s, long count);
void redisSetProcTitle(char *title); void redisSetProcTitle(char *title);
/* networking.c -- Networking and Client related operations */ /* networking.c -- Networking and Client related operations */
client *createClient(int fd); client *createClient(connection *conn);
void closeTimedoutClients(void); void closeTimedoutClients(void);
void freeClient(client *c); void freeClient(client *c);
void freeClientAsync(client *c); void freeClientAsync(client *c);
void resetClient(client *c); void resetClient(client *c);
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask); void sendReplyToClient(connection *conn);
void *addReplyDeferredLen(client *c); void *addReplyDeferredLen(client *c);
void setDeferredArrayLen(client *c, void *node, long length); void setDeferredArrayLen(client *c, void *node, long length);
void setDeferredMapLen(client *c, void *node, long length); void setDeferredMapLen(client *c, void *node, long length);
...@@ -1587,8 +1622,9 @@ void processInputBufferAndReplicate(client *c); ...@@ -1587,8 +1622,9 @@ void processInputBufferAndReplicate(client *c);
void processGopherRequest(client *c); void processGopherRequest(client *c);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask); void readQueryFromClient(connection *conn);
void addReplyNull(client *c); void addReplyNull(client *c);
void addReplyNullArray(client *c); void addReplyNullArray(client *c);
void addReplyBool(client *c, int b); void addReplyBool(client *c, int b);
...@@ -1646,7 +1682,7 @@ int handleClientsWithPendingReadsUsingThreads(void); ...@@ -1646,7 +1682,7 @@ int handleClientsWithPendingReadsUsingThreads(void);
int stopThreadedIOIfNeeded(void); int stopThreadedIOIfNeeded(void);
int clientHasPendingReplies(client *c); int clientHasPendingReplies(client *c);
void unlinkClient(client *c); void unlinkClient(client *c);
int writeToClient(int fd, client *c, int handler_installed); int writeToClient(client *c, int handler_installed);
void linkClient(client *c); void linkClient(client *c);
void protectClient(client *c); void protectClient(client *c);
void unprotectClient(client *c); void unprotectClient(client *c);
...@@ -1782,6 +1818,8 @@ void clearReplicationId2(void); ...@@ -1782,6 +1818,8 @@ void clearReplicationId2(void);
void chopReplicationBacklog(void); void chopReplicationBacklog(void);
void replicationCacheMasterUsingMyself(void); void replicationCacheMasterUsingMyself(void);
void feedReplicationBacklog(void *ptr, size_t len); void feedReplicationBacklog(void *ptr, size_t len);
void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
void rdbPipeWriteHandlerConnRemoved(struct connection *conn);
/* Generic persistence functions */ /* Generic persistence functions */
void startLoadingFile(FILE* fp, char* filename); void startLoadingFile(FILE* fp, char* filename);
...@@ -1954,6 +1992,7 @@ unsigned int LRU_CLOCK(void); ...@@ -1954,6 +1992,7 @@ unsigned int LRU_CLOCK(void);
const char *evictPolicyToString(void); const char *evictPolicyToString(void);
struct redisMemOverhead *getMemoryOverheadData(void); struct redisMemOverhead *getMemoryOverheadData(void);
void freeMemoryOverheadData(struct redisMemOverhead *mh); void freeMemoryOverheadData(struct redisMemOverhead *mh);
void checkChildrenDone(void);
#define RESTART_SERVER_NONE 0 #define RESTART_SERVER_NONE 0
#define RESTART_SERVER_GRACEFULLY (1<<0) /* Do proper shutdown. */ #define RESTART_SERVER_GRACEFULLY (1<<0) /* Do proper shutdown. */
...@@ -2369,6 +2408,10 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len); ...@@ -2369,6 +2408,10 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len);
void xorDigest(unsigned char *digest, void *ptr, size_t len); void xorDigest(unsigned char *digest, void *ptr, size_t len);
int populateCommandTableParseFlags(struct redisCommand *c, char *strflags); int populateCommandTableParseFlags(struct redisCommand *c, char *strflags);
/* TLS stuff */
void tlsInit(void);
int tlsConfigure(redisTLSContextConfig *ctx_config);
#define redisDebug(fmt, ...) \ #define redisDebug(fmt, ...) \
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__) printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define redisDebugMark() \ #define redisDebugMark() \
......
/*
* 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
...@@ -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 0 $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]
} }
} }
...@@ -52,7 +52,7 @@ tags {"aof"} { ...@@ -52,7 +52,7 @@ tags {"aof"} {
assert_equal 1 [is_alive $srv] assert_equal 1 [is_alive $srv]
} }
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
test "Truncated AOF loaded: we expect foo to be equal to 5" { test "Truncated AOF loaded: we expect foo to be equal to 5" {
assert {[$client get foo] eq "5"} assert {[$client get foo] eq "5"}
...@@ -69,7 +69,7 @@ tags {"aof"} { ...@@ -69,7 +69,7 @@ tags {"aof"} {
assert_equal 1 [is_alive $srv] assert_equal 1 [is_alive $srv]
} }
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
test "Truncated AOF loaded: we expect foo to be equal to 6 now" { test "Truncated AOF loaded: we expect foo to be equal to 6 now" {
assert {[$client get foo] eq "6"} assert {[$client get foo] eq "6"}
...@@ -170,7 +170,7 @@ tags {"aof"} { ...@@ -170,7 +170,7 @@ tags {"aof"} {
} }
test "Fixed AOF: Keyspace should contain values that were parseable" { test "Fixed AOF: Keyspace should contain values that were parseable" {
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_for_condition 50 100 {
[catch {$client ping} e] == 0 [catch {$client ping} e] == 0
} else { } else {
...@@ -194,7 +194,7 @@ tags {"aof"} { ...@@ -194,7 +194,7 @@ tags {"aof"} {
} }
test "AOF+SPOP: Set should have 1 member" { test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_for_condition 50 100 {
[catch {$client ping} e] == 0 [catch {$client ping} e] == 0
} else { } else {
...@@ -218,7 +218,7 @@ tags {"aof"} { ...@@ -218,7 +218,7 @@ tags {"aof"} {
} }
test "AOF+SPOP: Set should have 1 member" { test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_for_condition 50 100 {
[catch {$client ping} e] == 0 [catch {$client ping} e] == 0
} else { } else {
...@@ -241,7 +241,7 @@ tags {"aof"} { ...@@ -241,7 +241,7 @@ tags {"aof"} {
} }
test "AOF+EXPIRE: List should be empty" { test "AOF+EXPIRE: List should be empty" {
set client [redis [dict get $srv host] [dict get $srv port]] set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 { wait_for_condition 50 100 {
[catch {$client ping} e] == 0 [catch {$client ping} e] == 0
} else { } else {
...@@ -257,4 +257,35 @@ tags {"aof"} { ...@@ -257,4 +257,35 @@ tags {"aof"} {
r expire x -1 r expire x -1
} }
} }
start_server {overrides {appendonly {yes} appendfilename {appendonly.aof} appendfsync always}} {
test {AOF fsync always barrier issue} {
set rd [redis_deferring_client]
# Set a sleep when aof is flushed, so that we have a chance to look
# at the aof size and detect if the response of an incr command
# arrives before the data was written (and hopefully fsynced)
# We create a big reply, which will hopefully not have room in the
# socket buffers, and will install a write handler, then we sleep
# a big and issue the incr command, hoping that the last portion of
# the output buffer write, and the processing of the incr will happen
# in the same event loop cycle.
# Since the socket buffers and timing are unpredictable, we fuzz this
# test with slightly different sizes and sleeps a few times.
for {set i 0} {$i < 10} {incr i} {
r debug aof-flush-sleep 0
r del x
r setrange x [expr {int(rand()*5000000)+10000000}] x
r debug aof-flush-sleep 500000
set aof [file join [lindex [r config get dir] 1] appendonly.aof]
set size1 [file size $aof]
$rd get x
after [expr {int(rand()*30)}]
$rd incr new_value
$rd read
$rd read
set size2 [file size $aof]
assert {$size1 != $size2}
}
}
}
} }
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
# Unlike stream operations such operations are "pop" style, so they consume # Unlike stream operations such operations are "pop" style, so they consume
# the list or sorted set, and must be replicated correctly. # the list or sorted set, and must be replicated correctly.
proc start_bg_block_op {host port db ops} { proc start_bg_block_op {host port db ops tls} {
set tclsh [info nameofexecutable] set tclsh [info nameofexecutable]
exec $tclsh tests/helpers/bg_block_op.tcl $host $port $db $ops & exec $tclsh tests/helpers/bg_block_op.tcl $host $port $db $ops $tls &
} }
proc stop_bg_block_op {handle} { proc stop_bg_block_op {handle} {
...@@ -18,9 +18,9 @@ start_server {tags {"repl"}} { ...@@ -18,9 +18,9 @@ start_server {tags {"repl"}} {
set master_port [srv -1 port] set master_port [srv -1 port]
set slave [srv 0 client] set slave [srv 0 client]
set load_handle0 [start_bg_block_op $master_host $master_port 9 100000] set load_handle0 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
set load_handle1 [start_bg_block_op $master_host $master_port 9 100000] set load_handle1 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
set load_handle2 [start_bg_block_op $master_host $master_port 9 100000] set load_handle2 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
test {First server should have role slave after SLAVEOF} { test {First server should have role slave after SLAVEOF} {
$slave slaveof $master_host $master_port $slave slaveof $master_host $master_port
......
...@@ -18,6 +18,7 @@ start_server {} { ...@@ -18,6 +18,7 @@ start_server {} {
set R($j) [srv [expr 0-$j] client] set R($j) [srv [expr 0-$j] client]
set R_host($j) [srv [expr 0-$j] host] set R_host($j) [srv [expr 0-$j] host]
set R_port($j) [srv [expr 0-$j] port] set R_port($j) [srv [expr 0-$j] port]
set R_unixsocket($j) [srv [expr 0-$j] unixsocket]
if {$debug_msg} {puts "Log file: [srv [expr 0-$j] stdout]"} if {$debug_msg} {puts "Log file: [srv [expr 0-$j] stdout]"}
} }
...@@ -36,7 +37,7 @@ start_server {} { ...@@ -36,7 +37,7 @@ start_server {} {
} }
set cycle_start_time [clock milliseconds] set cycle_start_time [clock milliseconds]
set bench_pid [exec src/redis-benchmark -p $R_port(0) -n 10000000 -r 1000 incr __rand_int__ > /dev/null &] set bench_pid [exec src/redis-benchmark -s $R_unixsocket(0) -n 10000000 -r 1000 incr __rand_int__ > /dev/null &]
while 1 { while 1 {
set elapsed [expr {[clock milliseconds]-$cycle_start_time}] set elapsed [expr {[clock milliseconds]-$cycle_start_time}]
if {$elapsed > $duration*1000} break if {$elapsed > $duration*1000} break
......
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