Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
efb6495a
Unverified
Commit
efb6495a
authored
Oct 16, 2019
by
Salvatore Sanfilippo
Committed by
GitHub
Oct 16, 2019
Browse files
Merge pull request #6236 from yossigo/poc/conns
Abstract Connections I/O API & TLS Support
parents
b8e02f2b
8e29b0b2
Changes
56
Hide whitespace changes
Inline
Side-by-side
src/redis-cli.c
View file @
efb6495a
...
...
@@ -47,6 +47,10 @@
#include <math.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 "dict.h"
#include "adlist.h"
...
...
@@ -188,6 +192,12 @@ static struct config {
char
*
hostip
;
int
hostport
;
char
*
hostsocket
;
int
tls
;
char
*
sni
;
char
*
cacert
;
char
*
cacertdir
;
char
*
cert
;
char
*
key
;
long
repeat
;
long
interval
;
int
dbnum
;
...
...
@@ -758,6 +768,71 @@ static int cliSelect(void) {
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. */
static
int
cliSwitchProto
(
void
)
{
redisReply
*
reply
;
...
...
@@ -789,6 +864,16 @@ static int cliConnect(int flags) {
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
(
!
(
flags
&
CC_QUIET
))
{
fprintf
(
stderr
,
"Could not connect to Redis at "
);
...
...
@@ -804,6 +889,7 @@ static int cliConnect(int flags) {
return
REDIS_ERR
;
}
/* Set aggressive KEEP_ALIVE socket option in the Redis context socket
* in order to prevent timeouts caused by the execution of long
* commands. At the same time this improves the detection of real
...
...
@@ -1305,6 +1391,13 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
redisFree
(
c
);
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
);
}
...
...
@@ -1498,6 +1591,20 @@ static int parseOptions(int argc, char **argv) {
}
else
if
(
!
strcmp
(
argv
[
i
],
"--cluster-search-multiple-owners"
))
{
config
.
cluster_manager_command
.
flags
|=
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"
))
{
sds
version
=
cliVersion
();
printf
(
"redis-cli %s
\n
"
,
version
);
...
...
@@ -1591,6 +1698,15 @@ static void usage(void) {
" -x Read last argument from STDIN.
\n
"
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default:
\\
n).
\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
"
" 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) {
" --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
"
" 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
"
" --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
"
...
...
@@ -1638,8 +1756,7 @@ static void usage(void) {
" line interface.
\n
"
" --help Output this help and exit.
\n
"
" --version Output version and exit.
\n
"
"
\n
"
,
REDIS_CLI_DEFAULT_PIPE_TIMEOUT
);
"
\n
"
);
/* Using another fprintf call to avoid -Woverlength-strings compile warning */
fprintf
(
stderr
,
"Cluster Manager Commands:
\n
"
...
...
@@ -2407,6 +2524,15 @@ cleanup:
static
int
clusterManagerNodeConnect
(
clusterManagerNode
*
node
)
{
if
(
node
->
context
)
redisFree
(
node
->
context
);
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
)
{
fprintf
(
stderr
,
"Could not connect to Redis at "
);
fprintf
(
stderr
,
"%s:%d: %s
\n
"
,
node
->
ip
,
node
->
port
,
...
...
src/replication.c
View file @
efb6495a
...
...
@@ -39,7 +39,7 @@
#include <sys/stat.h>
void
replicationDiscardCachedMaster
(
void
);
void
replicationResurrectCachedMaster
(
int
newfd
);
void
replicationResurrectCachedMaster
(
connection
*
conn
);
void
replicationSendAck
(
void
);
void
putSlaveOnline
(
client
*
slave
);
int
cancelReplicationHandshake
(
void
);
...
...
@@ -57,7 +57,7 @@ char *replicationGetSlaveName(client *c) {
ip
[
0
]
=
'\0'
;
buf
[
0
]
=
'\0'
;
if
(
c
->
slave_ip
[
0
]
!=
'\0'
||
anet
PeerToString
(
c
->
fd
,
ip
,
sizeof
(
ip
),
NULL
)
!=
-
1
)
conn
PeerToString
(
c
->
conn
,
ip
,
sizeof
(
ip
),
NULL
)
!=
-
1
)
{
/* 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
));
...
...
@@ -432,7 +432,7 @@ int replicationSetupSlaveForFullResync(client *slave, long long offset) {
if
(
!
(
slave
->
flags
&
CLIENT_PRE_PSYNC
))
{
buflen
=
snprintf
(
buf
,
sizeof
(
buf
),
"+FULLRESYNC %s %lld
\r\n
"
,
server
.
replid
,
offset
);
if
(
w
rite
(
slave
->
fd
,
buf
,
buflen
)
!=
buflen
)
{
if
(
connW
rite
(
slave
->
conn
,
buf
,
buflen
)
!=
buflen
)
{
freeClientAsync
(
slave
);
return
C_ERR
;
}
...
...
@@ -519,7 +519,7 @@ int masterTryPartialResynchronization(client *c) {
}
else
{
buflen
=
snprintf
(
buf
,
sizeof
(
buf
),
"+CONTINUE
\r\n
"
);
}
if
(
w
rite
(
c
->
fd
,
buf
,
buflen
)
!=
buflen
)
{
if
(
connW
rite
(
c
->
conn
,
buf
,
buflen
)
!=
buflen
)
{
freeClientAsync
(
c
);
return
C_OK
;
}
...
...
@@ -685,7 +685,7 @@ void syncCommand(client *c) {
* paths will change the state if we handle the slave differently. */
c
->
replstate
=
SLAVE_STATE_WAIT_BGSAVE_START
;
if
(
server
.
repl_disable_tcp_nodelay
)
anet
DisableTcpNoDelay
(
NULL
,
c
->
fd
);
/* Non critical if it fails. */
conn
DisableTcpNoDelay
(
c
->
conn
);
/* Non critical if it fails. */
c
->
repldbfd
=
-
1
;
c
->
flags
|=
CLIENT_SLAVE
;
listAddNodeTail
(
server
.
slaves
,
c
);
...
...
@@ -862,8 +862,7 @@ void putSlaveOnline(client *slave) {
slave
->
replstate
=
SLAVE_STATE_ONLINE
;
slave
->
repl_put_online_on_ack
=
0
;
slave
->
repl_ack_time
=
server
.
unixtime
;
/* Prevent false timeout. */
if
(
aeCreateFileEvent
(
server
.
el
,
slave
->
fd
,
AE_WRITABLE
,
sendReplyToClient
,
slave
)
==
AE_ERR
)
{
if
(
connSetWriteHandler
(
slave
->
conn
,
sendReplyToClient
)
==
C_ERR
)
{
serverLog
(
LL_WARNING
,
"Unable to register writable event for replica bulk transfer: %s"
,
strerror
(
errno
));
freeClient
(
slave
);
return
;
...
...
@@ -873,10 +872,8 @@ void putSlaveOnline(client *slave) {
replicationGetSlaveName
(
slave
));
}
void
sendBulkToSlave
(
aeEventLoop
*
el
,
int
fd
,
void
*
privdata
,
int
mask
)
{
client
*
slave
=
privdata
;
UNUSED
(
el
);
UNUSED
(
mask
);
void
sendBulkToSlave
(
connection
*
conn
)
{
client
*
slave
=
connGetPrivateData
(
conn
);
char
buf
[
PROTO_IOBUF_LEN
];
ssize_t
nwritten
,
buflen
;
...
...
@@ -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
* the file in the form "$<length>\r\n". */
if
(
slave
->
replpreamble
)
{
nwritten
=
w
rite
(
fd
,
slave
->
replpreamble
,
sdslen
(
slave
->
replpreamble
));
nwritten
=
connW
rite
(
conn
,
slave
->
replpreamble
,
sdslen
(
slave
->
replpreamble
));
if
(
nwritten
==
-
1
)
{
serverLog
(
LL_VERBOSE
,
"Write error sending RDB preamble to replica: %s"
,
strerror
(
errno
));
connGetLastError
(
conn
));
freeClient
(
slave
);
return
;
}
...
...
@@ -911,10 +908,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
freeClient
(
slave
);
return
;
}
if
((
nwritten
=
w
rite
(
fd
,
buf
,
buflen
))
==
-
1
)
{
if
(
errno
!=
EAGAIN
)
{
if
((
nwritten
=
connW
rite
(
conn
,
buf
,
buflen
))
==
-
1
)
{
if
(
connGetState
(
conn
)
!=
CONN_STATE_CONNECTED
)
{
serverLog
(
LL_WARNING
,
"Write error sending DB to replica: %s"
,
strerror
(
errno
));
connGetLastError
(
conn
));
freeClient
(
slave
);
}
return
;
...
...
@@ -924,11 +921,157 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
if
(
slave
->
repldboff
==
slave
->
repldbsize
)
{
close
(
slave
->
repldbfd
);
slave
->
repldbfd
=
-
1
;
aeDeleteFileEvent
(
server
.
el
,
slave
->
fd
,
AE_WRITABLE
);
connSetWriteHandler
(
slave
->
conn
,
NULL
);
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,
* or when the replication RDB transfer strategy is modified from
* disk to socket or the other way around.
...
...
@@ -1015,8 +1158,8 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
slave
->
replpreamble
=
sdscatprintf
(
sdsempty
(),
"$%lld
\r\n
"
,
(
unsigned
long
long
)
slave
->
repldbsize
);
aeDeleteFileEvent
(
server
.
el
,
slave
->
fd
,
AE_WRITABLE
);
if
(
aeCreateFileEvent
(
server
.
el
,
slave
->
fd
,
AE_WRITABLE
,
sendBulkToSlave
,
slave
)
==
AE
_ERR
)
{
connSetWriteHandler
(
slave
->
conn
,
NULL
);
if
(
connSetWriteHandler
(
slave
->
conn
,
sendBulkToSlave
)
==
C
_ERR
)
{
freeClient
(
slave
);
continue
;
}
...
...
@@ -1084,9 +1227,8 @@ void replicationSendNewlineToMaster(void) {
static
time_t
newline_sent
;
if
(
time
(
NULL
)
!=
newline_sent
)
{
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) {
/* Once we have a link with the master and the synchroniziation was
* performed, this function materializes the master client we store
* at server.master, starting from the specified file descriptor. */
void
replicationCreateMasterClient
(
int
fd
,
int
dbid
)
{
server
.
master
=
createClient
(
fd
);
void
replicationCreateMasterClient
(
connection
*
conn
,
int
dbid
)
{
server
.
master
=
createClient
(
conn
);
if
(
conn
)
connSetReadHandler
(
server
.
master
->
conn
,
readQueryFromClient
);
server
.
master
->
flags
|=
CLIENT_MASTER
;
server
.
master
->
authenticated
=
1
;
server
.
master
->
reploff
=
server
.
master_initial_offset
;
...
...
@@ -1196,7 +1340,7 @@ void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags
/* Asynchronously read the SYNC payload we receive from a master */
#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
];
ssize_t
nread
,
readlen
,
nwritten
;
int
use_diskless_load
;
...
...
@@ -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
:
EMPTYDB_NO_FLAGS
;
off_t
left
;
UNUSED
(
el
);
UNUSED
(
privdata
);
UNUSED
(
mask
);
/* 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. */
...
...
@@ -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
* from the master reply. */
if
(
server
.
repl_transfer_size
==
-
1
)
{
if
(
s
yncReadLine
(
fd
,
buf
,
1024
,
server
.
repl_syncio_timeout
*
1000
)
==
-
1
)
{
if
(
connS
yncReadLine
(
conn
,
buf
,
1024
,
server
.
repl_syncio_timeout
*
1000
)
==
-
1
)
{
serverLog
(
LL_WARNING
,
"I/O error reading bulk count from MASTER: %s"
,
strerror
(
errno
));
...
...
@@ -1282,7 +1423,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
readlen
=
(
left
<
(
signed
)
sizeof
(
buf
))
?
left
:
(
signed
)
sizeof
(
buf
);
}
nread
=
read
(
fd
,
buf
,
readlen
);
nread
=
connRead
(
conn
,
buf
,
readlen
);
if
(
nread
<=
0
)
{
serverLog
(
LL_WARNING
,
"I/O error trying to sync with MASTER: %s"
,
(
nread
==
-
1
)
?
strerror
(
errno
)
:
"connection lost"
);
...
...
@@ -1390,17 +1531,17 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
* handler, otherwise it will get called recursively since
* rdbLoad() will call the event loop to process events from time to
* 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"
);
rdbSaveInfo
rsi
=
RDB_SAVE_INFO_INIT
;
if
(
use_diskless_load
)
{
rio
rdb
;
rioInitWith
Fd
(
&
rdb
,
fd
,
server
.
repl_transfer_size
);
rioInitWith
Conn
(
&
rdb
,
conn
,
server
.
repl_transfer_size
);
/* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the RDB is received. */
anet
Block
(
NULL
,
fd
);
anet
RecvTimeout
(
NULL
,
fd
,
server
.
repl_timeout
*
1000
);
conn
Block
(
conn
);
conn
RecvTimeout
(
conn
,
server
.
repl_timeout
*
1000
);
startLoading
(
server
.
repl_transfer_size
);
if
(
rdbLoadRio
(
&
rdb
,
&
rsi
,
0
)
!=
C_OK
)
{
...
...
@@ -1410,7 +1551,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
"Failed trying to load the MASTER synchronization DB "
"from socket"
);
cancelReplicationHandshake
();
rioFree
Fd
(
&
rdb
,
NULL
);
rioFree
Conn
(
&
rdb
,
NULL
);
if
(
server
.
repl_diskless_load
==
REPL_DISKLESS_LOAD_SWAPDB
)
{
/* Restore the backed up databases. */
disklessLoadRestoreBackups
(
diskless_load_backup
,
1
,
...
...
@@ -1443,16 +1584,16 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
{
serverLog
(
LL_WARNING
,
"Replication stream EOF marker is broken"
);
cancelReplicationHandshake
();
rioFree
Fd
(
&
rdb
,
NULL
);
rioFree
Conn
(
&
rdb
,
NULL
);
return
;
}
}
/* Cleanup and restore the socket to the original state to continue
* with the normal replication. */
rioFree
Fd
(
&
rdb
,
NULL
);
anet
NonBlock
(
NULL
,
fd
);
anet
RecvTimeout
(
NULL
,
fd
,
0
);
rioFree
Conn
(
&
rdb
,
NULL
);
conn
NonBlock
(
conn
);
conn
RecvTimeout
(
conn
,
0
);
}
else
{
/* Ensure background save doesn't overwrite synced data */
if
(
server
.
rdb_child_pid
!=
-
1
)
{
...
...
@@ -1529,7 +1670,7 @@ error:
#define SYNC_CMD_READ (1<<0)
#define SYNC_CMD_WRITE (1<<1)
#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
* protocol to make sure correct arguments are sent. This function
...
...
@@ -1540,7 +1681,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
sds
cmd
=
sdsempty
();
sds
cmdargs
=
sdsempty
();
size_t
argslen
=
0
;
va_start
(
ap
,
fd
);
va_start
(
ap
,
conn
);
while
(
1
)
{
arg
=
va_arg
(
ap
,
char
*
);
...
...
@@ -1557,12 +1698,12 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
sdsfree
(
cmdargs
);
/* Transfer command to the server. */
if
(
s
yncWrite
(
fd
,
cmd
,
sdslen
(
cmd
),
server
.
repl_syncio_timeout
*
1000
)
if
(
connS
yncWrite
(
conn
,
cmd
,
sdslen
(
cmd
),
server
.
repl_syncio_timeout
*
1000
)
==
-
1
)
{
sdsfree
(
cmd
);
return
sdscatprintf
(
sdsempty
(),
"-Writing to master: %s"
,
strerror
(
errno
));
connGetLastError
(
conn
));
}
sdsfree
(
cmd
);
}
...
...
@@ -1571,7 +1712,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
if
(
flags
&
SYNC_CMD_READ
)
{
char
buf
[
256
];
if
(
s
yncReadLine
(
fd
,
buf
,
sizeof
(
buf
),
server
.
repl_syncio_timeout
*
1000
)
if
(
connS
yncReadLine
(
conn
,
buf
,
sizeof
(
buf
),
server
.
repl_syncio_timeout
*
1000
)
==
-
1
)
{
return
sdscatprintf
(
sdsempty
(),
"-Reading from master: %s"
,
...
...
@@ -1637,7 +1778,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
#define PSYNC_FULLRESYNC 3
#define PSYNC_NOT_SUPPORTED 4
#define PSYNC_TRY_LATER 5
int
slaveTryPartialResynchronization
(
int
fd
,
int
read_reply
)
{
int
slaveTryPartialResynchronization
(
connection
*
conn
,
int
read_reply
)
{
char
*
psync_replid
;
char
psync_offset
[
32
];
sds
reply
;
...
...
@@ -1662,18 +1803,18 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
}
/* 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
)
{
serverLog
(
LL_WARNING
,
"Unable to send PSYNC to master: %s"
,
reply
);
sdsfree
(
reply
);
aeDeleteFileEvent
(
server
.
el
,
fd
,
AE_READABLE
);
connSetReadHandler
(
conn
,
NULL
);
return
PSYNC_WRITE_ERROR
;
}
return
PSYNC_WAIT_REPLY
;
}
/* Reading half */
reply
=
sendSynchronousCommand
(
SYNC_CMD_READ
,
fd
,
NULL
);
reply
=
sendSynchronousCommand
(
SYNC_CMD_READ
,
conn
,
NULL
);
if
(
sdslen
(
reply
)
==
0
)
{
/* The master may send empty newlines after it receives PSYNC
* and before to reply, just to keep the connection alive. */
...
...
@@ -1681,7 +1822,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
return
PSYNC_WAIT_REPLY
;
}
aeDeleteFileEvent
(
server
.
el
,
fd
,
AE_READABLE
);
connSetReadHandler
(
conn
,
NULL
);
if
(
!
strncmp
(
reply
,
"+FULLRESYNC"
,
11
))
{
char
*
replid
=
NULL
,
*
offset
=
NULL
;
...
...
@@ -1755,7 +1896,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
/* Setup the replication to continue. */
sdsfree
(
reply
);
replicationResurrectCachedMaster
(
fd
);
replicationResurrectCachedMaster
(
conn
);
/* If this instance was restarted and we read the metadata to
* PSYNC from the persistence file, our replication backlog could
...
...
@@ -1797,29 +1938,23 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
/* This handler fires when the non blocking connect was able to
* 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
;
int
dfd
=
-
1
,
maxtries
=
5
;
int
sockerr
=
0
,
psync_result
;
socklen_t
errlen
=
sizeof
(
sockerr
);
UNUSED
(
el
);
UNUSED
(
privdata
);
UNUSED
(
mask
);
int
psync_result
;
/* If this event fired after the user turned the instance into a master
* with SLAVEOF NO ONE we must just return ASAP. */
if
(
server
.
repl_state
==
REPL_STATE_NONE
)
{
close
(
fd
);
c
onnC
lose
(
conn
);
return
;
}
/* Check for errors in the socket: after a non blocking connect() we
* may find that the socket is in error state. */
if
(
getsockopt
(
fd
,
SOL_SOCKET
,
SO_ERROR
,
&
sockerr
,
&
errlen
)
==
-
1
)
sockerr
=
errno
;
if
(
sockerr
)
{
if
(
connGetState
(
conn
)
!=
CONN_STATE_CONNECTED
)
{
serverLog
(
LL_WARNING
,
"Error condition on socket for SYNC: %s"
,
strerror
(
sockerr
));
connGetLastError
(
conn
));
goto
error
;
}
...
...
@@ -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."
);
/* Delete the writable event so that the readable event remains
* 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
;
/* Send the PING, don't check for errors at all, we have the timeout
* 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
;
return
;
}
/* Receive the PONG command. */
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 just check for "+") or an authentication error.
...
...
@@ -1864,13 +2000,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* AUTH with the master if required. */
if
(
server
.
repl_state
==
REPL_STATE_SEND_AUTH
)
{
if
(
server
.
masteruser
&&
server
.
masterauth
)
{
err
=
sendSynchronousCommand
(
SYNC_CMD_WRITE
,
fd
,
"AUTH"
,
err
=
sendSynchronousCommand
(
SYNC_CMD_WRITE
,
conn
,
"AUTH"
,
server
.
masteruser
,
server
.
masterauth
,
NULL
);
if
(
err
)
goto
write_error
;
server
.
repl_state
=
REPL_STATE_RECEIVE_AUTH
;
return
;
}
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
;
server
.
repl_state
=
REPL_STATE_RECEIVE_AUTH
;
return
;
...
...
@@ -1881,7 +2017,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive AUTH reply. */
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
]
==
'-'
)
{
serverLog
(
LL_WARNING
,
"Unable to AUTH to MASTER: %s"
,
err
);
sdsfree
(
err
);
...
...
@@ -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
* slave listening port correctly. */
if
(
server
.
repl_state
==
REPL_STATE_SEND_PORT
)
{
sds
port
=
sdsfromlonglong
(
server
.
slave_announce_port
?
server
.
slave_announce_port
:
server
.
port
);
err
=
sendSynchronousCommand
(
SYNC_CMD_WRITE
,
fd
,
"REPLCONF"
,
"listening-port"
,
port
,
NULL
);
sdsfree
(
port
);
int
port
;
if
(
server
.
slave_announce_port
)
port
=
server
.
slave_announce_port
;
else
if
(
server
.
tls_replication
&&
server
.
tls_port
)
port
=
server
.
tls_port
;
else
port
=
server
.
port
;
sds
portstr
=
sdsfromlonglong
(
port
);
err
=
sendSynchronousCommand
(
SYNC_CMD_WRITE
,
conn
,
"REPLCONF"
,
"listening-port"
,
portstr
,
NULL
);
sdsfree
(
portstr
);
if
(
err
)
goto
write_error
;
sdsfree
(
err
);
server
.
repl_state
=
REPL_STATE_RECEIVE_PORT
;
...
...
@@ -1907,7 +2046,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive REPLCONF listening-port reply. */
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
* REPLCONF listening-port. */
if
(
err
[
0
]
==
'-'
)
{
...
...
@@ -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
* slave IP address port correctly in case of port forwarding or NAT. */
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
);
if
(
err
)
goto
write_error
;
sdsfree
(
err
);
...
...
@@ -1938,7 +2077,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive REPLCONF ip-address reply. */
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
* REPLCONF listening-port. */
if
(
err
[
0
]
==
'-'
)
{
...
...
@@ -1956,7 +2095,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
*
* The master will ignore capabilities it does not understand. */
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
);
if
(
err
)
goto
write_error
;
sdsfree
(
err
);
...
...
@@ -1966,7 +2105,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive CAPA reply. */
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
* REPLCONF capa. */
if
(
err
[
0
]
==
'-'
)
{
...
...
@@ -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
* reconnection attempt. */
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."
);
goto
write_error
;
}
...
...
@@ -1999,7 +2138,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
goto
error
;
}
psync_result
=
slaveTryPartialResynchronization
(
fd
,
1
);
psync_result
=
slaveTryPartialResynchronization
(
conn
,
1
);
if
(
psync_result
==
PSYNC_WAIT_REPLY
)
return
;
/* Try again later... */
/* 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) {
* already populated. */
if
(
psync_result
==
PSYNC_NOT_SUPPORTED
)
{
serverLog
(
LL_NOTICE
,
"Retrying with SYNC..."
);
if
(
s
yncWrite
(
fd
,
"SYNC
\r\n
"
,
6
,
server
.
repl_syncio_timeout
*
1000
)
==
-
1
)
{
if
(
connS
yncWrite
(
conn
,
"SYNC
\r\n
"
,
6
,
server
.
repl_syncio_timeout
*
1000
)
==
-
1
)
{
serverLog
(
LL_WARNING
,
"I/O error writing to MASTER: %s"
,
strerror
(
errno
));
goto
error
;
...
...
@@ -2053,12 +2192,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
}
/* Setup the non blocking download of the bulk file. */
if
(
aeCreateFileEvent
(
server
.
el
,
fd
,
AE_READABLE
,
readSyncBulkPayload
,
NULL
)
==
AE
_ERR
)
if
(
connSetReadHandler
(
conn
,
readSyncBulkPayload
)
==
C
_ERR
)
{
char
conninfo
[
CONN_INFO_LEN
];
serverLog
(
LL_WARNING
,
"Can't create readable event for SYNC: %s (
fd=%d
)"
,
strerror
(
errno
),
fd
);
"Can't create readable event for SYNC: %s (
%s
)"
,
strerror
(
errno
),
connGetInfo
(
conn
,
conninfo
,
sizeof
(
conninfo
))
);
goto
error
;
}
...
...
@@ -2070,16 +2210,15 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
return
;
error:
aeDeleteFileEvent
(
server
.
el
,
fd
,
AE_READABLE
|
AE_WRITABLE
);
if
(
dfd
!=
-
1
)
close
(
dfd
);
close
(
fd
);
connClose
(
conn
);
server
.
repl_transfer_s
=
NULL
;
if
(
server
.
repl_transfer_fd
!=
-
1
)
close
(
server
.
repl_transfer_fd
);
if
(
server
.
repl_transfer_tmpfile
)
zfree
(
server
.
repl_transfer_tmpfile
);
server
.
repl_transfer_tmpfile
=
NULL
;
server
.
repl_transfer_fd
=
-
1
;
server
.
repl_transfer_s
=
-
1
;
server
.
repl_state
=
REPL_STATE_CONNECT
;
return
;
...
...
@@ -2090,26 +2229,18 @@ write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */
}
int
connectWithMaster
(
void
)
{
int
fd
;
fd
=
anetTcpNonBlockBestEffortBindConnect
(
NULL
,
server
.
masterhost
,
server
.
masterport
,
NET_FIRST_BIND_ADDR
);
if
(
fd
==
-
1
)
{
server
.
repl_transfer_s
=
server
.
tls_replication
?
connCreateTLS
()
:
connCreateSocket
();
if
(
connConnect
(
server
.
repl_transfer_s
,
server
.
masterhost
,
server
.
masterport
,
NET_FIRST_BIND_ADDR
,
syncWithMaster
)
==
C_ERR
)
{
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
;
}
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_s
=
fd
;
server
.
repl_state
=
REPL_STATE_CONNECTING
;
return
C_OK
;
}
...
...
@@ -2119,11 +2250,8 @@ int connectWithMaster(void) {
* Never call this function directly, use cancelReplicationHandshake() instead.
*/
void
undoConnectWithMaster
(
void
)
{
int
fd
=
server
.
repl_transfer_s
;
aeDeleteFileEvent
(
server
.
el
,
fd
,
AE_READABLE
|
AE_WRITABLE
);
close
(
fd
);
server
.
repl_transfer_s
=
-
1
;
connClose
(
server
.
repl_transfer_s
);
server
.
repl_transfer_s
=
NULL
;
}
/* Abort the async download of the bulk dataset while SYNC-ing with master.
...
...
@@ -2311,7 +2439,7 @@ void roleCommand(client *c) {
char
ip
[
NET_IP_STR_LEN
],
*
slaveip
=
slave
->
slave_ip
;
if
(
slaveip
[
0
]
==
'\0'
)
{
if
(
anet
PeerToString
(
slave
->
fd
,
ip
,
sizeof
(
ip
),
NULL
)
==
-
1
)
if
(
conn
PeerToString
(
slave
->
conn
,
ip
,
sizeof
(
ip
),
NULL
)
==
-
1
)
continue
;
slaveip
=
ip
;
}
...
...
@@ -2433,7 +2561,7 @@ void replicationCacheMasterUsingMyself(void) {
/* The master client we create can be set to any DBID, because
* the new master will start its replication stream with SELECT. */
server
.
master_initial_offset
=
server
.
master_repl_offset
;
replicationCreateMasterClient
(
-
1
,
-
1
);
replicationCreateMasterClient
(
NULL
,
-
1
);
/* Use our own ID / offset. */
memcpy
(
server
.
master
->
replid
,
server
.
replid
,
sizeof
(
server
.
replid
));
...
...
@@ -2462,10 +2590,11 @@ void replicationDiscardCachedMaster(void) {
* This function is called when successfully setup a partial resynchronization
* so the stream of data that we'll receive will start from were this
* master left. */
void
replicationResurrectCachedMaster
(
int
newfd
)
{
void
replicationResurrectCachedMaster
(
connection
*
conn
)
{
server
.
master
=
server
.
cached_master
;
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
->
authenticated
=
1
;
server
.
master
->
lastinteraction
=
server
.
unixtime
;
...
...
@@ -2474,8 +2603,7 @@ void replicationResurrectCachedMaster(int newfd) {
/* Re-add to the list of clients. */
linkClient
(
server
.
master
);
if
(
aeCreateFileEvent
(
server
.
el
,
newfd
,
AE_READABLE
,
readQueryFromClient
,
server
.
master
))
{
if
(
connSetReadHandler
(
server
.
master
->
conn
,
readQueryFromClient
))
{
serverLog
(
LL_WARNING
,
"Error resurrecting the cached master, impossible to add the readable handler: %s"
,
strerror
(
errno
));
freeClientAsync
(
server
.
master
);
/* Close ASAP. */
}
...
...
@@ -2483,8 +2611,7 @@ void replicationResurrectCachedMaster(int newfd) {
/* We may also need to install the write handler as well if there is
* pending data in the write buffers. */
if
(
clientHasPendingReplies
(
server
.
master
))
{
if
(
aeCreateFileEvent
(
server
.
el
,
newfd
,
AE_WRITABLE
,
sendReplyToClient
,
server
.
master
))
{
if
(
connSetWriteHandler
(
server
.
master
->
conn
,
sendReplyToClient
))
{
serverLog
(
LL_WARNING
,
"Error resurrecting the cached master, impossible to add the writable handler: %s"
,
strerror
(
errno
));
freeClientAsync
(
server
.
master
);
/* Close ASAP. */
}
...
...
@@ -2854,9 +2981,7 @@ void replicationCron(void) {
server
.
rdb_child_type
!=
RDB_CHILD_TYPE_SOCKET
));
if
(
is_presync
)
{
if
(
write
(
slave
->
fd
,
"
\n
"
,
1
)
==
-
1
)
{
/* Don't worry about socket errors, it's just a ping. */
}
connWrite
(
slave
->
conn
,
"
\n
"
,
1
);
}
}
...
...
src/rio.c
View file @
efb6495a
...
...
@@ -159,13 +159,13 @@ void rioInitWithFile(rio *r, FILE *fp) {
r
->
io
.
file
.
autosync
=
0
;
}
/* -------------------
File descriptor
implementation -------------------
/* -------------------
Connection
implementation -------------------
* We use this RIO implemetnation when reading an RDB file directly from
* the
socket
to the memory via rdbLoadRio(), thus this implementation
* only implements reading from a
file descriptor
that is, normally,
* the
connection
to the memory via rdbLoadRio(), thus this implementation
* only implements reading from a
connection
that is, normally,
* just a socket. */
static
size_t
rio
Fd
Write
(
rio
*
r
,
const
void
*
buf
,
size_t
len
)
{
static
size_t
rio
Conn
Write
(
rio
*
r
,
const
void
*
buf
,
size_t
len
)
{
UNUSED
(
r
);
UNUSED
(
buf
);
UNUSED
(
len
);
...
...
@@ -173,72 +173,72 @@ static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
}
/* Returns 1 or 0 for success/failure. */
static
size_t
rio
Fd
Read
(
rio
*
r
,
void
*
buf
,
size_t
len
)
{
size_t
avail
=
sdslen
(
r
->
io
.
fd
.
buf
)
-
r
->
io
.
fd
.
pos
;
static
size_t
rio
Conn
Read
(
rio
*
r
,
void
*
buf
,
size_t
len
)
{
size_t
avail
=
sdslen
(
r
->
io
.
conn
.
buf
)
-
r
->
io
.
conn
.
pos
;
/* If the buffer is too small for the entire request: realloc. */
if
(
sdslen
(
r
->
io
.
fd
.
buf
)
+
sdsavail
(
r
->
io
.
fd
.
buf
)
<
len
)
r
->
io
.
fd
.
buf
=
sdsMakeRoomFor
(
r
->
io
.
fd
.
buf
,
len
-
sdslen
(
r
->
io
.
fd
.
buf
));
if
(
sdslen
(
r
->
io
.
conn
.
buf
)
+
sdsavail
(
r
->
io
.
conn
.
buf
)
<
len
)
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
* can read the rest. */
if
(
len
>
avail
&&
sdsavail
(
r
->
io
.
fd
.
buf
)
<
len
-
avail
)
{
sdsrange
(
r
->
io
.
fd
.
buf
,
r
->
io
.
fd
.
pos
,
-
1
);
r
->
io
.
fd
.
pos
=
0
;
if
(
len
>
avail
&&
sdsavail
(
r
->
io
.
conn
.
buf
)
<
len
-
avail
)
{
sdsrange
(
r
->
io
.
conn
.
buf
,
r
->
io
.
conn
.
pos
,
-
1
);
r
->
io
.
conn
.
pos
=
0
;
}
/* 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
)
{
size_t
buffered
=
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
.
conn
.
buf
)
-
r
->
io
.
conn
.
pos
;
size_t
toread
=
len
-
buffered
;
/* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of
* the two. */
if
(
toread
<
PROTO_IOBUF_LEN
)
toread
=
PROTO_IOBUF_LEN
;
if
(
toread
>
sdsavail
(
r
->
io
.
fd
.
buf
))
toread
=
sdsavail
(
r
->
io
.
fd
.
buf
);
if
(
r
->
io
.
fd
.
read_limit
!=
0
&&
r
->
io
.
fd
.
read_so_far
+
buffered
+
toread
>
r
->
io
.
fd
.
read_limit
)
if
(
toread
>
sdsavail
(
r
->
io
.
conn
.
buf
))
toread
=
sdsavail
(
r
->
io
.
conn
.
buf
);
if
(
r
->
io
.
conn
.
read_limit
!=
0
&&
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
)
toread
=
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
.
conn
.
read_limit
-
r
->
io
.
conn
.
read_so_far
-
buffered
;
else
{
errno
=
EOVERFLOW
;
return
0
;
}
}
int
retval
=
r
ead
(
r
->
io
.
fd
.
fd
,
(
char
*
)
r
->
io
.
fd
.
buf
+
sdslen
(
r
->
io
.
fd
.
buf
),
int
retval
=
connR
ead
(
r
->
io
.
conn
.
conn
,
(
char
*
)
r
->
io
.
conn
.
buf
+
sdslen
(
r
->
io
.
conn
.
buf
),
toread
);
if
(
retval
<=
0
)
{
if
(
errno
==
EWOULDBLOCK
)
errno
=
ETIMEDOUT
;
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
);
r
->
io
.
fd
.
read_so_far
+=
len
;
r
->
io
.
fd
.
pos
+=
len
;
memcpy
(
buf
,
(
char
*
)
r
->
io
.
conn
.
buf
+
r
->
io
.
conn
.
pos
,
len
);
r
->
io
.
conn
.
read_so_far
+=
len
;
r
->
io
.
conn
.
pos
+=
len
;
return
len
;
}
/* Returns read/write position in file. */
static
off_t
rio
Fd
Tell
(
rio
*
r
)
{
return
r
->
io
.
fd
.
read_so_far
;
static
off_t
rio
Conn
Tell
(
rio
*
r
)
{
return
r
->
io
.
conn
.
read_so_far
;
}
/* Flushes any buffer to target device if applicable. Returns 1 on success
* and 0 on failures. */
static
int
rio
Fd
Flush
(
rio
*
r
)
{
static
int
rio
Conn
Flush
(
rio
*
r
)
{
/* Our flush is implemented by the write method, that recognizes a
* buffer set to NULL with a count of zero as a flush request. */
return
rio
Fd
Write
(
r
,
NULL
,
0
);
return
rio
Conn
Write
(
r
,
NULL
,
0
);
}
static
const
rio
rio
Fd
IO
=
{
rio
Fd
Read
,
rio
Fd
Write
,
rio
Fd
Tell
,
rio
Fd
Flush
,
static
const
rio
rio
Conn
IO
=
{
rio
Conn
Read
,
rio
Conn
Write
,
rio
Conn
Tell
,
rio
Conn
Flush
,
NULL
,
/* update_checksum */
0
,
/* current checksum */
0
,
/* flags */
...
...
@@ -249,108 +249,90 @@ static const rio rioFdIO = {
/* Create an RIO that implements a buffered read from an fd
* read_limit argument stops buffering when the reaching the limit. */
void
rioInitWith
Fd
(
rio
*
r
,
int
fd
,
size_t
read_limit
)
{
*
r
=
rio
Fd
IO
;
r
->
io
.
fd
.
fd
=
fd
;
r
->
io
.
fd
.
pos
=
0
;
r
->
io
.
fd
.
read_limit
=
read_limit
;
r
->
io
.
fd
.
read_so_far
=
0
;
r
->
io
.
fd
.
buf
=
sdsnewlen
(
NULL
,
PROTO_IOBUF_LEN
);
sdsclear
(
r
->
io
.
fd
.
buf
);
void
rioInitWith
Conn
(
rio
*
r
,
connection
*
conn
,
size_t
read_limit
)
{
*
r
=
rio
Conn
IO
;
r
->
io
.
conn
.
conn
=
conn
;
r
->
io
.
conn
.
pos
=
0
;
r
->
io
.
conn
.
read_limit
=
read_limit
;
r
->
io
.
conn
.
read_so_far
=
0
;
r
->
io
.
conn
.
buf
=
sdsnewlen
(
NULL
,
PROTO_IOBUF_LEN
);
sdsclear
(
r
->
io
.
conn
.
buf
);
}
/* Release the RIO tream. Optionally returns the unread buffered data
* when the SDS pointer 'remaining' is passed. */
void
rioFree
Fd
(
rio
*
r
,
sds
*
remaining
)
{
if
(
remaining
&&
(
size_t
)
r
->
io
.
fd
.
pos
<
sdslen
(
r
->
io
.
fd
.
buf
))
{
if
(
r
->
io
.
fd
.
pos
>
0
)
sdsrange
(
r
->
io
.
fd
.
buf
,
r
->
io
.
fd
.
pos
,
-
1
);
*
remaining
=
r
->
io
.
fd
.
buf
;
void
rioFree
Conn
(
rio
*
r
,
sds
*
remaining
)
{
if
(
remaining
&&
(
size_t
)
r
->
io
.
conn
.
pos
<
sdslen
(
r
->
io
.
conn
.
buf
))
{
if
(
r
->
io
.
conn
.
pos
>
0
)
sdsrange
(
r
->
io
.
conn
.
buf
,
r
->
io
.
conn
.
pos
,
-
1
);
*
remaining
=
r
->
io
.
conn
.
buf
;
}
else
{
sdsfree
(
r
->
io
.
fd
.
buf
);
sdsfree
(
r
->
io
.
conn
.
buf
);
if
(
remaining
)
*
remaining
=
NULL
;
}
r
->
io
.
fd
.
buf
=
NULL
;
r
->
io
.
conn
.
buf
=
NULL
;
}
/* ------------------- File descriptor
s set
implementation ------------------
* This target is used to write the RDB file to
N different replicas via
*
sockets, when the master just
streams the data to the replicas without
*
creating an RDB on-disk image
(diskless replication option).
/* ------------------- File descriptor implementation ------------------
* This target is used to write the RDB file to
pipe, when the master just
* streams the data to the replicas without
creating an RDB on-disk image
* (diskless replication option).
* It only implements writes. */
/* 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
* if there is some pending buffer, so this function is also used in order
* to implement rioFd
set
Flush(). */
static
size_t
rioFd
set
Write
(
rio
*
r
,
const
void
*
buf
,
size_t
len
)
{
* to implement rioFdFlush(). */
static
size_t
rioFdWrite
(
rio
*
r
,
const
void
*
buf
,
size_t
len
)
{
ssize_t
retval
;
int
j
;
unsigned
char
*
p
=
(
unsigned
char
*
)
buf
;
int
doflush
=
(
buf
==
NULL
&&
len
==
0
);
/* To start we always append to our buffer. If it gets larger than
* a given size, we actually write to the sockets. */
if
(
len
)
{
r
->
io
.
fdset
.
buf
=
sdscatlen
(
r
->
io
.
fdset
.
buf
,
buf
,
len
);
len
=
0
;
/* Prevent entering the while below if we don't flush. */
if
(
sdslen
(
r
->
io
.
fdset
.
buf
)
>
PROTO_IOBUF_LEN
)
doflush
=
1
;
}
if
(
doflush
)
{
p
=
(
unsigned
char
*
)
r
->
io
.
fdset
.
buf
;
len
=
sdslen
(
r
->
io
.
fdset
.
buf
);
/* For small writes, we rather keep the data in user-space buffer, and flush
* it only when it grows. however for larger writes, we prefer to flush
* any pre-existing buffer, and write the new one directly without reallocs
* and memory copying. */
if
(
len
>
PROTO_IOBUF_LEN
)
{
/* 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. */
}
else
{
if
(
len
)
{
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. */
p
=
(
unsigned
char
*
)
r
->
io
.
fd
.
buf
;
len
=
sdslen
(
r
->
io
.
fd
.
buf
);
}
/* Write in little chunchs so that when there are big writes we
* parallelize while the kernel is sending data in background to
* 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
;
while
(
nwritten
!=
count
)
{
retval
=
write
(
r
->
io
.
fdset
.
fds
[
j
],
p
+
nwritten
,
count
-
nwritten
);
if
(
retval
<=
0
)
{
/* With blocking sockets, which is the sole user of this
* rio target, EWOULDBLOCK is returned only because of
* the SO_SNDTIMEO socket option, so we translate the error
* into one more recognizable by the user. */
if
(
retval
==
-
1
&&
errno
==
EWOULDBLOCK
)
errno
=
ETIMEDOUT
;
break
;
}
nwritten
+=
retval
;
}
if
(
nwritten
!=
count
)
{
/* Mark this FD as broken. */
r
->
io
.
fdset
.
state
[
j
]
=
errno
;
if
(
r
->
io
.
fdset
.
state
[
j
]
==
0
)
r
->
io
.
fdset
.
state
[
j
]
=
EIO
;
}
size_t
nwritten
=
0
;
while
(
nwritten
!=
len
)
{
retval
=
write
(
r
->
io
.
fd
.
fd
,
p
+
nwritten
,
len
-
nwritten
);
if
(
retval
<=
0
)
{
/* With blocking io, which is the sole user of this
* rio target, EWOULDBLOCK is returned only because of
* the SO_SNDTIMEO socket option, so we translate the error
* into one more recognizable by the user. */
if
(
retval
==
-
1
&&
errno
==
EWOULDBLOCK
)
errno
=
ETIMEDOUT
;
return
0
;
/* error. */
}
if
(
broken
==
r
->
io
.
fdset
.
numfds
)
return
0
;
/* All the FDs in error. */
p
+=
count
;
len
-=
count
;
r
->
io
.
fdset
.
pos
+=
count
;
nwritten
+=
retval
;
}
if
(
doflush
)
sdsclear
(
r
->
io
.
fdset
.
buf
);
r
->
io
.
fd
.
pos
+=
len
;
sdsclear
(
r
->
io
.
fd
.
buf
);
return
1
;
}
/* Returns 1 or 0 for success/failure. */
static
size_t
rioFd
set
Read
(
rio
*
r
,
void
*
buf
,
size_t
len
)
{
static
size_t
rioFdRead
(
rio
*
r
,
void
*
buf
,
size_t
len
)
{
UNUSED
(
r
);
UNUSED
(
buf
);
UNUSED
(
len
);
...
...
@@ -358,23 +340,23 @@ static size_t rioFdsetRead(rio *r, void *buf, size_t len) {
}
/* Returns read/write position in file. */
static
off_t
rioFd
set
Tell
(
rio
*
r
)
{
return
r
->
io
.
fd
set
.
pos
;
static
off_t
rioFdTell
(
rio
*
r
)
{
return
r
->
io
.
fd
.
pos
;
}
/* Flushes any buffer to target device if applicable. Returns 1 on success
* and 0 on failures. */
static
int
rioFd
set
Flush
(
rio
*
r
)
{
static
int
rioFdFlush
(
rio
*
r
)
{
/* Our flush is implemented by the write method, that recognizes a
* buffer set to NULL with a count of zero as a flush request. */
return
rioFd
set
Write
(
r
,
NULL
,
0
);
return
rioFdWrite
(
r
,
NULL
,
0
);
}
static
const
rio
rioFd
set
IO
=
{
rioFd
set
Read
,
rioFd
set
Write
,
rioFd
set
Tell
,
rioFd
set
Flush
,
static
const
rio
rioFdIO
=
{
rioFdRead
,
rioFdWrite
,
rioFdTell
,
rioFdFlush
,
NULL
,
/* update_checksum */
0
,
/* current checksum */
0
,
/* flags */
...
...
@@ -383,24 +365,16 @@ static const rio rioFdsetIO = {
{
{
NULL
,
0
}
}
/* union for io-specific vars */
};
void
rioInitWithFdset
(
rio
*
r
,
int
*
fds
,
int
numfds
)
{
int
j
;
*
r
=
rioFdsetIO
;
r
->
io
.
fdset
.
fds
=
zmalloc
(
sizeof
(
int
)
*
numfds
);
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
();
void
rioInitWithFd
(
rio
*
r
,
int
fd
)
{
*
r
=
rioFdIO
;
r
->
io
.
fd
.
fd
=
fd
;
r
->
io
.
fd
.
pos
=
0
;
r
->
io
.
fd
.
buf
=
sdsempty
();
}
/* release the rio stream. */
void
rioFreeFdset
(
rio
*
r
)
{
zfree
(
r
->
io
.
fdset
.
fds
);
zfree
(
r
->
io
.
fdset
.
state
);
sdsfree
(
r
->
io
.
fdset
.
buf
);
void
rioFreeFd
(
rio
*
r
)
{
sdsfree
(
r
->
io
.
fd
.
buf
);
}
/* ---------------------------- Generic functions ---------------------------- */
...
...
src/rio.h
View file @
efb6495a
...
...
@@ -35,6 +35,7 @@
#include <stdio.h>
#include <stdint.h>
#include "sds.h"
#include "connection.h"
#define RIO_FLAG_READ_ERROR (1<<0)
#define RIO_FLAG_WRITE_ERROR (1<<1)
...
...
@@ -76,22 +77,20 @@ struct _rio {
off_t
buffered
;
/* Bytes written since last fsync. */
off_t
autosync
;
/* fsync after 'autosync' bytes written. */
}
file
;
/*
file descriptor
*/
/*
Connection object (used to read from socket)
*/
struct
{
int
fd
;
/* File descriptor.
*/
connection
*
conn
;
/* Connection
*/
off_t
pos
;
/* pos in buf that was returned */
sds
buf
;
/* buffered data */
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) */
}
fd
;
/*
Multiple
FD
s
target (used to write to
N sockets
). */
}
conn
;
/* FD target (used to write to
pipe
). */
struct
{
int
*
fds
;
/* File descriptors. */
int
*
state
;
/* Error state of each fd. 0 (if ok) or errno. */
int
numfds
;
int
fd
;
/* File descriptor. */
off_t
pos
;
sds
buf
;
}
fd
set
;
}
fd
;
}
io
;
};
...
...
@@ -159,11 +158,11 @@ static inline void rioClearErrors(rio *r) {
void
rioInitWithFile
(
rio
*
r
,
FILE
*
fp
);
void
rioInitWithBuffer
(
rio
*
r
,
sds
s
);
void
rioInitWith
Fd
(
rio
*
r
,
int
fd
,
size_t
read_limit
);
void
rioInitWithFd
set
(
rio
*
r
,
int
*
fd
s
,
int
numfds
);
void
rioInitWith
Conn
(
rio
*
r
,
connection
*
conn
,
size_t
read_limit
);
void
rioInitWithFd
(
rio
*
r
,
int
fd
);
void
rioFreeFd
set
(
rio
*
r
);
void
rioFree
Fd
(
rio
*
r
,
sds
*
out_remainingBufferedData
);
void
rioFreeFd
(
rio
*
r
);
void
rioFree
Conn
(
rio
*
r
,
sds
*
out_remainingBufferedData
);
size_t
rioWriteBulkCount
(
rio
*
r
,
char
prefix
,
long
count
);
size_t
rioWriteBulkString
(
rio
*
r
,
const
char
*
buf
,
size_t
len
);
...
...
src/scripting.c
View file @
efb6495a
...
...
@@ -61,7 +61,7 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx);
#define LDB_BREAKPOINTS_MAX 64
/* Max number of breakpoints. */
#define LDB_MAX_LEN_DEFAULT 256
/* Default len limit for replies / var dumps. */
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
forked
;
/* Is this a fork()ed debugging session? */
list
*
logs
;
/* List of messages to send to the client. */
...
...
@@ -1243,7 +1243,7 @@ void scriptingInit(int setup) {
* Note: there is no need to create it again when this function is called
* by scriptingReset(). */
if
(
server
.
lua_client
==
NULL
)
{
server
.
lua_client
=
createClient
(
-
1
);
server
.
lua_client
=
createClient
(
NULL
);
server
.
lua_client
->
flags
|=
CLIENT_LUA
;
}
...
...
@@ -1734,7 +1734,7 @@ NULL
/* Initialize Lua debugger data structures. */
void
ldbInit
(
void
)
{
ldb
.
fd
=
-
1
;
ldb
.
conn
=
NULL
;
ldb
.
active
=
0
;
ldb
.
logs
=
listCreate
();
listSetFreeMethod
(
ldb
.
logs
,(
void
(
*
)(
void
*
))
sdsfree
);
...
...
@@ -1756,7 +1756,7 @@ void ldbFlushLog(list *log) {
void
ldbEnable
(
client
*
c
)
{
c
->
flags
|=
CLIENT_LUA_DEBUG
;
ldbFlushLog
(
ldb
.
logs
);
ldb
.
fd
=
c
->
fd
;
ldb
.
conn
=
c
->
conn
;
ldb
.
step
=
1
;
ldb
.
bpcount
=
0
;
ldb
.
luabp
=
0
;
...
...
@@ -1811,7 +1811,7 @@ void ldbSendLogs(void) {
proto
=
sdscatlen
(
proto
,
"
\r\n
"
,
2
);
listDelNode
(
ldb
.
logs
,
ln
);
}
if
(
w
rite
(
ldb
.
fd
,
proto
,
sdslen
(
proto
))
==
-
1
)
{
if
(
connW
rite
(
ldb
.
conn
,
proto
,
sdslen
(
proto
))
==
-
1
)
{
/* Avoid warning. We don't check the return value of write()
* since the next read() will catch the I/O error and will
* close the debugging session. */
...
...
@@ -1863,8 +1863,8 @@ int ldbStartSession(client *c) {
}
/* Setup our debugging session. */
anet
Block
(
NULL
,
ldb
.
fd
);
anet
SendTimeout
(
NULL
,
ldb
.
fd
,
5000
);
conn
Block
(
ldb
.
conn
);
conn
SendTimeout
(
ldb
.
conn
,
5000
);
ldb
.
active
=
1
;
/* First argument of EVAL is the script itself. We split it into different
...
...
@@ -1891,7 +1891,7 @@ void ldbEndSession(client *c) {
/* If it's a fork()ed session, we just exit. */
if
(
ldb
.
forked
)
{
writeToClient
(
c
->
fd
,
c
,
0
);
writeToClient
(
c
,
0
);
serverLog
(
LL_WARNING
,
"Lua debugging session child exiting"
);
exitFromChild
(
0
);
}
else
{
...
...
@@ -1900,8 +1900,8 @@ void ldbEndSession(client *c) {
}
/* Otherwise let's restore client's state. */
anet
NonBlock
(
NULL
,
ldb
.
fd
);
anet
SendTimeout
(
NULL
,
ldb
.
fd
,
0
);
conn
NonBlock
(
ldb
.
conn
);
conn
SendTimeout
(
ldb
.
conn
,
0
);
/* Close the client connectin after sending the final EVAL reply
* in order to signal the end of the debugging session. */
...
...
@@ -2538,7 +2538,7 @@ int ldbRepl(lua_State *lua) {
while
(
1
)
{
while
((
argv
=
ldbReplParseCommand
(
&
argc
))
==
NULL
)
{
char
buf
[
1024
];
int
nread
=
r
ead
(
ldb
.
fd
,
buf
,
sizeof
(
buf
));
int
nread
=
connR
ead
(
ldb
.
conn
,
buf
,
sizeof
(
buf
));
if
(
nread
<=
0
)
{
/* Make sure the script runs without user input since the
* client is no longer connected. */
...
...
src/sentinel.c
View file @
efb6495a
...
...
@@ -30,6 +30,10 @@
#include "server.h"
#include "hiredis.h"
#ifdef USE_OPENSSL
#include "openssl/ssl.h"
#include "hiredis_ssl.h"
#endif
#include "async.h"
#include <ctype.h>
...
...
@@ -40,6 +44,10 @@
extern
char
**
environ
;
#ifdef USE_OPENSSL
extern
SSL_CTX
*
redis_tls_ctx
;
#endif
#define REDIS_SENTINEL_PORT 26379
/* ======================== Sentinel global state =========================== */
...
...
@@ -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
* is disconnected. Note that link->disconnected is true even if just
* one of the two links (commands and pub/sub) is missing. */
...
...
@@ -2010,7 +2031,11 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Commands connection. */
if
(
link
->
cc
==
NULL
)
{
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"
,
link
->
cc
->
errstr
);
instanceLinkCloseConnection
(
link
,
link
->
cc
);
...
...
@@ -2033,7 +2058,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Pub / Sub */
if
((
ri
->
flags
&
(
SRI_MASTER
|
SRI_SLAVE
))
&&
link
->
pc
==
NULL
)
{
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"
,
link
->
pc
->
errstr
);
instanceLinkCloseConnection
(
link
,
link
->
pc
);
...
...
@@ -2584,8 +2612,9 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
return
C_ERR
;
announce_ip
=
ip
;
}
announce_port
=
sentinel
.
announce_port
?
sentinel
.
announce_port
:
server
.
port
;
if
(
sentinel
.
announce_port
)
announce_port
=
sentinel
.
announce_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. */
snprintf
(
payload
,
sizeof
(
payload
),
...
...
src/server.c
View file @
efb6495a
...
...
@@ -1752,6 +1752,62 @@ void updateCachedTime(void) {
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.
* Here is where we do a number of things that need to be done asynchronously.
* For instance:
...
...
@@ -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. */
if
(
hasActiveChildProcess
()
||
ldbPendingChildren
())
{
int
statloc
;
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
();
}
checkChildrenDone
();
}
else
{
/* If there is not a background saving/rewrite in progress check if
* we have to save/rewrite now. */
...
...
@@ -2054,6 +2066,11 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
void
beforeSleep
(
struct
aeEventLoop
*
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
* 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
...
...
@@ -2247,11 +2264,13 @@ void initServerConfig(void) {
server
.
dynamic_hz
=
CONFIG_DEFAULT_DYNAMIC_HZ
;
server
.
arch_bits
=
(
sizeof
(
long
)
==
8
)
?
64
:
32
;
server
.
port
=
CONFIG_DEFAULT_SERVER_PORT
;
server
.
tls_port
=
CONFIG_DEFAULT_SERVER_TLS_PORT
;
server
.
tcp_backlog
=
CONFIG_DEFAULT_TCP_BACKLOG
;
server
.
bindaddr_count
=
0
;
server
.
unixsocket
=
NULL
;
server
.
unixsocketperm
=
CONFIG_DEFAULT_UNIX_SOCKET_PERM
;
server
.
ipfd_count
=
0
;
server
.
tlsfd_count
=
0
;
server
.
sofd
=
-
1
;
server
.
protected_mode
=
CONFIG_DEFAULT_PROTECTED_MODE
;
server
.
gopher_enabled
=
CONFIG_DEFAULT_GOPHER_ENABLED
;
...
...
@@ -2286,6 +2305,7 @@ void initServerConfig(void) {
server
.
aof_rewrite_min_size
=
AOF_REWRITE_MIN_SIZE
;
server
.
aof_rewrite_base_size
=
0
;
server
.
aof_rewrite_scheduled
=
0
;
server
.
aof_flush_sleep
=
0
;
server
.
aof_last_fsync
=
time
(
NULL
);
server
.
aof_rewrite_time_last
=
-
1
;
server
.
aof_rewrite_time_start
=
-
1
;
...
...
@@ -2297,6 +2317,7 @@ void initServerConfig(void) {
server
.
aof_rewrite_incremental_fsync
=
CONFIG_DEFAULT_AOF_REWRITE_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
.
key_load_delay
=
CONFIG_DEFAULT_KEY_LOAD_DELAY
;
server
.
aof_load_truncated
=
CONFIG_DEFAULT_AOF_LOAD_TRUNCATED
;
server
.
aof_use_rdb_preamble
=
CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE
;
server
.
pidfile
=
NULL
;
...
...
@@ -2368,7 +2389,7 @@ void initServerConfig(void) {
server
.
repl_state
=
REPL_STATE_NONE
;
server
.
repl_transfer_tmpfile
=
NULL
;
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_serve_stale_data
=
CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA
;
server
.
repl_slave_ro
=
CONFIG_DEFAULT_SLAVE_READ_ONLY
;
...
...
@@ -2765,6 +2786,11 @@ void initServer(void) {
server
.
clients_paused
=
0
;
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
();
adjustOpenFilesLimit
();
server
.
el
=
aeCreateEventLoop
(
server
.
maxclients
+
CONFIG_FDSET_INCR
);
...
...
@@ -2780,6 +2806,9 @@ void initServer(void) {
if
(
server
.
port
!=
0
&&
listenToPort
(
server
.
port
,
server
.
ipfd
,
&
server
.
ipfd_count
)
==
C_ERR
)
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. */
if
(
server
.
unixsocket
!=
NULL
)
{
...
...
@@ -2794,7 +2823,7 @@ void initServer(void) {
}
/* 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."
);
exit
(
1
);
}
...
...
@@ -2820,6 +2849,11 @@ void initServer(void) {
server
.
aof_child_pid
=
-
1
;
server
.
module_child_pid
=
-
1
;
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
.
child_info_pipe
[
0
]
=
-
1
;
server
.
child_info_pipe
[
1
]
=
-
1
;
...
...
@@ -2866,6 +2900,14 @@ void initServer(void) {
"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
,
acceptUnixHandler
,
NULL
)
==
AE_ERR
)
serverPanic
(
"Unrecoverable error creating server.sofd file event."
);
...
...
@@ -3570,6 +3612,7 @@ void closeListeningSockets(int unlink_unix_socket) {
int
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
.
cluster_enabled
)
for
(
j
=
0
;
j
<
server
.
cfd_count
;
j
++
)
close
(
server
.
cfd
[
j
]);
...
...
@@ -3940,7 +3983,7 @@ sds genRedisInfoString(char *section) {
#endif
(
int64_t
)
getpid
(),
server
.
runid
,
server
.
port
,
server
.
port
?
server
.
port
:
server
.
tls_port
,
(
int64_t
)
uptime
,
(
int64_t
)(
uptime
/
(
3600
*
24
)),
server
.
hz
,
...
...
@@ -4324,7 +4367,7 @@ sds genRedisInfoString(char *section) {
long
lag
=
0
;
if
(
slaveip
[
0
]
==
'\0'
)
{
if
(
anet
PeerToString
(
slave
->
fd
,
ip
,
sizeof
(
ip
),
&
port
)
==
-
1
)
if
(
conn
PeerToString
(
slave
->
conn
,
ip
,
sizeof
(
ip
),
&
port
)
==
-
1
)
continue
;
slaveip
=
ip
;
}
...
...
@@ -4578,7 +4621,7 @@ void redisAsciiArt(void) {
if
(
!
show_logo
)
{
serverLog
(
LL_NOTICE
,
"Running mode=%s, port=%d."
,
mode
,
server
.
port
mode
,
server
.
port
?
server
.
port
:
server
.
tls_port
);
}
else
{
snprintf
(
buf
,
1024
*
16
,
ascii_logo
,
...
...
@@ -4586,7 +4629,7 @@ void redisAsciiArt(void) {
redisGitSHA1
(),
strtol
(
redisGitDirty
(),
NULL
,
10
)
>
0
,
(
sizeof
(
long
)
==
8
)
?
"64"
:
"32"
,
mode
,
server
.
port
,
mode
,
server
.
port
?
server
.
port
:
server
.
tls_port
,
(
long
)
getpid
()
);
serverLogRaw
(
LL_NOTICE
|
LL_RAW
,
buf
);
...
...
@@ -4769,7 +4812,7 @@ void redisSetProcTitle(char *title) {
setproctitle
(
"%s %s:%d%s"
,
title
,
server
.
bindaddr_count
?
server
.
bindaddr
[
0
]
:
"*"
,
server
.
port
,
server
.
port
?
server
.
port
:
server
.
tls_port
,
server_mode
);
#else
UNUSED
(
title
);
...
...
@@ -4920,6 +4963,7 @@ int main(int argc, char **argv) {
ACLInit
();
/* The ACL subsystem must be initialized ASAP because the
basic networking code and client creation depends on it. */
moduleInitModulesSystem
();
tlsInit
();
/* Store the executable path and arguments in a safe place in order
* to be able to restart the server later. */
...
...
@@ -5053,7 +5097,7 @@ int main(int argc, char **argv) {
exit
(
1
);
}
}
if
(
server
.
ipfd_count
>
0
)
if
(
server
.
ipfd_count
>
0
||
server
.
tlsfd_count
>
0
)
serverLog
(
LL_NOTICE
,
"Ready to accept connections"
);
if
(
server
.
sofd
>
0
)
serverLog
(
LL_NOTICE
,
"The server is now ready to accept connections at %s"
,
server
.
unixsocket
);
...
...
src/server.h
View file @
efb6495a
...
...
@@ -66,6 +66,7 @@ typedef long long mstime_t; /* millisecond time type. */
#include "quicklist.h"
/* Lists are encoded as linked lists of
N-elements flat arrays */
#include "rax.h"
/* Radix tree */
#include "connection.h"
/* Connection abstraction */
/* Following includes allow test functions to be called from Redis main() */
#include "zipmap.h"
...
...
@@ -84,6 +85,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_MAX_HZ 500
#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_TLS_PORT 0
/* TCP port. */
#define CONFIG_DEFAULT_TCP_BACKLOG 511
/* TCP listen backlog. */
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0
/* Default client timeout: infinite */
#define CONFIG_DEFAULT_DBNUM 16
...
...
@@ -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_DELAY 5
#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_READ_ONLY 1
#define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1
...
...
@@ -826,7 +829,7 @@ typedef struct user {
* Clients are taken in a linked list. */
typedef
struct
client
{
uint64_t
id
;
/* Client incremental unique ID. */
int
fd
;
/* Client socket. */
connection
*
conn
;
int
resp
;
/* RESP protocol version. Can be 2 or 3. */
redisDb
*
db
;
/* Pointer to currently SELECTed DB. */
robj
*
name
;
/* As set by CLIENT SETNAME. */
...
...
@@ -1034,6 +1037,22 @@ struct malloc_stats {
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
*----------------------------------------------------------------------------*/
...
...
@@ -1088,6 +1107,7 @@ struct redisServer {
pid_t
module_child_pid
;
/* PID of module child */
/* Networking */
int
port
;
/* TCP listening port */
int
tls_port
;
/* TLS listening port */
int
tcp_backlog
;
/* TCP listen() backlog */
char
*
bindaddr
[
CONFIG_BINDADDR_MAX
];
/* Addresses we should bind to */
int
bindaddr_count
;
/* Number of addresses in server.bindaddr[] */
...
...
@@ -1095,6 +1115,8 @@ struct redisServer {
mode_t
unixsocketperm
;
/* UNIX socket permission */
int
ipfd
[
CONFIG_BINDADDR_MAX
];
/* TCP socket file descriptors */
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
cfd
[
CONFIG_BINDADDR_MAX
];
/* Cluster bus listening socket */
int
cfd_count
;
/* Used slots in cfd[] */
...
...
@@ -1198,6 +1220,7 @@ struct redisServer {
off_t
aof_rewrite_base_size
;
/* AOF size on latest startup or rewrite. */
off_t
aof_current_size
;
/* AOF current size. */
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. */
pid_t
aof_child_pid
;
/* PID if rewriting process */
list
*
aof_rewrite_buf_blocks
;
/* Hold changes during an AOF rewrite. */
...
...
@@ -1243,10 +1266,17 @@ struct redisServer {
int
rdb_child_type
;
/* Type of save by active child. */
int
lastbgsave_status
;
/* C_OK or C_ERR */
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_read_result_from_child
;
/* of each slave in diskless SYNC. */
int
rdb_pipe_write
;
/* RDB pipes used to transfer the rdb */
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
* 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. */
int
child_info_pipe
[
2
];
/* Pipe used to write the child_info_data. */
struct
{
...
...
@@ -1299,7 +1329,7 @@ struct redisServer {
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_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 */
char
*
repl_transfer_tmpfile
;
/* Slave-> master SYNC temp file name */
time_t
repl_transfer_lastio
;
/* Unix time of the latest read, for timeout */
...
...
@@ -1423,6 +1453,11 @@ struct redisServer {
int
watchdog_period
;
/* Software watchdog period in ms. 0 = off */
/* System hardware info */
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
{
...
...
@@ -1570,12 +1605,12 @@ size_t redisPopcount(void *s, long count);
void
redisSetProcTitle
(
char
*
title
);
/* networking.c -- Networking and Client related operations */
client
*
createClient
(
int
fd
);
client
*
createClient
(
connection
*
conn
);
void
closeTimedoutClients
(
void
);
void
freeClient
(
client
*
c
);
void
freeClientAsync
(
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
setDeferredArrayLen
(
client
*
c
,
void
*
node
,
long
length
);
void
setDeferredMapLen
(
client
*
c
,
void
*
node
,
long
length
);
...
...
@@ -1587,8 +1622,9 @@ void processInputBufferAndReplicate(client *c);
void
processGopherRequest
(
client
*
c
);
void
acceptHandler
(
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
readQueryFromClient
(
aeEventLoop
*
el
,
int
fd
,
void
*
privdata
,
int
mask
);
void
readQueryFromClient
(
connection
*
conn
);
void
addReplyNull
(
client
*
c
);
void
addReplyNullArray
(
client
*
c
);
void
addReplyBool
(
client
*
c
,
int
b
);
...
...
@@ -1646,7 +1682,7 @@ int handleClientsWithPendingReadsUsingThreads(void);
int
stopThreadedIOIfNeeded
(
void
);
int
clientHasPendingReplies
(
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
protectClient
(
client
*
c
);
void
unprotectClient
(
client
*
c
);
...
...
@@ -1782,6 +1818,8 @@ void clearReplicationId2(void);
void
chopReplicationBacklog
(
void
);
void
replicationCacheMasterUsingMyself
(
void
);
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 */
void
startLoadingFile
(
FILE
*
fp
,
char
*
filename
);
...
...
@@ -1954,6 +1992,7 @@ unsigned int LRU_CLOCK(void);
const
char
*
evictPolicyToString
(
void
);
struct
redisMemOverhead
*
getMemoryOverheadData
(
void
);
void
freeMemoryOverheadData
(
struct
redisMemOverhead
*
mh
);
void
checkChildrenDone
(
void
);
#define RESTART_SERVER_NONE 0
#define RESTART_SERVER_GRACEFULLY (1<<0)
/* Do proper shutdown. */
...
...
@@ -2369,6 +2408,10 @@ void mixDigest(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
);
/* TLS stuff */
void
tlsInit
(
void
);
int
tlsConfigure
(
redisTLSContextConfig
*
ctx_config
);
#define redisDebug(fmt, ...) \
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define redisDebugMark() \
...
...
src/tls.c
0 → 100644
View file @
efb6495a
/*
* 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
tests/cluster/run.tcl
View file @
efb6495a
...
...
@@ -8,6 +8,7 @@ source ../instances.tcl
source ../../support/cluster.tcl
;
# Redis Cluster client.
set ::instances_count 20
;
# How many instances we use at max.
set ::tlsdir
"../../tls"
proc main
{}
{
parse_options
...
...
tests/cluster/tests/04-resharding.tcl
View file @
efb6495a
...
...
@@ -4,6 +4,7 @@
# are preseved across iterations.
source
"../tests/includes/init-tests.tcl"
source
"../../../tests/support/cli.tcl"
test
"Create a 5 nodes cluster"
{
create_cluster 5 5
...
...
@@ -79,6 +80,7 @@ test "Cluster consistency during live resharding" {
--cluster-to $target
\
--cluster-slots 100
\
--cluster-yes
\
{*}
[
rediscli_tls_config
"../../../tests"
]
\
|
[
info nameofexecutable
]
\
../tests/helpers/onlydots.tcl
\
&
]
0
]
...
...
tests/cluster/tests/12-replica-migration-2.tcl
View file @
efb6495a
...
...
@@ -5,6 +5,7 @@
# other masters have slaves.
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
# 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" {
set output
[
exec
\
../../../src/redis-cli --cluster rebalance
\
127.0.0.1:
[
get_instance_attrib redis 0 port
]
\
{*}
[
rediscli_tls_config
"../../../tests"
]
\
--cluster-weight $
{
master0_id
}
=0 >@ stdout
]
}
test
"Master #0 should lose its replicas"
{
...
...
@@ -51,6 +54,7 @@ test "Resharding back some slot to master #0" {
set output
[
exec
\
../../../src/redis-cli --cluster rebalance
\
127.0.0.1:
[
get_instance_attrib redis 0 port
]
\
{*}
[
rediscli_tls_config
"../../../tests"
]
\
--cluster-weight $
{
master0_id
}
=.01
\
--cluster-use-empty-masters >@ stdout
]
}
...
...
tests/helpers/bg_block_op.tcl
View file @
efb6495a
source tests/support/redis.tcl
source tests/support/util.tcl
set ::tlsdir
"tests/tls"
# This function sometimes writes sometimes blocking-reads from lists/sorted
# 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
...
...
@@ -8,8 +10,8 @@ source tests/support/util.tcl
# 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
# blocking.
proc bg_block_op
{
host port db ops
}
{
set r
[
redis $host $port
]
proc bg_block_op
{
host port db ops
tls
}
{
set r
[
redis $host $port
0 $tls
]
$r select $db
for
{
set j 0
}
{
$j
< $ops
}
{
incr j
}
{
...
...
@@ -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
]
tests/helpers/bg_complex_data.tcl
View file @
efb6495a
source tests/support/redis.tcl
source tests/support/util.tcl
proc bg_complex_data
{
host port db ops
}
{
set r
[
redis $host $port
]
set ::tlsdir
"tests/tls"
proc bg_complex_data
{
host port db ops tls
}
{
set r
[
redis $host $port 0 $tls
]
$r select $db
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
]
tests/helpers/gen_write_load.tcl
View file @
efb6495a
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 r
[
redis $host $port
1
]
set r
[
redis $host $port
0 $tls
]
$r select 9
while 1
{
$r set
[
expr rand
()]
[
expr rand
()]
...
...
@@ -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
]
tests/instances.tcl
View file @
efb6495a
...
...
@@ -17,6 +17,7 @@ source ../support/test.tcl
set ::verbose 0
set ::valgrind 0
set ::tls 0
set ::pause_on_error 0
set ::simulate_error 0
set ::failed 0
...
...
@@ -69,7 +70,19 @@ proc spawn_instance {type base_port count {conf {}}} {
# Write the instance config file.
set cfgfile
[
file join $dirname $type.conf
]
set cfg
[
open $cfgfile w
]
puts $cfg
"port
$port
"
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
"dir ./
$dirname
"
puts $cfg
"logfile log.txt"
# Add additional config files
...
...
@@ -88,7 +101,7 @@ proc spawn_instance {type base_port count {conf {}}} {
}
# 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
lappend ::$
{
type
}
_instances
[
list
\
pid $pid
\
...
...
@@ -148,6 +161,13 @@ proc parse_options {} {
set ::simulate_error 1
}
elseif
{
$opt
eq
{
--valgrind
}}
{
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"
}
{
puts
"Hello, I'm sentinel.tcl and I run Sentinel unit tests."
puts
"
\n
Options:"
...
...
@@ -492,7 +512,7 @@ proc restart_instance {type id} {
}
# 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
set_instance_attrib $type $id link $link
...
...
tests/integration/aof-race.tcl
View file @
efb6495a
...
...
@@ -13,8 +13,9 @@ tags {"aof"} {
# cleaned after a child responsible for an AOF rewrite exited. This buffer
# was subsequently appended to the new AOF, resulting in duplicate commands.
start_server_aof
[
list dir $server_path
]
{
set client
[
redis
[
srv host
]
[
srv port
]]
set bench
[
open
"|src/redis-benchmark -q -p
[
srv port
]
-c 20 -n 20000 incr foo"
"r+"
]
set client
[
redis
[
srv host
]
[
srv port
]
0 $::tls
]
set bench
[
open
"|src/redis-benchmark -q -s
[
srv unixsocket
]
-c 20 -n 20000 incr foo"
"r+"
]
after 100
# Benchmark should be running by now: start background rewrite
...
...
@@ -29,7 +30,7 @@ tags {"aof"} {
# Restart server to replay AOF
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
]
}
}
tests/integration/aof.tcl
View file @
efb6495a
...
...
@@ -52,7 +52,7 @@ tags {"aof"} {
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"
{
assert
{[
$client
get foo
]
eq
"5"
}
...
...
@@ -69,7 +69,7 @@ tags {"aof"} {
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"
{
assert
{[
$client
get foo
]
eq
"6"
}
...
...
@@ -170,7 +170,7 @@ tags {"aof"} {
}
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
{
[
catch
{
$client
ping
}
e
]
== 0
}
else
{
...
...
@@ -194,7 +194,7 @@ tags {"aof"} {
}
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
{
[
catch
{
$client
ping
}
e
]
== 0
}
else
{
...
...
@@ -218,7 +218,7 @@ tags {"aof"} {
}
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
{
[
catch
{
$client
ping
}
e
]
== 0
}
else
{
...
...
@@ -241,7 +241,7 @@ tags {"aof"} {
}
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
{
[
catch
{
$client
ping
}
e
]
== 0
}
else
{
...
...
@@ -257,4 +257,35 @@ tags {"aof"} {
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
}
}
}
}
}
tests/integration/block-repl.tcl
View file @
efb6495a
...
...
@@ -2,9 +2,9 @@
# Unlike stream operations such operations are
"pop"
style, so they consume
# 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
]
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
}
{
...
...
@@ -18,9 +18,9 @@ start_server {tags {"repl"}} {
set master_port
[
srv -1 port
]
set slave
[
srv 0 client
]
set load_handle0
[
start_bg_block_op $master_host $master_port 9 100000
]
set load_handle1
[
start_bg_block_op $master_host $master_port 9 100000
]
set load_handle2
[
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
$::tls
]
set load_handle2
[
start_bg_block_op $master_host $master_port 9 100000
$::tls
]
test
{
First server should have role slave after SLAVEOF
}
{
$slave slaveof $master_host $master_port
...
...
tests/integration/psync2-reg.tcl
View file @
efb6495a
...
...
@@ -18,6 +18,7 @@ start_server {} {
set R
(
$j
)
[
srv
[
expr 0-$j
]
client
]
set R_host
(
$j
)
[
srv
[
expr 0-$j
]
host
]
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
]
"
}
}
...
...
@@ -36,7 +37,7 @@ start_server {} {
}
set cycle_start_time
[
clock milliseconds
]
set bench_pid
[
exec src/redis-benchmark -
p
$R_
por
t
(
0
)
-n 10000000 -r 1000 incr __rand_int__ > /dev/null &
]
set bench_pid
[
exec src/redis-benchmark -
s
$R_
unixsocke
t
(
0
)
-n 10000000 -r 1000 incr __rand_int__ > /dev/null &
]
while 1
{
set elapsed
[
expr
{[
clock milliseconds
]
-$cycle_start_time
}]
if
{
$elapsed
> $duration*1000
}
break
...
...
Prev
1
2
3
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment