summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TLS.md133
-rw-r--r--deps/Makefile6
-rw-r--r--deps/hiredis/.gitignore1
-rw-r--r--deps/hiredis/.travis.yml74
-rw-r--r--deps/hiredis/CHANGELOG.md15
-rw-r--r--deps/hiredis/CMakeLists.txt90
-rw-r--r--deps/hiredis/Makefile106
-rw-r--r--deps/hiredis/README.md3
-rw-r--r--deps/hiredis/adapters/libevent.h112
-rw-r--r--deps/hiredis/appveyor.yml7
-rw-r--r--deps/hiredis/async.c170
-rw-r--r--deps/hiredis/async.h8
-rw-r--r--deps/hiredis/async_private.h72
-rw-r--r--deps/hiredis/examples/CMakeLists.txt46
-rw-r--r--deps/hiredis/examples/example-libevent-ssl.c73
-rw-r--r--deps/hiredis/examples/example-libevent.c15
-rw-r--r--deps/hiredis/examples/example-ssl.c97
-rw-r--r--deps/hiredis/examples/example.c17
-rw-r--r--deps/hiredis/hiredis.c223
-rw-r--r--deps/hiredis/hiredis.h101
-rw-r--r--deps/hiredis/hiredis.pc.in11
-rw-r--r--deps/hiredis/hiredis_ssl.h53
-rw-r--r--deps/hiredis/hiredis_ssl.pc.in12
-rw-r--r--deps/hiredis/net.c122
-rw-r--r--deps/hiredis/net.h4
-rw-r--r--deps/hiredis/read.c14
-rw-r--r--deps/hiredis/read.h3
-rw-r--r--deps/hiredis/sds.c2
-rw-r--r--deps/hiredis/sds.h31
-rw-r--r--deps/hiredis/sockcompat.c248
-rw-r--r--deps/hiredis/sockcompat.h91
-rw-r--r--deps/hiredis/ssl.c448
-rw-r--r--deps/hiredis/test.c93
-rwxr-xr-xdeps/hiredis/test.sh70
-rw-r--r--deps/hiredis/win32.h18
-rw-r--r--redis.conf44
-rw-r--r--src/Makefile8
-rw-r--r--src/anet.c22
-rw-r--r--src/anet.h12
-rw-r--r--src/aof.c2
-rw-r--r--src/cluster.c235
-rw-r--r--src/cluster.h2
-rw-r--r--src/config.c83
-rw-r--r--src/connection.c383
-rw-r--r--src/connection.h211
-rw-r--r--src/connhelpers.h60
-rw-r--r--src/debug.c5
-rw-r--r--src/module.c8
-rw-r--r--src/networking.c244
-rw-r--r--src/rdb.c40
-rw-r--r--src/redis-cli.c62
-rw-r--r--src/replication.c198
-rw-r--r--src/rio.c144
-rw-r--r--src/rio.h23
-rw-r--r--src/scripting.c22
-rw-r--r--src/sentinel.c32
-rw-r--r--src/server.c35
-rw-r--r--src/server.h32
-rw-r--r--src/tls.c669
-rw-r--r--tests/cluster/run.tcl1
-rw-r--r--tests/cluster/tests/04-resharding.tcl2
-rw-r--r--tests/cluster/tests/12-replica-migration-2.tcl4
-rw-r--r--tests/helpers/bg_block_op.tcl8
-rw-r--r--tests/helpers/bg_complex_data.tcl8
-rw-r--r--tests/helpers/gen_write_load.tcl8
-rw-r--r--tests/instances.tcl26
-rw-r--r--tests/integration/aof-race.tcl7
-rw-r--r--tests/integration/aof.tcl12
-rw-r--r--tests/integration/block-repl.tcl10
-rw-r--r--tests/integration/psync2-reg.tcl3
-rw-r--r--tests/integration/redis-cli.tcl9
-rw-r--r--tests/integration/replication.tcl14
-rw-r--r--tests/sentinel/tests/07-down-conditions.tcl3
-rw-r--r--tests/support/cli.tcl19
-rw-r--r--tests/support/cluster.tcl4
-rw-r--r--tests/support/redis.tcl20
-rw-r--r--tests/support/server.tcl32
-rw-r--r--tests/support/util.tcl4
-rw-r--r--tests/test_helper.tcl23
-rw-r--r--tests/unit/limits.tcl7
-rw-r--r--tests/unit/other.tcl6
-rw-r--r--tests/unit/protocol.tcl6
-rw-r--r--tests/unit/tls.tcl25
-rw-r--r--tests/unit/wait.tcl5
-rwxr-xr-xutils/gen-test-certs.sh23
85 files changed, 4622 insertions, 832 deletions
diff --git a/TLS.md b/TLS.md
new file mode 100644
index 000000000..ee24a8df5
--- /dev/null
+++ b/TLS.md
@@ -0,0 +1,133 @@
+TLS Support -- Work In Progress
+===============================
+
+This is a brief note to capture current thoughts/ideas and track pending action
+items.
+
+Getting Started
+---------------
+
+### Building
+
+To build with TLS support you'll need OpenSSL development libraries (e.g.
+libssl-dev on Debian/Ubuntu).
+
+Run `make BUILD_TLS=yes`.
+
+### Tests
+
+To run Redis test suite with TLS, you'll need TLS support for TCL (i.e.
+`tcl-tls` package on Debian/Ubuntu).
+
+1. Run `./utils/gen-test-certs.sh` to generate a root CA and a server
+ certificate.
+
+2. Run `./runtest --tls` or `./runtest-cluster --tls` to run Redis and Redis
+ Cluster tests in TLS mode.
+
+### Running manually
+
+To manually run a Redis server with TLS mode (assuming `gen-test-certs.sh` was
+invoked so sample certificates/keys are available):
+
+ ./src/redis-server --tls-port 6379 --port 0 \
+ --tls-cert-file ./tests/tls/redis.crt \
+ --tls-key-file ./tests/tls/redis.key \
+ --tls-ca-cert-file ./tests/tls/ca.crt
+
+To connect to this Redis server with `redis-cli`:
+
+ ./src/redis-cli --tls \
+ --cert ./tests/tls/redis.crt \
+ --key ./tests/tls/redis.key \
+ --cacert ./tests/tls/ca.crt
+
+This will disable TCP and enable TLS on port 6379. It's also possible to have
+both TCP and TLS available, but you'll need to assign different ports.
+
+To make a Replica connect to the master using TLS, use `--tls-replication yes`,
+and to make Redis Cluster use TLS across nodes use `--tls-cluster yes`.
+
+**NOTE: This is still very much work in progress and some configuration is still
+missing or may change.**
+
+Connections
+-----------
+
+Connection abstraction API is mostly done and seems to hold well for hiding
+implementation details between TLS and TCP.
+
+1. Still need to implement the equivalent of AE_BARRIER. Because TLS
+ socket-level read/write events don't correspond to logical operations, this
+ should probably be done at the Read/Write handler level.
+
+2. Multi-threading I/O is not supported. The main issue to address is the need
+ to manipulate AE based on OpenSSL return codes. We can either propagate this
+ out of the thread, or explore ways of further optimizing MT I/O by having
+ event loops that live inside the thread and borrow connections in/out.
+
+3. Finish cleaning up the implementation. Make sure all error cases are handled
+ and reflected into connection state, connection state validated before
+ certain operations, etc.
+ - Clean (non-errno) interface to report would-block.
+ - Consistent error reporting.
+
+4. Sync IO for TLS is currently implemented in a hackish way, i.e. making the
+ socket blocking and configuring socket-level timeout. This means the timeout
+ value may not be so accurate, and there would be a lot of syscall overhead.
+ However I believe that getting rid of syncio completely in favor of pure
+ async work is probably a better move than trying to fix that. For replication
+ it would probably not be so hard. For cluster keys migration it might be more
+ difficult, but there are probably other good reasons to improve that part
+ anyway.
+
+5. A mechanism to re-trigger read callbacks for connections with unread buffers
+ (the case of reading partial TLS frames):
+
+ a) Before sleep should iterate connections looking for those with a read handler,
+ SSL_pending() != 0 and no read event.
+ b) If found, trigger read handler for these conns.
+ c) After iteration if this state persists, epoll should be called in a way
+ that won't block so the process continues and this behave the same as a
+ level trigerred epoll.
+
+Replication
+-----------
+
+Diskless master replication is broken, until child/parent connection proxying is
+implemented.
+
+
+TLS Features
+------------
+
+1. Add metrics to INFO.
+2. Add certificate authentication configuration (i.e. option to skip client
+auth, master auth, etc.).
+3. Add TLS cipher configuration options.
+4. [Optional] Add session caching support. Check if/how it's handled by clients
+ to assess how useful/important it is.
+
+
+redis-benchmark
+---------------
+
+The current implementation is a mix of using hiredis for parsing and basic
+networking (establishing connections), but directly manipulating sockets for
+most actions.
+
+This will need to be cleaned up for proper TLS support. The best approach is
+probably to migrate to hiredis async mode.
+
+
+Others
+------
+
+Consider the implications of allowing TLS to be configured on a separate port,
+making Redis listening on multiple ports.
+
+This impacts many things, like
+1. Startup banner port notification
+2. Proctitle
+3. How slaves announce themselves
+4. Cluster bus port calculation
diff --git a/deps/Makefile b/deps/Makefile
index eb35c1e1f..700867f3b 100644
--- a/deps/Makefile
+++ b/deps/Makefile
@@ -41,9 +41,13 @@ distclean:
.PHONY: distclean
+ifeq ($(BUILD_TLS),yes)
+ HIREDIS_MAKE_FLAGS = USE_SSL=1
+endif
+
hiredis: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
- cd hiredis && $(MAKE) static
+ cd hiredis && $(MAKE) static $(HIREDIS_MAKE_FLAGS)
.PHONY: hiredis
diff --git a/deps/hiredis/.gitignore b/deps/hiredis/.gitignore
index c44b5c537..8e50b5434 100644
--- a/deps/hiredis/.gitignore
+++ b/deps/hiredis/.gitignore
@@ -5,3 +5,4 @@
/*.dylib
/*.a
/*.pc
+*.dSYM
diff --git a/deps/hiredis/.travis.yml b/deps/hiredis/.travis.yml
index faf2ce684..dd8e0e73d 100644
--- a/deps/hiredis/.travis.yml
+++ b/deps/hiredis/.travis.yml
@@ -26,20 +26,72 @@ addons:
- libc6-dev-i386
- libc6-dbg:i386
- gcc-multilib
+ - g++-multilib
- valgrind
env:
- - CFLAGS="-Werror"
- - PRE="valgrind --track-origins=yes --leak-check=full"
- - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
- - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
+ - BITS="32"
+ - BITS="64"
-matrix:
- exclude:
- - os: osx
- env: PRE="valgrind --track-origins=yes --leak-check=full"
+script:
+ - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON";
+ if [ "$TRAVIS_OS_NAME" == "osx" ]; then
+ if [ "$BITS" == "32" ]; then
+ CFLAGS="-m32 -Werror";
+ CXXFLAGS="-m32 -Werror";
+ LDFLAGS="-m32";
+ EXTRA_CMAKE_OPTS=;
+ else
+ CFLAGS="-Werror";
+ CXXFLAGS="-Werror";
+ fi;
+ else
+ TEST_PREFIX="valgrind --track-origins=yes --leak-check=full";
+ if [ "$BITS" == "32" ]; then
+ CFLAGS="-m32 -Werror";
+ CXXFLAGS="-m32 -Werror";
+ LDFLAGS="-m32";
+ EXTRA_CMAKE_OPTS=;
+ else
+ CFLAGS="-Werror";
+ CXXFLAGS="-Werror";
+ fi;
+ fi;
+ export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
+ - mkdir build/ && cd build/
+ - cmake .. ${EXTRA_CMAKE_OPTS}
+ - make VERBOSE=1
+ - ctest -V
- - os: osx
- env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
+matrix:
+ include:
+ # Windows MinGW cross compile on Linux
+ - os: linux
+ dist: xenial
+ compiler: mingw
+ addons:
+ apt:
+ packages:
+ - ninja-build
+ - gcc-mingw-w64-x86-64
+ - g++-mingw-w64-x86-64
+ script:
+ - mkdir build && cd build
+ - CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on
+ - ninja -v
-script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example
+ # Windows MSVC 2017
+ - os: windows
+ compiler: msvc
+ env:
+ - MATRIX_EVAL="CC=cl.exe && CXX=cl.exe"
+ before_install:
+ - eval "${MATRIX_EVAL}"
+ install:
+ - choco install ninja
+ script:
+ - mkdir build && cd build
+ - cmd.exe /C '"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64 &&
+ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release &&
+ ninja -v'
+ - ctest -V
diff --git a/deps/hiredis/CHANGELOG.md b/deps/hiredis/CHANGELOG.md
index a7fe3ac11..d1d37e515 100644
--- a/deps/hiredis/CHANGELOG.md
+++ b/deps/hiredis/CHANGELOG.md
@@ -12,6 +12,16 @@
compare to other values, casting might be necessary or can be removed, if
casting was applied before.
+### 0.x.x (unreleased)
+**BREAKING CHANGES**:
+
+* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
+
+User code should compare this to `size_t` values as well.
+If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
+
+* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
+
### 0.14.0 (2018-09-25)
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
@@ -50,8 +60,9 @@
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
* Fix warnings, when compiled with -Wshadow
* Make hiredis compile in Cygwin on Windows, now CI-tested
-
-**BREAKING CHANGES**:
+* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
+ protocol errors. This is consistent with the RESP specification. On 32-bit
+ platforms, the upper bound is lowered to `SIZE_MAX`.
* Remove backwards compatibility macro's
diff --git a/deps/hiredis/CMakeLists.txt b/deps/hiredis/CMakeLists.txt
new file mode 100644
index 000000000..9e78894f3
--- /dev/null
+++ b/deps/hiredis/CMakeLists.txt
@@ -0,0 +1,90 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0)
+INCLUDE(GNUInstallDirs)
+PROJECT(hiredis)
+
+OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
+
+MACRO(getVersionBit name)
+ SET(VERSION_REGEX "^#define ${name} (.+)$")
+ FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h"
+ VERSION_BIT REGEX ${VERSION_REGEX})
+ STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}")
+ENDMACRO(getVersionBit)
+
+getVersionBit(HIREDIS_MAJOR)
+getVersionBit(HIREDIS_MINOR)
+getVersionBit(HIREDIS_PATCH)
+getVersionBit(HIREDIS_SONAME)
+SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}")
+MESSAGE("Detected version: ${VERSION}")
+
+PROJECT(hiredis VERSION "${VERSION}")
+
+SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples")
+
+ADD_LIBRARY(hiredis SHARED
+ async.c
+ dict.c
+ hiredis.c
+ net.c
+ read.c
+ sds.c
+ sockcompat.c)
+
+SET_TARGET_PROPERTIES(hiredis
+ PROPERTIES
+ VERSION "${HIREDIS_SONAME}")
+IF(WIN32 OR MINGW)
+ TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32)
+ENDIF()
+TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .)
+
+CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
+
+INSTALL(TARGETS hiredis
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+
+INSTALL(FILES hiredis.h read.h sds.h async.h
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
+
+INSTALL(DIRECTORY adapters
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
+
+INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+
+IF(ENABLE_SSL)
+ IF (NOT OPENSSL_ROOT_DIR)
+ IF (APPLE)
+ SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
+ ENDIF()
+ ENDIF()
+ FIND_PACKAGE(OpenSSL REQUIRED)
+ ADD_LIBRARY(hiredis_ssl SHARED
+ ssl.c)
+ TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}")
+ TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES})
+ CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
+
+ INSTALL(TARGETS hiredis_ssl
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+
+ INSTALL(FILES hiredis_ssl.h
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
+
+ INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+ENDIF()
+
+IF(NOT (WIN32 OR MINGW))
+ ENABLE_TESTING()
+ ADD_EXECUTABLE(hiredis-test test.c)
+ TARGET_LINK_LIBRARIES(hiredis-test hiredis)
+ ADD_TEST(NAME hiredis-test
+ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
+ENDIF()
+
+# Add examples
+IF(ENABLE_EXAMPLES)
+ ADD_SUBDIRECTORY(examples)
+ENDIF(ENABLE_EXAMPLES)
diff --git a/deps/hiredis/Makefile b/deps/hiredis/Makefile
index 06ca99468..25ac15464 100644
--- a/deps/hiredis/Makefile
+++ b/deps/hiredis/Makefile
@@ -3,11 +3,17 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file
-OBJ=net.o hiredis.o sds.o async.o read.o
+OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o
+SSL_OBJ=ssl.o
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
+ifeq ($(USE_SSL),1)
+EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
+endif
TESTS=hiredis-test
LIBNAME=libhiredis
+SSL_LIBNAME=libhiredis_ssl
PKGCONFNAME=hiredis.pc
+SSL_PKGCONFNAME=hiredis_ssl.pc
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
@@ -39,7 +45,7 @@ export REDIS_TEST_CONFIG
CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
OPTIMIZATION?=-O3
-WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
+WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers
DEBUG_FLAGS?= -g -ggdb
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
REAL_LDFLAGS=$(LDFLAGS)
@@ -49,12 +55,30 @@ STLIBSUFFIX=a
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
-DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
+SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
+DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
-STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
+SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
+STLIB_MAKE_CMD=$(AR) rcs
# Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+
+USE_SSL?=0
+
+# This is required for test.c only
+ifeq ($(USE_SSL),1)
+ CFLAGS+=-DHIREDIS_TEST_SSL
+endif
+
+ifeq ($(uname_S),Linux)
+ SSL_LDFLAGS=-lssl -lcrypto
+else
+ OPENSSL_PREFIX?=/usr/local/opt/openssl
+ CFLAGS+=-I$(OPENSSL_PREFIX)/include
+ SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
+endif
+
ifeq ($(uname_S),SunOS)
REAL_LDFLAGS+= -ldl -lnsl -lsocket
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
@@ -66,40 +90,61 @@ ifeq ($(uname_S),Darwin)
endif
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
+ifeq ($(USE_SSL),1)
+all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
+endif
# Deps (use make dep to generate this)
async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
dict.o: dict.c fmacros.h dict.h
-hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h
-net.o: net.c fmacros.h net.h hiredis.h read.h sds.h
+hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h win32.h
+net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h
read.o: read.c fmacros.h read.h sds.h
sds.o: sds.c sds.h
+sockcompat.o: sockcompat.c sockcompat.h
+ssl.o: ssl.c hiredis.h
test.o: test.c fmacros.h hiredis.h read.h sds.h
$(DYLIBNAME): $(OBJ)
- $(DYLIB_MAKE_CMD) $(OBJ)
+ $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
$(STLIBNAME): $(OBJ)
- $(STLIB_MAKE_CMD) $(OBJ)
+ $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
+
+$(SSL_DYLIBNAME): $(SSL_OBJ)
+ $(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
+
+$(SSL_STLIBNAME): $(SSL_OBJ)
+ $(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
dynamic: $(DYLIBNAME)
static: $(STLIBNAME)
+ifeq ($(USE_SSL),1)
+dynamic: $(SSL_DYLIBNAME)
+static: $(SSL_STLIBNAME)
+endif
# Binaries:
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)
+
+hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS)
+
+hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
ifndef AE_DIR
hiredis-example-ae:
@@ -116,7 +161,7 @@ hiredis-example-libuv:
@false
else
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)
endif
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
@@ -133,32 +178,33 @@ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
endif
hiredis-example: examples/example.c $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
examples: $(EXAMPLES)
-hiredis-test: test.o $(STLIBNAME)
+TEST_LIBS = $(STLIBNAME)
+ifeq ($(USE_SSL),1)
+ TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread
+endif
+hiredis-test: test.o $(TEST_LIBS)
hiredis-%: %.o $(STLIBNAME)
- $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
+ $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
test: hiredis-test
./hiredis-test
check: hiredis-test
- @echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) -
- $(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \
- ( kill `cat /tmp/hiredis-test-redis.pid` && false )
- kill `cat /tmp/hiredis-test-redis.pid`
+ TEST_SSL=$(USE_SSL) ./test.sh
.c.o:
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
clean:
- rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
+ rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
dep:
- $(CC) -MM *.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
INSTALL?= cp -pPR
@@ -175,6 +221,20 @@ $(PKGCONFNAME): hiredis.h
@echo Libs: -L\$${libdir} -lhiredis >> $@
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
+$(SSL_PKGCONFNAME): hiredis.h
+ @echo "Generating $@ for pkgconfig..."
+ @echo prefix=$(PREFIX) > $@
+ @echo exec_prefix=\$${prefix} >> $@
+ @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
+ @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
+ @echo >> $@
+ @echo Name: hiredis_ssl >> $@
+ @echo Description: SSL Support for hiredis. >> $@
+ @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
+ @echo Requires: hiredis >> $@
+ @echo Libs: -L\$${libdir} -lhiredis_ssl >> $@
+ @echo Libs.private: -lssl -lcrypto >> $@
+
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
$(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)
diff --git a/deps/hiredis/README.md b/deps/hiredis/README.md
index 01223ea59..c0b432f07 100644
--- a/deps/hiredis/README.md
+++ b/deps/hiredis/README.md
@@ -286,6 +286,7 @@ return `REDIS_ERR`. The function to set the disconnect callback has the followin
```c
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
```
+`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback.
### Sending commands and their callbacks
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
@@ -406,6 +407,6 @@ as soon as possible in order to prevent allocation of useless memory.
## AUTHORS
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
-Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
+Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
Jan-Erik Rediger (janerik at fnordig dot com)
diff --git a/deps/hiredis/adapters/libevent.h b/deps/hiredis/adapters/libevent.h
index 7d2bef180..a4952776c 100644
--- a/deps/hiredis/adapters/libevent.h
+++ b/deps/hiredis/adapters/libevent.h
@@ -34,48 +34,113 @@
#include "../hiredis.h"
#include "../async.h"
+#define REDIS_LIBEVENT_DELETED 0x01
+#define REDIS_LIBEVENT_ENTERED 0x02
+
typedef struct redisLibeventEvents {
redisAsyncContext *context;
- struct event *rev, *wev;
+ struct event *ev;
+ struct event_base *base;
+ struct timeval tv;
+ short flags;
+ short state;
} redisLibeventEvents;
-static void redisLibeventReadEvent(int fd, short event, void *arg) {
- ((void)fd); ((void)event);
- redisLibeventEvents *e = (redisLibeventEvents*)arg;
- redisAsyncHandleRead(e->context);
+static void redisLibeventDestroy(redisLibeventEvents *e) {
+ free(e);
}
-static void redisLibeventWriteEvent(int fd, short event, void *arg) {
- ((void)fd); ((void)event);
+static void redisLibeventHandler(int fd, short event, void *arg) {
+ ((void)fd);
redisLibeventEvents *e = (redisLibeventEvents*)arg;
- redisAsyncHandleWrite(e->context);
+ e->state |= REDIS_LIBEVENT_ENTERED;
+
+ #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\
+ redisLibeventDestroy(e);\
+ return; \
+ }
+
+ if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
+ redisAsyncHandleTimeout(e->context);
+ CHECK_DELETED();
+ }
+
+ if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
+ redisAsyncHandleRead(e->context);
+ CHECK_DELETED();
+ }
+
+ if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
+ redisAsyncHandleWrite(e->context);
+ CHECK_DELETED();
+ }
+
+ e->state &= ~REDIS_LIBEVENT_ENTERED;
+ #undef CHECK_DELETED
+}
+
+static void redisLibeventUpdate(void *privdata, short flag, int isRemove) {
+ redisLibeventEvents *e = (redisLibeventEvents *)privdata;
+ const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL;
+
+ if (isRemove) {
+ if ((e->flags & flag) == 0) {
+ return;
+ } else {
+ e->flags &= ~flag;
+ }
+ } else {
+ if (e->flags & flag) {
+ return;
+ } else {
+ e->flags |= flag;
+ }
+ }
+
+ event_del(e->ev);
+ event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST,
+ redisLibeventHandler, privdata);
+ event_add(e->ev, tv);
}
static void redisLibeventAddRead(void *privdata) {
- redisLibeventEvents *e = (redisLibeventEvents*)privdata;
- event_add(e->rev,NULL);
+ redisLibeventUpdate(privdata, EV_READ, 0);
}
static void redisLibeventDelRead(void *privdata) {
- redisLibeventEvents *e = (redisLibeventEvents*)privdata;
- event_del(e->rev);
+ redisLibeventUpdate(privdata, EV_READ, 1);
}
static void redisLibeventAddWrite(void *privdata) {
- redisLibeventEvents *e = (redisLibeventEvents*)privdata;
- event_add(e->wev,NULL);
+ redisLibeventUpdate(privdata, EV_WRITE, 0);
}
static void redisLibeventDelWrite(void *privdata) {
- redisLibeventEvents *e = (redisLibeventEvents*)privdata;
- event_del(e->wev);
+ redisLibeventUpdate(privdata, EV_WRITE, 1);
}
static void redisLibeventCleanup(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
- event_free(e->rev);
- event_free(e->wev);
- free(e);
+ if (!e) {
+ return;
+ }
+ event_del(e->ev);
+ event_free(e->ev);
+ e->ev = NULL;
+
+ if (e->state & REDIS_LIBEVENT_ENTERED) {
+ e->state |= REDIS_LIBEVENT_DELETED;
+ } else {
+ redisLibeventDestroy(e);
+ }
+}
+
+static void redisLibeventSetTimeout(void *privdata, struct timeval tv) {
+ redisLibeventEvents *e = (redisLibeventEvents *)privdata;
+ short flags = e->flags;
+ e->flags = 0;
+ e->tv = tv;
+ redisLibeventUpdate(e, flags, 0);
}
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
@@ -87,7 +152,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
return REDIS_ERR;
/* Create container for context and r/w events */
- e = (redisLibeventEvents*)malloc(sizeof(*e));
+ e = (redisLibeventEvents*)calloc(1, sizeof(*e));
e->context = ac;
/* Register functions to start/stop listening for events */
@@ -96,13 +161,12 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
ac->ev.addWrite = redisLibeventAddWrite;
ac->ev.delWrite = redisLibeventDelWrite;
ac->ev.cleanup = redisLibeventCleanup;
+ ac->ev.scheduleTimer = redisLibeventSetTimeout;
ac->ev.data = e;
/* Initialize and install read/write events */
- e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e);
- e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e);
- event_add(e->rev, NULL);
- event_add(e->wev, NULL);
+ e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
+ e->base = base;
return REDIS_OK;
}
#endif
diff --git a/deps/hiredis/appveyor.yml b/deps/hiredis/appveyor.yml
index 819efbd58..5b43fdbeb 100644
--- a/deps/hiredis/appveyor.yml
+++ b/deps/hiredis/appveyor.yml
@@ -5,8 +5,9 @@ environment:
CC: gcc
- CYG_BASH: C:\cygwin\bin\bash
CC: gcc
- TARGET: 32bit
- TARGET_VARS: 32bit-vars
+ CFLAGS: -m32
+ CXXFLAGS: -m32
+ LDFLAGS: -m32
clone_depth: 1
@@ -20,4 +21,4 @@ install:
build_script:
- 'echo building...'
- - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make LDFLAGS=$LDFLAGS CC=$CC $TARGET CFLAGS=$CFLAGS && make LDFLAGS=$LDFLAGS CC=$CC $TARGET_VARS hiredis-example"'
+ - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; mkdir build && cd build && cmake .. -G \"Unix Makefiles\" && make VERBOSE=1"'
diff --git a/deps/hiredis/async.c b/deps/hiredis/async.c
index 0cecd30d9..4f422d566 100644
--- a/deps/hiredis/async.c
+++ b/deps/hiredis/async.c
@@ -32,7 +32,9 @@
#include "fmacros.h"
#include <stdlib.h>
#include <string.h>
+#ifndef _MSC_VER
#include <strings.h>
+#endif
#include <assert.h>
#include <ctype.h>
#include <errno.h>
@@ -40,22 +42,9 @@
#include "net.h"
#include "dict.c"
#include "sds.h"
+#include "win32.h"
-#define _EL_ADD_READ(ctx) do { \
- if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
- } while(0)
-#define _EL_DEL_READ(ctx) do { \
- if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
- } while(0)
-#define _EL_ADD_WRITE(ctx) do { \
- if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
- } while(0)
-#define _EL_DEL_WRITE(ctx) do { \
- if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
- } while(0)
-#define _EL_CLEANUP(ctx) do { \
- if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
- } while(0);
+#include "async_private.h"
/* Forward declaration of function in hiredis.c */
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
@@ -126,6 +115,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
ac->ev.addWrite = NULL;
ac->ev.delWrite = NULL;
ac->ev.cleanup = NULL;
+ ac->ev.scheduleTimer = NULL;
ac->onConnect = NULL;
ac->onDisconnect = NULL;
@@ -150,56 +140,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) {
ac->errstr = c->errstr;
}
-redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
+redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
+ redisOptions myOptions = *options;
redisContext *c;
redisAsyncContext *ac;
- c = redisConnectNonBlock(ip,port);
- if (c == NULL)
+ myOptions.options |= REDIS_OPT_NONBLOCK;
+ c = redisConnectWithOptions(&myOptions);
+ if (c == NULL) {
return NULL;
-
+ }
ac = redisAsyncInitialize(c);
if (ac == NULL) {
redisFree(c);
return NULL;
}
-
__redisAsyncCopyError(ac);
return ac;
}
+redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ return redisAsyncConnectWithOptions(&options);
+}
+
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisConnectBindNonBlock(ip,port,source_addr);
- redisAsyncContext *ac = redisAsyncInitialize(c);
- __redisAsyncCopyError(ac);
- return ac;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ return redisAsyncConnectWithOptions(&options);
}
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr);
- redisAsyncContext *ac = redisAsyncInitialize(c);
- __redisAsyncCopyError(ac);
- return ac;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.options |= REDIS_OPT_REUSEADDR;
+ options.endpoint.tcp.source_addr = source_addr;
+ return redisAsyncConnectWithOptions(&options);
}
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
- redisContext *c;
- redisAsyncContext *ac;
-
- c = redisConnectUnixNonBlock(path);
- if (c == NULL)
- return NULL;
-
- ac = redisAsyncInitialize(c);
- if (ac == NULL) {
- redisFree(c);
- return NULL;
- }
-
- __redisAsyncCopyError(ac);
- return ac;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ return redisAsyncConnectWithOptions(&options);
}
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
@@ -328,7 +314,7 @@ void redisAsyncFree(redisAsyncContext *ac) {
}
/* Helper function to make the disconnect happen and clean up. */
-static void __redisAsyncDisconnect(redisAsyncContext *ac) {
+void __redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
/* Make sure error is accessible if there is any */
@@ -344,9 +330,15 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
c->flags |= REDIS_DISCONNECTING;
}
+ /* cleanup event library on disconnect.
+ * this is safe to call multiple times */
+ _EL_CLEANUP(ac);
+
/* For non-clean disconnects, __redisAsyncFree() will execute pending
* callbacks with a NULL-reply. */
- __redisAsyncFree(ac);
+ if (!(c->flags & REDIS_NO_AUTO_FREE)) {
+ __redisAsyncFree(ac);
+ }
}
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
@@ -358,6 +350,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
void redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
c->flags |= REDIS_DISCONNECTING;
+
+ /** unset the auto-free flag here, because disconnect undoes this */
+ c->flags &= ~REDIS_NO_AUTO_FREE;
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
__redisAsyncDisconnect(ac);
}
@@ -408,7 +403,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
/* Unset subscribed flag only when no pipelined pending subscribe. */
- if (reply->element[2]->integer == 0
+ if (reply->element[2]->integer == 0
&& dictSize(ac->sub.channels) == 0
&& dictSize(ac->sub.patterns) == 0)
c->flags &= ~REDIS_SUBSCRIBED;
@@ -524,6 +519,18 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
}
}
+void redisAsyncRead(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+
+ if (redisBufferRead(c) == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ /* Always re-schedule reads */
+ _EL_ADD_READ(ac);
+ redisProcessCallbacks(ac);
+ }
+}
+
/* This function should be called when the socket is readable.
* It processes all replies that can be read and executes their callbacks.
*/
@@ -539,18 +546,29 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
return;
}
- if (redisBufferRead(c) == REDIS_ERR) {
+ c->funcs->async_read(ac);
+}
+
+void redisAsyncWrite(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ int done = 0;
+
+ if (redisBufferWrite(c,&done) == REDIS_ERR) {
__redisAsyncDisconnect(ac);
} else {
- /* Always re-schedule reads */
+ /* Continue writing when not done, stop writing otherwise */
+ if (!done)
+ _EL_ADD_WRITE(ac);
+ else
+ _EL_DEL_WRITE(ac);
+
+ /* Always schedule reads after writes */
_EL_ADD_READ(ac);
- redisProcessCallbacks(ac);
}
}
void redisAsyncHandleWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
- int done = 0;
if (!(c->flags & REDIS_CONNECTED)) {
/* Abort connect was not successful. */
@@ -561,18 +579,37 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
return;
}
- if (redisBufferWrite(c,&done) == REDIS_ERR) {
- __redisAsyncDisconnect(ac);
- } else {
- /* Continue writing when not done, stop writing otherwise */
- if (!done)
- _EL_ADD_WRITE(ac);
- else
- _EL_DEL_WRITE(ac);
+ c->funcs->async_write(ac);
+}
- /* Always schedule reads after writes */
- _EL_ADD_READ(ac);
+void __redisSetError(redisContext *c, int type, const char *str);
+
+void redisAsyncHandleTimeout(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisCallback cb;
+
+ if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) {
+ /* Nothing to do - just an idle timeout */
+ return;
+ }
+
+ if (!c->err) {
+ __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout");
+ }
+
+ if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) {
+ ac->onConnect(ac, REDIS_ERR);
}
+
+ while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
+ __redisRunCallback(ac, &cb, NULL);
+ }
+
+ /**
+ * TODO: Don't automatically sever the connection,
+ * rather, allow to ignore <x> responses before the queue is clear
+ */
+ __redisAsyncDisconnect(ac);
}
/* Sets a pointer to the first argument and its length starting at p. Returns
@@ -714,3 +751,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
return status;
}
+
+void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
+ if (!ac->c.timeout) {
+ ac->c.timeout = calloc(1, sizeof(tv));
+ }
+
+ if (tv.tv_sec == ac->c.timeout->tv_sec &&
+ tv.tv_usec == ac->c.timeout->tv_usec) {
+ return;
+ }
+
+ *ac->c.timeout = tv;
+}
diff --git a/deps/hiredis/async.h b/deps/hiredis/async.h
index 740555c24..4f6b3b783 100644
--- a/deps/hiredis/async.h
+++ b/deps/hiredis/async.h
@@ -57,6 +57,7 @@ typedef struct redisCallbackList {
/* Connection callback prototypes */
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
+typedef void(redisTimerCallback)(void *timer, void *privdata);
/* Context for an async connection to Redis */
typedef struct redisAsyncContext {
@@ -81,6 +82,7 @@ typedef struct redisAsyncContext {
void (*addWrite)(void *privdata);
void (*delWrite)(void *privdata);
void (*cleanup)(void *privdata);
+ void (*scheduleTimer)(void *privdata, struct timeval tv);
} ev;
/* Called when either the connection is terminated due to an error or per
@@ -106,6 +108,7 @@ typedef struct redisAsyncContext {
} redisAsyncContext;
/* Functions that proxy to hiredis */
+redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
@@ -113,12 +116,17 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+
+void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
void redisAsyncDisconnect(redisAsyncContext *ac);
void redisAsyncFree(redisAsyncContext *ac);
/* Handle read/write events */
void redisAsyncHandleRead(redisAsyncContext *ac);
void redisAsyncHandleWrite(redisAsyncContext *ac);
+void redisAsyncHandleTimeout(redisAsyncContext *ac);
+void redisAsyncRead(redisAsyncContext *ac);
+void redisAsyncWrite(redisAsyncContext *ac);
/* Command functions for an async context. Write the command to the
* output buffer and register the provided callback. */
diff --git a/deps/hiredis/async_private.h b/deps/hiredis/async_private.h
new file mode 100644
index 000000000..d0133ae18
--- /dev/null
+++ b/deps/hiredis/async_private.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * 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.
+ */
+
+#ifndef __HIREDIS_ASYNC_PRIVATE_H
+#define __HIREDIS_ASYNC_PRIVATE_H
+
+#define _EL_ADD_READ(ctx) \
+ do { \
+ refreshTimeout(ctx); \
+ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
+ } while (0)
+#define _EL_DEL_READ(ctx) do { \
+ if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
+ } while(0)
+#define _EL_ADD_WRITE(ctx) \
+ do { \
+ refreshTimeout(ctx); \
+ if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
+ } while (0)
+#define _EL_DEL_WRITE(ctx) do { \
+ if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
+ } while(0)
+#define _EL_CLEANUP(ctx) do { \
+ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
+ ctx->ev.cleanup = NULL; \
+ } while(0);
+
+static inline void refreshTimeout(redisAsyncContext *ctx) {
+ if (ctx->c.timeout && ctx->ev.scheduleTimer &&
+ (ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) {
+ ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout);
+ // } else {
+ // printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout);
+ // if (ctx->c.timeout){
+ // printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec,
+ // ctx->c.timeout->tv_usec);
+ // }
+ }
+}
+
+void __redisAsyncDisconnect(redisAsyncContext *ac);
+void redisProcessCallbacks(redisAsyncContext *ac);
+
+#endif /* __HIREDIS_ASYNC_PRIVATE_H */
diff --git a/deps/hiredis/examples/CMakeLists.txt b/deps/hiredis/examples/CMakeLists.txt
new file mode 100644
index 000000000..dd3a313ac
--- /dev/null
+++ b/deps/hiredis/examples/CMakeLists.txt
@@ -0,0 +1,46 @@
+INCLUDE(FindPkgConfig)
+# Check for GLib
+
+PKG_CHECK_MODULES(GLIB2 glib-2.0)
+if (GLIB2_FOUND)
+ INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
+ LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS})
+ ADD_EXECUTABLE(example-glib example-glib.c)
+ TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES})
+ENDIF(GLIB2_FOUND)
+
+FIND_PATH(LIBEV ev.h
+ HINTS /usr/local /usr/opt/local
+ ENV LIBEV_INCLUDE_DIR)
+
+if (LIBEV)
+ # Just compile and link with libev
+ ADD_EXECUTABLE(example-libev example-libev.c)
+ TARGET_LINK_LIBRARIES(example-libev hiredis ev)
+ENDIF()
+
+FIND_PATH(LIBEVENT event.h)
+if (LIBEVENT)
+ ADD_EXECUTABLE(example-libevent example-libevent)
+ TARGET_LINK_LIBRARIES(example-libevent hiredis event)
+ENDIF()
+
+FIND_PATH(LIBUV uv.h)
+IF (LIBUV)
+ ADD_EXECUTABLE(example-libuv example-libuv.c)
+ TARGET_LINK_LIBRARIES(example-libuv hiredis uv)
+ENDIF()
+
+IF (APPLE)
+ FIND_LIBRARY(CF CoreFoundation)
+ ADD_EXECUTABLE(example-macosx example-macosx.c)
+ TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF})
+ENDIF()
+
+IF (ENABLE_SSL)
+ ADD_EXECUTABLE(example-ssl example-ssl.c)
+ TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl)
+ENDIF()
+
+ADD_EXECUTABLE(example example.c)
+TARGET_LINK_LIBRARIES(example hiredis)
diff --git a/deps/hiredis/examples/example-libevent-ssl.c b/deps/hiredis/examples/example-libevent-ssl.c
new file mode 100644
index 000000000..1021113b9
--- /dev/null
+++ b/deps/hiredis/examples/example-libevent-ssl.c
@@ -0,0 +1,73 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <hiredis_ssl.h>
+#include <async.h>
+#include <adapters/libevent.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+ redisReply *reply = r;
+ if (reply == NULL) return;
+ printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* Disconnect after receiving the reply to GET */
+ redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+ signal(SIGPIPE, SIG_IGN);
+ struct event_base *base = event_base_new();
+ if (argc < 5) {
+ fprintf(stderr,
+ "Usage: %s <key> <host> <port> <cert> <certKey> [ca]\n", argv[0]);
+ exit(1);
+ }
+
+ const char *value = argv[1];
+ size_t nvalue = strlen(value);
+
+ const char *hostname = argv[2];
+ int port = atoi(argv[3]);
+
+ const char *cert = argv[4];
+ const char *certKey = argv[5];
+ const char *caCert = argc > 5 ? argv[6] : NULL;
+
+ redisAsyncContext *c = redisAsyncConnect(hostname, port);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+ if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) {
+ printf("SSL Error!\n");
+ exit(1);
+ }
+
+ redisLibeventAttach(c,base);
+ redisAsyncSetConnectCallback(c,connectCallback);
+ redisAsyncSetDisconnectCallback(c,disconnectCallback);
+ redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
+ redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+ event_base_dispatch(base);
+ return 0;
+}
diff --git a/deps/hiredis/examples/example-libevent.c b/deps/hiredis/examples/example-libevent.c
index d333c22b7..1fe71ae4e 100644
--- a/deps/hiredis/examples/example-libevent.c
+++ b/deps/hiredis/examples/example-libevent.c
@@ -9,7 +9,12 @@
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
- if (reply == NULL) return;
+ if (reply == NULL) {
+ if (c->errstr) {
+ printf("errstr: %s\n", c->errstr);
+ }
+ return;
+ }
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */
@@ -35,8 +40,14 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
struct event_base *base = event_base_new();
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
+ struct timeval tv = {0};
+ tv.tv_sec = 1;
+ options.timeout = &tv;
+
- redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
diff --git a/deps/hiredis/examples/example-ssl.c b/deps/hiredis/examples/example-ssl.c
new file mode 100644
index 000000000..81f4648c6
--- /dev/null
+++ b/deps/hiredis/examples/example-ssl.c
@@ -0,0 +1,97 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <hiredis.h>
+#include <hiredis_ssl.h>
+
+int main(int argc, char **argv) {
+ unsigned int j;
+ redisContext *c;
+ redisReply *reply;
+ if (argc < 4) {
+ printf("Usage: %s <host> <port> <cert> <key> [ca]\n", argv[0]);
+ exit(1);
+ }
+ const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
+ int port = atoi(argv[2]);
+ const char *cert = argv[3];
+ const char *key = argv[4];
+ const char *ca = argc > 4 ? argv[5] : NULL;
+
+ struct timeval tv = { 1, 500000 }; // 1.5 seconds
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, hostname, port);
+ options.timeout = &tv;
+ c = redisConnectWithOptions(&options);
+
+ if (c == NULL || c->err) {
+ if (c) {
+ printf("Connection error: %s\n", c->errstr);
+ redisFree(c);
+ } else {
+ printf("Connection error: can't allocate redis context\n");
+ }
+ exit(1);
+ }
+
+ if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) {
+ printf("Couldn't initialize SSL!\n");
+ printf("Error: %s\n", c->errstr);
+ redisFree(c);
+ exit(1);
+ }
+
+ /* PING server */
+ reply = redisCommand(c,"PING");
+ printf("PING: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Set a key */
+ reply = redisCommand(c,"SET %s %s", "foo", "hello world");
+ printf("SET: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Set a key using binary safe API */
+ reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5);
+ printf("SET (binary API): %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Try a GET and two INCR */
+ reply = redisCommand(c,"GET foo");
+ printf("GET foo: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ reply = redisCommand(c,"INCR counter");
+ printf("INCR counter: %lld\n", reply->integer);
+ freeReplyObject(reply);
+ /* again ... */
+ reply = redisCommand(c,"INCR counter");
+ printf("INCR counter: %lld\n", reply->integer);
+ freeReplyObject(reply);
+
+ /* Create a list of numbers, from 0 to 9 */
+ reply = redisCommand(c,"DEL mylist");
+ freeReplyObject(reply);
+ for (j = 0; j < 10; j++) {
+ char buf[64];
+
+ snprintf(buf,64,"%u",j);
+ reply = redisCommand(c,"LPUSH mylist element-%s", buf);
+ freeReplyObject(reply);
+ }
+
+ /* Let's check what we have inside the list */
+ reply = redisCommand(c,"LRANGE mylist 0 -1");
+ if (reply->type == REDIS_REPLY_ARRAY) {
+ for (j = 0; j < reply->elements; j++) {
+ printf("%u) %s\n", j, reply->element[j]->str);
+ }
+ }
+ freeReplyObject(reply);
+
+ /* Disconnects and frees the context */
+ redisFree(c);
+
+ return 0;
+}
diff --git a/deps/hiredis/examples/example.c b/deps/hiredis/examples/example.c
index 4d494c55a..0e93fc8b3 100644
--- a/deps/hiredis/examples/example.c
+++ b/deps/hiredis/examples/example.c
@@ -5,14 +5,27 @@
#include <hiredis.h>
int main(int argc, char **argv) {
- unsigned int j;
+ unsigned int j, isunix = 0;
redisContext *c;
redisReply *reply;
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
+
+ if (argc > 2) {
+ if (*argv[2] == 'u' || *argv[2] == 'U') {
+ isunix = 1;
+ /* in this case, host is the path to the unix socket */
+ printf("Will connect to unix socket @%s\n", hostname);
+ }
+ }
+
int port = (argc > 2) ? atoi(argv[2]) : 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
- c = redisConnectWithTimeout(hostname, port, timeout);
+ if (isunix) {
+ c = redisConnectUnixWithTimeout(hostname, timeout);
+ } else {
+ c = redisConnectWithTimeout(hostname, port, timeout);
+ }
if (c == NULL || c->err) {
if (c) {
printf("Connection error: %s\n", c->errstr);
diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c
index 0947d1ed7..c4611318f 100644
--- a/deps/hiredis/hiredis.c
+++ b/deps/hiredis/hiredis.c
@@ -34,7 +34,6 @@
#include "fmacros.h"
#include <string.h>
#include <stdlib.h>
-#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
@@ -42,10 +41,20 @@
#include "hiredis.h"
#include "net.h"
#include "sds.h"
+#include "async.h"
+#include "win32.h"
+
+static redisContextFuncs redisContextDefaultFuncs = {
+ .free_privdata = NULL,
+ .async_read = redisAsyncRead,
+ .async_write = redisAsyncWrite,
+ .read = redisNetRead,
+ .write = redisNetWrite
+};
static redisReply *createReplyObject(int type);
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
-static void *createArrayObject(const redisReadTask *task, int elements);
+static void *createArrayObject(const redisReadTask *task, size_t elements);
static void *createIntegerObject(const redisReadTask *task, long long value);
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
static void *createNilObject(const redisReadTask *task);
@@ -138,7 +147,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
return r;
}
-static void *createArrayObject(const redisReadTask *task, int elements) {
+static void *createArrayObject(const redisReadTask *task, size_t elements) {
redisReply *r, *parent;
r = createReplyObject(task->type);
@@ -649,29 +658,30 @@ redisReader *redisReaderCreate(void) {
return redisReaderCreateWithFunctions(&defaultFunctions);
}
-static redisContext *redisContextInit(void) {
+static redisContext *redisContextInit(const redisOptions *options) {
redisContext *c;
- c = calloc(1,sizeof(redisContext));
+ c = calloc(1, sizeof(*c));
if (c == NULL)
return NULL;
+ c->funcs = &redisContextDefaultFuncs;
c->obuf = sdsempty();
c->reader = redisReaderCreate();
+ c->fd = REDIS_INVALID_FD;
if (c->obuf == NULL || c->reader == NULL) {
redisFree(c);
return NULL;
}
-
+ (void)options; /* options are used in other functions */
return c;
}
void redisFree(redisContext *c) {
if (c == NULL)
return;
- if (c->fd > 0)
- close(c->fd);
+ redisNetClose(c);
sdsfree(c->obuf);
redisReaderFree(c->reader);
@@ -680,12 +690,16 @@ void redisFree(redisContext *c) {
free(c->unix_sock.path);
free(c->timeout);
free(c->saddr);
+ if (c->funcs->free_privdata) {
+ c->funcs->free_privdata(c->privdata);
+ }
+ memset(c, 0xff, sizeof(*c));
free(c);
}
-int redisFreeKeepFd(redisContext *c) {
- int fd = c->fd;
- c->fd = -1;
+redisFD redisFreeKeepFd(redisContext *c) {
+ redisFD fd = c->fd;
+ c->fd = REDIS_INVALID_FD;
redisFree(c);
return fd;
}
@@ -694,10 +708,13 @@ int redisReconnect(redisContext *c) {
c->err = 0;
memset(c->errstr, '\0', strlen(c->errstr));
- if (c->fd > 0) {
- close(c->fd);
+ if (c->privdata && c->funcs->free_privdata) {
+ c->funcs->free_privdata(c->privdata);
+ c->privdata = NULL;
}
+ redisNetClose(c);
+
sdsfree(c->obuf);
redisReaderFree(c->reader);
@@ -718,112 +735,107 @@ int redisReconnect(redisContext *c) {
return REDIS_ERR;
}
+redisContext *redisConnectWithOptions(const redisOptions *options) {
+ redisContext *c = redisContextInit(options);
+ if (c == NULL) {
+ return NULL;
+ }
+ if (!(options->options & REDIS_OPT_NONBLOCK)) {
+ c->flags |= REDIS_BLOCK;
+ }
+ if (options->options & REDIS_OPT_REUSEADDR) {
+ c->flags |= REDIS_REUSEADDR;
+ }
+ if (options->options & REDIS_OPT_NOAUTOFREE) {
+ c->flags |= REDIS_NO_AUTO_FREE;
+ }
+
+ if (options->type == REDIS_CONN_TCP) {
+ redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
+ options->endpoint.tcp.port, options->timeout,
+ options->endpoint.tcp.source_addr);
+ } else if (options->type == REDIS_CONN_UNIX) {
+ redisContextConnectUnix(c, options->endpoint.unix_socket,
+ options->timeout);
+ } else if (options->type == REDIS_CONN_USERFD) {
+ c->fd = options->endpoint.fd;
+ c->flags |= REDIS_CONNECTED;
+ } else {
+ // Unknown type - FIXME - FREE
+ return NULL;
+ }
+ if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
+ redisContextSetTimeout(c, *options->timeout);
+ }
+ return c;
+}
+
/* Connect to a Redis instance. On error the field error in the returned
* context will be set to the return value of the error function.
* When no set of reply functions is given, the default set will be used. */
redisContext *redisConnect(const char *ip, int port) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags |= REDIS_BLOCK;
- redisContextConnectTcp(c,ip,port,NULL);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags |= REDIS_BLOCK;
- redisContextConnectTcp(c,ip,port,&tv);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.timeout = &tv;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectNonBlock(const char *ip, int port) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags &= ~REDIS_BLOCK;
- redisContextConnectTcp(c,ip,port,NULL);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectBindNonBlock(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisContextInit();
- if (c == NULL)
- return NULL;
- c->flags &= ~REDIS_BLOCK;
- redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisContextInit();
- if (c == NULL)
- return NULL;
- c->flags &= ~REDIS_BLOCK;
- c->flags |= REDIS_REUSEADDR;
- redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectUnix(const char *path) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags |= REDIS_BLOCK;
- redisContextConnectUnix(c,path,NULL);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags |= REDIS_BLOCK;
- redisContextConnectUnix(c,path,&tv);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ options.timeout = &tv;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectUnixNonBlock(const char *path) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags &= ~REDIS_BLOCK;
- redisContextConnectUnix(c,path,NULL);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
}
-redisContext *redisConnectFd(int fd) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->fd = fd;
- c->flags |= REDIS_BLOCK | REDIS_CONNECTED;
- return c;
+redisContext *redisConnectFd(redisFD fd) {
+ redisOptions options = {0};
+ options.type = REDIS_CONN_USERFD;
+ options.endpoint.fd = fd;
+ return redisConnectWithOptions(&options);
}
/* Set read/write timeout on a blocking socket. */
@@ -853,22 +865,15 @@ int redisBufferRead(redisContext *c) {
if (c->err)
return REDIS_ERR;
- nread = read(c->fd,buf,sizeof(buf));
- if (nread == -1) {
- if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
- /* Try again later */
- } else {
- __redisSetError(c,REDIS_ERR_IO,NULL);
+ nread = c->funcs->read(c, buf, sizeof(buf));
+ if (nread > 0) {
+ if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
+ __redisSetError(c, c->reader->err, c->reader->errstr);
return REDIS_ERR;
+ } else {
}
- } else if (nread == 0) {
- __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
+ } else if (nread < 0) {
return REDIS_ERR;
- } else {
- if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
- __redisSetError(c,c->reader->err,c->reader->errstr);
- return REDIS_ERR;
- }
}
return REDIS_OK;
}
@@ -883,21 +888,15 @@ int redisBufferRead(redisContext *c) {
* c->errstr to hold the appropriate error string.
*/
int redisBufferWrite(redisContext *c, int *done) {
- int nwritten;
/* Return early when the context has seen an error. */
if (c->err)
return REDIS_ERR;
if (sdslen(c->obuf) > 0) {
- nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
- if (nwritten == -1) {
- if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
- /* Try again later */
- } else {
- __redisSetError(c,REDIS_ERR_IO,NULL);
- return REDIS_ERR;
- }
+ int nwritten = c->funcs->write(c);
+ if (nwritten < 0) {
+ return REDIS_ERR;
} else if (nwritten > 0) {
if (nwritten == (signed)sdslen(c->obuf)) {
sdsfree(c->obuf);
diff --git a/deps/hiredis/hiredis.h b/deps/hiredis/hiredis.h
index 47d7982e9..f02678a47 100644
--- a/deps/hiredis/hiredis.h
+++ b/deps/hiredis/hiredis.h
@@ -35,7 +35,11 @@
#define __HIREDIS_H
#include "read.h"
#include <stdarg.h> /* for va_list */
+#ifndef _MSC_VER
#include <sys/time.h> /* for struct timeval */
+#else
+struct timeval; /* forward declaration */
+#endif
#include <stdint.h> /* uintXX_t, etc */
#include "sds.h" /* for sds */
@@ -74,6 +78,12 @@
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
#define REDIS_REUSEADDR 0x80
+/**
+ * Flag that indicates the user does not want the context to
+ * be automatically freed upon error
+ */
+#define REDIS_NO_AUTO_FREE 0x200
+
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
@@ -111,14 +121,93 @@ void redisFreeSdsCommand(sds cmd);
enum redisConnectionType {
REDIS_CONN_TCP,
- REDIS_CONN_UNIX
+ REDIS_CONN_UNIX,
+ REDIS_CONN_USERFD
};
+struct redisSsl;
+
+#define REDIS_OPT_NONBLOCK 0x01
+#define REDIS_OPT_REUSEADDR 0x02
+
+/**
+ * Don't automatically free the async object on a connection failure,
+ * or other implicit conditions. Only free on an explicit call to disconnect() or free()
+ */
+#define REDIS_OPT_NOAUTOFREE 0x04
+
+/* In Unix systems a file descriptor is a regular signed int, with -1
+ * representing an invalid descriptor. In Windows it is a SOCKET
+ * (32- or 64-bit unsigned integer depending on the architecture), where
+ * all bits set (~0) is INVALID_SOCKET. */
+#ifndef _WIN32
+typedef int redisFD;
+#define REDIS_INVALID_FD -1
+#else
+#ifdef _WIN64
+typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */
+#else
+typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */
+#endif
+#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */
+#endif
+
+typedef struct {
+ /*
+ * the type of connection to use. This also indicates which
+ * `endpoint` member field to use
+ */
+ int type;
+ /* bit field of REDIS_OPT_xxx */
+ int options;
+ /* timeout value. if NULL, no timeout is used */
+ const struct timeval *timeout;
+ union {
+ /** use this field for tcp/ip connections */
+ struct {
+ const char *source_addr;
+ const char *ip;
+ int port;
+ } tcp;
+ /** use this field for unix domain sockets */
+ const char *unix_socket;
+ /**
+ * use this field to have hiredis operate an already-open
+ * file descriptor */
+ redisFD fd;
+ } endpoint;
+} redisOptions;
+
+/**
+ * Helper macros to initialize options to their specified fields.
+ */
+#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \
+ (opts)->type = REDIS_CONN_TCP; \
+ (opts)->endpoint.tcp.ip = ip_; \
+ (opts)->endpoint.tcp.port = port_;
+
+#define REDIS_OPTIONS_SET_UNIX(opts, path) \
+ (opts)->type = REDIS_CONN_UNIX; \
+ (opts)->endpoint.unix_socket = path;
+
+struct redisAsyncContext;
+struct redisContext;
+
+typedef struct redisContextFuncs {
+ void (*free_privdata)(void *);
+ void (*async_read)(struct redisAsyncContext *);
+ void (*async_write)(struct redisAsyncContext *);
+ int (*read)(struct redisContext *, char *, size_t);
+ int (*write)(struct redisContext *);
+} redisContextFuncs;
+
/* Context for a connection to Redis */
typedef struct redisContext {
+ const redisContextFuncs *funcs; /* Function table */
+
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
- int fd;
+ redisFD fd;
int flags;
char *obuf; /* Write buffer */
redisReader *reader; /* Protocol reader */
@@ -139,8 +228,12 @@ typedef struct redisContext {
/* For non-blocking connect */
struct sockadr *saddr;
size_t addrlen;
+
+ /* Additional private data for hiredis addons such as SSL */
+ void *privdata;
} redisContext;
+redisContext *redisConnectWithOptions(const redisOptions *options);
redisContext *redisConnect(const char *ip, int port);
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
redisContext *redisConnectNonBlock(const char *ip, int port);
@@ -151,7 +244,7 @@ redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
redisContext *redisConnectUnix(const char *path);
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
redisContext *redisConnectUnixNonBlock(const char *path);
-redisContext *redisConnectFd(int fd);
+redisContext *redisConnectFd(redisFD fd);
/**
* Reconnect the given context using the saved information.
@@ -167,7 +260,7 @@ int redisReconnect(redisContext *c);
int redisSetTimeout(redisContext *c, const struct timeval tv);
int redisEnableKeepAlive(redisContext *c);
void redisFree(redisContext *c);
-int redisFreeKeepFd(redisContext *c);
+redisFD redisFreeKeepFd(redisContext *c);
int redisBufferRead(redisContext *c);
int redisBufferWrite(redisContext *c, int *done);
diff --git a/deps/hiredis/hiredis.pc.in b/deps/hiredis/hiredis.pc.in
new file mode 100644
index 000000000..140b040f1
--- /dev/null
+++ b/deps/hiredis/hiredis.pc.in
@@ -0,0 +1,11 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=${prefix}/include
+pkgincludedir=${includedir}/hiredis
+
+Name: hiredis
+Description: Minimalistic C client library for Redis.
+Version: @PROJECT_VERSION@
+Libs: -L${libdir} -lhiredis
+Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64
diff --git a/deps/hiredis/hiredis_ssl.h b/deps/hiredis/hiredis_ssl.h
new file mode 100644
index 000000000..f844f9548
--- /dev/null
+++ b/deps/hiredis/hiredis_ssl.h
@@ -0,0 +1,53 @@
+
+/*
+ * 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.
+ */
+
+#ifndef __HIREDIS_SSL_H
+#define __HIREDIS_SSL_H
+
+/* This is the underlying struct for SSL in ssl.h, which is not included to
+ * keep build dependencies short here.
+ */
+struct ssl_st;
+
+/**
+ * Secure the connection using SSL. This should be done before any command is
+ * executed on the connection.
+ */
+int redisSecureConnection(redisContext *c, const char *capath, const char *certpath,
+ const char *keypath, const char *servername);
+
+/**
+ * Initiate SSL/TLS negotiation on a provided context.
+ */
+
+int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
+
+#endif /* __HIREDIS_SSL_H */
diff --git a/deps/hiredis/hiredis_ssl.pc.in b/deps/hiredis/hiredis_ssl.pc.in
new file mode 100644
index 000000000..588a978a5
--- /dev/null
+++ b/deps/hiredis/hiredis_ssl.pc.in
@@ -0,0 +1,12 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=${prefix}/include
+pkgincludedir=${includedir}/hiredis
+
+Name: hiredis_ssl
+Description: SSL Support for hiredis.
+Version: @PROJECT_VERSION@
+Requires: hiredis
+Libs: -L${libdir} -lhiredis_ssl
+Libs.private: -lssl -lcrypto
diff --git a/deps/hiredis/net.c b/deps/hiredis/net.c
index a4b3abc6d..e5f40b0a4 100644
--- a/deps/hiredis/net.c
+++ b/deps/hiredis/net.c
@@ -34,36 +34,64 @@
#include "fmacros.h"
#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/select.h>
-#include <sys/un.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-#include <unistd.h>
#include <fcntl.h>
#include <string.h>
-#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
-#include <poll.h>
#include <limits.h>
#include <stdlib.h>
#include "net.h"
#include "sds.h"
+#include "sockcompat.h"
+#include "win32.h"
/* Defined in hiredis.c */
void __redisSetError(redisContext *c, int type, const char *str);
-static void redisContextCloseFd(redisContext *c) {
- if (c && c->fd >= 0) {
+void redisNetClose(redisContext *c) {
+ if (c && c->fd != REDIS_INVALID_FD) {
close(c->fd);
- c->fd = -1;
+ c->fd = REDIS_INVALID_FD;
}
}
+int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
+ int nread = recv(c->fd, buf, bufcap, 0);
+ if (nread == -1) {
+ if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+ /* Try again later */
+ return 0;
+ } else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) {
+ /* especially in windows */
+ __redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
+ return -1;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ } else if (nread == 0) {
+ __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
+ return -1;
+ } else {
+ return nread;
+ }
+}
+
+int redisNetWrite(redisContext *c) {
+ int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);
+ if (nwritten < 0) {
+ if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+ /* Try again later */
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+ return nwritten;
+}
+
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
int errorno = errno; /* snprintf() may change errno */
char buf[128] = { 0 };
@@ -79,15 +107,15 @@ static int redisSetReuseAddr(redisContext *c) {
int on = 1;
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
return REDIS_OK;
}
static int redisCreateSocket(redisContext *c, int type) {
- int s;
- if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
+ redisFD s;
+ if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
return REDIS_ERR;
}
@@ -101,6 +129,7 @@ static int redisCreateSocket(redisContext *c, int type) {
}
static int redisSetBlocking(redisContext *c, int blocking) {
+#ifndef _WIN32
int flags;
/* Set the socket nonblocking.
@@ -108,7 +137,7 @@ static int redisSetBlocking(redisContext *c, int blocking) {
* interrupted by a signal. */
if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
@@ -119,15 +148,23 @@ static int redisSetBlocking(redisContext *c, int blocking) {
if (fcntl(c->fd, F_SETFL, flags) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
- redisContextCloseFd(c);
+ redisNetClose(c);
+ return REDIS_ERR;
+ }
+#else
+ u_long mode = blocking ? 0 : 1;
+ if (ioctl(c->fd, FIONBIO, &mode) == -1) {
+ __redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)");
+ redisNetClose(c);
return REDIS_ERR;
}
+#endif /* _WIN32 */
return REDIS_OK;
}
int redisKeepAlive(redisContext *c, int interval) {
int val = 1;
- int fd = c->fd;
+ redisFD fd = c->fd;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
@@ -170,7 +207,7 @@ static int redisSetTcpNoDelay(redisContext *c) {
int yes = 1;
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
return REDIS_OK;
@@ -212,12 +249,12 @@ static int redisContextWaitReady(redisContext *c, long msec) {
if ((res = poll(wfd, 1, msec)) == -1) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
} else if (res == 0) {
errno = ETIMEDOUT;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
@@ -230,7 +267,7 @@ static int redisContextWaitReady(redisContext *c, long msec) {
}
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
@@ -277,11 +314,18 @@ int redisCheckSocketError(redisContext *c) {
}
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
- if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
+ const void *to_ptr = &tv;
+ size_t to_sz = sizeof(tv);
+#ifdef _WIN32
+ DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000;
+ to_ptr = &timeout_msec;
+ to_sz = sizeof(timeout_msec);
+#endif
+ if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
return REDIS_ERR;
}
- if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
+ if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
return REDIS_ERR;
}
@@ -291,7 +335,8 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout,
const char *source_addr) {
- int s, rv, n;
+ redisFD s;
+ int rv, n;
char _port[6]; /* strlen("65535"); */
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
int blocking = (c->flags & REDIS_BLOCK);
@@ -360,7 +405,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
}
for (p = servinfo; p != NULL; p = p->ai_next) {
addrretry:
- if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
+ if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD)
continue;
c->fd = s;
@@ -401,16 +446,14 @@ addrretry:
}
/* For repeat connection */
- if (c->saddr) {
- free(c->saddr);
- }
+ free(c->saddr);
c->saddr = malloc(p->ai_addrlen);
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
c->addrlen = p->ai_addrlen;
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
if (errno == EHOSTUNREACH) {
- redisContextCloseFd(c);
+ redisNetClose(c);
continue;
} else if (errno == EINPROGRESS) {
if (blocking) {
@@ -424,7 +467,7 @@ addrretry:
if (++reuses >= REDIS_CONNECT_RETRIES) {
goto error;
} else {
- redisContextCloseFd(c);
+ redisNetClose(c);
goto addrretry;
}
} else {
@@ -471,8 +514,9 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
}
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
+#ifndef _WIN32
int blocking = (c->flags & REDIS_BLOCK);
- struct sockaddr_un sa;
+ struct sockaddr_un *sa;
long timeout_msec = -1;
if (redisCreateSocket(c,AF_UNIX) < 0)
@@ -499,9 +543,11 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
return REDIS_ERR;
- sa.sun_family = AF_UNIX;
- strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
- if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+ sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un)));
+ c->addrlen = sizeof(struct sockaddr_un);
+ sa->sun_family = AF_UNIX;
+ strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
+ if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) {
if (errno == EINPROGRESS && !blocking) {
/* This is ok. */
} else {
@@ -516,4 +562,10 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
c->flags |= REDIS_CONNECTED;
return REDIS_OK;
+#else
+ /* We currently do not support Unix sockets for Windows. */
+ /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */
+ errno = EPROTONOSUPPORT;
+ return REDIS_ERR;
+#endif /* _WIN32 */
}
diff --git a/deps/hiredis/net.h b/deps/hiredis/net.h
index a11594e68..a4393c06b 100644
--- a/deps/hiredis/net.h
+++ b/deps/hiredis/net.h
@@ -37,6 +37,10 @@
#include "hiredis.h"
+void redisNetClose(redisContext *c);
+int redisNetRead(redisContext *c, char *buf, size_t bufcap);
+int redisNetWrite(redisContext *c);
+
int redisCheckSocketError(redisContext *c);
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
diff --git a/deps/hiredis/read.c b/deps/hiredis/read.c
index cc0f3cc72..dfe196352 100644
--- a/deps/hiredis/read.c
+++ b/deps/hiredis/read.c
@@ -31,10 +31,10 @@
#include "fmacros.h"
#include <string.h>
-#include <strings.h>
#include <stdlib.h>
#ifndef _MSC_VER
#include <unistd.h>
+#include <strings.h>
#endif
#include <assert.h>
#include <errno.h>
@@ -44,6 +44,7 @@
#include "read.h"
#include "sds.h"
+#include "win32.h"
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
size_t len;
@@ -294,9 +295,9 @@ static int processLineItem(redisReader *r) {
buf[len] = '\0';
if (strcasecmp(buf,",inf") == 0) {
- d = 1.0/0.0; /* Positive infinite. */
+ d = INFINITY; /* Positive infinite. */
} else if (strcasecmp(buf,",-inf") == 0) {
- d = -1.0/0.0; /* Nevative infinite. */
+ d = -INFINITY; /* Nevative infinite. */
} else {
d = strtod((char*)buf,&eptr);
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
@@ -430,7 +431,7 @@ static int processAggregateItem(redisReader *r) {
root = (r->ridx == 0);
- if (elements < -1 || elements > INT_MAX) {
+ if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Multi-bulk length out of range");
return REDIS_ERR;
@@ -657,8 +658,11 @@ int redisReaderGetReply(redisReader *r, void **reply) {
/* Emit a reply when there is one. */
if (r->ridx == -1) {
- if (reply != NULL)
+ if (reply != NULL) {
*reply = r->reply;
+ } else if (r->reply != NULL && r->fn && r->fn->freeObject) {
+ r->fn->freeObject(r->reply);
+ }
r->reply = NULL;
}
return REDIS_OK;
diff --git a/deps/hiredis/read.h b/deps/hiredis/read.h
index f3d075843..af02aaf68 100644
--- a/deps/hiredis/read.h
+++ b/deps/hiredis/read.h
@@ -45,6 +45,7 @@
#define REDIS_ERR_EOF 3 /* End of file */
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
#define REDIS_ERR_OOM 5 /* Out of memory */
+#define REDIS_ERR_TIMEOUT 6 /* Timed out */
#define REDIS_ERR_OTHER 2 /* Everything else... */
#define REDIS_REPLY_STRING 1
@@ -79,7 +80,7 @@ typedef struct redisReadTask {
typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t);
- void *(*createArray)(const redisReadTask*, int);
+ void *(*createArray)(const redisReadTask*, size_t);
void *(*createInteger)(const redisReadTask*, long long);
void *(*createDouble)(const redisReadTask*, double, char*, size_t);
void *(*createNil)(const redisReadTask*);
diff --git a/deps/hiredis/sds.c b/deps/hiredis/sds.c
index 44777b10c..6cf75841c 100644
--- a/deps/hiredis/sds.c
+++ b/deps/hiredis/sds.c
@@ -1035,7 +1035,7 @@ sds *sdssplitargs(const char *line, int *argc) {
s_free(vector);
return NULL;
}
-
+
vector = new_vector;
vector[*argc] = current;
(*argc)++;
diff --git a/deps/hiredis/sds.h b/deps/hiredis/sds.h
index 13be75a9f..3f9a96457 100644
--- a/deps/hiredis/sds.h
+++ b/deps/hiredis/sds.h
@@ -34,6 +34,9 @@
#define __SDS_H
#define SDS_MAX_PREALLOC (1024*1024)
+#ifdef _MSC_VER
+#define __attribute__(x)
+#endif
#include <sys/types.h>
#include <stdarg.h>
@@ -132,20 +135,20 @@ static inline void sdssetlen(sds s, size_t newlen) {
case SDS_TYPE_5:
{
unsigned char *fp = ((unsigned char*)s)-1;
- *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
+ *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));
}
break;
case SDS_TYPE_8:
- SDS_HDR(8,s)->len = newlen;
+ SDS_HDR(8,s)->len = (uint8_t)newlen;
break;
case SDS_TYPE_16:
- SDS_HDR(16,s)->len = newlen;
+ SDS_HDR(16,s)->len = (uint16_t)newlen;
break;
case SDS_TYPE_32:
- SDS_HDR(32,s)->len = newlen;
+ SDS_HDR(32,s)->len = (uint32_t)newlen;
break;
case SDS_TYPE_64:
- SDS_HDR(64,s)->len = newlen;
+ SDS_HDR(64,s)->len = (uint64_t)newlen;
break;
}
}
@@ -156,21 +159,21 @@ static inline void sdsinclen(sds s, size_t inc) {
case SDS_TYPE_5:
{
unsigned char *fp = ((unsigned char*)s)-1;
- unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
+ unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
}
break;
case SDS_TYPE_8:
- SDS_HDR(8,s)->len += inc;
+ SDS_HDR(8,s)->len += (uint8_t)inc;
break;
case SDS_TYPE_16:
- SDS_HDR(16,s)->len += inc;
+ SDS_HDR(16,s)->len += (uint16_t)inc;
break;
case SDS_TYPE_32:
- SDS_HDR(32,s)->len += inc;
+ SDS_HDR(32,s)->len += (uint32_t)inc;
break;
case SDS_TYPE_64:
- SDS_HDR(64,s)->len += inc;
+ SDS_HDR(64,s)->len += (uint64_t)inc;
break;
}
}
@@ -200,16 +203,16 @@ static inline void sdssetalloc(sds s, size_t newlen) {
/* Nothing to do, this type has no total allocation info. */
break;
case SDS_TYPE_8:
- SDS_HDR(8,s)->alloc = newlen;
+ SDS_HDR(8,s)->alloc = (uint8_t)newlen;
break;
case SDS_TYPE_16:
- SDS_HDR(16,s)->alloc = newlen;
+ SDS_HDR(16,s)->alloc = (uint16_t)newlen;
break;
case SDS_TYPE_32:
- SDS_HDR(32,s)->alloc = newlen;
+ SDS_HDR(32,s)->alloc = (uint32_t)newlen;
break;
case SDS_TYPE_64:
- SDS_HDR(64,s)->alloc = newlen;
+ SDS_HDR(64,s)->alloc = (uint64_t)newlen;
break;
}
}
diff --git a/deps/hiredis/sockcompat.c b/deps/hiredis/sockcompat.c
new file mode 100644
index 000000000..4cc2f414f
--- /dev/null
+++ b/deps/hiredis/sockcompat.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
+ *
+ * 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.
+ */
+
+#define REDIS_SOCKCOMPAT_IMPLEMENTATION
+#include "sockcompat.h"
+
+#ifdef _WIN32
+static int _wsaErrorToErrno(int err) {
+ switch (err) {
+ case WSAEWOULDBLOCK:
+ return EWOULDBLOCK;
+ case WSAEINPROGRESS:
+ return EINPROGRESS;
+ case WSAEALREADY:
+ return EALREADY;
+ case WSAENOTSOCK:
+ return ENOTSOCK;
+ case WSAEDESTADDRREQ:
+ return EDESTADDRREQ;
+ case WSAEMSGSIZE:
+ return EMSGSIZE;
+ case WSAEPROTOTYPE:
+ return EPROTOTYPE;
+ case WSAENOPROTOOPT:
+ return ENOPROTOOPT;
+ case WSAEPROTONOSUPPORT:
+ return EPROTONOSUPPORT;
+ case WSAEOPNOTSUPP:
+ return EOPNOTSUPP;
+ case WSAEAFNOSUPPORT:
+ return EAFNOSUPPORT;
+ case WSAEADDRINUSE:
+ return EADDRINUSE;
+ case WSAEADDRNOTAVAIL:
+ return EADDRNOTAVAIL;
+ case WSAENETDOWN:
+ return ENETDOWN;
+ case WSAENETUNREACH:
+ return ENETUNREACH;
+ case WSAENETRESET:
+ return ENETRESET;
+ case WSAECONNABORTED:
+ return ECONNABORTED;
+ case WSAECONNRESET:
+ return ECONNRESET;
+ case WSAENOBUFS:
+ return ENOBUFS;
+ case WSAEISCONN:
+ return EISCONN;
+ case WSAENOTCONN:
+ return ENOTCONN;
+ case WSAETIMEDOUT:
+ return ETIMEDOUT;
+ case WSAECONNREFUSED:
+ return ECONNREFUSED;
+ case WSAELOOP:
+ return ELOOP;
+ case WSAENAMETOOLONG:
+ return ENAMETOOLONG;
+ case WSAEHOSTUNREACH:
+ return EHOSTUNREACH;
+ case WSAENOTEMPTY:
+ return ENOTEMPTY;
+ default:
+ /* We just return a generic I/O error if we could not find a relevant error. */
+ return EIO;
+ }
+}
+
+static void _updateErrno(int success) {
+ errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError());
+}
+
+static int _initWinsock() {
+ static int s_initialized = 0;
+ if (!s_initialized) {
+ static WSADATA wsadata;
+ int err = WSAStartup(MAKEWORD(2,2), &wsadata);
+ if (err != 0) {
+ errno = _wsaErrorToErrno(err);
+ return 0;
+ }
+ s_initialized = 1;
+ }
+ return 1;
+}
+
+int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
+ /* Note: This function is likely to be called before other functions, so run init here. */
+ if (!_initWinsock()) {
+ return EAI_FAIL;
+ }
+
+ switch (getaddrinfo(node, service, hints, res)) {
+ case 0: return 0;
+ case WSATRY_AGAIN: return EAI_AGAIN;
+ case WSAEINVAL: return EAI_BADFLAGS;
+ case WSAEAFNOSUPPORT: return EAI_FAMILY;
+ case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY;
+ case WSAHOST_NOT_FOUND: return EAI_NONAME;
+ case WSATYPE_NOT_FOUND: return EAI_SERVICE;
+ case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE;
+ default: return EAI_FAIL; /* Including WSANO_RECOVERY */
+ }
+}
+
+const char *win32_gai_strerror(int errcode) {
+ switch (errcode) {
+ case 0: errcode = 0; break;
+ case EAI_AGAIN: errcode = WSATRY_AGAIN; break;
+ case EAI_BADFLAGS: errcode = WSAEINVAL; break;
+ case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break;
+ case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break;
+ case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break;
+ case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break;
+ case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break;
+ default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */
+ }
+ return gai_strerror(errcode);
+}
+
+void win32_freeaddrinfo(struct addrinfo *res) {
+ freeaddrinfo(res);
+}
+
+SOCKET win32_socket(int domain, int type, int protocol) {
+ SOCKET s;
+
+ /* Note: This function is likely to be called before other functions, so run init here. */
+ if (!_initWinsock()) {
+ return INVALID_SOCKET;
+ }
+
+ _updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET);
+ return s;
+}
+
+int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) {
+ int ret = ioctlsocket(fd, (long)request, argp);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
+ int ret = bind(sockfd, addr, addrlen);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
+ int ret = connect(sockfd, addr, addrlen);
+ _updateErrno(ret != SOCKET_ERROR);
+
+ /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as
+ * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX
+ * logic consistent. */
+ if (errno == EWOULDBLOCK) {
+ errno = EINPROGRESS;
+ }
+
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) {
+ int ret = 0;
+ if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
+ if (*optlen >= sizeof (struct timeval)) {
+ struct timeval *tv = optval;
+ DWORD timeout = 0;
+ socklen_t dwlen = 0;
+ ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen);
+ tv->tv_sec = timeout / 1000;
+ tv->tv_usec = (timeout * 1000) % 1000000;
+ } else {
+ ret = WSAEFAULT;
+ }
+ *optlen = sizeof (struct timeval);
+ } else {
+ ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);
+ }
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
+ int ret = 0;
+ if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
+ struct timeval *tv = optval;
+ DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
+ ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
+ } else {
+ ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen);
+ }
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_close(SOCKET fd) {
+ int ret = closesocket(fd);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) {
+ int ret = recv(sockfd, (char*)buf, (int)len, flags);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) {
+ int ret = send(sockfd, (const char*)buf, (int)len, flags);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
+ int ret = WSAPoll(fds, nfds, timeout);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+#endif /* _WIN32 */
diff --git a/deps/hiredis/sockcompat.h b/deps/hiredis/sockcompat.h
new file mode 100644
index 000000000..56006c163
--- /dev/null
+++ b/deps/hiredis/sockcompat.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
+ *
+ * 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.
+ */
+
+#ifndef __SOCKCOMPAT_H
+#define __SOCKCOMPAT_H
+
+#ifndef _WIN32
+/* For POSIX systems we use the standard BSD socket API. */
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <poll.h>
+#else
+/* For Windows we use winsock. */
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <stddef.h>
+
+#ifdef _MSC_VER
+typedef signed long ssize_t;
+#endif
+
+/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */
+int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
+const char *win32_gai_strerror(int errcode);
+void win32_freeaddrinfo(struct addrinfo *res);
+SOCKET win32_socket(int domain, int type, int protocol);
+int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp);
+int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
+int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
+int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen);
+int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen);
+int win32_close(SOCKET fd);
+ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags);
+ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);
+typedef ULONG nfds_t;
+int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);
+
+#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION
+#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)
+#undef gai_strerror
+#define gai_strerror(errcode) win32_gai_strerror(errcode)
+#define freeaddrinfo(res) win32_freeaddrinfo(res)
+#define socket(domain, type, protocol) win32_socket(domain, type, protocol)
+#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp)
+#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen)
+#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen)
+#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen)
+#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen)
+#define close(fd) win32_close(fd)
+#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags)
+#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags)
+#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout)
+#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */
+#endif /* _WIN32 */
+
+#endif /* __SOCKCOMPAT_H */
diff --git a/deps/hiredis/ssl.c b/deps/hiredis/ssl.c
new file mode 100644
index 000000000..78ab9e43e
--- /dev/null
+++ b/deps/hiredis/ssl.c
@@ -0,0 +1,448 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * 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 "hiredis.h"
+#include "async.h"
+
+#include <assert.h>
+#include <pthread.h>
+#include <errno.h>
+#include <string.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include "async_private.h"
+
+void __redisSetError(redisContext *c, int type, const char *str);
+
+/* The SSL context is attached to SSL/TLS connections as a privdata. */
+typedef struct redisSSLContext {
+ /**
+ * OpenSSL SSL_CTX; It is optional and will not be set when using
+ * user-supplied SSL.
+ */
+ SSL_CTX *ssl_ctx;
+
+ /**
+ * OpenSSL SSL object.
+ */
+ SSL *ssl;
+
+ /**
+ * SSL_write() requires to be called again with the same arguments it was
+ * previously called with in the event of an SSL_read/SSL_write situation
+ */
+ size_t lastLen;
+
+ /** Whether the SSL layer requires read (possibly before a write) */
+ int wantRead;
+
+ /**
+ * Whether a write was requested prior to a read. If set, the write()
+ * should resume whenever a read takes place, if possible
+ */
+ int pendingWrite;
+} redisSSLContext;
+
+/* Forward declaration */
+redisContextFuncs redisContextSSLFuncs;
+
+#ifdef HIREDIS_SSL_TRACE
+/**
+ * Callback used for debugging
+ */
+static void sslLogCallback(const SSL *ssl, int where, int ret) {
+ const char *retstr = "";
+ int should_log = 1;
+ /* Ignore low-level SSL stuff */
+
+ if (where & SSL_CB_ALERT) {
+ should_log = 1;
+ }
+ if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) {
+ should_log = 1;
+ }
+ if ((where & SSL_CB_EXIT) && ret == 0) {
+ should_log = 1;
+ }
+
+ if (!should_log) {
+ return;
+ }
+
+ retstr = SSL_alert_type_string(ret);
+ printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr);
+
+ if (where == SSL_CB_HANDSHAKE_DONE) {
+ printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl));
+ }
+}
+#endif
+
+/**
+ * OpenSSL global initialization and locking handling callbacks.
+ * Note that this is only required for OpenSSL < 1.1.0.
+ */
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#define HIREDIS_USE_CRYPTO_LOCKS
+#endif
+
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
+typedef pthread_mutex_t sslLockType;
+static void sslLockInit(sslLockType *l) {
+ pthread_mutex_init(l, NULL);
+}
+static void sslLockAcquire(sslLockType *l) {
+ pthread_mutex_lock(l);
+}
+static void sslLockRelease(sslLockType *l) {
+ pthread_mutex_unlock(l);
+}
+static pthread_mutex_t *ossl_locks;
+
+static void opensslDoLock(int mode, int lkid, const char *f, int line) {
+ sslLockType *l = ossl_locks + lkid;
+
+ if (mode & CRYPTO_LOCK) {
+ sslLockAcquire(l);
+ } else {
+ sslLockRelease(l);
+ }
+
+ (void)f;
+ (void)line;
+}
+
+static void initOpensslLocks(void) {
+ unsigned ii, nlocks;
+ if (CRYPTO_get_locking_callback() != NULL) {
+ /* Someone already set the callback before us. Don't destroy it! */
+ return;
+ }
+ nlocks = CRYPTO_num_locks();
+ ossl_locks = malloc(sizeof(*ossl_locks) * nlocks);
+ for (ii = 0; ii < nlocks; ii++) {
+ sslLockInit(ossl_locks + ii);
+ }
+ CRYPTO_set_locking_callback(opensslDoLock);
+}
+#endif /* HIREDIS_USE_CRYPTO_LOCKS */
+
+/**
+ * SSL Connection initialization.
+ */
+
+static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
+ if (c->privdata) {
+ __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
+ return REDIS_ERR;
+ }
+ c->privdata = calloc(1, sizeof(redisSSLContext));
+
+ c->funcs = &redisContextSSLFuncs;
+ redisSSLContext *rssl = c->privdata;
+
+ rssl->ssl_ctx = ssl_ctx;
+ rssl->ssl = ssl;
+
+ SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ SSL_set_fd(rssl->ssl, c->fd);
+ SSL_set_connect_state(rssl->ssl);
+
+ ERR_clear_error();
+ int rv = SSL_connect(rssl->ssl);
+ if (rv == 1) {
+ return REDIS_OK;
+ }
+
+ rv = SSL_get_error(rssl->ssl, rv);
+ if (((c->flags & REDIS_BLOCK) == 0) &&
+ (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
+ return REDIS_OK;
+ }
+
+ if (c->err == 0) {
+ char err[512];
+ if (rv == SSL_ERROR_SYSCALL)
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
+ else {
+ unsigned long e = ERR_peek_last_error();
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
+ ERR_reason_error_string(e));
+ }
+ __redisSetError(c, REDIS_ERR_IO, err);
+ }
+ return REDIS_ERR;
+}
+
+int redisInitiateSSL(redisContext *c, SSL *ssl) {
+ return redisSSLConnect(c, NULL, ssl);
+}
+
+int redisSecureConnection(redisContext *c, const char *capath,
+ const char *certpath, const char *keypath, const char *servername) {
+
+ SSL_CTX *ssl_ctx = NULL;
+ SSL *ssl = NULL;
+
+ /* Initialize global OpenSSL stuff */
+ static int isInit = 0;
+ if (!isInit) {
+ isInit = 1;
+ SSL_library_init();
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
+ initOpensslLocks();
+#endif
+ }
+
+ ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+ if (!ssl_ctx) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX");
+ goto error;
+ }
+
+#ifdef HIREDIS_SSL_TRACE
+ SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback);
+#endif
+ 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 ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together");
+ goto error;
+ }
+
+ if (capath) {
+ if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate");
+ goto error;
+ }
+ }
+ if (certpath) {
+ if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate");
+ goto error;
+ }
+ if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Invalid client key");
+ goto error;
+ }
+ }
+
+ ssl = SSL_new(ssl_ctx);
+ if (!ssl) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
+ goto error;
+ }
+ if (servername) {
+ if (!SSL_set_tlsext_host_name(ssl, servername)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication");
+ goto error;
+ }
+ }
+
+ return redisSSLConnect(c, ssl_ctx, ssl);
+
+error:
+ if (ssl) SSL_free(ssl);
+ if (ssl_ctx) SSL_CTX_free(ssl_ctx);
+ return REDIS_ERR;
+}
+
+static int maybeCheckWant(redisSSLContext *rssl, int rv) {
+ /**
+ * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
+ * and true is returned. False is returned otherwise
+ */
+ if (rv == SSL_ERROR_WANT_READ) {
+ rssl->wantRead = 1;
+ return 1;
+ } else if (rv == SSL_ERROR_WANT_WRITE) {
+ rssl->pendingWrite = 1;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * Implementation of redisContextFuncs for SSL connections.
+ */
+
+static void redisSSLFreeContext(void *privdata){
+ redisSSLContext *rsc = privdata;
+
+ if (!rsc) return;
+ if (rsc->ssl) {
+ SSL_free(rsc->ssl);
+ rsc->ssl = NULL;
+ }
+ if (rsc->ssl_ctx) {
+ SSL_CTX_free(rsc->ssl_ctx);
+ rsc->ssl_ctx = NULL;
+ }
+ free(rsc);
+}
+
+static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
+ redisSSLContext *rssl = c->privdata;
+
+ int nread = SSL_read(rssl->ssl, buf, bufcap);
+ if (nread > 0) {
+ return nread;
+ } else if (nread == 0) {
+ __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
+ return -1;
+ } else {
+ int err = SSL_get_error(rssl->ssl, nread);
+ if (c->flags & REDIS_BLOCK) {
+ /**
+ * In blocking mode, we should never end up in a situation where
+ * we get an error without it being an actual error, except
+ * in the case of EINTR, which can be spuriously received from
+ * debuggers or whatever.
+ */
+ if (errno == EINTR) {
+ return 0;
+ } else {
+ const char *msg = NULL;
+ if (errno == EAGAIN) {
+ msg = "Resource temporarily unavailable";
+ }
+ __redisSetError(c, REDIS_ERR_IO, msg);
+ return -1;
+ }
+ }
+
+ /**
+ * We can very well get an EWOULDBLOCK/EAGAIN, however
+ */
+ if (maybeCheckWant(rssl, err)) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+}
+
+static int redisSSLWrite(redisContext *c) {
+ redisSSLContext *rssl = c->privdata;
+
+ size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
+ int rv = SSL_write(rssl->ssl, c->obuf, len);
+
+ if (rv > 0) {
+ rssl->lastLen = 0;
+ } else if (rv < 0) {
+ rssl->lastLen = len;
+
+ int err = SSL_get_error(rssl->ssl, rv);
+ if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+ return rv;
+}
+
+static void redisSSLAsyncRead(redisAsyncContext *ac) {
+ int rv;
+ redisSSLContext *rssl = ac->c.privdata;
+ redisContext *c = &ac->c;
+
+ rssl->wantRead = 0;
+
+ if (rssl->pendingWrite) {
+ int done;
+
+ /* This is probably just a write event */
+ rssl->pendingWrite = 0;
+ rv = redisBufferWrite(c, &done);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ return;
+ } else if (!done) {
+ _EL_ADD_WRITE(ac);
+ }
+ }
+
+ rv = redisBufferRead(c);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ _EL_ADD_READ(ac);
+ redisProcessCallbacks(ac);
+ }
+}
+
+static void redisSSLAsyncWrite(redisAsyncContext *ac) {
+ int rv, done = 0;
+ redisSSLContext *rssl = ac->c.privdata;
+ redisContext *c = &ac->c;
+
+ rssl->pendingWrite = 0;
+ rv = redisBufferWrite(c, &done);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ return;
+ }
+
+ if (!done) {
+ if (rssl->wantRead) {
+ /* Need to read-before-write */
+ rssl->pendingWrite = 1;
+ _EL_DEL_WRITE(ac);
+ } else {
+ /* No extra reads needed, just need to write more */
+ _EL_ADD_WRITE(ac);
+ }
+ } else {
+ /* Already done! */
+ _EL_DEL_WRITE(ac);
+ }
+
+ /* Always reschedule a read */
+ _EL_ADD_READ(ac);
+}
+
+redisContextFuncs redisContextSSLFuncs = {
+ .free_privdata = redisSSLFreeContext,
+ .async_read = redisSSLAsyncRead,
+ .async_write = redisSSLAsyncWrite,
+ .read = redisSSLRead,
+ .write = redisSSLWrite
+};
+
diff --git a/deps/hiredis/test.c b/deps/hiredis/test.c
index 79cff4308..8668e1856 100644
--- a/deps/hiredis/test.c
+++ b/deps/hiredis/test.c
@@ -13,12 +13,16 @@
#include <limits.h>
#include "hiredis.h"
+#ifdef HIREDIS_TEST_SSL
+#include "hiredis_ssl.h"
+#endif
#include "net.h"
enum connection_type {
CONN_TCP,
CONN_UNIX,
- CONN_FD
+ CONN_FD,
+ CONN_SSL
};
struct config {
@@ -33,6 +37,14 @@ struct config {
struct {
const char *path;
} unix_sock;
+
+ struct {
+ const char *host;
+ int port;
+ const char *ca_cert;
+ const char *cert;
+ const char *key;
+ } ssl;
};
/* The following lines make up our testing "framework" :) */
@@ -93,11 +105,27 @@ static int disconnect(redisContext *c, int keep_fd) {
return -1;
}
+static void do_ssl_handshake(redisContext *c, struct config config) {
+#ifdef HIREDIS_TEST_SSL
+ redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL);
+ if (c->err) {
+ printf("SSL error: %s\n", c->errstr);
+ redisFree(c);
+ exit(1);
+ }
+#else
+ (void) c;
+ (void) config;
+#endif
+}
+
static redisContext *do_connect(struct config config) {
redisContext *c = NULL;
if (config.type == CONN_TCP) {
c = redisConnect(config.tcp.host, config.tcp.port);
+ } else if (config.type == CONN_SSL) {
+ c = redisConnect(config.ssl.host, config.ssl.port);
} else if (config.type == CONN_UNIX) {
c = redisConnectUnix(config.unix_sock.path);
} else if (config.type == CONN_FD) {
@@ -121,9 +149,21 @@ static redisContext *do_connect(struct config config) {
exit(1);
}
+ if (config.type == CONN_SSL) {
+ do_ssl_handshake(c, config);
+ }
+
return select_database(c);
}
+static void do_reconnect(redisContext *c, struct config config) {
+ redisReconnect(c);
+
+ if (config.type == CONN_SSL) {
+ do_ssl_handshake(c, config);
+ }
+}
+
static void test_format_commands(void) {
char *cmd;
int len;
@@ -360,7 +400,8 @@ static void test_reply_reader(void) {
freeReplyObject(reply);
redisReaderFree(reader);
- test("Set error when array > INT_MAX: ");
+#if LLONG_MAX > SIZE_MAX
+ test("Set error when array > SIZE_MAX: ");
reader = redisReaderCreate();
redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
ret = redisReaderGetReply(reader,&reply);
@@ -369,7 +410,6 @@ static void test_reply_reader(void) {
freeReplyObject(reply);
redisReaderFree(reader);
-#if LLONG_MAX > SIZE_MAX
test("Set error when bulk > SIZE_MAX: ");
reader = redisReaderCreate();
redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
@@ -434,22 +474,23 @@ static void test_free_null(void) {
test_cond(reply == NULL);
}
+#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
static void test_blocking_connection_errors(void) {
redisContext *c;
struct addrinfo hints = {.ai_family = AF_INET};
struct addrinfo *ai_tmp = NULL;
- const char *bad_domain = "idontexist.com";
- int rv = getaddrinfo(bad_domain, "6379", &hints, &ai_tmp);
+ int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
if (rv != 0) {
// Address does *not* exist
test("Returns error when host cannot be resolved: ");
// First see if this domain name *actually* resolves to NXDOMAIN
- c = redisConnect("dontexist.com", 6379);
+ c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
test_cond(
c->err == REDIS_ERR_OTHER &&
(strcmp(c->errstr, "Name or service not known") == 0 ||
- strcmp(c->errstr, "Can't resolve: sadkfjaskfjsa.com") == 0 ||
+ strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
+ strcmp(c->errstr, "Name does not resolve") == 0 ||
strcmp(c->errstr,
"nodename nor servname provided, or not known") == 0 ||
strcmp(c->errstr, "No address associated with hostname") == 0 ||
@@ -574,7 +615,8 @@ static void test_blocking_connection_timeouts(struct config config) {
c = do_connect(config);
test("Does not return a reply when the command times out: ");
- s = write(c->fd, cmd, strlen(cmd));
+ redisAppendFormattedCommand(c, cmd, strlen(cmd));
+ s = c->funcs->write(c);
tv.tv_sec = 0;
tv.tv_usec = 10000;
redisSetTimeout(c, tv);
@@ -583,7 +625,7 @@ static void test_blocking_connection_timeouts(struct config config) {
freeReplyObject(reply);
test("Reconnect properly reconnects after a timeout: ");
- redisReconnect(c);
+ do_reconnect(c, config);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
freeReplyObject(reply);
@@ -591,7 +633,7 @@ static void test_blocking_connection_timeouts(struct config config) {
test("Reconnect properly uses owned parameters: ");
config.tcp.host = "foo";
config.unix_sock.path = "foo";
- redisReconnect(c);
+ do_reconnect(c, config);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
freeReplyObject(reply);
@@ -894,6 +936,23 @@ int main(int argc, char **argv) {
throughput = 0;
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
test_inherit_fd = 0;
+#ifdef HIREDIS_TEST_SSL
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
+ argv++; argc--;
+ cfg.ssl.port = atoi(argv[0]);
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) {
+ argv++; argc--;
+ cfg.ssl.host = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) {
+ argv++; argc--;
+ cfg.ssl.ca_cert = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) {
+ argv++; argc--;
+ cfg.ssl.cert = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) {
+ argv++; argc--;
+ cfg.ssl.key = argv[0];
+#endif
} else {
fprintf(stderr, "Invalid argument: %s\n", argv[0]);
exit(1);
@@ -922,6 +981,20 @@ int main(int argc, char **argv) {
test_blocking_io_errors(cfg);
if (throughput) test_throughput(cfg);
+#ifdef HIREDIS_TEST_SSL
+ if (cfg.ssl.port && cfg.ssl.host) {
+ printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
+ cfg.type = CONN_SSL;
+
+ test_blocking_connection(cfg);
+ test_blocking_connection_timeouts(cfg);
+ test_blocking_io_errors(cfg);
+ test_invalid_timeout_errors(cfg);
+ test_append_formatted_commands(cfg);
+ if (throughput) test_throughput(cfg);
+ }
+#endif
+
if (test_inherit_fd) {
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
cfg.type = CONN_FD;
diff --git a/deps/hiredis/test.sh b/deps/hiredis/test.sh
new file mode 100755
index 000000000..2cab9e6fb
--- /dev/null
+++ b/deps/hiredis/test.sh
@@ -0,0 +1,70 @@
+#!/bin/sh -ue
+
+REDIS_SERVER=${REDIS_SERVER:-redis-server}
+REDIS_PORT=${REDIS_PORT:-56379}
+REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
+TEST_SSL=${TEST_SSL:-0}
+SSL_TEST_ARGS=
+
+tmpdir=$(mktemp -d)
+PID_FILE=${tmpdir}/hiredis-test-redis.pid
+SOCK_FILE=${tmpdir}/hiredis-test-redis.sock
+
+if [ "$TEST_SSL" = "1" ]; then
+ SSL_CA_CERT=${tmpdir}/ca.crt
+ SSL_CA_KEY=${tmpdir}/ca.key
+ SSL_CERT=${tmpdir}/redis.crt
+ SSL_KEY=${tmpdir}/redis.key
+
+ openssl genrsa -out ${tmpdir}/ca.key 4096
+ openssl req \
+ -x509 -new -nodes -sha256 \
+ -key ${SSL_CA_KEY} \
+ -days 3650 \
+ -subj '/CN=Hiredis Test CA' \
+ -out ${SSL_CA_CERT}
+ openssl genrsa -out ${SSL_KEY} 2048
+ openssl req \
+ -new -sha256 \
+ -key ${SSL_KEY} \
+ -subj '/CN=Hiredis Test Cert' | \
+ openssl x509 \
+ -req -sha256 \
+ -CA ${SSL_CA_CERT} \
+ -CAkey ${SSL_CA_KEY} \
+ -CAserial ${tmpdir}/ca.txt \
+ -CAcreateserial \
+ -days 365 \
+ -out ${SSL_CERT}
+
+ SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}"
+fi
+
+cleanup() {
+ set +e
+ kill $(cat ${PID_FILE})
+ rm -rf ${tmpdir}
+}
+trap cleanup INT TERM EXIT
+
+cat > ${tmpdir}/redis.conf <<EOF
+daemonize yes
+pidfile ${PID_FILE}
+port ${REDIS_PORT}
+bind 127.0.0.1
+unixsocket ${SOCK_FILE}
+EOF
+
+if [ "$TEST_SSL" = "1" ]; then
+ cat >> ${tmpdir}/redis.conf <<EOF
+tls-port ${REDIS_SSL_PORT}
+tls-ca-cert-file ${SSL_CA_CERT}
+tls-cert-file ${SSL_CERT}
+tls-key-file ${SSL_KEY}
+EOF
+fi
+
+cat ${tmpdir}/redis.conf
+${REDIS_SERVER} ${tmpdir}/redis.conf
+
+${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS}
diff --git a/deps/hiredis/win32.h b/deps/hiredis/win32.h
index 1a27c18f2..04289c696 100644
--- a/deps/hiredis/win32.h
+++ b/deps/hiredis/win32.h
@@ -2,10 +2,20 @@
#define _WIN32_HELPER_INCLUDE
#ifdef _MSC_VER
+#include <winsock2.h> /* for struct timeval */
+
#ifndef inline
#define inline __inline
#endif
+#ifndef strcasecmp
+#define strcasecmp stricmp
+#endif
+
+#ifndef strncasecmp
+#define strncasecmp strnicmp
+#endif
+
#ifndef va_copy
#define va_copy(d,s) ((d) = (s))
#endif
@@ -37,6 +47,10 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...)
return count;
}
#endif
+#endif /* _MSC_VER */
-#endif
-#endif \ No newline at end of file
+#ifdef _WIN32
+#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
+#endif /* _WIN32 */
+
+#endif /* _WIN32_HELPER_INCLUDE */
diff --git a/redis.conf b/redis.conf
index 50ba823ac..2af422a93 100644
--- a/redis.conf
+++ b/redis.conf
@@ -129,6 +129,50 @@ timeout 0
# Redis default starting with Redis 3.2.1.
tcp-keepalive 300
+################################# TLS/SSL #####################################
+
+# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration
+# directive can be used to define TLS-listening ports. To enable TLS on the
+# default port, use:
+#
+# port 0
+# tls-port 6379
+
+# Configure a X.509 certificate and private key to use for authenticating the
+# server to connected clients, masters or cluster peers. These files should be
+# PEM formatted.
+#
+# tls-cert-file redis.crt tls-key-file redis.key
+
+# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
+#
+# tls-dh-params-file redis.dh
+
+# Configure a CA certificate(s) bundle to authenticate TLS/SSL clients and
+# peers.
+#
+# tls-ca-cert-file ca.crt
+
+# If TLS/SSL clients are required to authenticate using a client side
+# certificate, use this directive.
+#
+# Note: this applies to all incoming clients, including replicas.
+#
+# tls-auth-clients yes
+
+# If TLS/SSL should be used when connecting as a replica to a master, enable
+# this configuration directive:
+#
+# tls-replication yes
+
+# If TLS/SSL should be used for the Redis Cluster bus, enable this configuration
+# directive.
+#
+# NOTE: If TLS/SSL is enabled for Cluster Bus, mutual authentication is always
+# enforced.
+#
+# tls-cluster yes
+
################################# GENERAL #####################################
# By default Redis does not run as a daemon. Use 'yes' if you need it.
diff --git a/src/Makefile b/src/Makefile
index b6cc69e2f..b43b743dc 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -145,6 +145,12 @@ ifeq ($(MALLOC),jemalloc)
FINAL_LIBS := ../deps/jemalloc/lib/libjemalloc.a $(FINAL_LIBS)
endif
+ifeq ($(BUILD_TLS),yes)
+ FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS)
+ FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
+ FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a -lssl -lcrypto
+endif
+
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
@@ -164,7 +170,7 @@ endif
REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel
-REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o acl.o gopher.o tracking.o
+REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o acl.o gopher.o tracking.o connection.o tls.o
REDIS_CLI_NAME=redis-cli
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
REDIS_BENCHMARK_NAME=redis-benchmark
diff --git a/src/anet.c b/src/anet.c
index 2088f4fb1..46ea7e145 100644
--- a/src/anet.c
+++ b/src/anet.c
@@ -279,8 +279,8 @@ static int anetCreateSocket(char *err, int domain) {
#define ANET_CONNECT_NONE 0
#define ANET_CONNECT_NONBLOCK 1
#define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */
-static int anetTcpGenericConnect(char *err, char *addr, int port,
- char *source_addr, int flags)
+static int anetTcpGenericConnect(char *err, const char *addr, int port,
+ const char *source_addr, int flags)
{
int s = ANET_ERR, rv;
char portstr[6]; /* strlen("65535") + 1; */
@@ -359,31 +359,31 @@ end:
}
}
-int anetTcpConnect(char *err, char *addr, int port)
+int anetTcpConnect(char *err, const char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
}
-int anetTcpNonBlockConnect(char *err, char *addr, int port)
+int anetTcpNonBlockConnect(char *err, const char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
}
-int anetTcpNonBlockBindConnect(char *err, char *addr, int port,
- char *source_addr)
+int anetTcpNonBlockBindConnect(char *err, const char *addr, int port,
+ const char *source_addr)
{
return anetTcpGenericConnect(err,addr,port,source_addr,
ANET_CONNECT_NONBLOCK);
}
-int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port,
- char *source_addr)
+int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
+ const char *source_addr)
{
return anetTcpGenericConnect(err,addr,port,source_addr,
ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING);
}
-int anetUnixGenericConnect(char *err, char *path, int flags)
+int anetUnixGenericConnect(char *err, const char *path, int flags)
{
int s;
struct sockaddr_un sa;
@@ -411,12 +411,12 @@ int anetUnixGenericConnect(char *err, char *path, int flags)
return s;
}
-int anetUnixConnect(char *err, char *path)
+int anetUnixConnect(char *err, const char *path)
{
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
}
-int anetUnixNonBlockConnect(char *err, char *path)
+int anetUnixNonBlockConnect(char *err, const char *path)
{
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
}
diff --git a/src/anet.h b/src/anet.h
index dd735240d..23f19643c 100644
--- a/src/anet.h
+++ b/src/anet.h
@@ -49,12 +49,12 @@
#undef ip_len
#endif
-int anetTcpConnect(char *err, char *addr, int port);
-int anetTcpNonBlockConnect(char *err, char *addr, int port);
-int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr);
-int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port, char *source_addr);
-int anetUnixConnect(char *err, char *path);
-int anetUnixNonBlockConnect(char *err, char *path);
+int anetTcpConnect(char *err, const char *addr, int port);
+int anetTcpNonBlockConnect(char *err, const char *addr, int port);
+int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
+int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr);
+int anetUnixConnect(char *err, const char *path);
+int anetUnixNonBlockConnect(char *err, const char *path);
int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
diff --git a/src/aof.c b/src/aof.c
index 7237cdfbc..eed994bf2 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -653,7 +653,7 @@ struct client *createFakeClient(void) {
struct client *c = zmalloc(sizeof(*c));
selectDb(c,0);
- c->fd = -1;
+ c->conn = NULL;
c->name = NULL;
c->querybuf = sdsempty();
c->querybuf_peak = 0;
diff --git a/src/cluster.c b/src/cluster.c
index a2615fdc0..639ab1ea7 100644
--- a/src/cluster.c
+++ b/src/cluster.c
@@ -49,7 +49,7 @@ clusterNode *myself = NULL;
clusterNode *createClusterNode(char *nodename, int flags);
int clusterAddNode(clusterNode *node);
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
-void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask);
+void clusterReadHandler(connection *conn);
void clusterSendPing(clusterLink *link, int type);
void clusterSendFail(char *nodename);
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);
@@ -477,7 +477,8 @@ void clusterInit(void) {
/* Port sanity check II
* The other handshake port check is triggered too late to stop
* us from trying to use a too-high cluster port number. */
- if (server.port > (65535-CLUSTER_PORT_INCR)) {
+ int port = server.tls_cluster ? server.tls_port : server.port;
+ if (port > (65535-CLUSTER_PORT_INCR)) {
serverLog(LL_WARNING, "Redis port number too high. "
"Cluster communication port is 10,000 port "
"numbers higher than your Redis port. "
@@ -485,8 +486,7 @@ void clusterInit(void) {
"lower than 55535.");
exit(1);
}
-
- if (listenToPort(server.port+CLUSTER_PORT_INCR,
+ if (listenToPort(port+CLUSTER_PORT_INCR,
server.cfd,&server.cfd_count) == C_ERR)
{
exit(1);
@@ -508,8 +508,8 @@ void clusterInit(void) {
/* Set myself->port / cport to my listening ports, we'll just need to
* discover the IP address via MEET messages. */
- myself->port = server.port;
- myself->cport = server.port+CLUSTER_PORT_INCR;
+ myself->port = port;
+ myself->cport = port+CLUSTER_PORT_INCR;
if (server.cluster_announce_port)
myself->port = server.cluster_announce_port;
if (server.cluster_announce_bus_port)
@@ -593,7 +593,7 @@ clusterLink *createClusterLink(clusterNode *node) {
link->sndbuf = sdsempty();
link->rcvbuf = sdsempty();
link->node = node;
- link->fd = -1;
+ link->conn = NULL;
return link;
}
@@ -601,23 +601,45 @@ clusterLink *createClusterLink(clusterNode *node) {
* This function will just make sure that the original node associated
* with this link will have the 'link' field set to NULL. */
void freeClusterLink(clusterLink *link) {
- if (link->fd != -1) {
- aeDeleteFileEvent(server.el, link->fd, AE_READABLE|AE_WRITABLE);
+ if (link->conn) {
+ connClose(link->conn);
+ link->conn = NULL;
}
sdsfree(link->sndbuf);
sdsfree(link->rcvbuf);
if (link->node)
link->node->link = NULL;
- close(link->fd);
zfree(link);
}
+static void clusterConnAcceptHandler(connection *conn) {
+ clusterLink *link;
+
+ if (connGetState(conn) != CONN_STATE_CONNECTED) {
+ serverLog(LL_VERBOSE,
+ "Error accepting cluster node connection: %s", connGetLastError(conn));
+ connClose(conn);
+ return;
+ }
+
+ /* Create a link object we use to handle the connection.
+ * It gets passed to the readable handler when data is available.
+ * Initiallly the link->node pointer is set to NULL as we don't know
+ * which node is, but the right node is references once we know the
+ * node identity. */
+ link = createClusterLink(NULL);
+ link->conn = conn;
+ connSetPrivateData(conn, link);
+
+ /* Register read handler */
+ connSetReadHandler(conn, clusterReadHandler);
+}
+
#define MAX_CLUSTER_ACCEPTS_PER_CALL 1000
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd;
int max = MAX_CLUSTER_ACCEPTS_PER_CALL;
char cip[NET_IP_STR_LEN];
- clusterLink *link;
UNUSED(el);
UNUSED(mask);
UNUSED(privdata);
@@ -634,19 +656,24 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
"Error accepting cluster node: %s", server.neterr);
return;
}
- anetNonBlock(NULL,cfd);
- anetEnableTcpNoDelay(NULL,cfd);
+
+ connection *conn = server.tls_cluster ? connCreateAcceptedTLS(cfd,1) : connCreateAcceptedSocket(cfd);
+ connNonBlock(conn);
+ connEnableTcpNoDelay(conn);
/* Use non-blocking I/O for cluster messages. */
- serverLog(LL_VERBOSE,"Accepted cluster node %s:%d", cip, cport);
- /* Create a link object we use to handle the connection.
- * It gets passed to the readable handler when data is available.
- * Initiallly the link->node pointer is set to NULL as we don't know
- * which node is, but the right node is references once we know the
- * node identity. */
- link = createClusterLink(NULL);
- link->fd = cfd;
- aeCreateFileEvent(server.el,cfd,AE_READABLE,clusterReadHandler,link);
+ serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", cip, cport);
+
+ /* Accept the connection now. connAccept() may call our handler directly
+ * or schedule it for later depending on connection implementation.
+ */
+ if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) {
+ serverLog(LL_VERBOSE,
+ "Error accepting cluster node connection: %s",
+ connGetLastError(conn));
+ connClose(conn);
+ return;
+ }
}
}
@@ -1447,7 +1474,7 @@ void nodeIp2String(char *buf, clusterLink *link, char *announced_ip) {
memcpy(buf,announced_ip,NET_IP_STR_LEN);
buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */
} else {
- anetPeerToString(link->fd, buf, NET_IP_STR_LEN, NULL);
+ connPeerToString(link->conn, buf, NET_IP_STR_LEN, NULL);
}
}
@@ -1751,7 +1778,7 @@ int clusterProcessPacket(clusterLink *link) {
{
char ip[NET_IP_STR_LEN];
- if (anetSockName(link->fd,ip,sizeof(ip),NULL) != -1 &&
+ if (connSockName(link->conn,ip,sizeof(ip),NULL) != -1 &&
strcmp(ip,myself->ip))
{
memcpy(myself->ip,ip,NET_IP_STR_LEN);
@@ -2118,35 +2145,76 @@ void handleLinkIOError(clusterLink *link) {
/* Send data. This is handled using a trivial send buffer that gets
* consumed by write(). We don't try to optimize this for speed too much
* as this is a very low traffic channel. */
-void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
- clusterLink *link = (clusterLink*) privdata;
+void clusterWriteHandler(connection *conn) {
+ clusterLink *link = connGetPrivateData(conn);
ssize_t nwritten;
- UNUSED(el);
- UNUSED(mask);
- nwritten = write(fd, link->sndbuf, sdslen(link->sndbuf));
+ nwritten = connWrite(conn, link->sndbuf, sdslen(link->sndbuf));
if (nwritten <= 0) {
serverLog(LL_DEBUG,"I/O error writing to node link: %s",
- (nwritten == -1) ? strerror(errno) : "short write");
+ (nwritten == -1) ? connGetLastError(conn) : "short write");
handleLinkIOError(link);
return;
}
sdsrange(link->sndbuf,nwritten,-1);
if (sdslen(link->sndbuf) == 0)
- aeDeleteFileEvent(server.el, link->fd, AE_WRITABLE);
+ connSetWriteHandler(link->conn, NULL);
+}
+
+/* A connect handler that gets called when a connection to another node
+ * gets established.
+ */
+void clusterLinkConnectHandler(connection *conn) {
+ clusterLink *link = connGetPrivateData(conn);
+ clusterNode *node = link->node;
+
+ /* Check if connection succeeded */
+ if (connGetState(conn) != CONN_STATE_CONNECTED) {
+ serverLog(LL_VERBOSE, "Connection with Node %.40s at %s:%d failed: %s",
+ node->name, node->ip, node->cport,
+ connGetLastError(conn));
+ freeClusterLink(link);
+ return;
+ }
+
+ /* Register a read handler from now on */
+ connSetReadHandler(conn, clusterReadHandler);
+
+ /* Queue a PING in the new connection ASAP: this is crucial
+ * to avoid false positives in failure detection.
+ *
+ * If the node is flagged as MEET, we send a MEET message instead
+ * of a PING one, to force the receiver to add us in its node
+ * table. */
+ mstime_t old_ping_sent = node->ping_sent;
+ clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?
+ CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
+ if (old_ping_sent) {
+ /* If there was an active ping before the link was
+ * disconnected, we want to restore the ping time, otherwise
+ * replaced by the clusterSendPing() call. */
+ node->ping_sent = old_ping_sent;
+ }
+ /* We can clear the flag after the first packet is sent.
+ * If we'll never receive a PONG, we'll never send new packets
+ * to this node. Instead after the PONG is received and we
+ * are no longer in meet/handshake status, we want to send
+ * normal PING packets. */
+ node->flags &= ~CLUSTER_NODE_MEET;
+
+ serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d",
+ node->name, node->ip, node->cport);
}
/* Read data. Try to read the first field of the header first to check the
* full length of the packet. When a whole packet is in memory this function
* will call the function to process the packet. And so forth. */
-void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
+void clusterReadHandler(connection *conn) {
char buf[sizeof(clusterMsg)];
ssize_t nread;
clusterMsg *hdr;
- clusterLink *link = (clusterLink*) privdata;
+ clusterLink *link = connGetPrivateData(conn);
unsigned int readlen, rcvbuflen;
- UNUSED(el);
- UNUSED(mask);
while(1) { /* Read as long as there is data to read. */
rcvbuflen = sdslen(link->rcvbuf);
@@ -2174,13 +2242,13 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
if (readlen > sizeof(buf)) readlen = sizeof(buf);
}
- nread = read(fd,buf,readlen);
- if (nread == -1 && errno == EAGAIN) return; /* No more data ready. */
+ nread = connRead(conn,buf,readlen);
+ if (nread == -1 && (connGetState(conn) == CONN_STATE_CONNECTED)) return; /* No more data ready. */
if (nread <= 0) {
/* I/O error... */
serverLog(LL_DEBUG,"I/O error reading from node link: %s",
- (nread == 0) ? "connection closed" : strerror(errno));
+ (nread == 0) ? "connection closed" : connGetLastError(conn));
handleLinkIOError(link);
return;
} else {
@@ -2209,8 +2277,7 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
* from event handlers that will do stuff with the same link later. */
void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
if (sdslen(link->sndbuf) == 0 && msglen != 0)
- aeCreateFileEvent(server.el,link->fd,AE_WRITABLE|AE_BARRIER,
- clusterWriteHandler,link);
+ connSetWriteHandler(link->conn, clusterWriteHandler); /* TODO: Handle AE_BARRIER in conns */
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
@@ -2276,11 +2343,12 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
}
/* Handle cluster-announce-port as well. */
+ int port = server.tls_cluster ? server.tls_port : server.port;
int announced_port = server.cluster_announce_port ?
- server.cluster_announce_port : server.port;
+ server.cluster_announce_port : port;
int announced_cport = server.cluster_announce_bus_port ?
server.cluster_announce_bus_port :
- (server.port + CLUSTER_PORT_INCR);
+ (port + CLUSTER_PORT_INCR);
memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots));
memset(hdr->slaveof,0,CLUSTER_NAMELEN);
@@ -3383,13 +3451,11 @@ void clusterCron(void) {
}
if (node->link == NULL) {
- int fd;
- mstime_t old_ping_sent;
- clusterLink *link;
-
- fd = anetTcpNonBlockBindConnect(server.neterr, node->ip,
- node->cport, NET_FIRST_BIND_ADDR);
- if (fd == -1) {
+ clusterLink *link = createClusterLink(node);
+ link->conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
+ connSetPrivateData(link->conn, link);
+ if (connConnect(link->conn, node->ip, node->cport, NET_FIRST_BIND_ADDR,
+ clusterLinkConnectHandler) == -1) {
/* We got a synchronous error from connect before
* clusterSendPing() had a chance to be called.
* If node->ping_sent is zero, failure detection can't work,
@@ -3399,37 +3465,11 @@ void clusterCron(void) {
serverLog(LL_DEBUG, "Unable to connect to "
"Cluster Node [%s]:%d -> %s", node->ip,
node->cport, server.neterr);
+
+ freeClusterLink(link);
continue;
}
- link = createClusterLink(node);
- link->fd = fd;
node->link = link;
- aeCreateFileEvent(server.el,link->fd,AE_READABLE,
- clusterReadHandler,link);
- /* Queue a PING in the new connection ASAP: this is crucial
- * to avoid false positives in failure detection.
- *
- * If the node is flagged as MEET, we send a MEET message instead
- * of a PING one, to force the receiver to add us in its node
- * table. */
- old_ping_sent = node->ping_sent;
- clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?
- CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
- if (old_ping_sent) {
- /* If there was an active ping before the link was
- * disconnected, we want to restore the ping time, otherwise
- * replaced by the clusterSendPing() call. */
- node->ping_sent = old_ping_sent;
- }
- /* We can clear the flag after the first packet is sent.
- * If we'll never receive a PONG, we'll never send new packets
- * to this node. Instead after the PONG is received and we
- * are no longer in meet/handshake status, we want to send
- * normal PING packets. */
- node->flags &= ~CLUSTER_NODE_MEET;
-
- serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d",
- node->name, node->ip, node->cport);
}
}
dictReleaseIterator(di);
@@ -4940,7 +4980,7 @@ void restoreCommand(client *c) {
#define MIGRATE_SOCKET_CACHE_TTL 10 /* close cached sockets after 10 sec. */
typedef struct migrateCachedSocket {
- int fd;
+ connection *conn;
long last_dbid;
time_t last_use_time;
} migrateCachedSocket;
@@ -4957,7 +4997,7 @@ typedef struct migrateCachedSocket {
* should be called so that the connection will be created from scratch
* the next time. */
migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long timeout) {
- int fd;
+ connection *conn;
sds name = sdsempty();
migrateCachedSocket *cs;
@@ -4977,34 +5017,27 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
/* Too many items, drop one at random. */
dictEntry *de = dictGetRandomKey(server.migrate_cached_sockets);
cs = dictGetVal(de);
- close(cs->fd);
+ connClose(cs->conn);
zfree(cs);
dictDelete(server.migrate_cached_sockets,dictGetKey(de));
}
/* Create the socket */
- fd = anetTcpNonBlockConnect(server.neterr,c->argv[1]->ptr,
- atoi(c->argv[2]->ptr));
- if (fd == -1) {
- sdsfree(name);
- addReplyErrorFormat(c,"Can't connect to target node: %s",
- server.neterr);
- return NULL;
- }
- anetEnableTcpNoDelay(server.neterr,fd);
-
- /* Check if it connects within the specified timeout. */
- if ((aeWait(fd,AE_WRITABLE,timeout) & AE_WRITABLE) == 0) {
- sdsfree(name);
+ conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
+ if (connBlockingConnect(conn, c->argv[1]->ptr, atoi(c->argv[2]->ptr), timeout)
+ != C_OK) {
addReplySds(c,
sdsnew("-IOERR error or timeout connecting to the client\r\n"));
- close(fd);
+ connClose(conn);
+ sdsfree(name);
return NULL;
}
+ connEnableTcpNoDelay(conn);
/* Add to the cache and return it to the caller. */
cs = zmalloc(sizeof(*cs));
- cs->fd = fd;
+ cs->conn = conn;
+
cs->last_dbid = -1;
cs->last_use_time = server.unixtime;
dictAdd(server.migrate_cached_sockets,name,cs);
@@ -5025,7 +5058,7 @@ void migrateCloseSocket(robj *host, robj *port) {
return;
}
- close(cs->fd);
+ connClose(cs->conn);
zfree(cs);
dictDelete(server.migrate_cached_sockets,name);
sdsfree(name);
@@ -5039,7 +5072,7 @@ void migrateCloseTimedoutSockets(void) {
migrateCachedSocket *cs = dictGetVal(de);
if ((server.unixtime - cs->last_use_time) > MIGRATE_SOCKET_CACHE_TTL) {
- close(cs->fd);
+ connClose(cs->conn);
zfree(cs);
dictDelete(server.migrate_cached_sockets,dictGetKey(de));
}
@@ -5221,7 +5254,7 @@ try_again:
while ((towrite = sdslen(buf)-pos) > 0) {
towrite = (towrite > (64*1024) ? (64*1024) : towrite);
- nwritten = syncWrite(cs->fd,buf+pos,towrite,timeout);
+ nwritten = connSyncWrite(cs->conn,buf+pos,towrite,timeout);
if (nwritten != (signed)towrite) {
write_error = 1;
goto socket_err;
@@ -5235,11 +5268,11 @@ try_again:
char buf2[1024]; /* Restore reply. */
/* Read the AUTH reply if needed. */
- if (password && syncReadLine(cs->fd, buf0, sizeof(buf0), timeout) <= 0)
+ if (password && connSyncReadLine(cs->conn, buf0, sizeof(buf0), timeout) <= 0)
goto socket_err;
/* Read the SELECT reply if needed. */
- if (select && syncReadLine(cs->fd, buf1, sizeof(buf1), timeout) <= 0)
+ if (select && connSyncReadLine(cs->conn, buf1, sizeof(buf1), timeout) <= 0)
goto socket_err;
/* Read the RESTORE replies. */
@@ -5254,7 +5287,7 @@ try_again:
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
for (j = 0; j < num_keys; j++) {
- if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0) {
+ if (connSyncReadLine(cs->conn, buf2, sizeof(buf2), timeout) <= 0) {
socket_error = 1;
break;
}
diff --git a/src/cluster.h b/src/cluster.h
index 571b9c543..ffbb29f0d 100644
--- a/src/cluster.h
+++ b/src/cluster.h
@@ -40,7 +40,7 @@ struct clusterNode;
/* clusterLink encapsulates everything needed to talk with a remote node. */
typedef struct clusterLink {
mstime_t ctime; /* Link creation time */
- int fd; /* TCP socket file descriptor */
+ connection *conn; /* Connection to remote node */
sds sndbuf; /* Packet send buffer */
sds rcvbuf; /* Packet reception buffer */
struct clusterNode *node; /* Node related to this link if any, or NULL */
diff --git a/src/config.c b/src/config.c
index a72df2e78..456fb0226 100644
--- a/src/config.c
+++ b/src/config.c
@@ -286,6 +286,15 @@ void loadServerConfigFromString(char *config) {
if (server.port < 0 || server.port > 65535) {
err = "Invalid port"; goto loaderr;
}
+ } else if (!strcasecmp(argv[0],"tls-port") && argc == 2) {
+#ifdef USE_OPENSSL
+ server.tls_port = atoi(argv[1]);
+ if (server.port < 0 || server.port > 65535) {
+ err = "Invalid port"; goto loaderr;
+ }
+#else
+ err = "TLS not supported"; goto loaderr;
+#endif
} else if (!strcasecmp(argv[0],"tcp-backlog") && argc == 2) {
server.tcp_backlog = atoi(argv[1]);
if (server.tcp_backlog < 0) {
@@ -791,6 +800,24 @@ void loadServerConfigFromString(char *config) {
err = sentinelHandleConfiguration(argv+1,argc-1);
if (err) goto loaderr;
}
+ } else if (!strcasecmp(argv[0],"tls-cert-file") && argc == 2) {
+ zfree(server.tls_cert_file);
+ server.tls_cert_file = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-key-file") && argc == 2) {
+ zfree(server.tls_key_file);
+ server.tls_key_file = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-dh-params-file") && argc == 2) {
+ zfree(server.tls_dh_params_file);
+ server.tls_dh_params_file = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-ca-cert-file") && argc == 2) {
+ zfree(server.tls_ca_cert_file);
+ server.tls_ca_cert_file = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-cluster") && argc == 2) {
+ server.tls_cluster = yesnotoi(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-replication") && argc == 2) {
+ server.tls_replication = yesnotoi(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-auth-clients") && argc == 2) {
+ server.tls_auth_clients = yesnotoi(argv[1]);
} else {
err = "Bad directive or wrong number of arguments"; goto loaderr;
}
@@ -1234,6 +1261,45 @@ void configSetCommand(client *c) {
} config_set_enum_field(
"repl-diskless-load",server.repl_diskless_load,repl_diskless_load_enum) {
+ /* TLS fields. */
+ } config_set_special_field("tls-cert-file") {
+ if (tlsConfigure((char *) o->ptr, server.tls_key_file,
+ server.tls_dh_params_file, server.tls_ca_cert_file) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-cert-file. Check server logs.");
+ return;
+ }
+ zfree(server.tls_cert_file);
+ server.tls_cert_file = zstrdup(o->ptr);
+ } config_set_special_field("tls-key-file") {
+ if (tlsConfigure(server.tls_cert_file, (char *) o->ptr,
+ server.tls_dh_params_file, server.tls_ca_cert_file) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-key-file. Check server logs.");
+ return;
+ }
+ zfree(server.tls_key_file);
+ server.tls_key_file = zstrdup(o->ptr);
+ } config_set_special_field("tls-dh-params-file") {
+ if (tlsConfigure(server.tls_cert_file, server.tls_key_file,
+ (char *) o->ptr, server.tls_ca_cert_file) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-dh-params-file. Check server logs.");
+ return;
+ }
+ zfree(server.tls_dh_params_file);
+ server.tls_dh_params_file = zstrdup(o->ptr);
+ } config_set_special_field("tls-ca-cert-file") {
+ if (tlsConfigure(server.tls_cert_file, server.tls_key_file,
+ server.tls_dh_params_file, (char *) o->ptr) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-ca-cert-file. Check server logs.");
+ return;
+ }
+ zfree(server.tls_ca_cert_file);
+ server.tls_ca_cert_file = zstrdup(o->ptr);
+ } config_set_bool_field("tls-auth-clients", server.tls_auth_clients) {
+
/* Everyhing else is an error... */
} config_set_else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
@@ -1307,6 +1373,10 @@ void configGetCommand(client *c) {
config_get_string_field("pidfile",server.pidfile);
config_get_string_field("slave-announce-ip",server.slave_announce_ip);
config_get_string_field("replica-announce-ip",server.slave_announce_ip);
+ config_get_string_field("tls-cert-file",server.tls_cert_file);
+ config_get_string_field("tls-key-file",server.tls_key_file);
+ config_get_string_field("tls-dh-params-file",server.tls_dh_params_file);
+ config_get_string_field("tls-ca-cert-file",server.tls_ca_cert_file);
/* Numerical values */
config_get_numerical_field("maxmemory",server.maxmemory);
@@ -1354,6 +1424,7 @@ void configGetCommand(client *c) {
config_get_numerical_field("slowlog-max-len", server.slowlog_max_len);
config_get_numerical_field("tracking-table-max-fill", server.tracking_table_max_fill);
config_get_numerical_field("port",server.port);
+ config_get_numerical_field("tls-port",server.tls_port);
config_get_numerical_field("cluster-announce-port",server.cluster_announce_port);
config_get_numerical_field("cluster-announce-bus-port",server.cluster_announce_bus_port);
config_get_numerical_field("tcp-backlog",server.tcp_backlog);
@@ -1393,6 +1464,9 @@ void configGetCommand(client *c) {
}
config_get_bool_field("activedefrag", server.active_defrag_enabled);
+ config_get_bool_field("tls-cluster",server.tls_cluster);
+ config_get_bool_field("tls-replication",server.tls_replication);
+ config_get_bool_field("tls-auth-clients",server.tls_auth_clients);
/* Enum values */
config_get_enum_field("maxmemory-policy",
@@ -2113,10 +2187,13 @@ int rewriteConfig(char *path) {
}
rewriteConfigStringOption(state,"pidfile",server.pidfile,CONFIG_DEFAULT_PID_FILE);
- rewriteConfigNumericalOption(state,"port",server.port,CONFIG_DEFAULT_SERVER_PORT);
+ rewriteConfigNumericalOption(state,"tls-port",server.tls_port,CONFIG_DEFAULT_SERVER_TLS_PORT);
rewriteConfigNumericalOption(state,"cluster-announce-port",server.cluster_announce_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT);
rewriteConfigNumericalOption(state,"cluster-announce-bus-port",server.cluster_announce_bus_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_BUS_PORT);
rewriteConfigNumericalOption(state,"tcp-backlog",server.tcp_backlog,CONFIG_DEFAULT_TCP_BACKLOG);
+ rewriteConfigYesNoOption(state,"tls-cluster",server.tls_cluster,0);
+ rewriteConfigYesNoOption(state,"tls-replication",server.tls_replication,0);
+ rewriteConfigYesNoOption(state,"tls-auth-clients",server.tls_auth_clients,1);
rewriteConfigBindOption(state);
rewriteConfigStringOption(state,"unixsocket",server.unixsocket,NULL);
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,CONFIG_DEFAULT_UNIX_SOCKET_PERM);
@@ -2195,6 +2272,10 @@ int rewriteConfig(char *path) {
rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ);
rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,SUPERVISED_NONE);
rewriteConfigNumericalOption(state,"rdb-key-save-delay",server.rdb_key_save_delay,CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY);
+ rewriteConfigStringOption(state,"tls-cert-file",server.tls_cert_file,NULL);
+ rewriteConfigStringOption(state,"tls-key-file",server.tls_key_file,NULL);
+ rewriteConfigStringOption(state,"tls-dh-params-file",server.tls_dh_params_file,NULL);
+ rewriteConfigStringOption(state,"tls-ca-cert-file",server.tls_ca_cert_file,NULL);
/* Rewrite Sentinel config if in Sentinel mode. */
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
diff --git a/src/connection.c b/src/connection.c
new file mode 100644
index 000000000..62c6f3506
--- /dev/null
+++ b/src/connection.c
@@ -0,0 +1,383 @@
+/*
+ * 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"
+
+/* The connections module provides a lean abstraction of network connections
+ * to avoid direct socket and async event management across the Redis code base.
+ *
+ * It does NOT provide advanced connection features commonly found in similar
+ * libraries such as complete in/out buffer management, throttling, etc. These
+ * functions remain in networking.c.
+ *
+ * The primary goal is to allow transparent handling of TCP and TLS based
+ * connections. To do so, connections have the following properties:
+ *
+ * 1. A connection may live before its corresponding socket exists. This
+ * allows various context and configuration setting to be handled before
+ * establishing the actual connection.
+ * 2. The caller may register/unregister logical read/write handlers to be
+ * called when the connection has data to read from/can accept writes.
+ * These logical handlers may or may not correspond to actual AE events,
+ * depending on the implementation (for TCP they are; for TLS they aren't).
+ */
+
+ConnectionType CT_Socket;
+
+/* When a connection is created we must know its type already, but the
+ * underlying socket may or may not exist:
+ *
+ * - For accepted connections, it exists as we do not model the listen/accept
+ * part; So caller calls connCreateSocket() followed by connAccept().
+ * - For outgoing connections, the socket is created by the connection module
+ * itself; So caller calls connCreateSocket() followed by connConnect(),
+ * which registers a connect callback that fires on connected/error state
+ * (and after any transport level handshake was done).
+ *
+ * NOTE: An earlier version relied on connections being part of other structs
+ * and not independently allocated. This could lead to further optimizations
+ * like using container_of(), etc. However it was discontinued in favor of
+ * this approach for these reasons:
+ *
+ * 1. In some cases conns are created/handled outside the context of the
+ * containing struct, in which case it gets a bit awkward to copy them.
+ * 2. Future implementations may wish to allocate arbitrary data for the
+ * connection.
+ * 3. The container_of() approach is anyway risky because connections may
+ * be embedded in different structs, not just client.
+ */
+
+connection *connCreateSocket() {
+ connection *conn = zcalloc(sizeof(connection));
+ conn->type = &CT_Socket;
+ conn->fd = -1;
+
+ return conn;
+}
+
+/* Create a new socket-type connection that is already associated with
+ * an accepted connection.
+ *
+ * The socket is not read for I/O until connAccept() was called and
+ * invoked the connection-level accept handler.
+ */
+connection *connCreateAcceptedSocket(int fd) {
+ connection *conn = connCreateSocket();
+ conn->fd = fd;
+ conn->state = CONN_STATE_ACCEPTING;
+ return conn;
+}
+
+static int connSocketConnect(connection *conn, const char *addr, int port, const char *src_addr,
+ ConnectionCallbackFunc connect_handler) {
+ int fd = anetTcpNonBlockBestEffortBindConnect(NULL,addr,port,src_addr);
+ if (fd == -1) {
+ conn->state = CONN_STATE_ERROR;
+ conn->last_errno = errno;
+ return C_ERR;
+ }
+
+ conn->fd = fd;
+ conn->state = CONN_STATE_CONNECTING;
+
+ conn->conn_handler = connect_handler;
+ aeCreateFileEvent(server.el, conn->fd, AE_WRITABLE,
+ conn->type->ae_handler, conn);
+
+ return C_OK;
+}
+
+/* Returns true if a write handler is registered */
+int connHasWriteHandler(connection *conn) {
+ return conn->write_handler != NULL;
+}
+
+/* Returns true if a read handler is registered */
+int connHasReadHandler(connection *conn) {
+ return conn->read_handler != NULL;
+}
+
+/* Associate a private data pointer with the connection */
+void connSetPrivateData(connection *conn, void *data) {
+ conn->private_data = data;
+}
+
+/* Get the associated private data pointer */
+void *connGetPrivateData(connection *conn) {
+ return conn->private_data;
+}
+
+/* ------ Pure socket connections ------- */
+
+/* A very incomplete list of implementation-specific calls. Much of the above shall
+ * move here as we implement additional connection types.
+ */
+
+static int connSocketShutdown(connection *conn, int how) {
+ return shutdown(conn->fd, how);
+}
+
+/* Close the connection and free resources. */
+static void connSocketClose(connection *conn) {
+ if (conn->fd != -1) {
+ aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
+ aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
+ close(conn->fd);
+ conn->fd = -1;
+ }
+
+ /* If called from within a handler, schedule the close but
+ * keep the connection until the handler returns.
+ */
+ if (conn->flags & CONN_FLAG_IN_HANDLER) {
+ conn->flags |= CONN_FLAG_CLOSE_SCHEDULED;
+ return;
+ }
+
+ zfree(conn);
+}
+
+static int connSocketWrite(connection *conn, const void *data, size_t data_len) {
+ int ret = write(conn->fd, data, data_len);
+ if (!ret) {
+ conn->state = CONN_STATE_CLOSED;
+ } else if (ret < 0 && errno != EAGAIN) {
+ conn->last_errno = errno;
+ conn->state = CONN_STATE_ERROR;
+ }
+
+ return ret;
+}
+
+static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
+ int ret = read(conn->fd, buf, buf_len);
+ if (!ret) {
+ conn->state = CONN_STATE_CLOSED;
+ } else if (ret < 0 && errno != EAGAIN) {
+ conn->last_errno = errno;
+ conn->state = CONN_STATE_ERROR;
+ }
+
+ return ret;
+}
+
+static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
+ if (conn->state != CONN_STATE_ACCEPTING) return C_ERR;
+ conn->state = CONN_STATE_CONNECTED;
+ if (!callHandler(conn, accept_handler)) return C_ERR;
+ return C_OK;
+}
+
+/* Register a write handler, to be called when the connection is writable.
+ * If NULL, the existing handler is removed.
+ */
+static int connSocketSetWriteHandler(connection *conn, ConnectionCallbackFunc func) {
+ if (func == conn->write_handler) return C_OK;
+
+ conn->write_handler = func;
+ if (!conn->write_handler)
+ aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
+ else
+ if (aeCreateFileEvent(server.el,conn->fd,AE_WRITABLE,
+ conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
+ return C_OK;
+}
+
+/* Register a read handler, to be called when the connection is readable.
+ * If NULL, the existing handler is removed.
+ */
+static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
+ if (func == conn->read_handler) return C_OK;
+
+ conn->read_handler = func;
+ if (!conn->read_handler)
+ aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
+ else
+ if (aeCreateFileEvent(server.el,conn->fd,
+ AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
+ return C_OK;
+}
+
+static const char *connSocketGetLastError(connection *conn) {
+ return strerror(conn->last_errno);
+}
+
+static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask)
+{
+ UNUSED(el);
+ UNUSED(fd);
+ connection *conn = clientData;
+
+ if (conn->state == CONN_STATE_CONNECTING &&
+ (mask & AE_WRITABLE) && conn->conn_handler) {
+
+ if (connGetSocketError(conn)) {
+ conn->last_errno = errno;
+ conn->state = CONN_STATE_ERROR;
+ } else {
+ conn->state = CONN_STATE_CONNECTED;
+ }
+
+ if (!conn->write_handler) aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
+
+ if (!callHandler(conn, conn->conn_handler)) return;
+ conn->conn_handler = NULL;
+ }
+
+ /* Handle normal I/O flows */
+ if ((mask & AE_READABLE) && conn->read_handler) {
+ if (!callHandler(conn, conn->read_handler)) return;
+ }
+ if ((mask & AE_WRITABLE) && conn->write_handler) {
+ if (!callHandler(conn, conn->write_handler)) return;
+ }
+}
+
+static int connSocketBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
+ int fd = anetTcpNonBlockConnect(NULL,addr,port);
+ if (fd == -1) {
+ conn->state = CONN_STATE_ERROR;
+ conn->last_errno = errno;
+ return C_ERR;
+ }
+
+ if ((aeWait(fd, AE_WRITABLE, timeout) & AE_WRITABLE) == 0) {
+ conn->state = CONN_STATE_ERROR;
+ conn->last_errno = ETIMEDOUT;
+ }
+
+ conn->fd = fd;
+ conn->state = CONN_STATE_CONNECTED;
+ return C_OK;
+}
+
+/* Connection-based versions of syncio.c functions.
+ * NOTE: This should ideally be refactored out in favor of pure async work.
+ */
+
+static ssize_t connSocketSyncWrite(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return syncWrite(conn->fd, ptr, size, timeout);
+}
+
+static ssize_t connSocketSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return syncRead(conn->fd, ptr, size, timeout);
+}
+
+static ssize_t connSocketSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return syncReadLine(conn->fd, ptr, size, timeout);
+}
+
+
+ConnectionType CT_Socket = {
+ .ae_handler = connSocketEventHandler,
+ .close = connSocketClose,
+ .shutdown = connSocketShutdown,
+ .write = connSocketWrite,
+ .read = connSocketRead,
+ .accept = connSocketAccept,
+ .connect = connSocketConnect,
+ .set_write_handler = connSocketSetWriteHandler,
+ .set_read_handler = connSocketSetReadHandler,
+ .get_last_error = connSocketGetLastError,
+ .blocking_connect = connSocketBlockingConnect,
+ .sync_write = connSocketSyncWrite,
+ .sync_read = connSocketSyncRead,
+ .sync_readline = connSocketSyncReadLine
+};
+
+
+int connGetSocketError(connection *conn) {
+ int sockerr = 0;
+ socklen_t errlen = sizeof(sockerr);
+
+ if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
+ sockerr = errno;
+ return sockerr;
+}
+
+int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
+ return anetPeerToString(conn ? conn->fd : -1, ip, ip_len, port);
+}
+
+int connFormatPeer(connection *conn, char *buf, size_t buf_len) {
+ return anetFormatPeer(conn ? conn->fd : -1, buf, buf_len);
+}
+
+int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
+ return anetSockName(conn->fd, ip, ip_len, port);
+}
+
+int connBlock(connection *conn) {
+ if (conn->fd == -1) return C_ERR;
+ return anetBlock(NULL, conn->fd);
+}
+
+int connNonBlock(connection *conn) {
+ if (conn->fd == -1) return C_ERR;
+ return anetNonBlock(NULL, conn->fd);
+}
+
+int connEnableTcpNoDelay(connection *conn) {
+ if (conn->fd == -1) return C_ERR;
+ return anetEnableTcpNoDelay(NULL, conn->fd);
+}
+
+int connDisableTcpNoDelay(connection *conn) {
+ if (conn->fd == -1) return C_ERR;
+ return anetDisableTcpNoDelay(NULL, conn->fd);
+}
+
+int connKeepAlive(connection *conn, int interval) {
+ if (conn->fd == -1) return C_ERR;
+ return anetKeepAlive(NULL, conn->fd, interval);
+}
+
+int connSendTimeout(connection *conn, long long ms) {
+ return anetSendTimeout(NULL, conn->fd, ms);
+}
+
+int connRecvTimeout(connection *conn, long long ms) {
+ return anetRecvTimeout(NULL, conn->fd, ms);
+}
+
+int connGetState(connection *conn) {
+ return conn->state;
+}
+
+/* Return a text that describes the connection, suitable for inclusion
+ * in CLIENT LIST and similar outputs.
+ *
+ * For sockets, we always return "fd=<fdnum>" to maintain compatibility.
+ */
+const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
+ snprintf(buf, buf_len-1, "fd=%i", conn->fd);
+ return buf;
+}
+
diff --git a/src/connection.h b/src/connection.h
new file mode 100644
index 000000000..e3e844f95
--- /dev/null
+++ b/src/connection.h
@@ -0,0 +1,211 @@
+
+/*
+ * 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.
+ */
+
+#ifndef __REDIS_CONNECTION_H
+#define __REDIS_CONNECTION_H
+
+#define CONN_INFO_LEN 32
+
+struct aeEventLoop;
+typedef struct connection connection;
+
+typedef enum {
+ CONN_STATE_NONE = 0,
+ CONN_STATE_CONNECTING,
+ CONN_STATE_ACCEPTING,
+ CONN_STATE_CONNECTED,
+ CONN_STATE_CLOSED,
+ CONN_STATE_ERROR
+} ConnectionState;
+
+#define CONN_FLAG_IN_HANDLER (1<<0) /* A handler execution is in progress */
+#define CONN_FLAG_CLOSE_SCHEDULED (1<<1) /* Closed scheduled by a handler */
+
+typedef void (*ConnectionCallbackFunc)(struct connection *conn);
+
+typedef struct ConnectionType {
+ void (*ae_handler)(struct aeEventLoop *el, int fd, void *clientData, int mask);
+ int (*connect)(struct connection *conn, const char *addr, int port, const char *source_addr, ConnectionCallbackFunc connect_handler);
+ int (*write)(struct connection *conn, const void *data, size_t data_len);
+ int (*read)(struct connection *conn, void *buf, size_t buf_len);
+ int (*shutdown)(struct connection *conn, int how);
+ void (*close)(struct connection *conn);
+ int (*accept)(struct connection *conn, ConnectionCallbackFunc accept_handler);
+ int (*set_write_handler)(struct connection *conn, ConnectionCallbackFunc handler);
+ int (*set_read_handler)(struct connection *conn, ConnectionCallbackFunc handler);
+ const char *(*get_last_error)(struct connection *conn);
+ int (*blocking_connect)(struct connection *conn, const char *addr, int port, long long timeout);
+ ssize_t (*sync_write)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
+ ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
+ ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
+} ConnectionType;
+
+struct connection {
+ ConnectionType *type;
+ ConnectionState state;
+ int flags;
+ int last_errno;
+ void *private_data;
+ ConnectionCallbackFunc conn_handler;
+ ConnectionCallbackFunc write_handler;
+ ConnectionCallbackFunc read_handler;
+ int fd;
+};
+
+/* The connection module does not deal with listening and accepting sockets,
+ * so we assume we have a socket when an incoming connection is created.
+ *
+ * The fd supplied should therefore be associated with an already accept()ed
+ * socket.
+ *
+ * connAccept() may directly call accept_handler(), or return and call it
+ * at a later time. This behavior is a bit awkward but aims to reduce the need
+ * to wait for the next event loop, if no additional handshake is required.
+ */
+
+static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
+ return conn->type->accept(conn, accept_handler);
+}
+
+/* Establish a connection. The connect_handler will be called when the connection
+ * is established, or if an error has occured.
+ *
+ * The connection handler will be responsible to set up any read/write handlers
+ * as needed.
+ *
+ * If C_ERR is returned, the operation failed and the connection handler shall
+ * not be expected.
+ */
+static inline int connConnect(connection *conn, const char *addr, int port, const char *src_addr,
+ ConnectionCallbackFunc connect_handler) {
+ return conn->type->connect(conn, addr, port, src_addr, connect_handler);
+}
+
+/* Blocking connect.
+ *
+ * NOTE: This is implemented in order to simplify the transition to the abstract
+ * connections, but should probably be refactored out of cluster.c and replication.c,
+ * in favor of a pure async implementation.
+ */
+static inline int connBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
+ return conn->type->blocking_connect(conn, addr, port, timeout);
+}
+
+/* Write to connection, behaves the same as write(2).
+ *
+ * Like write(2), a short write is possible. A -1 return indicates an error.
+ *
+ * The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
+ * connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
+ */
+static inline int connWrite(connection *conn, const void *data, size_t data_len) {
+ return conn->type->write(conn, data, data_len);
+}
+
+/* Read from the connection, behaves the same as read(2).
+ *
+ * Like read(2), a short read is possible. A return value of 0 will indicate the
+ * connection was closed, and -1 will indicate an error.
+ *
+ * The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
+ * connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
+ */
+static inline int connRead(connection *conn, void *buf, size_t buf_len) {
+ return conn->type->read(conn, buf, buf_len);
+}
+
+/* Register a write handler, to be called when the connection is writable.
+ * If NULL, the existing handler is removed.
+ */
+static inline int connSetWriteHandler(connection *conn, ConnectionCallbackFunc func) {
+ return conn->type->set_write_handler(conn, func);
+}
+
+/* Register a read handler, to be called when the connection is readable.
+ * If NULL, the existing handler is removed.
+ */
+static inline int connSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
+ return conn->type->set_read_handler(conn, func);
+}
+
+static inline void connClose(connection *conn) {
+ conn->type->close(conn);
+}
+
+static inline int connShutdown(connection *conn, int how) {
+ return conn->type->shutdown(conn, how);
+}
+
+/* Returns the last error encountered by the connection, as a string. If no error,
+ * a NULL is returned.
+ */
+static inline const char *connGetLastError(connection *conn) {
+ return conn->type->get_last_error(conn);
+}
+
+static inline ssize_t connSyncWrite(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return conn->type->sync_write(conn, ptr, size, timeout);
+}
+
+static inline ssize_t connSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return conn->type->sync_read(conn, ptr, size, timeout);
+}
+
+static inline ssize_t connSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return conn->type->sync_readline(conn, ptr, size, timeout);
+}
+
+connection *connCreateSocket();
+connection *connCreateAcceptedSocket(int fd);
+
+connection *connCreateTLS();
+connection *connCreateAcceptedTLS(int fd, int require_auth);
+
+void connSetPrivateData(connection *conn, void *data);
+void *connGetPrivateData(connection *conn);
+int connGetState(connection *conn);
+int connHasWriteHandler(connection *conn);
+int connHasReadHandler(connection *conn);
+int connGetSocketError(connection *conn);
+
+/* anet-style wrappers to conns */
+int connBlock(connection *conn);
+int connNonBlock(connection *conn);
+int connEnableTcpNoDelay(connection *conn);
+int connDisableTcpNoDelay(connection *conn);
+int connKeepAlive(connection *conn, int interval);
+int connSendTimeout(connection *conn, long long ms);
+int connRecvTimeout(connection *conn, long long ms);
+int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port);
+int connFormatPeer(connection *conn, char *buf, size_t buf_len);
+int connSockName(connection *conn, char *ip, size_t ip_len, int *port);
+const char *connGetInfo(connection *conn, char *buf, size_t buf_len);
+
+#endif /* __REDIS_CONNECTION_H */
diff --git a/src/connhelpers.h b/src/connhelpers.h
new file mode 100644
index 000000000..2ceccd085
--- /dev/null
+++ b/src/connhelpers.h
@@ -0,0 +1,60 @@
+
+/*
+ * 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.
+ */
+
+#ifndef __REDIS_CONNHELPERS_H
+#define __REDIS_CONNHELPERS_H
+
+#include "connection.h"
+
+static inline void enterHandler(connection *conn) {
+ conn->flags |= CONN_FLAG_IN_HANDLER;
+}
+
+static inline int exitHandler(connection *conn) {
+ conn->flags &= ~CONN_FLAG_IN_HANDLER;
+ if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
+ connClose(conn);
+ return 0;
+ }
+ return 1;
+}
+
+static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
+ conn->flags |= CONN_FLAG_IN_HANDLER;
+ if (handler) handler(conn);
+ conn->flags &= ~CONN_FLAG_IN_HANDLER;
+ if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
+ connClose(conn);
+ return 0;
+ }
+ return 1;
+}
+
+#endif /* __REDIS_CONNHELPERS_H */
diff --git a/src/debug.c b/src/debug.c
index 1f1157d4a..a2d61d8ab 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -699,11 +699,12 @@ void _serverAssert(const char *estr, const char *file, int line) {
void _serverAssertPrintClientInfo(const client *c) {
int j;
+ char conninfo[CONN_INFO_LEN];
bugReportStart();
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
- serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long)c->flags);
- serverLog(LL_WARNING,"client->fd = %d", c->fd);
+ serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long) c->flags);
+ serverLog(LL_WARNING,"client->conn = %s", connGetInfo(c->conn, conninfo, sizeof(conninfo)));
serverLog(LL_WARNING,"client->argc = %d", c->argc);
for (j=0; j < c->argc; j++) {
char buf[128];
diff --git a/src/module.c b/src/module.c
index ab614c529..8669586f4 100644
--- a/src/module.c
+++ b/src/module.c
@@ -2764,7 +2764,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
/* Create the client and dispatch the command. */
va_start(ap, fmt);
- c = createClient(-1);
+ c = createClient(NULL);
c->user = NULL; /* Root user. */
argv = moduleCreateArgvFromUserFormat(cmdname,fmt,&argc,&flags,ap);
replicate = flags & REDISMODULE_ARGV_REPLICATE;
@@ -3681,7 +3681,7 @@ RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc
bc->disconnect_callback = NULL; /* Set by RM_SetDisconnectCallback() */
bc->free_privdata = free_privdata;
bc->privdata = NULL;
- bc->reply_client = createClient(-1);
+ bc->reply_client = createClient(NULL);
bc->reply_client->flags |= CLIENT_MODULE;
bc->dbid = c->db->id;
c->bpop.timeout = timeout_ms ? (mstime()+timeout_ms) : 0;
@@ -3922,7 +3922,7 @@ RedisModuleCtx *RM_GetThreadSafeContext(RedisModuleBlockedClient *bc) {
* access it safely from another thread, so we create a fake client here
* in order to keep things like the currently selected database and similar
* things. */
- ctx->client = createClient(-1);
+ ctx->client = createClient(NULL);
if (bc) {
selectDb(ctx->client,bc->dbid);
ctx->client->id = bc->client->id;
@@ -5113,7 +5113,7 @@ void moduleInitModulesSystem(void) {
/* Set up the keyspace notification susbscriber list and static client */
moduleKeyspaceSubscribers = listCreate();
- moduleFreeContextReusedClient = createClient(-1);
+ moduleFreeContextReusedClient = createClient(NULL);
moduleFreeContextReusedClient->flags |= CLIENT_MODULE;
moduleFreeContextReusedClient->user = NULL; /* root user. */
diff --git a/src/networking.c b/src/networking.c
index 7555ca77d..2d00b0e74 100644
--- a/src/networking.c
+++ b/src/networking.c
@@ -84,32 +84,27 @@ void linkClient(client *c) {
raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL);
}
-client *createClient(int fd) {
+client *createClient(connection *conn) {
client *c = zmalloc(sizeof(client));
- /* passing -1 as fd it is possible to create a non connected client.
+ /* passing NULL as conn it is possible to create a non connected client.
* This is useful since all the commands needs to be executed
* in the context of a client. When commands are executed in other
* contexts (for instance a Lua script) we need a non connected client. */
- if (fd != -1) {
- anetNonBlock(NULL,fd);
- anetEnableTcpNoDelay(NULL,fd);
+ if (conn) {
+ connNonBlock(conn);
+ connEnableTcpNoDelay(conn);
if (server.tcpkeepalive)
- anetKeepAlive(NULL,fd,server.tcpkeepalive);
- if (aeCreateFileEvent(server.el,fd,AE_READABLE,
- readQueryFromClient, c) == AE_ERR)
- {
- close(fd);
- zfree(c);
- return NULL;
- }
+ connKeepAlive(conn,server.tcpkeepalive);
+ connSetReadHandler(conn, readQueryFromClient);
+ connSetPrivateData(conn, c);
}
selectDb(c,0);
uint64_t client_id = ++server.next_client_id;
c->id = client_id;
c->resp = 2;
- c->fd = fd;
+ c->conn = conn;
c->name = NULL;
c->bufpos = 0;
c->qb_pos = 0;
@@ -161,7 +156,7 @@ client *createClient(int fd) {
c->client_tracking_redirection = 0;
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
- if (fd != -1) linkClient(c);
+ if (conn) linkClient(c);
initClientMultiState(c);
return c;
}
@@ -227,7 +222,7 @@ int prepareClientToWrite(client *c) {
if ((c->flags & CLIENT_MASTER) &&
!(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;
- if (c->fd <= 0) return C_ERR; /* Fake client for AOF loading. */
+ if (!c->conn) return C_ERR; /* Fake client for AOF loading. */
/* Schedule the client to write the output buffers to the socket, unless
* it should already be setup to do so (it has already pending data). */
@@ -777,28 +772,13 @@ int clientHasPendingReplies(client *c) {
return c->bufpos || listLength(c->reply);
}
-#define MAX_ACCEPTS_PER_CALL 1000
-static void acceptCommonHandler(int fd, int flags, char *ip) {
- client *c;
- if ((c = createClient(fd)) == NULL) {
- serverLog(LL_WARNING,
- "Error registering fd event for the new client: %s (fd=%d)",
- strerror(errno),fd);
- close(fd); /* May be already closed, just ignore errors */
- return;
- }
- /* If maxclient directive is set and this is one client more... close the
- * connection. Note that we create the client instead to check before
- * for this condition, since now the socket is already set in non-blocking
- * mode and we can send an error for free using the Kernel I/O */
- if (listLength(server.clients) > server.maxclients) {
- char *err = "-ERR max number of clients reached\r\n";
+void clientAcceptHandler(connection *conn) {
+ client *c = connGetPrivateData(conn);
- /* That's a best effort error message, don't check write errors */
- if (write(c->fd,err,strlen(err)) == -1) {
- /* Nothing to do, Just to avoid the warning... */
- }
- server.stat_rejected_conn++;
+ if (connGetState(conn) != CONN_STATE_CONNECTED) {
+ serverLog(LL_WARNING,
+ "Error accepting a client connection: %s",
+ connGetLastError(conn));
freeClient(c);
return;
}
@@ -810,10 +790,12 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
if (server.protected_mode &&
server.bindaddr_count == 0 &&
DefaultUser->flags & USER_FLAG_NOPASS &&
- !(flags & CLIENT_UNIX_SOCKET) &&
- ip != NULL)
+ !(c->flags & CLIENT_UNIX_SOCKET))
{
- if (strcmp(ip,"127.0.0.1") && strcmp(ip,"::1")) {
+ char cip[NET_IP_STR_LEN+1] = { 0 };
+ connPeerToString(conn, cip, sizeof(cip)-1, NULL);
+
+ if (strcmp(cip,"127.0.0.1") && strcmp(cip,"::1")) {
char *err =
"-DENIED Redis is running in protected mode because protected "
"mode is enabled, no bind address was specified, no "
@@ -835,7 +817,7 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
"4) Setup a bind address or an authentication password. "
"NOTE: You only need to do one of the above things in order for "
"the server to start accepting connections from the outside.\r\n";
- if (write(c->fd,err,strlen(err)) == -1) {
+ if (connWrite(c->conn,err,strlen(err)) == -1) {
/* Nothing to do, Just to avoid the warning... */
}
server.stat_rejected_conn++;
@@ -845,7 +827,63 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
}
server.stat_numconnections++;
+}
+
+
+#define MAX_ACCEPTS_PER_CALL 1000
+static void acceptCommonHandler(connection *conn, int flags, char *ip) {
+ client *c;
+ UNUSED(ip);
+
+ /* Admission control will happen before a client is created and connAccept()
+ * called, because we don't want to even start transport-level negotiation
+ * if rejected.
+ */
+ if (listLength(server.clients) >= server.maxclients) {
+ char *err = "-ERR max number of clients reached\r\n";
+
+ /* That's a best effort error message, don't check write errors.
+ * Note that for TLS connections, no handshake was done yet so nothing is written
+ * and the connection will just drop.
+ */
+ if (connWrite(conn,err,strlen(err)) == -1) {
+ /* Nothing to do, Just to avoid the warning... */
+ }
+ server.stat_rejected_conn++;
+ connClose(conn);
+ return;
+ }
+
+ /* Create connection and client */
+ if ((c = createClient(conn)) == NULL) {
+ char conninfo[100];
+ serverLog(LL_WARNING,
+ "Error registering fd event for the new client: %s (conn: %s)",
+ connGetLastError(conn),
+ connGetInfo(conn, conninfo, sizeof(conninfo)));
+ connClose(conn); /* May be already closed, just ignore errors */
+ return;
+ }
+
+ /* Last chance to keep flags */
c->flags |= flags;
+
+ /* Initiate accept.
+ *
+ * Note that connAccept() is free to do two things here:
+ * 1. Call clientAcceptHandler() immediately;
+ * 2. Schedule a future call to clientAcceptHandler().
+ *
+ * Because of that, we must do nothing else afterwards.
+ */
+ if (connAccept(conn, clientAcceptHandler) == C_ERR) {
+ char conninfo[100];
+ serverLog(LL_WARNING,
+ "Error accepting a client connection: %s (conn: %s)",
+ connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo)));
+ connClose(conn);
+ return;
+ }
}
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
@@ -864,7 +902,27 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
- acceptCommonHandler(cfd,0,cip);
+ acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);
+ }
+}
+
+void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
+ int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
+ char cip[NET_IP_STR_LEN];
+ UNUSED(el);
+ UNUSED(mask);
+ UNUSED(privdata);
+
+ while(max--) {
+ cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
+ if (cfd == ANET_ERR) {
+ if (errno != EWOULDBLOCK)
+ serverLog(LL_WARNING,
+ "Accepting client connection: %s", server.neterr);
+ return;
+ }
+ serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
+ acceptCommonHandler(connCreateAcceptedTLS(cfd, server.tls_auth_clients),0,cip);
}
}
@@ -883,7 +941,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
serverLog(LL_VERBOSE,"Accepted connection to %s", server.unixsocket);
- acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL);
+ acceptCommonHandler(connCreateAcceptedSocket(cfd),CLIENT_UNIX_SOCKET,NULL);
}
}
@@ -914,10 +972,10 @@ void unlinkClient(client *c) {
/* If this is marked as current client unset it. */
if (server.current_client == c) server.current_client = NULL;
- /* Certain operations must be done only if the client has an active socket.
+ /* Certain operations must be done only if the client has an active connection.
* If the client was already unlinked or if it's a "fake client" the
- * fd is already set to -1. */
- if (c->fd != -1) {
+ * conn is already set to NULL. */
+ if (c->conn) {
/* Remove from the list of active clients. */
if (c->client_list_node) {
uint64_t id = htonu64(c->id);
@@ -931,16 +989,11 @@ void unlinkClient(client *c) {
* shutdown the socket the fork will continue to write to the slave
* and the salve will only find out that it was disconnected when
* it will finish reading the rdb. */
- if ((c->flags & CLIENT_SLAVE) &&
- (c->replstate == SLAVE_STATE_WAIT_BGSAVE_END)) {
- shutdown(c->fd, SHUT_RDWR);
- }
-
- /* Unregister async I/O handlers and close the socket. */
- aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
- aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
- close(c->fd);
- c->fd = -1;
+ int need_shutdown = ((c->flags & CLIENT_SLAVE) &&
+ (c->replstate == SLAVE_STATE_WAIT_BGSAVE_END));
+ if (need_shutdown) connShutdown(c->conn, SHUT_RDWR);
+ connClose(c->conn);
+ c->conn = NULL;
}
/* Remove from the list of pending writes if needed. */
@@ -1112,19 +1165,20 @@ client *lookupClientByID(uint64_t id) {
/* Write data in output buffers to client. Return C_OK if the client
* is still valid after the call, C_ERR if it was freed because of some
- * error.
+ * error. If handler_installed is set, it will attempt to clear the
+ * write event.
*
* This function is called by threads, but always with handler_installed
* set to 0. So when handler_installed is set to 0 the function must be
* thread safe. */
-int writeToClient(int fd, client *c, int handler_installed) {
+int writeToClient(client *c, int handler_installed) {
ssize_t nwritten = 0, totwritten = 0;
size_t objlen;
clientReplyBlock *o;
while(clientHasPendingReplies(c)) {
if (c->bufpos > 0) {
- nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
+ nwritten = connWrite(c->conn,c->buf+c->sentlen,c->bufpos-c->sentlen);
if (nwritten <= 0) break;
c->sentlen += nwritten;
totwritten += nwritten;
@@ -1145,7 +1199,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
continue;
}
- nwritten = write(fd, o->buf + c->sentlen, objlen - c->sentlen);
+ nwritten = connWrite(c->conn, o->buf + c->sentlen, objlen - c->sentlen);
if (nwritten <= 0) break;
c->sentlen += nwritten;
totwritten += nwritten;
@@ -1180,11 +1234,11 @@ int writeToClient(int fd, client *c, int handler_installed) {
}
server.stat_net_output_bytes += totwritten;
if (nwritten == -1) {
- if (errno == EAGAIN) {
+ if (connGetState(c->conn) == CONN_STATE_CONNECTED) {
nwritten = 0;
} else {
serverLog(LL_VERBOSE,
- "Error writing to client: %s", strerror(errno));
+ "Error writing to client: %s", connGetLastError(c->conn));
freeClientAsync(c);
return C_ERR;
}
@@ -1202,7 +1256,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
* adDeleteFileEvent() is not thread safe: however writeToClient()
* is always called with handler_installed set to 0 from threads
* so we are fine. */
- if (handler_installed) aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
+ if (handler_installed) connSetWriteHandler(c->conn, NULL);
/* Close connection after entire reply has been sent. */
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
@@ -1214,10 +1268,9 @@ int writeToClient(int fd, client *c, int handler_installed) {
}
/* Write event handler. Just send data to the client. */
-void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
- UNUSED(el);
- UNUSED(mask);
- writeToClient(fd,privdata,1);
+void sendReplyToClient(connection *conn) {
+ client *c = connGetPrivateData(conn);
+ writeToClient(c,1);
}
/* This function is called just before entering the event loop, in the hope
@@ -1240,7 +1293,7 @@ int handleClientsWithPendingWrites(void) {
if (c->flags & CLIENT_PROTECTED) continue;
/* Try to write buffers to the client socket. */
- if (writeToClient(c->fd,c,0) == C_ERR) continue;
+ if (writeToClient(c,0) == C_ERR) continue;
/* If after the synchronous writes above we still have data to
* output to the client, we need to install the writable handler. */
@@ -1256,10 +1309,9 @@ int handleClientsWithPendingWrites(void) {
{
ae_flags |= AE_BARRIER;
}
- if (aeCreateFileEvent(server.el, c->fd, ae_flags,
- sendReplyToClient, c) == AE_ERR)
- {
- freeClientAsync(c);
+ /* TODO: Handle write barriers in connection */
+ if (connSetWriteHandler(c->conn, sendReplyToClient) == C_ERR) {
+ freeClientAsync(c);
}
}
}
@@ -1305,15 +1357,15 @@ void resetClient(client *c) {
* path, it is not really released, but only marked for later release. */
void protectClient(client *c) {
c->flags |= CLIENT_PROTECTED;
- aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
- aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
+ connSetReadHandler(c->conn,NULL);
+ connSetWriteHandler(c->conn,NULL);
}
/* This will undo the client protection done by protectClient() */
void unprotectClient(client *c) {
if (c->flags & CLIENT_PROTECTED) {
c->flags &= ~CLIENT_PROTECTED;
- aeCreateFileEvent(server.el,c->fd,AE_READABLE,readQueryFromClient,c);
+ connSetReadHandler(c->conn,readQueryFromClient);
if (clientHasPendingReplies(c)) clientInstallWriteHandler(c);
}
}
@@ -1710,12 +1762,10 @@ void processInputBufferAndReplicate(client *c) {
}
}
-void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
- client *c = (client*) privdata;
+void readQueryFromClient(connection *conn) {
+ client *c = connGetPrivateData(conn);
int nread, readlen;
size_t qblen;
- UNUSED(el);
- UNUSED(mask);
/* Check if we want to read from the client later when exiting from
* the event loop. This is the case if threaded I/O is enabled. */
@@ -1741,12 +1791,12 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
qblen = sdslen(c->querybuf);
if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);
- nread = read(fd, c->querybuf+qblen, readlen);
+ nread = connRead(c->conn, c->querybuf+qblen, readlen);
if (nread == -1) {
- if (errno == EAGAIN) {
+ if (connGetState(conn) == CONN_STATE_CONNECTED) {
return;
} else {
- serverLog(LL_VERBOSE, "Reading from client: %s",strerror(errno));
+ serverLog(LL_VERBOSE, "Reading from client: %s",connGetLastError(c->conn));
freeClientAsync(c);
return;
}
@@ -1818,7 +1868,7 @@ void genClientPeerId(client *client, char *peerid,
snprintf(peerid,peerid_len,"%s:0",server.unixsocket);
} else {
/* TCP client. */
- anetFormatPeer(client->fd,peerid,peerid_len);
+ connFormatPeer(client->conn,peerid,peerid_len);
}
}
@@ -1839,8 +1889,7 @@ char *getClientPeerId(client *c) {
/* Concatenate a string representing the state of a client in an human
* readable format, into the sds string 's'. */
sds catClientInfoString(sds s, client *client) {
- char flags[16], events[3], *p;
- int emask;
+ char flags[16], events[3], conninfo[CONN_INFO_LEN], *p;
p = flags;
if (client->flags & CLIENT_SLAVE) {
@@ -1864,16 +1913,17 @@ sds catClientInfoString(sds s, client *client) {
if (p == flags) *p++ = 'N';
*p++ = '\0';
- emask = client->fd == -1 ? 0 : aeGetFileEvents(server.el,client->fd);
p = events;
- if (emask & AE_READABLE) *p++ = 'r';
- if (emask & AE_WRITABLE) *p++ = 'w';
+ if (client->conn) {
+ if (connHasReadHandler(client->conn)) *p++ = 'r';
+ if (connHasWriteHandler(client->conn)) *p++ = 'w';
+ }
*p = '\0';
return sdscatfmt(s,
- "id=%U addr=%s fd=%i name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U obl=%U oll=%U omem=%U events=%s cmd=%s user=%s",
+ "id=%U addr=%s %s name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U obl=%U oll=%U omem=%U events=%s cmd=%s user=%s",
(unsigned long long) client->id,
getClientPeerId(client),
- client->fd,
+ connGetInfo(client->conn, conninfo, sizeof(conninfo)),
client->name ? (char*)client->name->ptr : "",
(long long)(server.unixtime - client->ctime),
(long long)(server.unixtime - client->lastinteraction),
@@ -2445,7 +2495,7 @@ int checkClientOutputBufferLimits(client *c) {
* called from contexts where the client can't be freed safely, i.e. from the
* lower level functions pushing data inside the client output buffers. */
void asyncCloseClientOnOutputBufferLimitReached(client *c) {
- if (c->fd == -1) return; /* It is unsafe to free fake clients. */
+ if (!c->conn) return; /* It is unsafe to free fake clients. */
serverAssert(c->reply_bytes < SIZE_MAX-(1024*64));
if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return;
if (checkClientOutputBufferLimits(c)) {
@@ -2468,8 +2518,7 @@ void flushSlavesOutputBuffers(void) {
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
client *slave = listNodeValue(ln);
- int events = aeGetFileEvents(server.el,slave->fd);
- int can_receive_writes = (events & AE_WRITABLE) ||
+ int can_receive_writes = connHasWriteHandler(slave->conn) ||
(slave->flags & CLIENT_PENDING_WRITE);
/* We don't want to send the pending data to the replica in a few
@@ -2491,7 +2540,7 @@ void flushSlavesOutputBuffers(void) {
!slave->repl_put_online_on_ack &&
clientHasPendingReplies(slave))
{
- writeToClient(slave->fd,slave,0);
+ writeToClient(slave,0);
}
}
}
@@ -2618,9 +2667,9 @@ void *IOThreadMain(void *myid) {
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
if (io_threads_op == IO_THREADS_OP_WRITE) {
- writeToClient(c->fd,c,0);
+ writeToClient(c,0);
} else if (io_threads_op == IO_THREADS_OP_READ) {
- readQueryFromClient(NULL,c->fd,c,0);
+ readQueryFromClient(c->conn);
} else {
serverPanic("io_threads_op value is unknown");
}
@@ -2761,8 +2810,7 @@ int handleClientsWithPendingWritesUsingThreads(void) {
/* Install the write handler if there are pending writes in some
* of the clients. */
if (clientHasPendingReplies(c) &&
- aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
- sendReplyToClient, c) == AE_ERR)
+ connSetWriteHandler(c->conn, sendReplyToClient) == AE_ERR)
{
freeClientAsync(c);
}
diff --git a/src/rdb.c b/src/rdb.c
index e4dfc46b7..3b98f57bb 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -2387,8 +2387,8 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
"Slave %s correctly received the streamed RDB file.",
replicationGetSlaveName(slave));
/* Restore the socket as non-blocking. */
- anetNonBlock(NULL,slave->fd);
- anetSendTimeout(NULL,slave->fd,0);
+ connNonBlock(slave->conn);
+ connSendTimeout(slave->conn,0);
}
}
}
@@ -2425,9 +2425,9 @@ void killRDBChild(void) {
/* Spawn an RDB child that writes the RDB to the sockets of the slaves
* that are currently in SLAVE_STATE_WAIT_BGSAVE_START state. */
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
- int *fds;
+ connection **conns;
uint64_t *clientids;
- int numfds;
+ int numconns;
listNode *ln;
listIter li;
pid_t childpid;
@@ -2445,26 +2445,26 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
/* Collect the file descriptors of the slaves we want to transfer
* the RDB to, which are i WAIT_BGSAVE_START state. */
- fds = zmalloc(sizeof(int)*listLength(server.slaves));
+ conns = zmalloc(sizeof(connection *)*listLength(server.slaves));
/* We also allocate an array of corresponding client IDs. This will
* be useful for the child process in order to build the report
* (sent via unix pipe) that will be sent to the parent. */
clientids = zmalloc(sizeof(uint64_t)*listLength(server.slaves));
- numfds = 0;
+ numconns = 0;
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
client *slave = ln->value;
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
- clientids[numfds] = slave->id;
- fds[numfds++] = slave->fd;
+ clientids[numconns] = slave->id;
+ conns[numconns++] = slave->conn;
replicationSetupSlaveForFullResync(slave,getPsyncInitialOffset());
/* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the children returns (since duped socket
* will share the O_NONBLOCK attribute with the parent). */
- anetBlock(NULL,slave->fd);
- anetSendTimeout(NULL,slave->fd,server.repl_timeout*1000);
+ connBlock(slave->conn);
+ connSendTimeout(slave->conn,server.repl_timeout*1000);
}
}
@@ -2476,8 +2476,8 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
int retval;
rio slave_sockets;
- rioInitWithFdset(&slave_sockets,fds,numfds);
- zfree(fds);
+ rioInitWithConnset(&slave_sockets,conns,numconns);
+ zfree(conns);
closeListeningSockets(0);
redisSetProcTitle("redis-rdb-to-slaves");
@@ -2513,22 +2513,22 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
* can match the report with a specific slave, and 'error' is
* set to 0 if the replication process terminated with a success
* or the error code if an error occurred. */
- void *msg = zmalloc(sizeof(uint64_t)*(1+2*numfds));
+ void *msg = zmalloc(sizeof(uint64_t)*(1+2*numconns));
uint64_t *len = msg;
uint64_t *ids = len+1;
int j, msglen;
- *len = numfds;
- for (j = 0; j < numfds; j++) {
+ *len = numconns;
+ for (j = 0; j < numconns; j++) {
*ids++ = clientids[j];
- *ids++ = slave_sockets.io.fdset.state[j];
+ *ids++ = slave_sockets.io.connset.state[j];
}
/* Write the message to the parent. If we have no good slaves or
* we are unable to transfer the message to the parent, we exit
* with an error so that the parent will abort the replication
* process with all the childre that were waiting. */
- msglen = sizeof(uint64_t)*(1+2*numfds);
+ msglen = sizeof(uint64_t)*(1+2*numconns);
if (*len == 0 ||
write(server.rdb_pipe_write_result_to_parent,msg,msglen)
!= msglen)
@@ -2538,7 +2538,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
zfree(msg);
}
zfree(clientids);
- rioFreeFdset(&slave_sockets);
+ rioFreeConnset(&slave_sockets);
exitFromChild((retval == C_OK) ? 0 : 1);
} else {
/* Parent */
@@ -2554,7 +2554,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
client *slave = ln->value;
int j;
- for (j = 0; j < numfds; j++) {
+ for (j = 0; j < numconns; j++) {
if (slave->id == clientids[j]) {
slave->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
break;
@@ -2577,7 +2577,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
updateDictResizePolicy();
}
zfree(clientids);
- zfree(fds);
+ zfree(conns);
return (childpid == -1) ? C_ERR : C_OK;
}
return C_OK; /* Unreached. */
diff --git a/src/redis-cli.c b/src/redis-cli.c
index db53fc7d8..c7145927a 100644
--- a/src/redis-cli.c
+++ b/src/redis-cli.c
@@ -47,6 +47,9 @@
#include <math.h>
#include <hiredis.h>
+#ifdef USE_OPENSSL
+#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 +191,11 @@ static struct config {
char *hostip;
int hostport;
char *hostsocket;
+ int tls;
+ char *sni;
+ char *cacert;
+ char *cert;
+ char *key;
long repeat;
long interval;
int dbnum;
@@ -751,6 +759,18 @@ 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) {
+#ifdef USE_OPENSSL
+ return redisSecureConnection(c, config.cacert, config.cert, config.key, config.sni);
+#else
+ (void) c;
+ return REDIS_OK;
+#endif
+}
+
/* Connect to the server. It is possible to pass certain flags to the function:
* CC_FORCE: The connection is performed even if there is already
* a connected socket.
@@ -767,6 +787,16 @@ static int cliConnect(int flags) {
context = redisConnectUnix(config.hostsocket);
}
+ if (!context->err && config.tls) {
+ if (cliSecureConnection(context) == REDIS_ERR && !context->err) {
+ /* TODO: this check should be redundant, redis-cli should set err=1 */
+ fprintf(stderr, "Could not negotiate a TLS connection.\n");
+ context = NULL;
+ redisFree(context);
+ return REDIS_ERR;
+ }
+ }
+
if (context->err) {
if (!(flags & CC_QUIET)) {
fprintf(stderr,"Could not connect to Redis at ");
@@ -782,6 +812,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
@@ -1245,6 +1276,9 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
redisFree(c);
c = redisConnect(config.hostip,config.hostport);
+ if (!c->err && config.tls) {
+ cliSecureConnection(c);
+ }
usleep(1000000);
}
@@ -1434,6 +1468,18 @@ 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],"--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);
@@ -1522,6 +1568,12 @@ 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"
+" --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"
@@ -1544,7 +1596,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",
+ version, 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"
@@ -1567,8 +1621,7 @@ static void usage(void) {
" line interface.\n"
" --help Output this help and exit.\n"
" --version Output version and exit.\n"
-"\n",
- version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
+"\n");
/* Using another fprintf call to avoid -Woverlength-strings compile warning */
fprintf(stderr,
"Cluster Manager Commands:\n"
@@ -2336,6 +2389,9 @@ 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) {
+ cliSecureConnection(node->context);
+ }
if (node->context->err) {
fprintf(stderr,"Could not connect to Redis at ");
fprintf(stderr,"%s:%d: %s\n", node->ip, node->port,
diff --git a/src/replication.c b/src/replication.c
index d6646c9ef..cbc52cb2e 100644
--- a/src/replication.c
+++ b/src/replication.c
@@ -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' ||
- anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1)
+ connPeerToString(c->conn,ip,sizeof(ip),NULL) != -1)
{
/* Note that the 'ip' buffer is always larger than 'c->slave_ip' */
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 (write(slave->fd,buf,buflen) != buflen) {
+ if (connWrite(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 (write(c->fd,buf,buflen) != buflen) {
+ if (connWrite(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)
- anetDisableTcpNoDelay(NULL, c->fd); /* Non critical if it fails. */
+ connDisableTcpNoDelay(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,7 +881,7 @@ 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 = write(fd,slave->replpreamble,sdslen(slave->replpreamble));
+ nwritten = connWrite(conn,slave->replpreamble,sdslen(slave->replpreamble));
if (nwritten == -1) {
serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
strerror(errno));
@@ -911,8 +908,8 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
freeClient(slave);
return;
}
- if ((nwritten = write(fd,buf,buflen)) == -1) {
- if (errno != EAGAIN) {
+ if ((nwritten = connWrite(conn,buf,buflen)) == -1) {
+ if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_WARNING,"Write error sending DB to replica: %s",
strerror(errno));
freeClient(slave);
@@ -924,7 +921,7 @@ 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);
}
}
@@ -1015,8 +1012,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 +1081,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 +1096,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;
@@ -1189,7 +1187,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;
@@ -1197,9 +1195,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. */
@@ -1210,7 +1205,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 (syncReadLine(fd,buf,1024,server.repl_syncio_timeout*1000) == -1) {
+ if (connSyncReadLine(conn,buf,1024,server.repl_syncio_timeout*1000) == -1) {
serverLog(LL_WARNING,
"I/O error reading bulk count from MASTER: %s",
strerror(errno));
@@ -1275,7 +1270,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");
@@ -1383,17 +1378,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;
- rioInitWithFd(&rdb,fd,server.repl_transfer_size);
+ rioInitWithConn(&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. */
- anetBlock(NULL,fd);
- anetRecvTimeout(NULL,fd,server.repl_timeout*1000);
+ connBlock(conn);
+ connRecvTimeout(conn, server.repl_timeout*1000);
startLoading(server.repl_transfer_size);
if (rdbLoadRio(&rdb,&rsi,0) != C_OK) {
@@ -1403,7 +1398,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
"Failed trying to load the MASTER synchronization DB "
"from socket");
cancelReplicationHandshake();
- rioFreeFd(&rdb, NULL);
+ rioFreeConn(&rdb, NULL);
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
/* Restore the backed up databases. */
disklessLoadRestoreBackups(diskless_load_backup,1,
@@ -1436,16 +1431,16 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
{
serverLog(LL_WARNING,"Replication stream EOF marker is broken");
cancelReplicationHandshake();
- rioFreeFd(&rdb, NULL);
+ rioFreeConn(&rdb, NULL);
return;
}
}
/* Cleanup and restore the socket to the original state to continue
* with the normal replication. */
- rioFreeFd(&rdb, NULL);
- anetNonBlock(NULL,fd);
- anetRecvTimeout(NULL,fd,0);
+ rioFreeConn(&rdb, NULL);
+ connNonBlock(conn);
+ connRecvTimeout(conn,0);
} else {
/* Ensure background save doesn't overwrite synced data */
if (server.rdb_child_pid != -1) {
@@ -1522,7 +1517,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
@@ -1533,7 +1528,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*);
@@ -1550,12 +1545,12 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
sdsfree(cmdargs);
/* Transfer command to the server. */
- if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
+ if (connSyncWrite(conn,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
== -1)
{
sdsfree(cmd);
return sdscatprintf(sdsempty(),"-Writing to master: %s",
- strerror(errno));
+ connGetLastError(conn));
}
sdsfree(cmd);
}
@@ -1564,7 +1559,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
if (flags & SYNC_CMD_READ) {
char buf[256];
- if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout*1000)
+ if (connSyncReadLine(conn,buf,sizeof(buf),server.repl_syncio_timeout*1000)
== -1)
{
return sdscatprintf(sdsempty(),"-Reading from master: %s",
@@ -1630,7 +1625,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;
@@ -1655,18 +1650,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. */
@@ -1674,7 +1669,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;
@@ -1748,7 +1743,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
@@ -1790,29 +1785,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);
+ connClose(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;
}
@@ -1821,18 +1810,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.
@@ -1857,13 +1847,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;
@@ -1874,7 +1864,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);
@@ -1889,7 +1879,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
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",
+ err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
"listening-port",port, NULL);
sdsfree(port);
if (err) goto write_error;
@@ -1900,7 +1890,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] == '-') {
@@ -1921,7 +1911,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);
@@ -1931,7 +1921,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] == '-') {
@@ -1949,7 +1939,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);
@@ -1959,7 +1949,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] == '-') {
@@ -1976,7 +1966,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;
}
@@ -1992,7 +1982,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
@@ -2021,7 +2011,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 (syncWrite(fd,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) {
+ if (connSyncWrite(conn,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) {
serverLog(LL_WARNING,"I/O error writing to MASTER: %s",
strerror(errno));
goto error;
@@ -2046,12 +2036,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;
}
@@ -2063,16 +2054,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;
@@ -2083,26 +2073,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;
}
@@ -2112,11 +2094,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.
@@ -2301,7 +2280,7 @@ void roleCommand(client *c) {
char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip;
if (slaveip[0] == '\0') {
- if (anetPeerToString(slave->fd,ip,sizeof(ip),NULL) == -1)
+ if (connPeerToString(slave->conn,ip,sizeof(ip),NULL) == -1)
continue;
slaveip = ip;
}
@@ -2423,7 +2402,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));
@@ -2452,10 +2431,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;
@@ -2464,8 +2444,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. */
}
@@ -2473,8 +2452,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. */
}
@@ -2844,9 +2822,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);
}
}
diff --git a/src/rio.c b/src/rio.c
index bdbc5d0e9..5e3b4a06e 100644
--- a/src/rio.c
+++ b/src/rio.c
@@ -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 rioFdWrite(rio *r, const void *buf, size_t len) {
+static size_t rioConnWrite(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 rioFdRead(rio *r, void *buf, size_t len) {
- size_t avail = sdslen(r->io.fd.buf)-r->io.fd.pos;
+static size_t rioConnRead(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 = read(r->io.fd.fd,
- (char*)r->io.fd.buf + sdslen(r->io.fd.buf),
+ int retval = connRead(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 rioFdTell(rio *r) {
- return r->io.fd.read_so_far;
+static off_t rioConnTell(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 rioFdFlush(rio *r) {
+static int rioConnFlush(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 rioFdWrite(r,NULL,0);
+ return rioConnWrite(r,NULL,0);
}
-static const rio rioFdIO = {
- rioFdRead,
- rioFdWrite,
- rioFdTell,
- rioFdFlush,
+static const rio rioConnIO = {
+ rioConnRead,
+ rioConnWrite,
+ rioConnTell,
+ rioConnFlush,
NULL, /* update_checksum */
0, /* current checksum */
0, /* flags */
@@ -249,27 +249,27 @@ 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 rioInitWithFd(rio *r, int fd, size_t read_limit) {
- *r = rioFdIO;
- 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 rioInitWithConn(rio *r, connection *conn, size_t read_limit) {
+ *r = rioConnIO;
+ 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 rioFreeFd(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 rioFreeConn(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 descriptors set implementation ------------------
@@ -294,14 +294,14 @@ static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
/* 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);
+ r->io.connset.buf = sdscatlen(r->io.connset.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 (sdslen(r->io.connset.buf) > PROTO_IOBUF_LEN) doflush = 1;
}
if (doflush) {
- p = (unsigned char*) r->io.fdset.buf;
- len = sdslen(r->io.fdset.buf);
+ p = (unsigned char*) r->io.connset.buf;
+ len = sdslen(r->io.connset.buf);
}
/* Write in little chunchs so that when there are big writes we
@@ -310,8 +310,8 @@ static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
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) {
+ for (j = 0; j < r->io.connset.numconns; j++) {
+ if (r->io.connset.state[j] != 0) {
/* Skip FDs alraedy in error. */
broken++;
continue;
@@ -321,7 +321,7 @@ static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
* of short writes. */
size_t nwritten = 0;
while(nwritten != count) {
- retval = write(r->io.fdset.fds[j],p+nwritten,count-nwritten);
+ retval = connWrite(r->io.connset.conns[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
@@ -335,17 +335,17 @@ static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
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;
+ r->io.connset.state[j] = errno;
+ if (r->io.connset.state[j] == 0) r->io.connset.state[j] = EIO;
}
}
- if (broken == r->io.fdset.numfds) return 0; /* All the FDs in error. */
+ if (broken == r->io.connset.numconns) return 0; /* All the FDs in error. */
p += count;
len -= count;
- r->io.fdset.pos += count;
+ r->io.connset.pos += count;
}
- if (doflush) sdsclear(r->io.fdset.buf);
+ if (doflush) sdsclear(r->io.connset.buf);
return 1;
}
@@ -359,7 +359,7 @@ static size_t rioFdsetRead(rio *r, void *buf, size_t len) {
/* Returns read/write position in file. */
static off_t rioFdsetTell(rio *r) {
- return r->io.fdset.pos;
+ return r->io.connset.pos;
}
/* Flushes any buffer to target device if applicable. Returns 1 on success
@@ -383,24 +383,24 @@ static const rio rioFdsetIO = {
{ { NULL, 0 } } /* union for io-specific vars */
};
-void rioInitWithFdset(rio *r, int *fds, int numfds) {
+void rioInitWithConnset(rio *r, connection **conns, int numconns) {
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();
+ r->io.connset.conns = zmalloc(sizeof(connection *)*numconns);
+ r->io.connset.state = zmalloc(sizeof(int)*numconns);
+ memcpy(r->io.connset.conns,conns,sizeof(connection *)*numconns);
+ for (j = 0; j < numconns; j++) r->io.connset.state[j] = 0;
+ r->io.connset.numconns = numconns;
+ r->io.connset.pos = 0;
+ r->io.connset.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 rioFreeConnset(rio *r) {
+ zfree(r->io.connset.conns);
+ zfree(r->io.connset.state);
+ sdsfree(r->io.connset.buf);
}
/* ---------------------------- Generic functions ---------------------------- */
diff --git a/src/rio.h b/src/rio.h
index eb7a05748..fdde7c20e 100644
--- a/src/rio.h
+++ b/src/rio.h
@@ -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,22 @@ struct _rio {
off_t buffered; /* Bytes written since last fsync. */
off_t autosync; /* fsync after 'autosync' bytes written. */
} file;
- /* file descriptor */
+ /* Connection object */
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;
+ } conn;
/* Multiple FDs target (used to write to N sockets). */
struct {
- int *fds; /* File descriptors. */
- int *state; /* Error state of each fd. 0 (if ok) or errno. */
- int numfds;
+ connection **conns; /* Connections */
+ int *state; /* Error state of each fd. 0 (if ok) or errno. */
+ int numconns;
off_t pos;
sds buf;
- } fdset;
+ } connset;
} io;
};
@@ -159,11 +160,11 @@ static inline void rioClearErrors(rio *r) {
void rioInitWithFile(rio *r, FILE *fp);
void rioInitWithBuffer(rio *r, sds s);
-void rioInitWithFd(rio *r, int fd, size_t read_limit);
-void rioInitWithFdset(rio *r, int *fds, int numfds);
+void rioInitWithConn(rio *r, connection *conn, size_t read_limit);
+void rioInitWithConnset(rio *r, connection **conns, int numconns);
-void rioFreeFdset(rio *r);
-void rioFreeFd(rio *r, sds* out_remainingBufferedData);
+void rioFreeConnset(rio *r);
+void rioFreeConn(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);
diff --git a/src/scripting.c b/src/scripting.c
index 032bfdf10..6c1b89dbe 100644
--- a/src/scripting.c
+++ b/src/scripting.c
@@ -58,7 +58,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. */
@@ -1109,7 +1109,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;
}
@@ -1593,7 +1593,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);
@@ -1615,7 +1615,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;
@@ -1670,7 +1670,7 @@ void ldbSendLogs(void) {
proto = sdscatlen(proto,"\r\n",2);
listDelNode(ldb.logs,ln);
}
- if (write(ldb.fd,proto,sdslen(proto)) == -1) {
+ if (connWrite(ldb.conn,proto,sdslen(proto)) == -1) {
/* Avoid warning. We don't check the return value of write()
* since the next read() will catch the I/O error and will
* close the debugging session. */
@@ -1723,8 +1723,8 @@ int ldbStartSession(client *c) {
}
/* Setup our debugging session. */
- anetBlock(NULL,ldb.fd);
- anetSendTimeout(NULL,ldb.fd,5000);
+ connBlock(ldb.conn);
+ connSendTimeout(ldb.conn,5000);
ldb.active = 1;
/* First argument of EVAL is the script itself. We split it into different
@@ -1751,7 +1751,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 {
@@ -1760,8 +1760,8 @@ void ldbEndSession(client *c) {
}
/* Otherwise let's restore client's state. */
- anetNonBlock(NULL,ldb.fd);
- anetSendTimeout(NULL,ldb.fd,0);
+ connNonBlock(ldb.conn);
+ connSendTimeout(ldb.conn,0);
/* Close the client connectin after sending the final EVAL reply
* in order to signal the end of the debugging session. */
@@ -2332,7 +2332,7 @@ int ldbRepl(lua_State *lua) {
while(1) {
while((argv = ldbReplParseCommand(&argc)) == NULL) {
char buf[1024];
- int nread = read(ldb.fd,buf,sizeof(buf));
+ int nread = connRead(ldb.conn,buf,sizeof(buf));
if (nread <= 0) {
/* Make sure the script runs without user input since the
* client is no longer connected. */
diff --git a/src/sentinel.c b/src/sentinel.c
index 92ea75436..95f189485 100644
--- a/src/sentinel.c
+++ b/src/sentinel.c
@@ -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) link;
+#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);
diff --git a/src/server.c b/src/server.c
index fb308e5f8..930a01723 100644
--- a/src/server.c
+++ b/src/server.c
@@ -2229,11 +2229,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;
@@ -2349,7 +2351,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;
@@ -2430,6 +2432,9 @@ void initServerConfig(void) {
* script to the slave / AOF. This is the new way starting from
* Redis 5. However it is possible to revert it via redis.conf. */
server.lua_always_replicate_commands = 1;
+
+ /* TLS */
+ server.tls_auth_clients = 1;
}
extern char **environ;
@@ -2746,6 +2751,11 @@ void initServer(void) {
server.clients_paused = 0;
server.system_memory_size = zmalloc_get_memory_size();
+ if (server.tls_port && tlsConfigureServer() == 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);
@@ -2761,6 +2771,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) {
@@ -2775,7 +2788,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);
}
@@ -2845,6 +2858,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.");
@@ -3536,6 +3557,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]);
@@ -4274,7 +4296,7 @@ sds genRedisInfoString(char *section) {
long lag = 0;
if (slaveip[0] == '\0') {
- if (anetPeerToString(slave->fd,ip,sizeof(ip),&port) == -1)
+ if (connPeerToString(slave->conn,ip,sizeof(ip),&port) == -1)
continue;
slaveip = ip;
}
@@ -4516,7 +4538,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);
@@ -4642,7 +4664,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);
@@ -4793,6 +4815,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. */
@@ -4925,7 +4948,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);
diff --git a/src/server.h b/src/server.h
index 31d253066..1b41c9ac5 100644
--- a/src/server.h
+++ b/src/server.h
@@ -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
@@ -818,7 +820,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. */
@@ -1078,6 +1080,7 @@ struct redisServer {
to be processed. */
/* 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[] */
@@ -1085,6 +1088,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[] */
@@ -1287,7 +1292,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 */
@@ -1410,6 +1415,14 @@ 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;
+ char *tls_cert_file;
+ char *tls_key_file;
+ char *tls_dh_params_file;
+ char *tls_ca_cert_file;
+ int tls_auth_clients;
};
typedef struct pubsubPattern {
@@ -1553,12 +1566,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);
@@ -1570,8 +1583,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);
@@ -1629,7 +1643,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);
@@ -2343,6 +2357,12 @@ 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 tlsConfigureServer(void);
+int tlsConfigure(const char *cert_file, const char *key_file,
+ const char *dh_params_file, const char *ca_cert_file);
+
#define redisDebug(fmt, ...) \
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define redisDebugMark() \
diff --git a/src/tls.c b/src/tls.c
new file mode 100644
index 000000000..dabb2ee0f
--- /dev/null
+++ b/src/tls.c
@@ -0,0 +1,669 @@
+/*
+ * 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"
+
+#ifdef USE_OPENSSL
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+
+extern ConnectionType CT_Socket;
+
+SSL_CTX *redis_tls_ctx;
+
+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.");
+ }
+}
+
+int tlsConfigureServer(void) {
+ return tlsConfigure(server.tls_cert_file, server.tls_key_file,
+ server.tls_dh_params_file, server.tls_ca_cert_file);
+}
+
+/* Attempt to configure/reconfigure TLS. This operation is atomic and will
+ * leave the SSL_CTX unchanged if fails.
+ */
+int tlsConfigure(const char *cert_file, const char *key_file,
+ const char *dh_params_file, const char *ca_cert_file) {
+
+ char errbuf[256];
+ SSL_CTX *ctx = NULL;
+
+ if (!cert_file) {
+ serverLog(LL_WARNING, "No tls-cert-file configured!");
+ goto error;
+ }
+
+ if (!key_file) {
+ serverLog(LL_WARNING, "No tls-key-file configured!");
+ goto error;
+ }
+
+ if (!ca_cert_file) {
+ serverLog(LL_WARNING, "No tls-ca-cert-file configured!");
+ goto error;
+ }
+
+ ctx = SSL_CTX_new(TLS_method());
+ 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, 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", cert_file, errbuf);
+ goto error;
+ }
+
+ if (SSL_CTX_use_PrivateKey_file(ctx, 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", key_file, errbuf);
+ goto error;
+ }
+
+ if (SSL_CTX_load_verify_locations(ctx, ca_cert_file, NULL) <= 0) {
+ ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
+ serverLog(LL_WARNING, "Failed to load CA certificate(s) file: %s: %s", ca_cert_file, errbuf);
+ goto error;
+ }
+
+ if (dh_params_file) {
+ FILE *dhfile = fopen(dh_params_file, "r");
+ DH *dh = NULL;
+ if (!dhfile) {
+ serverLog(LL_WARNING, "Failed to load %s: %s", 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.", 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", 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;
+} 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 tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask) {
+ UNUSED(el);
+ UNUSED(fd);
+ tls_connection *conn = clientData;
+ 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:
+ if ((mask & AE_READABLE) && (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ)) {
+ conn->flags &= ~TLS_CONN_FLAG_WRITE_WANT_READ;
+ if (!callHandler((connection *) conn, conn->c.write_handler)) return;
+ }
+
+ if ((mask & AE_WRITABLE) && (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE)) {
+ conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE;
+ if (!callHandler((connection *) conn, conn->c.read_handler)) return;
+ }
+
+ if ((mask & AE_READABLE) && conn->c.read_handler) {
+ if (!callHandler((connection *) conn, conn->c.read_handler)) return;
+ }
+
+ if ((mask & AE_WRITABLE) && conn->c.write_handler) {
+ if (!callHandler((connection *) conn, conn->c.write_handler)) return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ updateSSLEvent(conn);
+}
+
+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;
+ }
+
+ 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) {
+ conn->write_handler = func;
+ 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;
+}
+
+/* TODO: This is probably not the right thing to do, but as we handle proxying from child
+ * processes we'll probably not need any shutdown mechanism anyway so this is just a
+ * place holder for now.
+ */
+static int connTLSShutdown(connection *conn_, int how) {
+ UNUSED(how);
+ tls_connection *conn = (tls_connection *) conn_;
+
+ return SSL_shutdown(conn->ssl);
+}
+
+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,
+ .shutdown = connTLSShutdown
+};
+
+#else /* USE_OPENSSL */
+
+void tlsInit(void) {
+}
+
+int tlsConfigure(const char *cert_file, const char *key_file,
+ const char *dh_params_file, const char *ca_cert_file) {
+ UNUSED(cert_file);
+ UNUSED(key_file);
+ UNUSED(dh_params_file);
+ UNUSED(ca_cert_file);
+ return C_OK;
+}
+
+int tlsConfigureServer(void) {
+ return C_OK;
+}
+
+connection *connCreateTLS(void) {
+ return NULL;
+}
+
+connection *connCreateAcceptedTLS(int fd, int require_auth) {
+ UNUSED(fd);
+
+ return NULL;
+}
+
+#endif
diff --git a/tests/cluster/run.tcl b/tests/cluster/run.tcl
index 93603ddc9..d9a7d7ee5 100644
--- a/tests/cluster/run.tcl
+++ b/tests/cluster/run.tcl
@@ -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
diff --git a/tests/cluster/tests/04-resharding.tcl b/tests/cluster/tests/04-resharding.tcl
index 68fba135e..33f861dc5 100644
--- a/tests/cluster/tests/04-resharding.tcl
+++ b/tests/cluster/tests/04-resharding.tcl
@@ -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]
diff --git a/tests/cluster/tests/12-replica-migration-2.tcl b/tests/cluster/tests/12-replica-migration-2.tcl
index 3d8b7b04b..dd18a979a 100644
--- a/tests/cluster/tests/12-replica-migration-2.tcl
+++ b/tests/cluster/tests/12-replica-migration-2.tcl
@@ -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]
}
diff --git a/tests/helpers/bg_block_op.tcl b/tests/helpers/bg_block_op.tcl
index 238d3874f..c8b323308 100644
--- a/tests/helpers/bg_block_op.tcl
+++ b/tests/helpers/bg_block_op.tcl
@@ -1,6 +1,8 @@
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]
diff --git a/tests/helpers/bg_complex_data.tcl b/tests/helpers/bg_complex_data.tcl
index dffd7c668..e888748a7 100644
--- a/tests/helpers/bg_complex_data.tcl
+++ b/tests/helpers/bg_complex_data.tcl
@@ -1,10 +1,12 @@
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]
diff --git a/tests/helpers/gen_write_load.tcl b/tests/helpers/gen_write_load.tcl
index 6d1a34516..fd6aad40c 100644
--- a/tests/helpers/gen_write_load.tcl
+++ b/tests/helpers/gen_write_load.tcl
@@ -1,8 +1,10 @@
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]
diff --git a/tests/instances.tcl b/tests/instances.tcl
index 357b34818..0a0cbab12 100644
--- a/tests/instances.tcl
+++ b/tests/instances.tcl
@@ -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 "\nOptions:"
@@ -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
diff --git a/tests/integration/aof-race.tcl b/tests/integration/aof-race.tcl
index fb8d71083..2991e7962 100644
--- a/tests/integration/aof-race.tcl
+++ b/tests/integration/aof-race.tcl
@@ -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]
}
}
diff --git a/tests/integration/aof.tcl b/tests/integration/aof.tcl
index e397faeeb..e276a6254 100644
--- a/tests/integration/aof.tcl
+++ b/tests/integration/aof.tcl
@@ -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 {
diff --git a/tests/integration/block-repl.tcl b/tests/integration/block-repl.tcl
index c111b805b..07eceb228 100644
--- a/tests/integration/block-repl.tcl
+++ b/tests/integration/block-repl.tcl
@@ -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
diff --git a/tests/integration/psync2-reg.tcl b/tests/integration/psync2-reg.tcl
index 3d408368e..b5ad021e2 100644
--- a/tests/integration/psync2-reg.tcl
+++ b/tests/integration/psync2-reg.tcl
@@ -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_port(0) -n 10000000 -r 1000 incr __rand_int__ > /dev/null &]
+ set bench_pid [exec src/redis-benchmark -s $R_unixsocket(0) -n 10000000 -r 1000 incr __rand_int__ > /dev/null &]
while 1 {
set elapsed [expr {[clock milliseconds]-$cycle_start_time}]
if {$elapsed > $duration*1000} break
diff --git a/tests/integration/redis-cli.tcl b/tests/integration/redis-cli.tcl
index 40e4222e3..5d1635950 100644
--- a/tests/integration/redis-cli.tcl
+++ b/tests/integration/redis-cli.tcl
@@ -1,7 +1,10 @@
+source tests/support/cli.tcl
+
start_server {tags {"cli"}} {
proc open_cli {} {
set ::env(TERM) dumb
- set fd [open [format "|src/redis-cli -p %d -n 9" [srv port]] "r+"]
+ set cmdline [rediscli [srv port] "-n 9"]
+ set fd [open "|$cmdline" "r+"]
fconfigure $fd -buffering none
fconfigure $fd -blocking false
fconfigure $fd -translation binary
@@ -54,8 +57,8 @@ start_server {tags {"cli"}} {
}
proc _run_cli {opts args} {
- set cmd [format "src/redis-cli -p %d -n 9 $args" [srv port]]
- foreach {key value} $opts {
+ set cmd [rediscli [srv port] [list -n 9 {*}$args]]
+ foreach {key value} $args {
if {$key eq "pipe"} {
set cmd "sh -c \"$value | $cmd\""
}
diff --git a/tests/integration/replication.tcl b/tests/integration/replication.tcl
index 5d32555b0..43bc684b4 100644
--- a/tests/integration/replication.tcl
+++ b/tests/integration/replication.tcl
@@ -29,6 +29,9 @@ start_server {tags {"repl"}} {
$slave slaveof $master_host $master_port
test {Slave enters handshake} {
+ if {$::tls} {
+ fail "TLS with repl-diskless-sync not supported yet."
+ }
wait_for_condition 50 1000 {
[string match *handshake* [$slave role]]
} else {
@@ -184,6 +187,10 @@ start_server {tags {"repl"}} {
}
foreach mdl {no yes} {
+ if {$::tls && $mdl eq "yes"} {
+ puts "** Skipping test: TLS with repl-diskless-sync not supported yet."
+ continue
+ }
foreach sdl {disabled swapdb} {
start_server {tags {"repl"}} {
set master [srv 0 client]
@@ -320,6 +327,9 @@ start_server {tags {"repl"}} {
}
test {slave fails full sync and diskless load swapdb recoveres it} {
+ if {$::tls} {
+ fail ""
+ }
start_server {tags {"repl"}} {
set slave [srv 0 client]
set slave_host [srv 0 host]
@@ -387,6 +397,10 @@ test {slave fails full sync and diskless load swapdb recoveres it} {
}
test {diskless loading short read} {
+ if {$::tls} {
+ fail "TLS with repl-diskless-sync not supported yet."
+ }
+
start_server {tags {"repl"}} {
set replica [srv 0 client]
set replica_host [srv 0 host]
diff --git a/tests/sentinel/tests/07-down-conditions.tcl b/tests/sentinel/tests/07-down-conditions.tcl
index fb2993b6f..a12ea3151 100644
--- a/tests/sentinel/tests/07-down-conditions.tcl
+++ b/tests/sentinel/tests/07-down-conditions.tcl
@@ -1,6 +1,7 @@
# Test conditions where an instance is considered to be down
source "../tests/includes/init-tests.tcl"
+source "../../../tests/support/cli.tcl"
proc ensure_master_up {} {
wait_for_condition 1000 50 {
@@ -28,7 +29,7 @@ test "Crash the majority of Sentinels to prevent failovers for this unit" {
test "SDOWN is triggered by non-responding but not crashed instance" {
lassign [S 4 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] host port
ensure_master_up
- exec ../../../src/redis-cli -h $host -p $port debug sleep 10 > /dev/null &
+ exec ../../../src/redis-cli -h $host -p $port {*}[rediscli_tls_config "../../../tests"] debug sleep 10 > /dev/null &
ensure_master_down
ensure_master_up
}
diff --git a/tests/support/cli.tcl b/tests/support/cli.tcl
new file mode 100644
index 000000000..37c902a50
--- /dev/null
+++ b/tests/support/cli.tcl
@@ -0,0 +1,19 @@
+proc rediscli_tls_config {testsdir} {
+ set tlsdir [file join $testsdir tls]
+ set cert [file join $tlsdir redis.crt]
+ set key [file join $tlsdir redis.key]
+ set cacert [file join $tlsdir ca.crt]
+
+ if {$::tls} {
+ return [list --tls --cert $cert --key $key --cacert $cacert]
+ } else {
+ return {}
+ }
+}
+
+proc rediscli {port {opts {}}} {
+ set cmd [list src/redis-cli -p $port]
+ lappend cmd {*}[rediscli_tls_config "tests"]
+ lappend cmd {*}$opts
+ return $cmd
+}
diff --git a/tests/support/cluster.tcl b/tests/support/cluster.tcl
index 1576053b4..74587e1f7 100644
--- a/tests/support/cluster.tcl
+++ b/tests/support/cluster.tcl
@@ -62,7 +62,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
lassign [split $ip_port :] start_host start_port
if {[catch {
set r {}
- set r [redis $start_host $start_port]
+ set r [redis $start_host $start_port 0 $::tls]
set nodes_descr [$r cluster nodes]
$r close
} e]} {
@@ -107,7 +107,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
# Connect to the node
set link {}
- catch {set link [redis $host $port]}
+ catch {set link [redis $host $port 0 $::tls]}
# Build this node description as an hash.
set node [dict create \
diff --git a/tests/support/redis.tcl b/tests/support/redis.tcl
index cd8ae3a34..be6a11c7a 100644
--- a/tests/support/redis.tcl
+++ b/tests/support/redis.tcl
@@ -39,8 +39,17 @@ array set ::redis::callback {}
array set ::redis::state {} ;# State in non-blocking reply reading
array set ::redis::statestack {} ;# Stack of states, for nested mbulks
-proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
- set fd [socket $server $port]
+proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0}} {
+ if {$tls} {
+ package require tls
+ ::tls::init \
+ -cafile "$::tlsdir/ca.crt" \
+ -certfile "$::tlsdir/redis.crt" \
+ -keyfile "$::tlsdir/redis.key"
+ set fd [::tls::socket $server $port]
+ } else {
+ set fd [socket $server $port]
+ }
fconfigure $fd -translation binary
set id [incr ::redis::id]
set ::redis::fd($id) $fd
@@ -48,6 +57,7 @@ proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
set ::redis::blocking($id) 1
set ::redis::deferred($id) $defer
set ::redis::reconnect($id) 0
+ set ::redis::tls $tls
::redis::redis_reset_state $id
interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $id
}
@@ -72,7 +82,11 @@ proc ::redis::__dispatch__raw__ {id method argv} {
# Reconnect the link if needed.
if {$fd eq {}} {
lassign $::redis::addr($id) host port
- set ::redis::fd($id) [socket $host $port]
+ if {$::redis::tls} {
+ set ::redis::fd($id) [::tls::socket $host $port]
+ } else {
+ set ::redis::fd($id) [socket $host $port]
+ }
fconfigure $::redis::fd($id) -translation binary
set fd $::redis::fd($id)
}
diff --git a/tests/support/server.tcl b/tests/support/server.tcl
index 0edb25d8a..b20f1ad36 100644
--- a/tests/support/server.tcl
+++ b/tests/support/server.tcl
@@ -92,7 +92,11 @@ proc is_alive config {
proc ping_server {host port} {
set retval 0
if {[catch {
- set fd [socket $host $port]
+ if {$::tls} {
+ set fd [::tls::socket $host $port]
+ } else {
+ set fd [socket $host $port]
+ }
fconfigure $fd -translation binary
puts $fd "PING\r\n"
flush $fd
@@ -136,7 +140,6 @@ proc tags {tags code} {
uplevel 1 $code
set ::tags [lrange $::tags 0 end-[llength $tags]]
}
-
proc start_server {options {code undefined}} {
# If we are running against an external server, we just push the
# host/port pair in the stack the first time
@@ -145,7 +148,7 @@ proc start_server {options {code undefined}} {
set srv {}
dict set srv "host" $::host
dict set srv "port" $::port
- set client [redis $::host $::port]
+ set client [redis $::host $::port 0 $::tls]
dict set srv "client" $client
$client select 9
@@ -178,6 +181,13 @@ proc start_server {options {code undefined}} {
set data [split [exec cat "tests/assets/$baseconfig"] "\n"]
set config {}
+ if {$::tls} {
+ dict set config "tls-cert-file" [format "%s/tests/tls/redis.crt" [pwd]]
+ dict set config "tls-key-file" [format "%s/tests/tls/redis.key" [pwd]]
+ dict set config "tls-dh-params-file" [format "%s/tests/tls/redis.dh" [pwd]]
+ dict set config "tls-ca-cert-file" [format "%s/tests/tls/ca.crt" [pwd]]
+ dict set config "loglevel" "debug"
+ }
foreach line $data {
if {[string length $line] > 0 && [string index $line 0] ne "#"} {
set elements [split $line " "]
@@ -192,7 +202,17 @@ proc start_server {options {code undefined}} {
# start every server on a different port
set ::port [find_available_port [expr {$::port+1}]]
- dict set config port $::port
+ if {$::tls} {
+ dict set config "port" 0
+ dict set config "tls-port" $::port
+ dict set config "tls-cluster" "yes"
+ dict set config "tls-replication" "yes"
+ } else {
+ dict set config port $::port
+ }
+
+ set unixsocket [file normalize [format "%s/%s" [dict get $config "dir"] "socket"]]
+ dict set config "unixsocket" $unixsocket
# apply overrides from global space and arguments
foreach {directive arguments} [concat $::global_overrides $overrides] {
@@ -254,10 +274,11 @@ proc start_server {options {code undefined}} {
}
# setup properties to be able to initialize a client object
+ set port_param [expr $::tls ? {"tls-port"} : {"port"}]
set host $::host
set port $::port
if {[dict exists $config bind]} { set host [dict get $config bind] }
- if {[dict exists $config port]} { set port [dict get $config port] }
+ if {[dict exists $config $port_param]} { set port [dict get $config $port_param] }
# setup config dict
dict set srv "config_file" $config_file
@@ -267,6 +288,7 @@ proc start_server {options {code undefined}} {
dict set srv "port" $port
dict set srv "stdout" $stdout
dict set srv "stderr" $stderr
+ dict set srv "unixsocket" $unixsocket
# if a block of code is supplied, we wait for the server to become
# available, create a client object and kill the server afterwards
diff --git a/tests/support/util.tcl b/tests/support/util.tcl
index c2e76afad..7ecf5b79c 100644
--- a/tests/support/util.tcl
+++ b/tests/support/util.tcl
@@ -395,7 +395,7 @@ proc colorstr {color str} {
# of seconds to the specified Redis instance.
proc start_write_load {host port seconds} {
set tclsh [info nameofexecutable]
- exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds &
+ exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds $::tls &
}
# Stop a process generating write load executed with start_write_load.
@@ -423,7 +423,7 @@ proc lshuffle {list} {
# of ops to the specified Redis instance.
proc start_bg_complex_data {host port db ops} {
set tclsh [info nameofexecutable]
- exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops &
+ exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops $::tls &
}
# Stop a process generating write load executed with start_bg_complex_data.
diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl
index 1442067f5..cb7e4e328 100644
--- a/tests/test_helper.tcl
+++ b/tests/test_helper.tcl
@@ -63,6 +63,7 @@ set ::all_tests {
unit/lazyfree
unit/wait
unit/pendingquerybuf
+ unit/tls
}
# Index to the next test to run in the ::all_tests list.
set ::next_test 0
@@ -71,6 +72,7 @@ set ::host 127.0.0.1
set ::port 21111
set ::traceleaks 0
set ::valgrind 0
+set ::tls 0
set ::stack_logging 0
set ::verbose 0
set ::quiet 0
@@ -92,6 +94,7 @@ set ::dont_clean 0
set ::wait_server 0
set ::stop_on_failure 0
set ::loop 0
+set ::tlsdir "tests/tls"
# Set to 1 when we are running in client mode. The Redis test uses a
# server-client model to run tests simultaneously. The server instance
@@ -146,7 +149,7 @@ proc reconnect {args} {
set host [dict get $srv "host"]
set port [dict get $srv "port"]
set config [dict get $srv "config"]
- set client [redis $host $port]
+ set client [redis $host $port 0 $::tls]
dict set srv "client" $client
# select the right db when we don't have to authenticate
@@ -166,7 +169,7 @@ proc redis_deferring_client {args} {
}
# create client that defers reading reply
- set client [redis [srv $level "host"] [srv $level "port"] 1]
+ set client [redis [srv $level "host"] [srv $level "port"] 1 $::tls]
# select the right db and read the response (OK)
$client select 9
@@ -204,7 +207,7 @@ proc test_server_main {} {
if {!$::quiet} {
puts "Starting test server at port $port"
}
- socket -server accept_test_clients -myaddr 127.0.0.1 $port
+ socket -server accept_test_clients -myaddr 127.0.0.1 $port
# Start the client instances
set ::clients_pids {}
@@ -450,6 +453,7 @@ proc print_help_screen {} {
"--stop Blocks once the first test fails."
"--loop Execute the specified set of tests forever."
"--wait-server Wait after server is started (so that you can attach a debugger)."
+ "--tls Run tests in TLS mode."
"--help Print this help screen."
} "\n"]
}
@@ -486,6 +490,13 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
}
} elseif {$opt eq {--quiet}} {
set ::quiet 1
+ } elseif {$opt eq {--tls}} {
+ package require tls 1.6
+ set ::tls 1
+ ::tls::init \
+ -cafile "$::tlsdir/ca.crt" \
+ -certfile "$::tlsdir/redis.crt" \
+ -keyfile "$::tlsdir/redis.key"
} elseif {$opt eq {--host}} {
set ::external 1
set ::host $arg
@@ -565,7 +576,11 @@ if {[llength $::single_tests] > 0} {
}
proc attach_to_replication_stream {} {
- set s [socket [srv 0 "host"] [srv 0 "port"]]
+ if {$::tls} {
+ set s [::tls::socket [srv 0 "host"] [srv 0 "port"]]
+ } else {
+ set s [socket [srv 0 "host"] [srv 0 "port"]]
+ }
fconfigure $s -translation binary
puts -nonewline $s "SYNC\r\n"
flush $s
diff --git a/tests/unit/limits.tcl b/tests/unit/limits.tcl
index b37ea9b0f..38ba76208 100644
--- a/tests/unit/limits.tcl
+++ b/tests/unit/limits.tcl
@@ -1,4 +1,9 @@
start_server {tags {"limits"} overrides {maxclients 10}} {
+ if {$::tls} {
+ set expected_code "*I/O error*"
+ } else {
+ set expected_code "*ERR max*reached*"
+ }
test {Check if maxclients works refusing connections} {
set c 0
catch {
@@ -12,5 +17,5 @@ start_server {tags {"limits"} overrides {maxclients 10}} {
} e
assert {$c > 8 && $c <= 10}
set e
- } {*ERR max*reached*}
+ } $expected_code
}
diff --git a/tests/unit/other.tcl b/tests/unit/other.tcl
index 965902456..7720c055a 100644
--- a/tests/unit/other.tcl
+++ b/tests/unit/other.tcl
@@ -166,7 +166,11 @@ start_server {tags {"other"}} {
tags {protocol} {
test {PIPELINING stresser (also a regression for the old epoll bug)} {
- set fd2 [socket $::host $::port]
+ if {$::tls} {
+ set fd2 [::tls::socket $::host $::port]
+ } else {
+ set fd2 [socket $::host $::port]
+ }
fconfigure $fd2 -encoding binary -translation binary
puts -nonewline $fd2 "SELECT 9\r\n"
flush $fd2
diff --git a/tests/unit/protocol.tcl b/tests/unit/protocol.tcl
index ac99c3abb..4dfdc6f59 100644
--- a/tests/unit/protocol.tcl
+++ b/tests/unit/protocol.tcl
@@ -72,7 +72,11 @@ start_server {tags {"protocol"}} {
foreach seq [list "\x00" "*\x00" "$\x00"] {
incr c
test "Protocol desync regression test #$c" {
- set s [socket [srv 0 host] [srv 0 port]]
+ if {$::tls} {
+ set s [::tls::socket [srv 0 host] [srv 0 port]]
+ } else {
+ set s [socket [srv 0 host] [srv 0 port]]
+ }
puts -nonewline $s $seq
set payload [string repeat A 1024]"\n"
set test_start [clock seconds]
diff --git a/tests/unit/tls.tcl b/tests/unit/tls.tcl
new file mode 100644
index 000000000..58acdb6a9
--- /dev/null
+++ b/tests/unit/tls.tcl
@@ -0,0 +1,25 @@
+start_server {tags {"tls"}} {
+ if {$::tls} {
+ package require tls
+
+ test {TLS: Not accepting non-TLS connections on a TLS port} {
+ set s [redis [srv 0 host] [srv 0 port]]
+ catch {$s PING} e
+ set e
+ } {*I/O error*}
+
+ test {TLS: Verify tls-auth-clients behaves as expected} {
+ set s [redis [srv 0 host] [srv 0 port]]
+ ::tls::import [$s channel]
+ catch {$s PING} e
+ assert_match {*error*} $e
+
+ set resp [r CONFIG SET tls-auth-clients no]
+
+ set s [redis [srv 0 host] [srv 0 port]]
+ ::tls::import [$s channel]
+ catch {$s PING} e
+ assert_match {PONG} $e
+ } {}
+ }
+}
diff --git a/tests/unit/wait.tcl b/tests/unit/wait.tcl
index e2f5d2942..c9cfa6ed4 100644
--- a/tests/unit/wait.tcl
+++ b/tests/unit/wait.tcl
@@ -1,3 +1,5 @@
+source tests/support/cli.tcl
+
start_server {tags {"wait"}} {
start_server {} {
set slave [srv 0 client]
@@ -31,7 +33,8 @@ start_server {} {
}
test {WAIT should not acknowledge 1 additional copy if slave is blocked} {
- exec src/redis-cli -h $slave_host -p $slave_port debug sleep 5 > /dev/null 2> /dev/null &
+ set cmd [rediscli $slave_port "-h $slave_host debug sleep 5"]
+ exec {*}$cmd > /dev/null 2> /dev/null &
after 1000 ;# Give redis-cli the time to execute the command.
$master set foo 0
$master incr foo
diff --git a/utils/gen-test-certs.sh b/utils/gen-test-certs.sh
new file mode 100755
index 000000000..a46edc55a
--- /dev/null
+++ b/utils/gen-test-certs.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+mkdir -p tests/tls
+openssl genrsa -out tests/tls/ca.key 4096
+openssl req \
+ -x509 -new -nodes -sha256 \
+ -key tests/tls/ca.key \
+ -days 3650 \
+ -subj '/O=Redis Test/CN=Certificate Authority' \
+ -out tests/tls/ca.crt
+openssl genrsa -out tests/tls/redis.key 2048
+openssl req \
+ -new -sha256 \
+ -key tests/tls/redis.key \
+ -subj '/O=Redis Test/CN=Server' | \
+ openssl x509 \
+ -req -sha256 \
+ -CA tests/tls/ca.crt \
+ -CAkey tests/tls/ca.key \
+ -CAserial tests/tls/ca.txt \
+ -CAcreateserial \
+ -days 365 \
+ -out tests/tls/redis.crt
+openssl dhparam -out tests/tls/redis.dh 2048